Compare commits

..

258 Commits

Author SHA1 Message Date
Ainar Garipov
ceb178fcd5 all: imp chlog 2024-12-11 16:09:24 +03:00
Ainar Garipov
0d202cb544 all: upd chlog 2024-12-11 14:33:55 +03:00
Eugene Burkov
6ce6c2c04d all: move release date 2024-12-05 17:07:56 +03:00
Eugene Burkov
3f95db98d3 all: sync with master 2024-12-05 16:00:18 +03:00
Eugene Burkov
54f3a5f990 all: sync hotfix 2024-11-05 19:32:44 +03:00
Eugene Burkov
44cebc06ec all: upd release date 2024-11-05 13:46:19 +03:00
Eugene Burkov
6affa96490 all: sync with master 2024-10-29 18:53:56 +03:00
Ainar Garipov
2aaf8ab3c1 all: upd chlog 2024-10-03 15:34:48 +03:00
Ainar Garipov
ee1eb80786 all: resync fix 2024-10-02 20:25:07 +03:00
Ainar Garipov
e8fd4b1872 all: add permcheck, client fix; imp chlog 2024-10-02 18:24:07 +03:00
Ainar Garipov
8cb5781770 all: resync with master 2024-09-30 20:17:20 +03:00
Ainar Garipov
c7d8b9ede1 client: import querylog fix 2024-07-04 17:06:16 +03:00
Ainar Garipov
5c6bb33e3a all: resync with master 2024-07-03 15:53:40 +03:00
Ainar Garipov
158d4f0249 all: sync with master 2024-07-03 15:38:37 +03:00
Ainar Garipov
f73717ec08 all: upd chlog 2024-06-06 17:31:01 +03:00
Ainar Garipov
1807198a9b all: sync with master 2024-06-05 19:00:28 +03:00
Eugene Burkov
1ccf8fe116 all: upd chlog 2024-05-23 15:32:15 +03:00
Eugene Burkov
d22f0eefe2 all: sync with master; upd chlog 2024-05-22 18:32:21 +03:00
Ainar Garipov
344c66f7ab all: fix release date in chlog 2024-05-21 17:10:16 +03:00
Ainar Garipov
83be002b41 all: upd chlog 2024-05-15 17:24:12 +03:00
Ainar Garipov
9945cd3991 all: upd chlog; sync with master 2024-05-15 16:30:36 +03:00
Ainar Garipov
667263a3a8 all: sync with master 2024-05-15 13:34:12 +03:00
Ainar Garipov
6318fc424b all: import hotfix; upd chlog 2024-04-05 14:58:11 +03:00
Ainar Garipov
e32a37a747 all: upd chlog, x/net 2024-04-04 16:29:15 +03:00
Ainar Garipov
7805a71332 all: sync more; upd chlog 2024-04-03 21:30:46 +03:00
Ainar Garipov
6fb2aee210 all: sync with master; upd chlog 2024-04-02 20:22:19 +03:00
Ainar Garipov
ce9bb588ed all: sync with master 2024-03-19 16:20:32 +03:00
Ainar Garipov
55fb914537 all: sync rc fix with master 2024-03-13 16:25:51 +03:00
Ainar Garipov
6f7bfd6c9c all: sync with master 2024-03-12 18:15:58 +03:00
Ainar Garipov
fbc0d981ba all: partially sync with master; upd chlog 2024-03-06 18:33:53 +03:00
Ainar Garipov
48d1c673a9 all: fix chlog 2024-02-06 15:21:25 +03:00
Ainar Garipov
889a0eb8b3 all: upd chlog, i18n; fix os resolv 2024-02-01 15:20:53 +03:00
Ainar Garipov
b01c10b73e all: sync with master 2024-01-30 18:44:31 +03:00
Eugene Burkov
f6ad64bf69 all: fix chlog link 2023-12-11 15:36:34 +03:00
Eugene Burkov
a5e8443735 all: fix chlog link 2023-12-11 15:26:48 +03:00
Eugene Burkov
2860929a47 all: sync with master; upd chlog 2023-12-11 14:26:01 +03:00
Ainar Garipov
ecdac56616 all: fix chlog link 2023-12-07 17:44:22 +03:00
Ainar Garipov
25918e56fa Pull request 2100: v0.107.42-rc
Squashed commit of the following:

commit 284190f748345c7556e60b67f051ec5f6f080948
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 6 19:36:00 2023 +0300

    all: sync with master; upd chlog
2023-12-07 17:23:00 +03:00
Ainar Garipov
df91f016f2 all: fix chlog 2023-11-13 18:34:42 +03:00
Ainar Garipov
f7d259f653 all: fix chlog 2023-11-13 18:00:38 +03:00
Ainar Garipov
82ab4328d4 all: sync with master; upd chlog 2023-11-13 17:39:48 +03:00
Ainar Garipov
b21e19a223 all: sync with master; upd chlog 2023-10-18 17:04:44 +03:00
Ainar Garipov
c6aed4eb57 cherry-pick: 6180 revert 2023-10-11 18:35:46 +03:00
Ainar Garipov
760d466b38 all: sync with master; upd chlog 2023-10-11 17:31:41 +03:00
Ainar Garipov
258eecc55b all: sync with master; upd chlog 2023-09-11 17:51:50 +03:00
Ainar Garipov
7b93f5d7cf all: sync with master; upd chlog 2023-09-07 17:13:48 +03:00
Ainar Garipov
3be7676970 all: sync with master; upd chlog 2023-08-02 16:26:34 +03:00
Ainar Garipov
48ee2f8a42 all: sync with master; upd chlog 2023-07-26 13:18:44 +03:00
Ainar Garipov
ec83d0eb86 all: sync with master; upd chlog 2023-07-12 15:13:31 +03:00
Ainar Garipov
19347d263a cherry-pick: 5959-fix-error-days
Updates #5959.

* commit '4b9264531be50e81fe610050a12827b71bc3a9cd':
  clients: use constant a day in milliseconds
  clients: fix lint
  fix error days
2023-07-03 14:38:16 +03:00
Ainar Garipov
b22b16d98c all: sync with master; upd chlog 2023-07-03 14:10:40 +03:00
Ainar Garipov
cadb765b7d all: sync with master; upd chlog 2023-06-13 17:04:47 +03:00
Ainar Garipov
1116da8b83 all: sync with master; upd chlog 2023-06-08 19:48:01 +03:00
Ainar Garipov
c65700923a all: sync with master; upd chlog 2023-06-07 20:04:01 +03:00
Ainar Garipov
7030c7c24c all: imp chlog 2023-04-18 16:56:12 +03:00
Ainar Garipov
09718a2170 all: sync with master; upd chlog 2023-04-18 16:07:11 +03:00
Ainar Garipov
77cda2c2c5 all: imp chlog 2023-04-12 16:18:02 +03:00
Ainar Garipov
d9c57cdd9a all: sync with master; upd chlog 2023-04-12 14:48:42 +03:00
Ainar Garipov
0dad53b5f7 all: fix chlog 2023-04-05 16:38:18 +03:00
Ainar Garipov
9a7315dbea all: upd go, tools, ui; fix panics 2023-04-05 16:35:27 +03:00
Ainar Garipov
a21558f418 all: sync with master; upd chlog 2023-03-09 15:39:35 +03:00
Ainar Garipov
4f928be393 bamboo-specs: do not require make where not needed 2023-02-21 15:12:18 +03:00
Ainar Garipov
f543b47261 dnsforward: fix panic; take Host into account 2023-02-21 14:55:10 +03:00
Ainar Garipov
66b831072c all: sync with master; upd chlog 2023-02-15 16:53:29 +03:00
Ainar Garipov
80eb339896 all: sync with master; upd chlog 2023-02-01 15:41:34 +03:00
Ainar Garipov
c69639c013 all: imp chlog 2023-01-19 15:29:10 +03:00
Ainar Garipov
5f6fbe8e08 all: sync with master; upd chlog 2023-01-19 15:04:46 +03:00
Ainar Garipov
b40bbf0260 all: upd chlog 2023-01-19 15:00:14 +03:00
Ainar Garipov
a11c8e91ab all: sync with master 2022-12-15 17:50:08 +03:00
Ainar Garipov
618d0e596c all: fix chlog 2022-12-07 16:49:19 +03:00
Ainar Garipov
fde9ea5cb1 all: sync with master 2022-12-07 16:46:59 +03:00
Ainar Garipov
03d9803238 all: upd chlog 2022-11-23 17:00:27 +03:00
Ainar Garipov
bd64b8b014 all: sync with master 2022-11-23 16:52:05 +03:00
Ainar Garipov
67fe064fcf all: sync with master 2022-11-08 17:53:30 +03:00
Ainar Garipov
471668d19a all: fix chlog 2022-11-02 18:29:57 +03:00
Ainar Garipov
42762dfe54 all: upd chlog 2022-11-02 16:25:08 +03:00
Ainar Garipov
c9314610d4 all: sync with master 2022-11-02 16:18:02 +03:00
Ainar Garipov
16755c37d8 all: upd go 2022-10-07 15:57:26 +03:00
Ainar Garipov
73fcbd6ea2 all: sync with master 2022-10-03 18:52:20 +03:00
Ainar Garipov
30244f361f all: sync with master 2022-09-29 19:10:03 +03:00
Ainar Garipov
083991fb21 home: sync with master 2022-09-29 18:54:54 +03:00
Ainar Garipov
e3200d5046 all: upd chlog 2022-09-29 17:43:04 +03:00
Ainar Garipov
21f6ed36fe all: sync with master 2022-09-29 17:36:01 +03:00
Ainar Garipov
77d04d44eb all: sync with master 2022-09-14 16:36:29 +03:00
Ainar Garipov
b34d119255 all: imp chlog 2022-09-07 18:38:03 +03:00
Ainar Garipov
63bd71a10c all: imp chlog 2022-09-07 18:07:52 +03:00
Ainar Garipov
faf2b32389 all: sync with master 2022-09-07 18:03:18 +03:00
Ainar Garipov
d23da1b757 all: sync with master 2022-08-19 15:45:54 +03:00
Ainar Garipov
beb8e36eee cherry-pick: 4557-asuswrt-readme
Updates #4557.

* commit 'e3624ec5880361b8afccd0ddac9dc31fd7ce4a07':
  all: fix abbreviation
  Update README.md
2022-08-19 15:21:21 +03:00
Ainar Garipov
fe70161c01 cherry-pick: upd-dnsproxy
Merge in DNS/adguard-home from upd-dnsproxy to master

Squashed commit of the following:

commit 3c5b683e96191b9cf0abf35229c3c665370d782e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 18 18:04:13 2022 +0300

    all: upd dnsproxy
2022-08-19 15:20:59 +03:00
Ainar Garipov
39fa4b1f8e cherry-pick: 4846-migration-fix
Updates #4846.

Squashed commit of the following:

commit 22e2e89e5390c7b1486fb69064c55da40fc5c7e7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 18 16:25:07 2022 +0300

    home: fix yaml object type
2022-08-19 15:19:11 +03:00
Ainar Garipov
c7a8883201 cherry-pick: 4795-bilibili
Updates #4795.

* commit 'e6ebb8efef4430c48b06469ba566349bba3d9856':
  filtering: fmt
  filtering: add Bilibili and Weibo domains
  filtering: add Bilibili service
2022-08-19 15:18:14 +03:00
Ainar Garipov
3fd467413c cherry-pick: 4446-readme-fix
Updates #4446.

* commit 'ea5d165a703dd37ef40254f3f775e049b6cebf93':
  all: imp readme
  Enable code block syntax hightlight in README.md
2022-08-19 15:17:57 +03:00
Ainar Garipov
9728dd856f cherry-pick: 4387-fix-openapi-schema
Updates #4387.

* commit 'f54a2dc1da5dfd578f156cf1e0f53f32516eb844':
  home: imp filtering handling
  correct openapi schema
2022-08-19 15:17:43 +03:00
Ainar Garipov
ecadf78d60 all: upd chlog 2022-08-19 15:02:37 +03:00
Ainar Garipov
eba4612d72 all: fix chlog 2022-08-17 18:55:20 +03:00
Ainar Garipov
9200163f85 all: sync with master 2022-08-17 18:23:30 +03:00
Ainar Garipov
3c17853344 cherry-pick: 4844-snap-core22
Closes #4843.
Updates #4844.

* commit '385a873b0f006f26832e73744845fdbc2864aad0':
  all: chlog
  Update Snap to Ubuntu Core 22 #4843
2022-08-17 18:16:57 +03:00
Eugene Burkov
993a3fc42c cherry-pick: 4358 stats races
Merge in DNS/adguard-home from 4358-stats-races to master

Updates #4358

Squashed commit of the following:

commit 162d17b04d95adad21fb9b3c5a6fb64df2e037ec
Merge: 17732cfa d4c3a43b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 17 14:04:20 2022 +0300

    Merge branch 'master' into 4358-stats-races

commit 17732cfa0f3b2589bf2c252697eee1d6b358a66c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 17 13:53:42 2022 +0300

    stats: imp docs, locking

commit 4ee090869af0fa2b777c12027c3b77d5acd6e4de
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 20:26:19 2022 +0300

    stats: revert const

commit a7681a1b882cef04511fcd5d569f5abe2f955239
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 20:23:00 2022 +0300

    stats: imp concurrency

commit a6c6c1a0572e4201cd24644fd3f86f51fc27f633
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 19:51:30 2022 +0300

    stats: imp code, tests, docs

commit 954196b49f5ad91d91f445ff656e63c318e4124c
Merge: 281e00da 6e63757f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 16 13:07:32 2022 +0300

    Merge branch 'master' into 4358-stats-races

commit 281e00daf781d045269584ce0158eed1d77918df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 16:22:18 2022 +0300

    stats: imp closing

commit ed036d9aa7e25498869edfb866b6e923538970eb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 16:11:12 2022 +0300

    stats: imp tests more

commit f848a12487ecd2afc8416e800510090cc1be7330
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Aug 12 13:54:19 2022 +0300

    stats: imp tests, code

commit 60e11f042d51ec68850143129e61c701c5e4f3a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 11 16:36:07 2022 +0300

    stats: fix test

commit 6d97f1db093b5ce0d37984ff96a9ef6f4e02dba1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 11 14:53:21 2022 +0300

    stats: imp code, docs

commit 20c70c2847b0de6c7f9271a8d9a831175ed0c499
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 10 20:53:36 2022 +0300

    stats: imp shared memory safety

commit 8b3945670a190bab070171e6b4976edab1e3e2a2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 10 17:22:55 2022 +0300

    stats: imp code
2022-08-17 18:15:41 +03:00
Ainar Garipov
7bb9b2416b cherry-pick: upd-specs
Merge in DNS/adguard-home from upd-specs to master

Squashed commit of the following:

commit d7ac1dc1ef305098ff741d557c13db8a60ffe1f9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 19:16:51 2022 +0300

    bamboo-specs: allow larger keys
2022-08-17 18:15:16 +03:00
Eugene Burkov
2de321ce24 cherry-pick: Fix frontend CI build
Merge in DNS/adguard-home from fix-bamboo-specs to master

Squashed commit of the following:

commit e59b75ab9528bbe8fbf5e15054d848abffbae312
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:52:10 2022 +0300

    all: fix ci frontend build
2022-08-17 18:14:59 +03:00
Eugene Burkov
30b2b85ff1 cherry-pick: Separate front- and back- end builds
Merge in DNS/adguard-home from imp-bamboo-specs to master

Squashed commit of the following:

commit 3415b650e48aefef3ad16030be3d797de4015403
Merge: e37c0a2b f58265ec
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:42:42 2022 +0300

    Merge branch 'master' into imp-bamboo-specs

commit e37c0a2bb52fab98e133332e8f54d500d0f96b06
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 18:30:33 2022 +0300

    scripts: replace find with loop

commit 826a02f6a11000cce4b3205229d6bbb050c8dd73
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 18:00:41 2022 +0300

    all: ...again

commit 54aebf5d4aeba35e3dc320436236759f4d1ccdad
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 17:59:24 2022 +0300

    all: fix spec yaml

commit 87b92b30504f2427c40303265354afba4855e0bb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 15 17:48:19 2022 +0300

    all: separate front- and back-end builds
2022-08-17 18:14:44 +03:00
Ainar Garipov
6ea4788f56 cherry-pick: 4836-revert-dhcp-upd
Updates #4836.

Squashed commit of the following:

commit 6fe1721d44be1c23e524d477e28b5f7cc5dd2dc6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 17:48:41 2022 +0300

    dhcpd: reverd mod upd
2022-08-17 18:13:27 +03:00
Ainar Garipov
3c52a021b9 cherry-pick: add-ar-i18n
Merge in DNS/adguard-home from add-ar-i18n to master

Squashed commit of the following:

commit 6ef7c70bceb6f6ebabd81011154022a75fc91bd3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 10 20:55:39 2022 +0300

    client: add ar locale
2022-08-17 18:12:15 +03:00
Ainar Garipov
0ceea9af5f cherry-pick: upd-yaml
Merge in DNS/adguard-home from upd-yaml to master

Squashed commit of the following:

commit f0c3a1896e7eba73b1c8a02533637cdabc89909b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 8 15:28:02 2022 +0300

    home: restore indent lvl

commit b52c124d2e786e8575c58e75efa7d2cd2b70b67f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 8 15:06:41 2022 +0300

    all: upd tools, yaml mod
2022-08-17 18:10:49 +03:00
Eugene Burkov
39b404be19 cherry-pick: 4358 fix stats
Merge in DNS/adguard-home from 4358-fix-stats to master

Updates #4358.
Updates #4342.

Squashed commit of the following:

commit 5683cb304688ea639e5ba7f219a7bf12370211a4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 18:20:54 2022 +0300

    stats: rm races test

commit 63dd67650ed64eaf9685b955a4fdf3c0067a7f8c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 17:13:36 2022 +0300

    stats: try to imp test

commit 59a0f249fc00566872db62e362c87bc0c201b333
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 16:38:57 2022 +0300

    stats: fix nil ptr deref

commit 7fc3ff18a34a1d0e0fec3ca83a33f499ac752572
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 7 16:02:51 2022 +0300

    stats: fix races finally, imp tests

commit c63f5f4e7929819fe79b3a1e392f6b91cd630846
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 00:56:49 2022 +0300

    aghhttp: add register func

commit 61adc7f0e95279c1b7f4a0c0af5ab387ee461411
Merge: edbdb2d4 9b3adac1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Aug 4 00:36:01 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit edbdb2d4c6a06dcbf8107a28c4c3a61ba394e907
Merge: a91e4d7a a481ff4c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 21:00:42 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit a91e4d7af13591eeef45cb7980d1ebc1650a5cb7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:46:19 2022 +0300

    stats: imp code, docs

commit c5f3814c5c1a734ca8ff6726cc9ffc1177a055cf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:16:13 2022 +0300

    all: log changes

commit 5e6caafc771dddc4c6be07c34658de359106fbe5
Merge: 091ba756 eb8e8166
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:09:10 2022 +0300

    Merge branch 'master' into 4358-fix-stats

commit 091ba75618d3689b9c04f05431283417c8cc52f9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Aug 3 18:07:39 2022 +0300

    stats: imp docs, code

commit f2b2de77ce5f0448d6df9232a614a3710f1e2e8a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Aug 2 17:09:30 2022 +0300

    all: refactor stats & add mutexes

commit b3f11c455ceaa3738ec20eefc46f866ff36ed046
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 27 15:30:09 2022 +0300

    WIP
2022-08-17 18:10:16 +03:00
Ainar Garipov
56dc3eab02 cherry-pick: 4801-hassio-link
Updates #4801.

* commit '73f935f3f370ad7e1dfb2495fe71d1dc5e415672':
  Update Hass.io AdGuard Home integration link
2022-08-17 18:07:06 +03:00
Ainar Garipov
554a38eeb1 cherry-pick: 4800-upd-link
Updates #4800.

* commit 'bbccd616148f63240afee6ccf643179ff322c6f4':
  Update RFC 9250 link
2022-08-17 18:06:29 +03:00
Ainar Garipov
c8d3afe869 cherry-pick: 4670-invalid-arg-cap-check
Updates #4670.

Squashed commit of the following:

commit 9c32739eb92ef57c78a4dc3ec3c0f280aebf7182
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 20:04:54 2022 +0300

    aghnet: imp port check for older linuxes
2022-08-17 18:05:28 +03:00
Ainar Garipov
44222c604c all: upd chlog 2022-08-17 18:05:16 +03:00
Ainar Garipov
cbf221585e all: upd chlog 2022-08-03 16:22:44 +03:00
Ainar Garipov
48322f6d0d all: upd chlog 2022-08-03 16:21:12 +03:00
Ainar Garipov
d5a213c639 cherry-pick: upd-i18n
Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 366600a32ecbb163ab43b43145898bbadcfbc2e9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 15:09:16 2022 +0300

    client: fix si-lk

commit 2a55ee3846251e53529f4ef6562e5f4939381eae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 15:03:45 2022 +0300

    client: upd i18n
2022-08-03 16:01:22 +03:00
Ainar Garipov
8166c4bc33 cherry-pick: upd-go
Merge in DNS/adguard-home from upd-go to master

Squashed commit of the following:

commit 8edfb5cc3466c1e4ee2eacae5157bd93c135a284
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Aug 3 14:25:45 2022 +0300

    all: imp docs; fmt

commit 080b8a85c02afbdaa079c0da47cb7b6311d50fbe
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:51:20 2022 +0300

    all: upd go, imp generic code
2022-08-03 16:01:02 +03:00
Ildar Kamalov
133cd9ef6b cherry-pick: 4776 add word break for query log domains
Updates #4776

Squashed commit of the following:

commit 6f1778fbd11da529ae934ee33c8f1ad227cdfa66
Merge: 753bd44c 053bb72a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Aug 2 11:52:07 2022 +0300

    Merge branch 'master' into 4776-domains

commit 753bd44cbb592903ed996713a79e4dbf073d780b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 1 16:58:07 2022 +0300

    client: add word break for query log domains
2022-08-03 15:59:23 +03:00
Ildar Kamalov
11146f73ed cherry-pick: 4775 fix query log issue on tablet devices
Updates #4775

Squashed commit of the following:

commit 9ad85d2306b68227e11c7b1dd792e3fe6389939d
Merge: 95aa29d6 41f081d8
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Aug 2 11:44:04 2022 +0300

    Merge branch 'master' into 4775-popup

commit 95aa29d68bdf5e9c4e7aa59f42d04328b1872115
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Aug 1 16:21:23 2022 +0300

    client: fix query log issue on tablet devices
2022-08-03 15:57:05 +03:00
Eugene Burkov
1beb18db47 cherry-pick: 4517 warning wording
Merge in DNS/adguard-home from 4517-warning-label to master

Updates #4517.

Squashed commit of the following:

commit 4987f63937253da2954cf20c7b99a3b8a0adf112
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Aug 1 13:59:28 2022 +0300

    client: imp wording
2022-08-03 15:56:45 +03:00
Eugene Burkov
f7bc2273a7 cherry-pick: 4517 domain specific test
Merge in DNS/adguard-home from 4517-domain-specific-test to master

Updates #4517.

Squashed commit of the following:

commit 03a803f831749a060923ec966592696f99591786
Merge: 8ea24170 f5959a0d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 19:17:28 2022 +0300

    Merge branch 'master' into 4517-domain-specific-test

commit 8ea2417036547996bb2d39b75b0ff31de4fe9b21
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 18:44:26 2022 +0300

    all: log changes, imp docs

commit aa74c8be64f2796a2dfa7166f0155fff5bb395b6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 18:07:12 2022 +0300

    dnsforward: imp logging

commit 02dccca4e7d766bbfbe0826933e8be70fcd93f58
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 17:24:08 2022 +0300

    all: imp code, docs

commit 3b21067d07b208baf574a34fb06ec808b37c4ee3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 29 13:34:55 2022 +0300

    client: add warning toast

commit ea2272dc77f87e34dc6aff0af99c7a51a04e3770
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 20:11:55 2022 +0300

    dnsforward: imp err msg, docs

commit fd9ee82afef9d93961c30ebafcc7a11d984247b5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 19:24:58 2022 +0300

    dnsforward: test doain specific upstreams

commit 9a83ebfa7a73bf4e03eaf1ff4a33f79771159fc7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 28 18:22:49 2022 +0300

    dnsforward: merge some logic
2022-08-03 15:56:25 +03:00
Ainar Garipov
d1e735a003 cherry-pick: upd-links-etc
Merge in DNS/adguard-home from upd-links-etc to master

Squashed commit of the following:

commit 49856df394f1a2123a27afdb35047d3b1a49860f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:43:10 2022 +0300

    all: revert cdn link revert

commit 59bbe4bbd300f48674c1a6224a91f9a567d6c79c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 20:40:50 2022 +0300

    all: revert static link revert

commit fe2acc4a0d6d5ee31cb8dbb0d0e0984c3cd723db
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Aug 2 18:24:02 2022 +0300

    all: revert links up in README; imp tools
2022-08-03 15:54:47 +03:00
Ainar Garipov
af4ff5c748 cherry-pick: upd-domains-and-links
Merge in DNS/adguard-home from upd-domains-and-links to master

Squashed commit of the following:

commit 5e5ff2fec358104995877da689da24749ac470ce
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 19:53:19 2022 +0300

    all: upd urls

    Update domains and URLs to make them more resistant to state blocking.
2022-08-03 15:54:23 +03:00
Ainar Garipov
fc951c1226 cherry-pick: 4755-youtube-domain
Updates #4755.

Squashed commit of the following:

commit cb0ab8b26f6f277ef76ee3492c99870cbfc24666
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 17:21:02 2022 +0300

    filtering: add another youtube domain
2022-08-03 15:52:00 +03:00
Ainar Garipov
f81fd42472 cherry-pick: imp-issue-tmpl
Merge in DNS/adguard-home from imp-issue-tmpl to master

Squashed commit of the following:

commit 3941dd135911d850f3ec9b01f55bc45269a7b91c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 15:24:26 2022 +0300

    all: fix links in issue tmpls

commit 438375a4666f951fc24ab47e4b0de5a61714973b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 28 15:23:00 2022 +0300

    all: imp issue tmpls
2022-08-03 15:51:37 +03:00
Ainar Garipov
1029ea5966 cherry-pick: issue-templates
Merge in DNS/adguard-home from issue-templates to master

Squashed commit of the following:

commit 989253530047a463804e81c8fda82ac268f39adc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:56:35 2022 +0300

    all: fix issue tmpl schema

commit e69df09ab4b4f713d124dc6eeb1ed34e0f4aaa70
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:41:14 2022 +0300

    all: rename tmpl files

commit 542306da1ea1bdc09ca328856367c64139a8ec60
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 27 16:37:02 2022 +0300

    all: imp github issue templates
2022-08-03 15:51:23 +03:00
Ainar Garipov
c0abdb4bc7 cherry-pick: 4782-server-name-label
Updates #4782.

Squashed commit of the following:

commit d350b3853bf722c0f2a8d1fc4a1c28dc384c5ca0
Author: Natalia Sokolova <n.sokolova@adguard.com>
Date:   Tue Jul 26 18:39:38 2022 +0300

    client: imp wording

commit d0785311bfe38fb10477bf8971a46d6c61aecfda
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 26 17:16:41 2022 +0300

    client: imp tls server name label
2022-08-03 15:50:48 +03:00
Ainar Garipov
6681178ad3 all: upd chlog 2022-08-03 15:43:45 +03:00
Ainar Garipov
e73605c4c5 all: add ms link 2022-07-13 15:24:17 +03:00
Ainar Garipov
c7017d49aa all: upd chlog 2022-07-13 15:22:20 +03:00
Ainar Garipov
191d3bde49 cherry-pick: home: fix exe path finding
Closes #4735.

Squashed commit of the following:

commit 8228e5f82c9d8056d5567a7f1b13b1365346c4d4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 11 17:41:19 2022 +0300

    home: fix exe path finding
2022-07-13 15:15:10 +03:00
Ainar Garipov
18876a8e5c cherry-pick: aghalg: impl json.Marshaler for NullBool
Updates #4735.

Squashed commit of the following:

commit 93a0b1dc6b668f7d9fd89d06b8f0f24dcd345356
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jul 11 17:02:36 2022 +0300

    aghalg: impl json.Marshaler for NullBool
2022-07-13 15:14:54 +03:00
Eugene Burkov
aa4a0d9880 cherry-pick: 4698 Gateway IP in DHCP Lease
Closes #4698.

Squashed commit of the following:

commit 6be0caee58926f8cea1e10650fbde0c8d97d0dac
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 8 13:41:50 2022 +0300

    update translation

commit e0370656d05e8463d73ea73568cae81187c6b2e3
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 8 13:40:54 2022 +0300

    client: validate static lease ip

commit 7f4d00f9f3a54dc93ce5d5c45e9c21745f6e39d1
Merge: 2ee79626 77e5e27d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jul 8 13:20:15 2022 +0300

    Merge branch 'master' into 4698-lease-with-gateway

commit 2ee79626a1b0c7b113dbd22ba4ef6e85ea9913ec
Merge: 471b96b8 3505ce87
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 19:34:33 2022 +0300

    Merge branch 'master' into 4698-lease-with-gateway

commit 471b96b81da8920c1e71b7110050154f912677d2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 16:07:23 2022 +0300

    dhcpd: imp docs

commit 67dd6c76f7d2df4712a57281e0f40f2ee1a1efa2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 7 15:48:47 2022 +0300

    dhcpd: restrict gateway ip for lease
2022-07-13 15:14:32 +03:00
Dimitry Kolyshev
d03d731d65 cherry-pick: all: updater exe name
Merge in DNS/adguard-home from 4219-updater to master

Squashed commit of the following:

commit f569a5f232330b83c234838a5bff8ae5277f152f
Merge: a90b4fa7 3505ce87
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 22:14:50 2022 +0530

    Merge remote-tracking branch 'origin/master' into 4219-updater

    # Conflicts:
    #	CHANGELOG.md

commit a90b4fa7782c5ec4531d8e305c0d448e84898239
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 21:56:17 2022 +0530

    home: imp code

commit da0f96b976e430fffc531072ef3e2384bc8b1f09
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 21:48:40 2022 +0530

    updater: exe name

commit 246dc9ca3b133cbc93ea59edd272674b87ff8de3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 19:18:02 2022 +0530

    all: imp docs

commit 042382d170c4d68ff67fe5544a75371337529623
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 18:02:25 2022 +0530

    all: updater exe name

commit a180c4673ead66788969865784348634af1a739e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:47:46 2022 +0530

    docs: updater exe name

commit 1a98a6eadbd96add0a488fb8f89fb7d8b0ffb3d0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:40:44 2022 +0530

    all: updater exe name

commit 1b13f5d85550dc71b08fd8e5b4258f8414a38759
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Jul 7 17:14:57 2022 +0530

    all: updater exe name
2022-07-13 15:14:06 +03:00
Ainar Garipov
33b58a42fe cherry-pick: all: use canonical names for hosts file runtime clients
Updates #4683.

Squashed commit of the following:

commit daa8fdaee574d4ac2171f6b13c5ce3f3fedd9801
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 7 19:13:29 2022 +0300

    all: use canonical names for hosts file runtime clients
2022-07-13 15:13:06 +03:00
Eugene Burkov
2e9e708647 cherry-pick: 4699 dhcp ptr
Merge in DNS/adguard-home from 4699-dhcp-ptr to master

Closes #4699.

Squashed commit of the following:

commit 0a8e2b3e22b7fad28a53db65031cc39d8755ecf4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 18:40:53 2022 +0300

    dnsforward: imp naming again

commit 0b0884a8305f18f7f69560b86be8837933e220e9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 18:26:58 2022 +0300

    dnsforward: imp naming

commit e193f53d9a1dd76d41396c06e2ec5a1e7d176557
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:26:00 2022 +0300

    all: imp chlog

commit 8ac9f84f086d9cb0b0f9da72bfc51f9b70a3dab7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:18:48 2022 +0300

    all: log changes

commit 7cdc175d02b6eacfcb6ba62a5424d11e2561a879
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 28 17:03:52 2022 +0300

    dnsforward: add tld to dhcp leased hostnames
2022-07-13 15:11:37 +03:00
Eugene Burkov
8ad22841ab cherry-pick: 4677 openwrt service
Merge in DNS/adguard-home from 4677-openwrt-service to master

Updates #4677.

Squashed commit of the following:

commit 6aed4036d3338a601a7ec5ef1ca74a407ae4c0e2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 14:49:33 2022 +0300

    home: imp docs

commit 54e32fa47ed11e50c6405ced90a400e4e69f021d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 20 14:30:08 2022 +0300

    home: fix wrt svc
2022-07-13 15:06:04 +03:00
Ainar Garipov
32cf02264c cherry-pick: 4326 improve dockerfile
Updates #4326.

* commit 'f987c2559825923b22e910d01c2d42fb06231acc':
  scripts: imp docs; upd alpine
  Simplify Dockerfile Alpine Linux apk usage
2022-07-13 15:04:26 +03:00
Ildar Kamalov
0e8445b38f cherry-pick: 4659 fix url value in filter table actions
Updates #4659

Squashed commit of the following:

commit e1bcda9566bd9f1cca965f4308c337a9adf2ce04
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 14 17:40:09 2022 +0300

    client: fix url value in filter table actions
2022-07-13 15:03:54 +03:00
Eugene Burkov
cb27ecd6c0 cherry-pick: more sysv
Merge in DNS/adguard-home from 4480-sysv-again to master

Updates #4480.

Squashed commit of the following:

commit 263fa05ab19de95b18fb07f6c89e4b9a1b24657b
Merge: 360a6468 d3f39b0a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jun 14 13:36:15 2022 +0300

    Merge branch 'master' into 4480-sysv-again

commit 360a646833ca9e0e01cb6d085e70b898a30dc2d0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 18:15:41 2022 +0300

    home: rename linux file

commit c3032533b7e00136c25d15a4ad771bb8a9c13e31
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 18:06:25 2022 +0300

    home: imp code

commit 2381c4a6ab4f6dca88123ff7b0a92f2cf9a420a8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 9 17:48:22 2022 +0300

    home: wrap sysv service
2022-07-13 15:03:38 +03:00
Ildar Kamalov
535220b3df cherry-pick: 4637 fix blocked services icons and actions highlight
Updates #4637

Squashed commit of the following:

commit d69887586d15582406fab642e576a46f8984107b
Merge: 65453371 e738508d
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jun 10 12:07:29 2022 +0300

    Merge branch 'master' into 4637-table

commit 65453371fc7309e772a12fb9f522247e1392a64a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jun 9 18:43:44 2022 +0300

    client: fix blocked services icons and actions highlight
2022-07-13 15:03:18 +03:00
Ainar Garipov
7b9cfa94f8 cherry-pick: all: imp updater
Merge in DNS/adguard-home from imp-updater to master

Squashed commit of the following:

commit 6ed487359e56a35b36f13dcbf2efbf2a7a2d8734
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 9 16:29:35 2022 +0300

    all: imp logs, err handling

commit e930044cb619a43e5a44c230dadbe2228e9a93f5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 9 15:53:35 2022 +0300

    all: imp updater
2022-07-13 15:02:51 +03:00
Ildar Kamalov
b3f2e88e9c cherry-pick: 4642 update dns addresses on encryption update
Updates #4642

Squashed commit of the following:

commit 75729120d3532dc2bd12b6c9e724a691043a1870
Merge: 5b681867 1c1ca1c6
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jun 9 11:58:13 2022 +0300

    Merge branch 'master' into 4642-dns-privacy

commit 5b68186705c3a9287a44e33c8cf7ab79060f35a4
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 18:39:02 2022 +0300

    fix

commit 46a9346154d33206e829a97021f3ef47ac2a5611
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 18:18:18 2022 +0300

    client: update dns addresses on encryption update
2022-07-13 15:02:25 +03:00
Ildar Kamalov
aa7a8d45e4 cherry-pick: 4641 fix button clickable area
Updates #4641

Squashed commit of the following:

commit f9f018388a198d7712e5caabba94035e42e393c4
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jun 7 16:21:37 2022 +0300

    client: fix button clickable area
2022-07-13 15:02:01 +03:00
Ainar Garipov
49cdef3d6a all: upd chlog, go 2022-07-13 14:46:30 +03:00
Ainar Garipov
fecd146552 client: upd i18n 2022-07-13 13:43:21 +03:00
Ainar Garipov
b01efd8c98 all: upd chlog 2022-06-06 18:10:40 +03:00
Ainar Garipov
bd4dfb261c cherry-pick: all: fix quic reply id
Merge in DNS/adguard-home from upd-dnsproxy-quic-fix to master

Squashed commit of the following:

commit a6ffa24769259c73e397e02d087dc155ed58a3e2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jun 6 15:06:00 2022 +0300

    all: fix quic reply id
2022-06-06 16:46:20 +03:00
Ainar Garipov
e754e4d2f6 cherry-pick: client: upd i18n
Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit 3feadfe31609ef52726b582ad6ba18bfa435a081
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 16:34:36 2022 +0300

    client: upd i18n
2022-06-03 16:40:11 +03:00
Ainar Garipov
b220e35c99 cherry-pick: all: replace uuid pkg; upd deps
Merge in DNS/adguard-home from 4622-upd-deps to master

Squashed commit of the following:

commit 36f407d8ab103da0f7eacdf91c153c23a5b7c3f2
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 15:22:47 2022 +0300

    home: imp mobileconfig uuid gen

commit dddd162461a4830f7c0636338430cd6e77199214
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Jun 3 13:54:29 2022 +0300

    all: replace uuid pkg; upd deps
2022-06-03 16:31:56 +03:00
Ainar Garipov
4f5131f423 all: sync more 2022-06-02 17:55:48 +03:00
Ainar Garipov
dcb043df5f all: sync with master more 2022-06-02 17:28:16 +03:00
Ainar Garipov
86e5756262 client: sync with master 2022-06-02 17:23:58 +03:00
Eugene Burkov
ba0cf5739b cherry-pick: 3142 swap arp and rdns priority
Merge in DNS/adguard-home from 3142-fix-clients to master

Updates #3142.
Updates #3597.

Squashed commit of the following:

commit 4dcabedbfb1a4e4a0aaba588f708e4625442fce8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 15:13:15 2022 +0300

    all: imp log of changes

commit 481088d05eecac1109daf378e0b4d5f6b2cf099b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 22 14:36:44 2022 +0300

    all: swap arp and rdns priority
2022-06-02 17:11:09 +03:00
Eugene Burkov
c4a13b92d2 cherry-pick: 3157 excessive ptrs
Merge in DNS/adguard-home from 3157-excessive-ptrs to master

Updates #3157.

Squashed commit of the following:

commit 6803988240dca2f147bb80a5b3f78d7749d2fa14
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:50:01 2022 +0300

    aghnet: and again

commit 1a7f4d1dbc8fd4d3ae620349917526a75fa71b47
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:49:20 2022 +0300

    aghnet: docs again

commit d88da1fc7135f3cd03aff10b02d9957c8ffdfd30
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:47:36 2022 +0300

    aghnet: imp docs

commit c45dbc7800e882c6c4110aab640c32b03046f89a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 14:41:19 2022 +0300

    aghnet: keep alphabetical order

commit b61781785d096ef43f60fb4f1905a4ed3cdf7c68
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 19 13:50:56 2022 +0300

    aghnet: imp code quality

commit 578dbd71ed2f2089c69343d7d4bf8bbc29150ace
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 12 17:02:38 2022 +0300

    aghnet: imp arp container
2022-06-02 17:05:18 +03:00
Dimitry Kolyshev
723279121a cherry-pick: whotracksme tracker links
Merge in DNS/adguard-home from 4416-ui-tracker-href to master

Squashed commit of the following:

commit 979ea82a3b4d2c2a895b81aacd613fb7e5bec586
Merge: 4fe6328b 12ee287d
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 19 15:03:13 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4416-ui-tracker-href

commit 4fe6328b276e697a2aa351c6543d2efe6d2dc2e1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 19 14:08:10 2022 +0200

    whotracksme tracker links
2022-06-02 16:53:32 +03:00
Ainar Garipov
3ad7649f7d cherry-pick: all: do not mark help-wanted issues as stale
Merge in DNS/adguard-home from help-wanted-stale to master

Squashed commit of the following:

commit 1c5ffcdd0153dd7d9d9bcc1e35dee4a0b3113f59
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Apr 22 20:04:01 2022 +0300

    all: do not mark help-wanted issues as stale
2022-06-02 16:53:13 +03:00
Ainar Garipov
2898a49d86 cherry-pick: home: rm unnecessary locking in update; refactor
Merge in DNS/adguard-home from 4499-rm-unnecessary-locking to master

Squashed commit of the following:

commit 6d70472506dd0fd69225454c73d9f7f6a208b76b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Apr 25 17:26:54 2022 +0300

    home: rm unnecessary locking in update; refactor
2022-06-02 16:51:50 +03:00
Ildar Kamalov
1547f9d35e cherry-pick: client: fix constant loading for blocked requests
Updates #4420

Squashed commit of the following:

commit 461a59e1541626020bf0bcfaf34ba7d2f4509dc7
Merge: 5c5e7b5d 2a1ad532
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Apr 25 18:46:02 2022 +0300

    Merge branch 'master' into 4420-loading-log

commit 5c5e7b5d1a69d30e40e71f49f46dea89fa8c40a2
Author: Ildar Kamalov <ik@adguard.com>
Date:   Sun Apr 24 22:18:22 2022 +0300

    client: fix constant loading for blocked requests
2022-06-02 16:45:06 +03:00
Eugene Burkov
adadd55c42 cherry-pick: 4525 fix panic
Merge in DNS/adguard-home from 3020-fix-panic to master

Closes #4525.

Squashed commit of the following:

commit f8d9e25eccb485269aa2f0275d4e08da767f9d05
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 26 15:09:11 2022 +0300

    home: imp code

commit 8fe02c8f057c05b9e8ce1de056a92e7cd69ae4c6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 26 14:44:33 2022 +0300

    home: fix panic
2022-06-02 16:44:48 +03:00
Ainar Garipov
33b0225aa4 cherry-pick: home: imp client finding logging
Updates #4526.

Squashed commit of the following:

commit 970476ea238cbab797912e1c50eca35e3f74a52f
Merge: 3e2dde81 c4ff80fd
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 14:01:17 2022 +0300

    Merge branch 'master' into 4526-add-client-logs

commit 3e2dde81d7325b75c257f333e2c4e417f4ae203d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 27 13:59:19 2022 +0300

    home: imp logs

commit 094bfe34770b4bdc504b5ae97dd2d3842b2f73cf
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 26 21:11:18 2022 +0300

    home: imp client finding logging
2022-06-02 16:43:14 +03:00
Ainar Garipov
97d4058d80 cherry-pick: home: imp openbsd init script
Closes #4533.

Squashed commit of the following:

commit 48ca9e100619e714eab565273daeb4ee9adb5b74
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 28 20:25:15 2022 +0300

    home: imp openbsd init script
2022-06-02 16:42:57 +03:00
Eugene Burkov
86207e719d cherry-pick: 4542 clientid case
Merge in DNS/adguard-home from 4542-clientid-case to master

Updates #4542.

Squashed commit of the following:

commit 2a3111ebcef09460b407cd1c870cad2391cd5650
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 20:44:18 2022 +0300

    all: fix changelog link

commit 3732def83e2a36eeff2d682149dc4dcef4e92a7d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 20:43:37 2022 +0300

    all: log changes

commit 9fe1001cf586669ae238c9c4818070cf94e23ce8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 4 19:37:33 2022 +0300

    dnsforward: lowercase clientid
2022-06-02 16:42:15 +03:00
Eugene Burkov
113f94ff46 cherry-pick: all: log changes
Updates #4273.

Squashed commit of the following:

commit ebae1a4d0944fa348b7dcb7e73e59d083c7a5e97
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed May 18 14:48:16 2022 +0300

    all: log changes
2022-06-02 16:40:13 +03:00
Dimitry Kolyshev
5673deb391 cherry-pick: all: upd dnsproxy
Merge in DNS/adguard-home from 4503-upstream-conf to master

Squashed commit of the following:

commit c6cb1babd4cbf9aacafe902e3d54ce17e8d2cc81
Merge: 75d85ed1 79d85a24
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 23 13:06:00 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4503-upstream-conf

commit 75d85ed1f4d8d5060800b2f8a4cde662db02ae30
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri May 20 13:14:16 2022 +0200

    all: upd dnsproxy

commit 781768d639388a60fc90631f819cfc5dd90b9eba
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:27:25 2022 +0200

    all: docs

commit 0dafb5b3fe11b1952d9a04294bcaaa8091b9c2a7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:17:35 2022 +0200

    all: docs

commit 0d5463e4157132b0e6be78fd97eaf5a5cb8d1edc
Merge: e2c86909 f289f4b1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 16:01:40 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4503-upstream-conf

    # Conflicts:
    #	go.mod
    #	go.sum

commit e2c869091b1386065076f44dbf9498a31c9d5451
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon May 16 15:29:17 2022 +0200

    all: upd dnsrpoxy
2022-06-02 16:39:29 +03:00
Eugene Burkov
3548a393ed cherry-pick: 4480 fix sysv service script
Merge in DNS/adguard-home from 4480-sysv-boot to master

Updates #4480.

Squashed commit of the following:

commit c9645b1f3bd22a249c666e4485818bab6769f32d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue May 24 14:25:09 2022 +0300

    home: imp sysv script

commit cc323364ba6cce0284cbc6be9133a50a51b71f56
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon May 23 21:13:06 2022 +0300

    home: fix sysv service script
2022-06-02 16:35:58 +03:00
Ainar Garipov
254515f274 cherry-pick: all: upd dnsproxy, supp rfc 9250
Updates #4592.

Squashed commit of the following:

commit 1a80875d6aa7811d7d1d978f6fa8d558dec1ca87
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue May 24 19:28:27 2022 +0300

    all: upd dnsproxy, supp rfc 9250
2022-06-02 16:34:36 +03:00
Dimitry Kolyshev
bccbecc6ea cherry-pick: all: filters json
Merge in DNS/adguard-home from 4581-filters-json to master

Squashed commit of the following:

commit da0b86983432ac1791645da328df5848daac5ea6
Merge: 62fa4fc6 a82ec09a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 25 12:58:25 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4581-filters-json

commit 62fa4fc6ff150ebb8dbd8888a58819fb644d43ad
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 25 11:55:52 2022 +0200

    all: filters json

commit 96486ffbb41947b5e748f6e35eb96ee73867eba1
Merge: 9956f0af c0ac82be
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 24 15:57:52 2022 +0200

    Merge branch 'master' into 4581-filters-json

commit 9956f0aff1b7029f336d22013a62f2871a964322
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue May 24 15:53:43 2022 +0200

    all: filters json
2022-06-02 16:33:33 +03:00
Ainar Garipov
66f53803af cherry-pick: querylog: fix oldest calc
Updates #4591.

Squashed commit of the following:

commit 70b70c78c85311363535536c7ea12336b21accf8
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed May 25 17:35:54 2022 +0300

    querylog: fix oldest calc
2022-06-02 16:32:45 +03:00
Ildar Kamalov
faef005ce7 cherry-pick: client: reset filtered logs on url params clear
Merge in DNS/adguard-home from fix-querylog-link to master

Squashed commit of the following:

commit fc4043258eb1e427a76ee44d2a4a525a6d659ab9
Merge: 25b91504 549b20bd
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:42:02 2022 +0300

    Merge branch 'master' into fix-querylog-link

commit 25b91504e8949bd381e6774148e4a7ecbb81610e
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:21:57 2022 +0300

    fix

commit f567b9b1e4eeb6499c79b05e4d837e905850a6b9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu May 26 12:20:48 2022 +0300

    client: reset filtered logs on url params clear
2022-06-02 16:32:18 +03:00
Eugene Burkov
941cd2a562 cherry-pick: 4166 udp upstream
Merge in DNS/adguard-home from 4166-udp-upstream to master

Closes #4166.

Squashed commit of the following:

commit b8b6d1c7ac1e11e83c0c68e46e7f66fdc6043839
Merge: e5f01273 ea6e033d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 20:36:40 2022 +0300

    Merge branch 'master' into 4166-udp-upstream

commit e5f0127384d84c4395da5b79a1fd4a47acbe122c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 19:41:33 2022 +0300

    client: upd upstream examples

commit bd974f22231f11f4c57e19d6d13bc45dbfdf2fdf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 1 18:36:10 2022 +0300

    all: upd proxy

commit badf1325090ecd1dc86e42e7406dfb6653e07bf1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 4 14:36:50 2022 +0300

    WIP
2022-06-02 16:31:11 +03:00
Eugene Burkov
6a4a9a0239 cherry-pick: 3978 Query Log ECS
Merge in DNS/adguard-home from 3978-ecs-ip to master

Updates #3978.

Squashed commit of the following:

commit 915b94afa4b6d90169f73d4fa171bc81bcc267a7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:46:40 2022 +0300

    all: rm dot

commit 2dd2ed081b199de7e5d8269dae5d08d53b5eea6d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 17:42:45 2022 +0300

    client: imp txt

commit 8d5a23df739f0b650f9f3870141fd83e8fa0c1e0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:36:04 2022 +0300

    client: imp text

commit 69c856749a20144822ef3f1f67c5f3e3c24f5374
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Mar 3 14:24:56 2022 +0300

    client: imp description

commit cd0150128ad29d1874492735a5d621c0803ad0bd
Merge: 28181fbc e0b557ed
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 2 21:02:16 2022 +0300

    Merge branch 'master' into 3978-ecs-ip

commit 28181fbc79eb22e7fd13cbd1d5a3c040af9fa2a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Mar 2 20:45:50 2022 +0300

    client: show ecs

commit cdc5e7f8c4155b798426d815eed0da547ef6efb7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:15:56 2022 +0300

    openapi: fix milestone

commit 404d6d822fa1ba4ed4cd41d92d4c1b805342fe55
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 20:08:21 2022 +0300

    all: fix deps, docs

commit 8fb80526f1e251d3b7b193c53a4a6dee0e22c145
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 17 19:39:34 2022 +0300

    all: add querylog ecs backend
2022-06-02 16:29:22 +03:00
Eugene Burkov
b9dbe6f1b6 cherry-pick: 4213 add bsd syslog
Merge in DNS/adguard-home from 4213-bsd-syslog to master

Updates #4046.
Closes #4213.

Squashed commit of the following:

commit 1e57c75c4184e83b09cfd27456340ca9447791be
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 16:20:32 2022 +0300

    home: imp error msg

commit 63059d031153ff9b6dc9aecd9522d2ad4f8448da
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:36:37 2022 +0300

    all: imp log of changes

commit 682c3c9e8986b6bdf2d0c665c9cad4a71fd2cc83
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:29:29 2022 +0300

    home: imp code

commit 86c311a71d07823c18521890bea7c898c117466b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 28 15:03:02 2022 +0300

    home: add bsd syslog
2022-06-02 16:26:25 +03:00
Ainar Garipov
7fec111ef8 cherry-pick: home: imp openbsd init script
Closes #4533.

Squashed commit of the following:

commit 48ca9e100619e714eab565273daeb4ee9adb5b74
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 28 20:25:15 2022 +0300

    home: imp openbsd init script
2022-06-02 16:25:01 +03:00
Eugene Burkov
5e1bd99718 cherry-pick: 4276 upd quic port
Merge in DNS/adguard-home from 4276-doq-port to master

Closes #4276.

Squashed commit of the following:

commit cbdde622b54d0d5d11d1b4809f95a41ace990a1b
Merge: d32c13e9 2c33ab6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 15:47:43 2022 +0300

    Merge branch 'master' into 4276-doq-port

commit d32c13e98f0fed2c863160e4e2de02ae3038e3df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:55:09 2022 +0300

    all: fix link

commit 0afd702f5192d727927df2f8d95b9317811a1be0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:47:38 2022 +0300

    all: imp docs, log changes

commit 9a77fc3daf78d32c577f1bc49aa1f8bc352d44e3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:41:30 2022 +0300

    home: upd quic port
2022-06-02 16:23:15 +03:00
Eugene Burkov
9d75f72ceb cherry-pick: 1730 bogus cidr
Merge in DNS/adguard-home from 1730-bogus-cidr to master

Closes #1730.

Squashed commit of the following:

commit 0be54259ca4edb8752e9f7e5ea5104a2b51ed440
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 25 18:50:01 2022 +0300

    all: imp log of changes

commit 59fb7a8c469216823ff54621ec40a4d084836132
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 25 18:46:34 2022 +0300

    all: log changes

commit 9206b13dd715fdf1180d1d572d1b80024b9e6592
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 25 18:41:26 2022 +0300

    all: upd dnsproxy
2022-06-02 16:20:41 +03:00
Ainar Garipov
d98d96db1a all: upd chlog 2022-06-02 16:09:50 +03:00
Ainar Garipov
6a0ef2df15 all: upd chlog, go 2022-04-13 14:30:17 +03:00
Dimitry Kolyshev
75c2eb4c8a cherry-pick: svcb dohpath support
Merge in DNS/adguard-home from 4463-ddr-support to master

Squashed commit of the following:

commit 99a149e9024354ad0341739c3c9b08cefbd74468
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Apr 12 14:13:17 2022 +0200

    imp docs

commit 26150be8df8b35e47c108f6e3319c57b39fb8e38
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 20:36:18 2022 +0200

    imp code docs

commit 5a4607f71abba83a9ac8753abd74c9fb97e4a545
Merge: 00f0abf5 9f0fdc5e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 16:14:49 2022 +0200

    Merge remote-tracking branch 'origin/master' into 4463-ddr-support

    # Conflicts:
    #	internal/dnsforward/svcbmsg.go

commit 00f0abf5eea07aeeebc2a856a958215021a51ab7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 11 16:06:42 2022 +0200

    svcb dohpath support

commit ace81ce1ea2fb96c4434c6c1fded4a79427cf17e
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Thu Apr 7 14:31:32 2022 +0200

    svcb dohpath support

commit a1b5df4fb2e87dab265d6ca55928610a6acc1c00
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 6 16:53:17 2022 +0200

    svcb dohpath support
2022-04-12 21:09:28 +03:00
Dimitry Kolyshev
d021a67d66 cherry-pick: upd bamboo-specs snapcraft
Merge in DNS/adguard-home from upd-bamboo-spec to master

Squashed commit of the following:

commit c26c70f97cbce98afd5c7d4241188d6949869c2a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 8 13:51:23 2022 +0200

    upd bamboo-specs snapcraft

commit afe40c03b70d2b2dff9c7c25044d7924bdd3c765
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Fri Apr 8 13:10:38 2022 +0200

    upd bamboo-specs snapcraft
2022-04-12 21:09:04 +03:00
Ainar Garipov
4ed97cab12 cherry-pick: dnsforward: upd svcp param ech name
Merge in DNS/adguard-home from upd-ech-dnsrewrite to master

Squashed commit of the following:

commit b5d9e8643fcb0d7fe7bc44c6d8fc8a9d3f2c9595
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 7 18:01:18 2022 +0300

    all: imp chlog

commit 447c5ea6bc2031d4af46578bdb8d724bff001ca0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Apr 7 15:40:18 2022 +0300

    dnsforward: upd svcp param ech name
2022-04-12 21:08:40 +03:00
Eugene Burkov
a38742eed7 cherry-pick: 4437 imp help output
Merge in DNS/adguard-home from imp-help to master

Updates #4437.

Squashed commit of the following:

commit 941338b93e19021c5b211e9e644387e4326533ce
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 7 13:59:55 2022 +0300

    home: imp help output
2022-04-12 21:08:24 +03:00
Eugene Burkov
5efa95ed26 cherry-pick: 4437 depr memory opt
Merge in DNS/adguard-home from 4437-rm-mem-opt to master

Updates #4437.
Updates #2044.

Squashed commit of the following:

commit d1e5520213f6b68570d18a8d831d4923112901ba
Merge: 73a6b494 8bb95469
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 19:37:09 2022 +0300

    Merge branch 'master' into 4437-rm-mem-opt

commit 73a6b4948cb32f1cb79a54b244018b29382fad76
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 18:33:23 2022 +0300

    all: imp log of changes

commit a62efcdcd44de300726c906c7f6198c0a02d4ccf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 6 18:27:42 2022 +0300

    home: depr memory opt
2022-04-12 21:07:46 +03:00
Ildar Kamalov
04db7db607 cherry-pick: 700 validate only enabled encryption form
Merge in DNS/adguard-home from 700-validate to master

Updates #700.

Squashed commit of the following:

commit 9cd9ff2d23352e00c7782cf68195809111c832e5
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Apr 6 18:50:11 2022 +0300

    client: validate only enabled encryption form
2022-04-12 21:07:26 +03:00
Ainar Garipov
d17c6c6bb3 all: upd go, chlog, tools 2022-04-06 18:27:21 +03:00
Ildar Kamalov
b2052f2ef1 cherry-pick: fix down flag
Squashed commit of the following:

commit ea446e844a21e7e7e0271d4d133c581014facda1
Merge: bb8cabfa 5e71f5df
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Mar 31 10:49:20 2022 +0300

    Merge branch 'master' into client-down-flag

commit bb8cabfae8e2e3eaa09f48ffe7d2fb3b308d31fb
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Mar 30 19:27:30 2022 +0300

    client: fix down flag
2022-04-06 17:50:54 +03:00
Eugene Burkov
cddcf852c2 cherry-pick: aghnet: fix catching timeout errors
Merge in DNS/adguard-home from fix-is-timeout to master

Squashed commit of the following:

commit b0fefd01f27a835a34e44beb2eb2c34027960a51
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Mar 29 15:57:06 2022 +0300

    aghnet: fix catching timeout errors
2022-04-06 17:50:27 +03:00
Eugene Burkov
1def426b45 cherry-pick: add go sumdb env
Merge in DNS/adguard-home from cn-sumdb to master

Squashed commit of the following:

commit 439973292f473efa72fb6a733a32be45e634274e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 28 16:51:28 2022 +0300

    Makefile: add go sumdb env
2022-04-06 17:50:08 +03:00
Ainar Garipov
b114fd5279 cherry-pick: home: fix types
Updates #4424.

Squashed commit of the following:

commit 784b4940d46ce74edbfbbde6e5b24f95dcb4bc70
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Mar 24 17:07:41 2022 +0300

    home: fix types
2022-04-06 17:49:44 +03:00
Eugene Burkov
d27c3284f6 cherry-pick: 4276 upd quic port
Merge in DNS/adguard-home from 4276-doq-port to master

Closes #4276.

Squashed commit of the following:

commit cbdde622b54d0d5d11d1b4809f95a41ace990a1b
Merge: d32c13e9 2c33ab6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 15:47:43 2022 +0300

    Merge branch 'master' into 4276-doq-port

commit d32c13e98f0fed2c863160e4e2de02ae3038e3df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:55:09 2022 +0300

    all: fix link

commit 0afd702f5192d727927df2f8d95b9317811a1be0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:47:38 2022 +0300

    all: imp docs, log changes

commit 9a77fc3daf78d32c577f1bc49aa1f8bc352d44e3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Mar 21 21:41:30 2022 +0300

    home: upd quic port
2022-04-06 17:49:12 +03:00
Ildar Kamalov
ba24a26b53 cherry-pick: 4409 fix icons height
Updates #4409

Squashed commit of the following:

commit 132073ccf00ba6eb6ddacfc82c8d2e01f3d4b011
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 15:22:33 2022 +0300

    client: remove height

commit 29970f33e7af26e406c442510d626fc0cfdae0ce
Merge: 96b3abcf 77858586
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 15:10:49 2022 +0300

    Merge branch 'master' into 4409-icon

commit 96b3abcfa4561da466cc53331b8f751d55f59351
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Mar 21 10:22:55 2022 +0300

    client: fix icons height
2022-04-06 17:44:11 +03:00
Eugene Burkov
3e6678b6b4 cherry-pick: filtering: fix qq regex legacy
Merge in DNS/adguard-home from qq-rule to master

Updates #3717.

Squashed commit of the following:

commit 1e2d50077067e5f95da645091686349ce9c8a6bc
Merge: 7290a1c4 b16b1d1d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Mar 23 14:14:10 2022 +0300

    Merge branch 'master' into qq-rule

commit 7290a1c456a7f47e91cc9485f5e112b92cb595ba
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Mar 18 20:36:17 2022 +0300

    filtering: fix qq regex legacy
2022-04-06 17:43:05 +03:00
Ainar Garipov
83fd6f9782 cherry-pick: Fix unsupported regex for QQ blocked rules
Updates #3717.

* commit 'ded9842cd7fbbae0c3a55cd1f468ade22cab0d97':
  Fix unsupported regex for QQ blocked rules
2022-04-06 17:42:49 +03:00
Ainar Garipov
52bc1b3f10 all: upd go, chlog 2022-03-04 15:38:59 +03:00
Ainar Garipov
dd2153b7ac cherry-pick: scripts: imp snap building
Closes #4239.

Squashed commit of the following:

commit 942c03bd88b81d813a12136a135ca6dc003fedf3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Feb 9 20:38:36 2022 +0300

    scripts: imp snap building
2022-03-01 15:44:43 +03:00
Ainar Garipov
dd96a34861 all: upd chlog 2022-03-01 15:15:59 +03:00
Ainar Garipov
daf26ee25a all: upd chlog 2022-03-01 15:12:34 +03:00
Ainar Garipov
7e140eaaac cherry-pick: client: upd i18n
Merge in DNS/adguard-home from 2643-upd-i18n to master

Squashed commit of the following:

commit 1f36b960877ee2c30319e26132db892fb8a2ef71
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Mar 1 15:05:24 2022 +0300

    client: upd i18n
2022-03-01 15:11:21 +03:00
Ainar Garipov
d07a712988 all: upd chlog 2022-02-28 19:15:59 +03:00
Ainar Garipov
95863288bf cherry-pick: client: fix link in client form
Updates #4244.

Squashed commit of the following:

commit 20d558e9e6935555a13e1aebc7d364e6f1910e9e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 28 19:01:32 2022 +0300

    client: fix link in client form
2022-02-28 19:14:46 +03:00
Ainar Garipov
ea12be658b all: upd chlog 2022-02-21 17:10:19 +03:00
Ainar Garipov
faa7c9aae5 cherry-pick: client: upd i18n
Updates #2643.

Squashed commit of the following:

commit 048c245ab682f0799c2f7a7f0435a1898a482392
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Feb 21 16:58:10 2022 +0300

    client: upd i18n
2022-02-21 17:08:25 +03:00
Ainar Garipov
e3653e8c25 all: upd chlog 2022-02-18 21:01:24 +03:00
Ainar Garipov
b40cb24822 all: upd chlog 2022-02-14 17:14:37 +03:00
Ainar Garipov
74004c1aa0 cherry-pick: client: use strict search by client
Updates #4271.

Squashed commit of the following:

commit 10a113126306fce51b4dd10a696b8c7d3213a445
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Feb 11 18:37:18 2022 +0300

    client: more strict search

commit 7aa24129195c0eba442bfe43564469fdb2a5b138
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Feb 11 18:22:18 2022 +0300

    client: use strict search by client
2022-02-14 17:06:16 +03:00
Ainar Garipov
3e240741f1 cherry-pick: scripts: imp mips compat
Updates #4269.

Squashed commit of the following:

commit f633e875f4f0ab767a0537d9bfe95734823f8a51
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Feb 11 17:33:53 2022 +0300

    scripts: imp mips compat
2022-02-14 17:06:06 +03:00
Ainar Garipov
6cfdbef1a5 cherry-pick: client: imp validation texts
Merge in DNS/adguard-home from imp-i18n to master

Squashed commit of the following:

commit c58c00383824a88ea8e22a845e422ba2ff7d225e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Feb 10 20:21:00 2022 +0300

    client: imp validation texts
2022-02-14 17:05:33 +03:00
Ainar Garipov
d9bde6425b cherry-pick: all: use "ClientID" consistently
Closes #4242.
Updates #4244.

Squashed commit of the following:

commit 3a2296a7a70006cf6777e54ce1e2fc3559aec5be
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Feb 9 21:23:43 2022 +0300

    client: imp more

commit 3aacc8696ac694ff459fd33ba7beeeabd2569a55
Merge: b28a120f 2a5b5f19
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Feb 9 21:21:59 2022 +0300

    Merge branch 'master' into 4244-imp-i18n

commit b28a120fe9aa68507b173717059b7b259097d6a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Feb 9 14:49:49 2022 +0300

    client: imp texts more

commit c1fa6ca336f2d5bdcc67836f348be4843a0a8f79
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 8 21:12:15 2022 +0300

    all: use "ClientID" consistently
2022-02-14 17:04:33 +03:00
Ainar Garipov
e2ae9e1591 cherry-pick: client: upd i18n
Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit e2f9e9f52a424b7c13beebfc2f8fea3814d3b2f4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 8 13:48:17 2022 +0300

    client: upd i18n
2022-02-14 17:04:18 +03:00
Ainar Garipov
5ebcbfa9ad all: upd go 2022-02-11 16:27:53 +03:00
Ainar Garipov
e276bd7a31 all: upd chlog, minimize diff to master 2022-02-07 20:35:33 +03:00
Eugene Burkov
659b2529bf cherry-pick: upd changelog
Merge in DNS/adguard-home from changelog-right-now to master

Squashed commit of the following:

commit b391a1f8ac666de67ad6d00c9cbf6e90614f16c7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 7 20:18:25 2022 +0300

    fix changelog

commit 39878b75c9ecc91668be759d4cc033961c91c2c5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 7 20:15:43 2022 +0300

    all: log changes
2022-02-07 20:26:43 +03:00
Eugene Burkov
97b3ed43ab cherry-pick: 4254 fix optimistic
Merge in DNS/adguard-home from 4254-fix-optimistic to master

Updates #4254.

Squashed commit of the following:

commit 652e2c2ab9405b9a6ed5d153b6b508e3b87ce66e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Feb 7 18:55:34 2022 +0300

    all: upd proxy
2022-02-07 20:23:51 +03:00
Ainar Garipov
767d6d3f28 cherry-pick: all: add gh milestone links to chlog
Merge in DNS/adguard-home from chlog-ms-links to master

Squashed commit of the following:

commit 97156f1452a7713e5e8d66a9b5eeac25fb97ab04
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Feb 4 17:56:58 2022 +0300

    all: add gh milestone links to chlog
2022-02-07 20:12:54 +03:00
Ainar Garipov
31fc9bfc52 cherry-pick: scripts: add link to platforms page
Closes #4209.

Squashed commit of the following:

commit 12d99e7454ff01e00f29e51d002147a04a77a2b3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 1 19:55:31 2022 +0300

    scripts: imp docs

commit 12c4dabea2bac04601202a05d0c820ff2e32c93e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 1 19:49:16 2022 +0300

    scripts: add link to platforms page
2022-02-07 20:10:11 +03:00
Ainar Garipov
3f06b02409 cherry-pick: all: imp ann url
Updates #4209.

Squashed commit of the following:

commit 0c31a59c5bf6bcc27a4779adf226d9a1ac9eece1
Merge: 803f32db 8455940b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Feb 1 19:33:55 2022 +0300

    Merge branch 'master' into 4209-ann-url

commit 803f32dbc7276077a4374ed0f5e0a1fa36f91c9b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Feb 1 14:46:47 2022 +0300

    client: add manual update link to update topline

commit ca375b52fa53503a3987b9723eb9a1d74878e890
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jan 31 20:49:42 2022 +0300

    all: imp ann url
2022-02-07 20:09:54 +03:00
Ildar Kamalov
5bf958ec6b cherry-pick: 4212 fix query log search results
Closes #4212.

Squashed commit of the following:

commit cd854e5bf71953c753c690c28b5571f2c8b1ea0f
Merge: 8532ca80 bf9b35b9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jan 31 20:10:17 2022 +0300

    Merge branch 'master' into 4212-logs

commit 8532ca80d135e4c306ac4d0c999475d77ba51a02
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Jan 31 19:22:52 2022 +0300

    fix lint

commit 1a85074180d95d7a7aad854c75a7a811aee719e9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Jan 31 19:14:54 2022 +0300

    client: fix query log search results
2022-02-07 20:09:27 +03:00
Ainar Garipov
959d9ff9a0 cherry-pick: client: upd manual upd link
Closes #4208.

Squashed commit of the following:

commit 4ae27b5f7cd6b0f4ec0c9041d92c4d1ac00dd622
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jan 31 18:34:18 2022 +0300

    client: upd manual upd link
2022-02-07 20:09:09 +03:00
Ainar Garipov
4813b4de25 all: upd chlog, minimize diff to master 2022-01-28 17:44:10 +03:00
Eugene Burkov
119100924c cherry-pick: 4216 simpl hosts
Merge in DNS/adguard-home from 4216-hosts-explode to master

Updates #4216.

Squashed commit of the following:

commit a6ed131923496d9bbd1d80c652d4584951528c4a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 27 19:11:23 2022 +0300

    aghnet: imp docs

commit 25cca065c3c6dc227288cdd0803dc3ff8f9c3ca4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 27 18:53:16 2022 +0300

    aghnet: simpl hosts container
2022-01-28 16:31:04 +03:00
Ainar Garipov
bd584de4ee cherry-pick: 4162 fix theme color
Updates #4162.

* commit '2263adbbe0c14cb914451d131d94ab6fd236852c':
  Update login.html
  Update install.html
  Update index.html
2022-01-28 16:30:35 +03:00
Ainar Garipov
ede85ab2f2 all: upd chlog 2022-01-25 14:12:12 +03:00
Ainar Garipov
12c20288e4 cherry-pick: client: upd i18n
Updates #2643.

Squashed commit of the following:

commit bd6bc0aeaa1bd928ae39642691b913befbc0f396
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jan 25 14:04:10 2022 +0300

    client: upd i18n
2022-01-25 14:11:12 +03:00
Ainar Garipov
5bbbf89c10 cherry-pick: all: upd dnsproxy
Merge in DNS/adguard-home from imp-logs to master

Squashed commit of the following:

commit bff4c3757b61db63320af72e1af56649f6f70a50
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jan 24 17:25:34 2022 +0300

    all: upd dnsproxy
2022-01-24 17:44:54 +03:00
Eugene Burkov
d55393ecd5 cherry-pick: client: upd i18n
Merge in DNS/adguard-home from upd-i18n to master

Squashed commit of the following:

commit e3dfb6cd66813d45591f74c9cdddab8b61143db3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jan 24 14:52:19 2022 +0300

    client: upd i18n
2022-01-24 17:41:04 +03:00
Eugene Burkov
2b5927306f cherry-pick: 2846 cover aghnet vol.1
Merge in DNS/adguard-home from 2846-cover-aghnet-vol.1 to master

Updates #2846.

Squashed commit of the following:

commit 368e75b0bacb290f9929b8a5a682b06f2d75df6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jan 21 19:11:59 2022 +0300

    aghnet: imp tests

commit 8bb3e2a1680fd30294f7c82693891ffb19474c6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jan 21 18:27:06 2022 +0300

    aghnet: rm unused test

commit 28d8e64880f845810d0af629e5d1f06b9bde5b28
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jan 21 18:18:22 2022 +0300

    aghnet: cover with tests
2022-01-21 19:24:38 +03:00
Ainar Garipov
4f016b6ed7 all: upd chlog 2022-01-21 17:11:27 +03:00
Ildar Kamalov
3a2a6d10ec cherry-pick: 3971 fix client id error message
Updates #3971

Squashed commit of the following:

commit f6b855a16daaec7bfca1e1653b4b9c4180c2d80e
Merge: 0cb31dbb 5ec4a4da
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jan 20 18:19:20 2022 +0300

    Merge branch 'master' into 3971-client-id

commit 0cb31dbbea785fb5ba11a8efe2b6653aece7cd97
Author: Natalia Sokolova <n.sokolova@adguard.com>
Date:   Thu Jan 20 11:41:06 2022 +0300

    client/src/__locales/en.json edited online with Bitbucket

commit 7999f260d83adcb2fc8d5d5e40cb1934e0333873
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Jan 19 15:58:18 2022 +0300

    client: fix client id error message
2022-01-21 17:08:07 +03:00
Eugene Burkov
2491426b09 cherry-pick: 4142 stats panic
Merge in DNS/adguard-home from 4142-stats-panic to master

Updates #4142.

Squashed commit of the following:

commit bf168f50ac86bdfdab73bf7285705f09f87b6c72
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 20 17:13:41 2022 +0300

    stats: imp more

commit bb638211da7d0c51959ded2dacb72faea00befb4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 20 17:09:31 2022 +0300

    stats: imp code quality

commit 27ac52f15e4e0f4112ce7a6b47b03f963463393e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 20 17:00:09 2022 +0300

    stats: recover panic on init

commit 1ffcebbb9062438170b010e1c7bad3c6cef4cfc1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 20 14:19:01 2022 +0300

    all: fix some typos
2022-01-21 17:08:07 +03:00
Ildar Kamalov
5ebdd1390e cherry-pick: 4143 sort client ids
Merge in DNS/adguard-home from 4143-clients-sort to master

Updates #4143.

Squashed commit of the following:

commit a4b547eb46a54bdfdc7d342fab5f8ecfa54f5d06
Merge: d369c11c d82b2902
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Jan 20 11:58:42 2022 +0300

    Merge branch 'master' into 4143-clients-sort

commit d369c11c69665510043f63e0283e1ca1b2974289
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Jan 19 16:53:39 2022 +0300

    client: fix sort ip method

commit d767a1199c37ad9df7f3bc2d362d840b0226d836
Author: Ildar Kamalov <ik@adguard.com>
Date:   Wed Jan 19 16:23:23 2022 +0300

    client: sort client ids
2022-01-21 17:08:07 +03:00
Eugene Burkov
b7f0247575 cherry-pick: 4095 fix duplicating port
Merge in DNS/adguard-home from 4095-port-3000 to master

Updates #4095.

Squashed commit of the following:

commit 968cc806264898523d29c4ec20b3ce6a69abb09c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:26:33 2022 +0300

    home: fix typo

commit 03c6798db6a4ca726a7b5a683e475a8a74f79fe1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:20:34 2022 +0300

    all: more naming imps

commit d3d417fcb24a1859f53a743b3533faa81b6bef19
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:10:14 2022 +0300

    aghalgo: rename into aghalg

commit 6e106006d07a747ff4ddf1271532106c3a3e2b20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 20:05:43 2022 +0300

    all: imp names, docs

commit 12c8d9fde0d0cc5b953da30b042171ba7c53da5d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 19:57:21 2022 +0300

    all: fix log of changes

commit 49c7a705b9b1ad8f2ef68fa807f9b6b8c447b421
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 19 19:51:00 2022 +0300

    home: fix duplicating port 3000
2022-01-21 17:08:07 +03:00
Ainar Garipov
e28186a28a cherry-pick: scripts: imp sh lint 2022-01-21 17:08:03 +03:00
Eugene Burkov
de1a7ce48f cherry-pick: 4133 empty rewrite
Merge in DNS/adguard-home from 4133-empty-rewrite to master

Closes #4133.

Squashed commit of the following:

commit 4d2313c211c3955922d340c006b323c65e5e5ba4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 18 21:36:21 2022 +0300

    all: log changes

commit 5b8e392a2225c215fc117223d3f6553f8bdf21cd
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 18 21:32:57 2022 +0300

    all: upd urlfilter
2022-01-21 17:02:42 +03:00
Ainar Garipov
48480fb33b cherry-pick: home: show version in install api
Closes #4026.

Squashed commit of the following:

commit bcd1315a10e819daee3aee323427d90a27860b4a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jan 18 14:57:49 2022 +0300

    openapi: fix example

commit b56e27c5ac1fc7c3f595057d77607479d72ec50a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Tue Jan 18 14:55:51 2022 +0300

    client: show version on install page

commit 95dfbfaa1235deef7b55e51457d11c677f6ef6b5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jan 18 14:29:08 2022 +0300

    home: show version in install api
2022-01-21 16:59:57 +03:00
Eugene Burkov
f41332fe6b cherry-pick: 4120 service domain validation
Merge in DNS/adguard-home from 4120-fix-services to master

Closes #4120.

Squashed commit of the following:

commit ca2e5faf64f567cc6647a300181712236158e69d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 18 14:14:54 2022 +0300

    dnsforward: imp docs

commit 9ed5f536e691dcdee5b7c94e161c738d31ff8588
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 18 13:50:33 2022 +0300

    dnsforward: fix reverse domain validation
2022-01-21 16:59:39 +03:00
Ainar Garipov
1f8b340b8f cherry-pick: all: upd dnsproxy
Updates #4065.

Squashed commit of the following:

commit d65d2e3a783910b9cb95c5bcfbcf1af11da666d5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Jan 17 18:47:17 2022 +0300

    all: upd dnsproxy
2022-01-21 16:57:39 +03:00
Eugene Burkov
fdaf1d09d3 cherry-pick: 4074 fix upstream test
Merge in DNS/adguard-home from 4074-upstream-test to master

Updates #4074.

Squashed commit of the following:

commit 0de155b1e175a892b259791ff6d6e6f351bcfcf2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 12 19:20:01 2022 +0500

    dnsforward: fix upstream test
2022-01-21 16:50:46 +03:00
Eugene Burkov
b9682c4f10 cherry-pick: 4079 fix hosts container aliases
Merge in DNS/adguard-home from 4079-hosts-again to master

Updates #4079.

Squashed commit of the following:

commit 6aa8cbf32e8e47ba46bf5fba7681a10b68b4bc01
Merge: 19dba371 34c95f99
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 12 14:05:30 2022 +0500

    Merge branch 'master' into 4079-hosts-again

commit 19dba371cc30ab8b75b0116833f4ecf0ef0f182f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 12 14:05:20 2022 +0500

    aghnet: imp docs

commit 9f341eb8ee4ba8468240bc3eeeb4951a3f7f5e6d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jan 10 18:44:17 2022 +0500

    aghnet: fix races

commit fd66191c7637c8584711e5bb8186494327ce0f87
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 6 17:21:14 2022 +0500

    aghnet: fix hosts container aliases
2022-01-21 16:48:17 +03:00
Eugene Burkov
69dcb4effd cherry-pick: 4046 darwin service message
Merge in DNS/adguard-home from 4046-log-dir to master

Closes #4046.

Squashed commit of the following:

commit 05140550b14f477f52487c575f56428ce9e6fa10
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jan 5 17:54:11 2022 +0500

    all: add macOS service msg
2022-01-21 16:47:34 +03:00
Ainar Garipov
d50fd0ba91 all: upd chlog 2021-12-29 22:39:11 +03:00
Ainar Garipov
c2c7b4c731 cherry-pick: all: upd dnsproxy
Updates #4042.

Squashed commit of the following:

commit 7531b974a6142fafee825ce9ca2ea202619b95af
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 29 22:01:54 2021 +0300

    all: upd dnsproxy
2021-12-29 22:38:22 +03:00
Ainar Garipov
952d5f3a3d all: fix release script 2021-12-29 19:20:26 +03:00
Ainar Garipov
3f126c9ec9 all: prepare chlog 2021-12-29 16:22:14 +03:00
Ainar Garipov
0be58ef918 all: imp chlog 2021-12-29 16:16:40 +03:00
Ainar Garipov
8f9053e2fc all: backport, prepare release 2021-12-29 16:09:01 +03:00
Ainar Garipov
68452e5330 cherry-pick: client: upd i18n
Updates #2643.

Squashed commit of the following:

commit bc3de579e00762bc2c4b62fb1f7ba73837c10bff
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 29 15:37:29 2021 +0300

    client: upd si-lk i18n again

commit 2cd5436b6e8c1918855aff58dd0958fe47b47e90
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 29 15:08:21 2021 +0300

    client: upd i18n
2021-12-29 16:03:34 +03:00
Ainar Garipov
2eacc46eaa cherry-pick: all: opt log levels more
Updates #3929.

Squashed commit of the following:

commit 0d4aadeff1c4de1440795faf83eb072c46392ff3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Dec 28 16:34:44 2021 +0300

    all: opt log levels more
2021-12-29 16:03:23 +03:00
Ainar Garipov
74dcc91ea7 cherry-pick: all: imp uniq validation err msgs
Updates #3975.

Squashed commit of the following:

commit f8578c2afb1bb5786e7b855a1715e0757bc08510
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Dec 28 16:39:13 2021 +0300

    aghalgo: imp docs

commit d9fc625f7c4ede2cf4b0683ad5efd0ddf9b966b1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Dec 28 16:21:24 2021 +0300

    all: imp uniq validation err msgs
2021-12-29 16:03:03 +03:00
Ainar Garipov
dd7bf61323 cherry-pick: aghnet: fix ipset init errors
Updates #4027.

Squashed commit of the following:

commit 9ac0cc27ca94e630cc321c90b60b271499af4d9b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 20:26:22 2021 +0300

    aghnet: fix ipset init errors
2021-12-29 16:02:50 +03:00
Ainar Garipov
2819d6cace cherry-pick: filtering: fix rw to subdomain
Updates #4016.

Squashed commit of the following:

commit 83bb15c5a5098103cd17e76b49f456fb4fa73408
Merge: 81905503 313555b1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:36:44 2021 +0300

    Merge branch 'master' into 4016-rw-subdomain

commit 81905503c977c004d7ddca1d4e7537bf76443a6e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:35:51 2021 +0300

    filtering: fix self reqs

commit b706f481f00232d28dade0bd747a7496753c7deb
Merge: 29cf83de 661f4ece
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:13:08 2021 +0300

    Merge branch 'master' into 4016-rw-subdomain

commit 29cf83de8e3ff60ea1c471c2a161055b1377392d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:07:08 2021 +0300

    all: fix docs

commit 9213fd8ec2b81e65b1198ab241400065f14684b1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 18:44:06 2021 +0300

    filtering: fix rw to subdomain
2021-12-29 16:02:04 +03:00
Eugene Burkov
75355a6883 cherry-pick: 3868 log freebsd reload fix
Merge in DNS/adguard-home from 3868-changelog to master

Squashed commit of the following:

commit 92ccf7422c4c1342c160e4806cbf9fb17c22749b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 27 19:22:47 2021 +0300

    all: log more changes
2021-12-29 16:01:31 +03:00
Eugene Burkov
e9c007d56b cherry-pick: 3868 imp service uninstall
Merge in DNS/adguard-home from 3868-imp-uninstall to master

Closes #3868.
Updates #3457.

Squashed commit of the following:

commit 6f50713407980c27e5b14bef4dc8839e134ec5c8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 27 19:06:13 2021 +0300

    all: imp openwrt

commit 59f058f8ec7f5ac8cb795bf837c396601652a6ff
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 27 17:26:32 2021 +0300

    all: imp code && docs

commit bab95366b0ffa40d96de5bb8116ec14606e310ed
Merge: 92ebc210 52f36f20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 27 17:06:25 2021 +0300

    Merge branch 'master' into 3868-imp-uninstall

commit 92ebc210f04d5e02c3eef726017a0d5687f4bc4c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 27 13:18:58 2021 +0300

    home: imp freebsd script & log changes

commit 583ffc256e9f87cf19da2eca8bbefc9e00ea86cc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 16 14:08:46 2021 +0300

    all: imp service uninstall
2021-12-29 16:01:09 +03:00
Ainar Garipov
84c9085516 cherry-pick: filtering: restore rewrite behavior with other question types
Updates #4008.

Squashed commit of the following:

commit babbc29331cfc2603c0c3b0987f5ba926690ec3e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Dec 24 18:46:20 2021 +0300

    filtering: restore rewrite behavior with other question types
2021-12-24 22:41:10 +03:00
Ainar Garipov
9f36e57c1e cherry-pick: all: opt log levels
Updates #3929.

Squashed commit of the following:

commit bfb2361d81a0667c36193484ca125d08e5638b21
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Dec 24 17:23:39 2021 +0300

    all: opt log levels
2021-12-24 22:41:10 +03:00
Eugene Burkov
7528699fc2 cherry-pick: 3987 Fix nil pointer dereference
Merge in DNS/adguard-home from 3987-fix-nil-deref to master

Updates #3987.
Updates #2846.

Squashed commit of the following:

commit d653e09ce88a8b10b2a17fea1563c419895c714c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 20:08:51 2021 +0300

    all: log changes

commit c47a4eeacf76fa7df2d01af166dee9d52528ac58
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 19:22:39 2021 +0300

    aghnet: fix windows tests

commit 9c91f14ccfe967ada3c00ddb86d673238e52c12d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 19:09:49 2021 +0300

    aghnet: imp code readability, docs

commit d3df15d1892e4ebfe7f8ea7144e39a0c712fce52
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 18:47:28 2021 +0300

    aghnet: fix nil pointer dereference
2021-12-24 22:41:10 +03:00
Eugene Burkov
d280151c18 cherry-pick: 3998 Make hosts rules match exactly
Merge in DNS/adguard-home from 3998-fix-hosts-gen to master

Closes #3998

Squashed commit of the following:

commit b565d51afb6c292dd16accd45b7d37ed386714e8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 16:25:02 2021 +0300

    aghnet: make hosts rules match exactly
2021-12-24 22:41:10 +03:00
Ainar Garipov
b44c755d25 cherry-pick: all: upd dnsproxy
Updates #3977.

Squashed commit of the following:

commit 3aaaacac102cdea04ae46b36d2dd3a3be7d50147
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Dec 23 16:15:11 2021 +0300

    all: upd dnsproxy
2021-12-24 22:41:05 +03:00
Ainar Garipov
e4078e87a1 cherry-pick: 3945 log success
Updates #3945.

* commit 'ebe86ce00ebca3431a96a44c3616af3ac42250ab':
  home: imp auth
  Log successful login attempts in addition to failed ones
2021-12-24 22:23:22 +03:00
Eugene Burkov
be36204756 cherry-pick: Update miekg/dns
Merge in DNS/adguard-home from upd-dns-lib to master

Updates #2275.

Squashed commit of the following:

commit 54d0485157ac4f08830ad7d8ca9be49eef87d678
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Dec 23 13:31:34 2021 +0300

    all: upd dns lib
2021-12-24 22:22:47 +03:00
Ainar Garipov
b5409d6d00 cherry-pick: client: imp en i18n
Merge in DNS/adguard-home from en-i18n-safe-browsing to master

Squashed commit of the following:

commit dd32a58c3761818a10386b4a1d9e6871da59c71e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 22 17:31:35 2021 +0300

    client: imp en i18n
2021-12-24 22:19:07 +03:00
Ainar Garipov
f3d6bce03e cherry-pick: scripts: add network-control plug
Updates #3976.

Squashed commit of the following:

commit 49d8a3a2d333c7896530c8a44c5ef06c396b5ae0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 22 16:20:45 2021 +0300

    scripts: add network-control plug
2021-12-24 22:18:19 +03:00
242 changed files with 14298 additions and 11719 deletions

View File

@@ -1,8 +1,8 @@
'name': 'build'
'env':
'GO_VERSION': '1.24.1'
'NODE_VERSION': '18'
'GO_VERSION': '1.23.4'
'NODE_VERSION': '16'
'on':
'push':
@@ -39,7 +39,7 @@
'with':
'node-version': '${{ env.NODE_VERSION }}'
- 'name': 'Set up Go modules cache'
'uses': 'actions/cache@v4'
'uses': 'actions/cache@v2'
'with':
'path': '~/go/pkg/mod'
'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}"
@@ -48,7 +48,7 @@
'id': 'npm-cache'
'run': 'echo "::set-output name=dir::$( npm config get cache )"'
- 'name': 'Set up npm cache'
'uses': 'actions/cache@v4'
'uses': 'actions/cache@v2'
'with':
'path': '${{ steps.npm-cache.outputs.dir }}'
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
@@ -80,7 +80,7 @@
'with':
'node-version': '${{ env.NODE_VERSION }}'
- 'name': 'Set up Go modules cache'
'uses': 'actions/cache@v4'
'uses': 'actions/cache@v2'
'with':
'path': '~/go/pkg/mod'
'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}"
@@ -89,7 +89,7 @@
'id': 'npm-cache'
'run': 'echo "::set-output name=dir::$(npm config get cache)"'
- 'name': 'Set up npm cache'
'uses': 'actions/cache@v4'
'uses': 'actions/cache@v2'
'with':
'path': '${{ steps.npm-cache.outputs.dir }}'
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"

View File

@@ -1,7 +1,7 @@
'name': 'lint'
'env':
'GO_VERSION': '1.24.1'
'GO_VERSION': '1.23.4'
'on':
'push':

4
.gitignore vendored
View File

@@ -19,10 +19,6 @@
/agh-backup/
/bin/
/build/*
/client/blob-report/
/client/playwright-report/
/client/playwright/.cache/
/client/test-results/
/data/
/dist/
/filtering/tests/filtering.TestLotsOfRules*.pprof

View File

@@ -1,25 +0,0 @@
{
"ul-indent": {
"indent": 4
},
"ul-style": {
"style": "dash"
},
"emphasis-style": {
"style": "asterisk"
},
"no-duplicate-heading": {
"siblings_only": true
},
"no-inline-html": {
"allowed_elements": [
"a"
]
},
"no-trailing-spaces": {
"br_spaces": 0
},
"line-length": false,
"no-bare-urls": false,
"link-fragments": false
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,12 +27,13 @@ DIST_DIR = dist
GOAMD64 = v1
GOPROXY = https://proxy.golang.org|direct
GOTELEMETRY = off
GOTOOLCHAIN = go1.24.1
GOTOOLCHAIN = go1.23.4
GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm
NPM_FLAGS = --prefix $(CLIENT_DIR)
NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress
NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress --ignore-engines\
--ignore-optional --ignore-platform --ignore-scripts
RACE = 0
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
SIGN = 1
@@ -104,12 +105,10 @@ build-docker: ; $(ENV) "$(SHELL)" ./scripts/make/build-docker.sh
build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT))
$(ENV) "$(SHELL)" ./scripts/make/build-release.sh
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
js-typecheck: ; $(NPM) $(NPM_FLAGS) run typecheck
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
js-test: ; $(NPM) $(NPM_FLAGS) run test
js-test-e2e: ; $(NPM) $(NPM_FLAGS) run test:e2e
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
js-test: ; $(NPM) $(NPM_FLAGS) run test
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh

View File

@@ -290,22 +290,6 @@ When you need to debug the frontend without recompiling the production version e
[targ-docker]: https://github.com/AdguardTeam/AdGuardHome/tree/master/scripts#build-dockersh-build-a-multi-architecture-docker-image
[targ-release]: https://github.com/AdguardTeam/AdGuardHome/tree/master/scripts#build-releasesh-build-a-release-for-all-platforms
#### <a href="#e2e-frontend-tests" id="e2e-frontend-tests" name="e2e-frontend-tests">End-to-End (E2E) Frontend Tests</a>
AdGuard Home uses [Playwright](https://playwright.dev) for E2E testing. Tests are located in `tests/e2e`.
**Running Tests:**
- `npm run test:e2e` run all tests (headless).
- `npm run test:e2e:interactive` run tests interactively.
- `npm run test:e2e:debug` run tests in debug mode.
- `npm run test:e2e:codegen` generate new test code.
**Setup:**
1. Run `npm install` to install dependencies.
2. Run `npx playwright install` to set up required browsers.
> **Warning:** Playwright will download and install its own browser binaries for testing, which may differ from the browsers installed on your system.
## <a href="#contributing" id="contributing" name="contributing">Contributing</a>
You are welcome to fork this repository, make your changes and [submit a pull request][pr]. Please make sure you follow our [code guidelines][guide] though.

View File

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

View File

@@ -5,8 +5,8 @@
'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests'
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.24.1--1'
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.4--1'
'channel': 'development'
'stages':
@@ -29,12 +29,6 @@
jobs:
- 'Artifact'
- 'E2E':
manual: false
final: false
jobs:
- 'Test e2e'
'Test frontend':
'docker':
'image': '${bamboo.dockerFrontend}'
@@ -54,7 +48,7 @@
set -e -f -u -x
make VERBOSE=1 js-deps js-typecheck js-lint js-test
make VERBOSE=1 js-deps js-lint js-test
'final-tasks':
- 'clean'
'requirements':
@@ -171,38 +165,6 @@
'requirements':
- 'adg-docker': 'true'
'Test e2e':
'artifact-subscriptions':
- 'artifact': 'AdGuardHome_linux_amd64'
- 'artifact': 'AdGuardHome frontend'
'docker':
'image': '${bamboo.dockerFrontend}'
'volumes':
'${system.YARN_DIR}': '${bamboo.cacheYarn}'
'key': 'E2ETEST'
'other':
'clean-working-dir': true
'tasks':
- 'checkout':
'force-clean-build': true
- 'script':
'interpreter': 'SHELL'
'scripts':
- |
#!/bin/sh
set -e -f -u -x
export CI=true
tar -xzf dist/AdGuardHome_linux_amd64.tar.gz -C /tmp
mv /tmp/AdGuardHome/AdGuardHome ./AdGuardHome
make VERBOSE=1 js-deps js-test-e2e
'requirements':
- 'adg-docker': 'true'
'branches':
'create': 'for-pull-request'
'delete':
@@ -233,6 +195,6 @@
# Set the default release channel on the release branch to beta, as we
# may need to build a few of these.
'variables':
'dockerFrontend': 'adguard/home-js-builder:2.1-bullseye'
'dockerGo': 'adguard/go-builder:1.24.1--1'
'dockerFrontend': 'adguard/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.4--1'
'channel': 'candidate'

22
client/.eslintrc.json vendored
View File

@@ -1,7 +1,5 @@
{
"plugins": [
"prettier"
],
"plugins": ["prettier"],
"extends": [
"airbnb-base",
"prettier",
@@ -23,23 +21,12 @@
},
"import/resolver": {
"node": {
"extensions": [
".js",
".jsx",
".ts",
".tsx"
]
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_"
}
],
"import/extensions": [
"error",
"ignorePackages",
@@ -56,10 +43,7 @@
"no-console": [
"warn",
{
"allow": [
"warn",
"error"
]
"allow": ["warn", "error"]
}
],
"import/no-extraneous-dependencies": [

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

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

8728
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

23
client/package.json vendored
View File

@@ -7,14 +7,11 @@
"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",
"lint": "echo 'Lint temporarily disabled'",
"lint-new": "eslint './src/**/*.(ts|tsx)'",
"lint:fix": "eslint './src/**/*.(ts|tsx)' --fix",
"test": "jest",
"test:watch": "jest --watch",
"typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch"
},
@@ -23,7 +20,6 @@
"@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",
@@ -38,7 +34,6 @@
"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",
@@ -51,6 +46,7 @@
"react-transition-group": "^4.4.5",
"redux": "^4.0.5",
"redux-actions": "^2.6.5",
"redux-form": "^8.3.10",
"redux-thunk": "^2.3.0",
"ts-migrate": "^0.1.35",
"url-polyfill": "^1.1.12"
@@ -64,15 +60,15 @@
"@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/jest": "^29.5.12",
"@types/lodash": "^4.17.4",
"@types/node": "^22.10.2",
"@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",
"@types/redux-form": "^8.3.10",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.10.0",
"babel-loader": "^9.1.3",
@@ -89,6 +85,8 @@
"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",
@@ -99,7 +97,6 @@
"stylelint": "^16.5.0",
"ts-loader": "^9.5.1",
"url-loader": "^4.1.1",
"vitest": "^3.0.4",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4",

View File

@@ -1,52 +0,0 @@
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,
},
});

View File

@@ -284,7 +284,6 @@
"blocklist": "Черен списък",
"filter_category_general": "General",
"filter_category_security": "Сигурност",
"filter_category_other": "Друго",
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това.",
"parental_control": "Родителски контрол",
"sunday_short": "Нд",

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.",
"parallel_requests": "Paralelní požadavky",
"load_balancing": "Optimalizace vytížení",
"load_balancing_desc": "Dotazy jednoho odchozího serveru ve stejný čas.<br/>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í.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "TTL blokované odezvy",
"blocked_response_ttl_desc": "Určuje, na kolik sekund by měli klienti ukládat filtrovanou odezvu do mezipaměti",
"form_enter_blocked_response_ttl": "Zadejte TTL blokované odezvy (v sekundách)",
"upstream_timeout": "Časový limit odchozího serveru",
"upstream_timeout_desc": "Určuje počet sekund čekání na odpověď od odchozího serveru",
"form_enter_upstream_timeout": "Zadejte dobu časového limitu odchozího serveru v sekundách",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS skrze HTTPS",
"dns_over_tls": "DNS skrze TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Zakázat řešení IPv6 adres",
"disable_ipv6_desc": "Odstranění všech dotazů DNS na adresy IPv6 (typ AAAA) a odstranění náznaků IPv6 z odpovědí HTTPS.",
"fastest_addr": "Nejrychlejší IP adresa",
"fastest_addr_desc": "Počká na odpovědi <b>všech</b> serverů DNS, změří rychlost připojení TCP pro každý server a vrátí IP adresu serveru s nejvyšší rychlostí připojení.<br/>Tento režim může výrazně zpomalit dotazy DNS, pokud jeden nebo více odchozích serverů neodpovídá. Ujistěte se, že vaše odchozí servery jsou stabilní a že časový limit odchozích serverů je nízký.",
"fastest_addr_desc": "Dotazovat všechny DNS servery a vrátit nejrychlejší IP adresu ze všech odpovědí. To zpomalí dotazy DNS, protože AdGuard Home musí čekat na odpovědi ze všech serverů DNS, ale celková konektivita se zlepší.",
"autofix_warning_text": "Pokud kliknete na „Opravit“, AdGuard Home nakonfiguruje váš systém tak, aby používal DNS server AdGuard Home.",
"autofix_warning_list": "Jsou prováděny následující úlohy: <0>Deaktivace systému DNSStubListener</0> <0>Nastavení adresy serveru DNS na 127.0.0.1</0> <0>Nahrazení cíle symbolického odkazu z /etc/resolv.conf do /run/systemd/resolve/resolv.conf</0> <0>Zastavení služby DNSStubListener (znovu načtení služby systemd-resolved)</0>",
"autofix_warning_result": "Výsledkem je, že všechny požadavky DNS z vašeho systému jsou ve výchozím nastavení zpracovány službou AdGuard Home.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
"parallel_requests": "Parallelle forespørgsler",
"load_balancing": "Belastningsfordeling",
"load_balancing_desc": "Forespørg én upstream-server ad gangen.<br/>AdGuard Home bruger en vægtet tilfældighedsalgoritme til vælg af servere med det laveste antal fejlslagne opslag og den laveste gennemsnitlige opslagstid.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Blokeret svar TTL",
"blocked_response_ttl_desc": "Angiver, i hvor mange sekunder klienterne skal cache-lagre et filtreret svar",
"form_enter_blocked_response_ttl": "Angiv blokeringssvar TTL (sekunder)",
"upstream_timeout": "Upstream-timeout",
"upstream_timeout_desc": "Angiver antallet af sekunder, der skal ventes på et svar fra upstream-serveren",
"form_enter_upstream_timeout": "Angiv varigheden af upstream-server timeout i sekunder",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Deaktivér IPv6-adresseopløsning",
"disable_ipv6_desc": "Drop alle DNS-forespørgsler for IPv6-adresser (type AAAA), og fjern IPv6-tips fra HTTPS-svar.",
"fastest_addr": "Hurtigste IP-adresse",
"fastest_addr_desc": "Vent på svar fra <b>alle</b> DNS-servere, mål TCP-forbindelseshastigheden for hver server, og returner IP-adressen på serveren med den hurtigste forbindelseshastighed.<br/>Denne tilstand kan sinke DNS-forespørgsler, betydeligt hvis en eller flere upstream-servere ikke svarer. Sørg for, at upstream-serverene er stabile, og at upstream-timeouten er lav.",
"fastest_addr_desc": "Forespørger alle DNS-servere og returner den hurtigste IP-adresse blandt alle svar. Dette vil gøre DNS-forespørgslerne langsommere grundet afventning af svar fra alle DNS-servere, men forbedrer samlet set forbindelsen.",
"autofix_warning_text": "Klikker du på \"Reparér\", opsætter AdGuard Home dit system til brug med AdGuard Home DNS-server.",
"autofix_warning_list": "Den vil udføre disse opgaver: <0>Deaktivere system DNSStubListener</0> <0>Opsætte DNS-serveradressen til 127.0.0.1</0> <0>Erstatte symbolsk linkmål for /etc/resolv.conf med /run/systemd/resolve/resolv.conf</0> <0>Stoppe DNSStubListener (genindlæs systemd-opløst tjeneste)</0>",
"autofix_warning_result": "Det betyder, at alle DNS-forespørgsler fra dit system som standard behandles af AdGuard Home.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
"parallel_requests": "Paralleles Abfragen",
"load_balancing": "Lastverteilung",
"load_balancing_desc": "Es wird jeweils ein Upstream-Server abgefragt.<br/> AdGuard Home verwendet einen gewichteten Zufallsalgorithmus, um die Server mit der geringsten Anzahl an fehlgeschlagenen Suchvorgängen und der niedrigsten durchschnittlichen Suchzeit auszuwählen.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Gültigkeitsdauer der blockierten Antwort",
"blocked_response_ttl_desc": "Gibt an, wie viele Sekunden lang die Clients eine gefilterte Antwort zwischenspeichern sollen",
"form_enter_blocked_response_ttl": "Geben Sie die Gültigkeitsdauer für blockierte Antworten ein (in Sekunden)",
"upstream_timeout": "Upstream-Timeout",
"upstream_timeout_desc": "Gibt die Anzahl der Sekunden an, die auf eine Antwort des Upstream-Servers gewartet werden soll",
"form_enter_upstream_timeout": "Geben Sie die Timeout-Dauer des Upstream-Servers in Sekunden ein",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "IPv6 deaktivieren",
"disable_ipv6_desc": "Alle DNS-Anfragen für IPv6-Adressen (Typ AAAA) verwerfen und IPv6-Hinweise aus HTTPS-Antworten entfernen.",
"fastest_addr": "Schnellste IP-Adresse",
"fastest_addr_desc": "Auf Antworten von <b>allen</b> DNS-Servern warten, die TCP-Verbindungsgeschwindigkeit für jeden Server messen und die IP-Adresse des Servers mit der schnellsten Verbindungsgeschwindigkeit zurückgeben.<br/>Dieser Modus kann DNS-Anfragen erheblich verlangsamen, wenn ein oder mehrere Server nicht antworten. Stellen Sie sicher, dass Ihre Server stabil laufen und das Upstream-Timeout niedrig ist.",
"fastest_addr_desc": "Fragen Sie alle DNS-Server ab und geben Sie die schnellste IP-Adresse unter allen Antworten zurück. Dies verlangsamt DNS-Abfragen, da AdGuard Home auf Antworten von allen DNS-Servern warten muss, verbessert jedoch die Gesamtkonnektivität.",
"autofix_warning_text": "Wenn Sie auf „Beheben“ klicken, konfiguriert AdGuardHome Ihr System für die Verwendung des AdGuardHome-DNS-Servers.",
"autofix_warning_list": "Es werden folgende Aufgaben ausgeführt: <0>Deaktivieren des DNSStubListener-Systems</0> <0>Festlegen der DNS-Server-Adresse auf 127.0.0.1</0> <0>Ersetzen des symbolischen Linkziels von /etc/resolv.conf auf /run/systemd/resolve/resolv.conf</0> <0>Anhalten des DNSStubListener (systemseitig aufgelöster Dienst wird nachladen)</0>",
"autofix_warning_result": "Als Folge daraus werden alle DNS-Anforderungen von Ihrem System standardmäßig von AdGuardHome verarbeitet.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
"parallel_requests": "Parallel requests",
"load_balancing": "Load-balancing",
"load_balancing_desc": "Query one upstream server at a time.<br/>AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Blocked response TTL",
"blocked_response_ttl_desc": "Specifies for how many seconds the clients should cache a filtered response",
"form_enter_blocked_response_ttl": "Enter blocked response TTL (seconds)",
"upstream_timeout": "Upstream timeout",
"upstream_timeout_desc": "Specifies the number of seconds to wait for a response from the upstream server",
"form_enter_upstream_timeout": "Enter the upstream server timeout duration in seconds",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Disable resolving of IPv6 addresses",
"disable_ipv6_desc": "Drop all DNS queries for IPv6 addresses (type AAAA) and remove IPv6 hints from HTTPS responses.",
"fastest_addr": "Fastest IP address",
"fastest_addr_desc": "Wait for responses from <b>all</b> DNS servers, measure the TCP connection speed for each server, and return the IP address of the server with the fastest connection speed.<br/>This mode can significantly slow down DNS queries, if one or more upstream servers are not responding. Make sure that your upstream servers are stable and your upstream timeout is low.",
"fastest_addr_desc": "Query all DNS servers and return the fastest IP address among all responses. This slows down DNS queries as AdGuard Home has to wait for responses from all DNS servers, but improves the overall connectivity.",
"autofix_warning_text": "If you click \"Fix\", AdGuard Home will configure your system to use AdGuard Home DNS server.",
"autofix_warning_list": "It will perform these tasks: <0>Deactivate system DNSStubListener</0> <0>Set DNS server address to 127.0.0.1</0> <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf</0> <0>Stop DNSStubListener (reload systemd-resolved service)</0>",
"autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.",
@@ -620,10 +617,6 @@
"check_cname": "CNAME: {{cname}}",
"check_reason": "Reason: {{reason}}",
"check_service": "Service name: {{service}}",
"check_hostname": "Hostname or domain name",
"check_client_id": "Client identifier (ClientID or IP address)",
"check_enter_client_id": "Enter client identifier",
"check_dns_record": "Select DNS record type",
"service_name": "Service name",
"check_not_found": "Not found in your filter lists",
"client_confirm_block": "Are you sure you want to block the client \"{{ip}}\"?",

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.",
"parallel_requests": "Consultas paralelas",
"load_balancing": "Balanceo de carga",
"load_balancing_desc": "Consulta un servidor Dns upstream a la vez.<br/>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.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Respuesta TTL bloqueada",
"blocked_response_ttl_desc": "Especifica durante cuántos segundos los clientes deben almacenar en cache una respuesta filtrada",
"form_enter_blocked_response_ttl": "Ingresa el TTL de respuesta bloqueada (segundos)",
"upstream_timeout": "Tiempo de espera del upstream",
"upstream_timeout_desc": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream",
"form_enter_upstream_timeout": "Ingresa la duración del tiempo de espera del servidor DNS upstream en segundos",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS mediante HTTPS",
"dns_over_tls": "DNS mediante TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Deshabilitar resolución de direcciones IPv6",
"disable_ipv6_desc": "Descarta todas las consultas de DNS para direcciones IPv6 (tipo AAAA) y elimina las sugerencias de IPv6 de las respuestas HTTPS.",
"fastest_addr": "Dirección IP más rápida",
"fastest_addr_desc": "Espera a que respondan <b>todos</b> los servidores DNS, mide la velocidad de conexión TCP de cada servidor y devuelve la Dirección IP del servidor con la velocidad de conexión más rápida.<br/>Este modo puede ralentizar significativamente las consultas DNS, si uno o más servidores DNS de upstream no están respondiendo. Asegúrate de que tus servidores DNS upstream sean estables y tu tiempo de espera de upstream sea bajo.",
"fastest_addr_desc": "Consulta todos los servidores DNS y devuelve la dirección IP más rápida de todas las respuestas. Esto ralentiza las consultas DNS ya que AdGuard Home tiene que esperar las respuestas de todos los servidores DNS, pero mejora la conectividad general.",
"autofix_warning_text": "Si haces clic en \"Corregir\", AdGuard Home configurará tu sistema para utilizar el servidor DNS de AdGuard Home.",
"autofix_warning_list": "Realizará estas tareas: <0>Deshabilitar el sistema DNSStubListener</0> <0>Establecer la dirección del servidor DNS en 127.0.0.1</0> <0>Reemplazar el destino del enlace simbólico de /etc/resolv.conf por /run/systemd/resolve/resolv.conf</0> <0>Detener DNSStubListener (recargar el servicio systemd-resolved)</0>",
"autofix_warning_result": "Como resultado, todas las peticiones DNS de tu sistema serán procesadas por AdGuard Home de manera predeterminada.",

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.",
"parallel_requests": "Requêtes en parallèle",
"load_balancing": "Équilibrage de charge",
"load_balancing_desc": "Une requête par serveur en amont à la fois.<br/>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.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Réponse bloquée TTL",
"blocked_response_ttl_desc": "Spécifie pendant combien de secondes les clients doivent mettre en cache une réponse filtrée",
"form_enter_blocked_response_ttl": "Saisir le TTL de la réponse bloquée (secondes)",
"upstream_timeout": "Délai d'attente en amont",
"upstream_timeout_desc": "Spécifie le nombre de secondes à attendre pour une réponse du serveur en amont",
"form_enter_upstream_timeout": "Saisir le délai d'attente du serveur en amont en secondes",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Désactiver la résolution des adresses IPv6",
"disable_ipv6_desc": "Supprimer toutes les requêtes DNS pour les adresses IPv6 (type AAAA) et supprimer les indices IPv6 des réponses HTTPS.",
"fastest_addr": "Adresse IP la plus rapide",
"fastest_addr_desc": "Attente les réponses de <b>tous</b> les serveurs DNS, mesure de la vitesse de connexion TCP pour chaque serveur et renvoi de l'adresse IP du serveur avec la vitesse de connexion la plus rapide.<br/>Ce mode peut considérablement ralentir les requêtes DNS, si un ou plusieurs serveurs en amont ne répondent pas. Assurez-vous que vos serveurs en amont sont stables et que votre délai dépassé en amont est faible.",
"fastest_addr_desc": "Rechercher tous les serveurs DNS et renvoyer ladresse IP la plus rapide parmi toutes les réponses. Cela ralentit les requêtes DNS car AdGuard Home doit attendre les réponses de tous les serveurs DNS, mais la connectivité globale s'améliore.",
"autofix_warning_text": "Si vous cliquez sur « Réparer », AdGuard Home configurera votre système pour utiliser le serveur DNS AdGuard Home.",
"autofix_warning_list": "Ceci effectuera les tâches suivantes : <0>Désactiver le système DNSStubListener</0> <0>Définir ladresse du serveur DNS à 127.0.0.1 </0> <0>Remplacer la cible du lien symbolique de /etc/resolv.conf par /run/systemd/resolve/resolv.conf</0> <0>Arrêter DNSStubListener (recharger le service résolu par systemd)</0>",
"autofix_warning_result": "Par conséquent, toutes les demandes DNS de votre système seront traitées par AdGuardHome par défaut.",

View File

@@ -1,26 +1,26 @@
{
"client_settings": "Pengaturan klien",
"example_upstream_reserved": "hulu <0>untuk domain tertentu</0>;",
"example_multiple_upstreams_reserved": "beberapa hulu <0>untuk domain tertentu</0>;",
"example_upstream_reserved": "upstream <0>untuk domain spesifik</0>;",
"example_multiple_upstreams_reserved": "beberapa server upstream <0>untuk domain spesifik</0>;",
"example_upstream_comment": "komentar.",
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat penyelesaian dengan mengkueri seluruh server hulu secara bersamaan.",
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
"parallel_requests": "Permintaan paralel",
"load_balancing": "Penyeimbang beban",
"load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.",
"bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Alamat IP server DNS yang digunakan untuk menyelesaikan alamat IP penyelesai DoH/DoT yang Anda tentukan sebagai hulu. Tidak diizinkan untuk berkomentar.",
"bootstrap_dns_desc": "Alamat IP server DNS yang digunakan untuk menyelesaikan alamat IP resolver DoH/DoT yang Anda tentukan sebagai upstream. Komentar tidak diizinkan.",
"fallback_dns_title": "Server DNS cadangan",
"fallback_dns_desc": "Daftar server DNS cadangan yang digunakan ketika server hulu DNS tidak merespons. Sintaksnya sama dengan kolom hulu utama di atas.",
"fallback_dns_placeholder": "Masukkan satu server DNS cadangan per baris",
"local_ptr_title": "Server pembalik DNS pribadi",
"local_ptr_desc": "Server DNS yang digunakan oleh AdGuard Home untuk permintaan PTR, SOA, dan NS pribadi. Permintaan dianggap pribadi jika meminta domain ARPA yang berisi subnet dalam rentang IP pribadi (seperti \"192.168.12.34\") dan berasal dari klien dengan alamat IP pribadi. Jika tidak ditetapkan, standar pemecah DNS milik OS Anda akan digunakan, kecuali untuk alamat IP AdGuard Home.",
"local_ptr_desc": "Server DNS yang digunakan AdGuard Home untuk kueri PTR lokal. Server ini digunakan untuk menyelesaikan nama host klien dengan alamat IP pribadi, misalnya \"192.168.12.34\", menggunakan DNS terbalik. Jika tidak disetel, AdGuard Home menggunakan alamat resolver DNS default OS Anda kecuali untuk alamat AdGuard Home itu sendiri.",
"local_ptr_default_resolver": "Secara bawaan, AdGuard Home menggunakan pemecah DNS terbalik: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home tidak dapat menentukan pemecah DNS terbalik yang sesuai untuk sistem ini.",
"local_ptr_placeholder": "Masukkan satu alamat IP per baris",
"resolve_clients_title": "Aktifkan resolusi hostname klien",
"resolve_clients_desc": "Selesaikan alamat IP klien secara terbalik ke dalam nama host mereka dengan mengirimkan kueri PTR ke penyelesai yang sesuai (server DNS pribadi untuk klien lokal, server hulu untuk klien dengan alamat IP publik).",
"resolve_clients_desc": "Menyelesaikan alamat IP klien secara terbalik ke nama host mereka dengan mengirimkan kueri PTR ke resolver yang sesuai (server DNS pribadi untuk klien lokal, server upstream untuk klien dengan alamat IP publik).",
"use_private_ptr_resolvers_title": "Gunakan server pembalik DNS pribadi",
"use_private_ptr_resolvers_desc": "Menyelesaikan permintaan PTR, SOA, dan NS untuk domain ARPA yang berisi alamat IP pribadi melalui server hulu pribadi, DHCP, /etc/hosts, dll. Jika dinonaktifkan, AdGuard Home akan merespons semua permintaan tersebut dengan NXDOMAIN.",
"use_private_ptr_resolvers_desc": "Lakukan pencarian DNS terbalik untuk alamat yang disajikan secara lokal menggunakan server hulu ini. Jika dinonaktifkan, Adguard Home merespons dengan NXDOMAIN untuk semua permintaan PTR tersebut kecuali untuk klien yang diketahui dari DHCP, /etc/hosts, dan seterusnya.",
"check_dhcp_servers": "Cek untuk server DHCP",
"save_config": "Simpan pengaturan",
"enabled_dhcp": "Server DHCP diaktifkan",
@@ -49,12 +49,12 @@
"form_error_server_name": "Nama server tidak valid",
"form_error_subnet": "Subnet \"{{cidr}}\" tidak berisi alamat IP \"{{ip}}\"",
"form_error_positive": "Harus lebih dari 0",
"form_error_gateway_ip": "Lease tidak dapat memiliki gerbang alamat IP",
"form_error_gateway_ip": "Sewa tidak dapat memiliki alamat IP gateway",
"out_of_range_error": "Harus di luar rentang \"{{start}}\"-\"{{end}}\"",
"lower_range_start_error": "Harus lebih rendah dari rentang awal",
"greater_range_start_error": "Harus lebih besar dari rentang awal",
"subnet_error": "Alamat harus dalam satu subnet",
"gateway_or_subnet_invalid": "Subnet samaran tidak valid",
"gateway_or_subnet_invalid": "Subnet mask tidak valid",
"dhcp_form_gateway_input": "IP gateway",
"dhcp_form_subnet_input": "Subnet mask",
"dhcp_form_range_title": "Rentang alamat IP",
@@ -132,8 +132,8 @@
"top_clients": "Klien teratas",
"no_clients_found": "Tidak ditemukan klien",
"general_statistics": "Statistik umum",
"top_upstreams": "Hulu teratas",
"no_upstreams_data_found": "Tidak ada data hulu yang ditemukan",
"top_upstreams": "Top servers upstream",
"no_upstreams_data_found": "Tidak ada data server upstream yang ditemukan",
"number_of_dns_query_days": "Jumlah kueri DNS diproses selama {{value}} hari terakhir",
"number_of_dns_query_days_plural": "Jumlah kueri DNS yang diproses selama {{count}} hari terakhir",
"number_of_dns_query_hours": "Jumlah kueri DNS diproses selama {{{count}} jam terakhir",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Gunakan layanan web kontrol orang tua AdGuard",
"use_adguard_parental_hint": "AdGuard Home akan mengecek jika domain mengandung materi dewasa. Akan menggunakan API yang ramah privasi yang sama sebagai layanan web keamanan penjelajahan.",
"enforce_safe_search": "Pakai pencarian aman",
"enforce_save_search_hint": "AdGuard Home akan memberlakukan pencarian yang aman di mesin pencari berikut ini: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.",
"enforce_save_search_hint": "AdGuard Home dapat memaksa penelusuran aman pada mesin pencari berikut: Google, Youtube, Bing, DuckDuckGo, Yandex, dan Pixabay.",
"no_servers_specified": "Sever tidak disebutkan",
"general_settings": "Pengaturan umum",
"dns_settings": "Pengaturan DNS",
@@ -169,8 +169,8 @@
"upstream_dns_help": "Masukkan satu alamat server per baris. <a>Pelajari lebih lanjut</a> mengenai cara mengonfigurasi server DNS hulu.",
"upstream_dns_configured_in_file": "Diatur dalam {{path}}",
"test_upstream_btn": "Uji hulu",
"upstreams": "Hulu",
"upstream": "Hulu",
"upstreams": "Upstream",
"upstream": "Server upstream",
"apply_btn": "Terapkan",
"disabled_filtering_toast": "Penyaringan nonaktif",
"enabled_filtering_toast": "Penyaringan aktif",
@@ -215,29 +215,29 @@
"system_host_files": "Berkas host sistem",
"examples_title": "Contoh",
"example_meaning_filter_block": "blokir akses ke example.org dan seluruh subdomainnya;",
"example_meaning_filter_whitelist": "buka blokir akses ke domain example.org dan seluruh subdomainnya;",
"example_meaning_filter_whitelist": "buka blokir akses ke domain example.orf dan seluruh subdomainnya;",
"example_meaning_host_block": "merespons dengan 127.0.0.1 untuk example.org (tetapi tidak untuk subdomainnya);",
"example_comment": "! Komentar di sini.",
"example_comment_meaning": "hanya sebuah komentar;",
"example_comment_hash": "# Juga sebuah komentar.",
"example_regex_meaning": "blokir akses ke domain yang cocok dengan ekspresi reguler yang ditentukan.",
"example_upstream_regular": "DNS biasa (melalui UDP);",
"example_upstream_regular_port": "DNS biasa (melalui UDP, dengan port);",
"example_upstream_udp": "DNS biasa (melalui UDP, nama host);",
"example_upstream_regular": "DNS reguler (melalui UDP);",
"example_upstream_regular_port": "DNS biasa (lebih dari UDP, dengan port);",
"example_upstream_udp": "DNS biasa (lebih dari UDP, nama host);",
"example_upstream_dot": "<0>DNS melalui TLS</0> terenkripsi;",
"example_upstream_doh": "<0>DNS melalui HTTPS</0> terenkripsi;",
"example_upstream_doh3": "DNS melalui HTTPS terenkripsi dengan <0>HTTP/3</0> secara paksa dan tidak ada cadangan ke HTTP/2 atau lebih rendah;",
"example_upstream_doq": "<0>DNS melalui QUIC</0> terenkripsi;",
"example_upstream_sdns": "<0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS melalui HTTPS</2>;",
"example_upstream_tcp": "DNS biasa (melalui TCP);",
"example_upstream_sdns": "<0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>;",
"example_upstream_tcp": "DNS reguler (melalui TCP);",
"example_upstream_tcp_port": "DNS biasa (melalui TCP, dengan port);",
"example_upstream_tcp_hostname": "DNS biasa (melalui TCP, nama host);",
"example_upstream_tcp_hostname": "DNS biasa (lebih dari TCP, nama host);",
"all_lists_up_to_date_toast": "Semua daftar sudah diperbarui",
"updated_upstream_dns_toast": "Server hulu berhasil disimpan",
"updated_upstream_dns_toast": "Server upstream berhasil disimpan",
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
"dns_test_parsing_error_toast": "Bagian {{section}}: baris {{line}}: tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
"dns_test_warning_toast": "Hulu \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan benar",
"dns_test_warning_toast": "Upstream \"{{key}}\" tidak menanggapi permintaan pengujian dan mungkin tidak berfungsi dengan baik",
"unblock": "Buka Blokir",
"block": "Blok",
"disallow_this_client": "Cabut ijin untuk klien ini",
@@ -268,18 +268,18 @@
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus: {{rule}}",
"query_log_response_status": "Status: {{value}}",
"query_log_filtered": "Difilter oleh {{filter}}",
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh catatan kueri?",
"query_log_cleared": "Catatan kueri berhasil dihapus",
"query_log_updated": "Catatan kueri berhasil diperbarui",
"query_log_clear": "Hapus catatan kueri",
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
"query_log_cleared": "Kueri log telah berhasil dihapus",
"query_log_updated": "Log permintaan telah berhasil diperbarui",
"query_log_clear": "Hapus kueri log",
"query_log_retention": "Rotasi kueri log",
"query_log_enable": "Aktifkan catatan",
"query_log_configuration": "Konfigurasi catatan",
"query_log_disabled": "Catatan kueri dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
"query_log_enable": "Aktifkan log",
"query_log_configuration": "Konfigurasi log",
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah rotasi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
"anonymize_client_ip": "Anonim IP klien",
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam catatan atau statistik",
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam log dan statistik",
"dns_config": "Konfigurasi server DNS",
"dns_cache_config": "Konfigurasi cache DNS",
"dns_cache_config_desc": "Disini Anda bisa mengonfigurasi cache DNS",
@@ -308,8 +308,8 @@
"form_enter_rate_limit": "Masukkan batas nilai",
"rate_limit": "Batas nilai",
"edns_enable": "Aktifkan EDNS Klien Subnet",
"edns_cs_desc": "Tambahkan opsi EDNS Client Subnet (ECS) ke permintaan hulu dan catat nilai yang dikirim oleh klien dalam catatan kueri.",
"edns_use_custom_ip": "Gunakan IP kustom untuk EDNS",
"edns_cs_desc": "Tambahkan opsi EDNS Client Subnet (ECS) ke permintaan upstream dan catat nilai yang dikirim oleh klien di log kueri.",
"edns_use_custom_ip": "Gunakan IP khusus untuk EDNS",
"edns_use_custom_ip_desc": "Izinkan untuk menggunakan IP kustom untuk EDNS",
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien. Atur ke 0 untuk tidak terbatas.",
"rate_limit_subnet_len_ipv4": "Panjang awalan subnet untuk alamat IPv4",
@@ -329,13 +329,13 @@
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
"theme_auto": "Otomatis",
"theme_auto": "Auto",
"theme_light": "Terang",
"theme_dark": "Gelap",
"upstream_dns_client_desc": "Jika Anda biarkan kolom ini kosong, AdGuard Home akan menggunakan server yang dikonfigurasi di <0>pengaturan DNS</0>.",
"tracker_source": "Sumber pelacak",
"source_label": "Sumber",
"found_in_known_domain_db": "Ditemukan di basis data domain yang dikenal.",
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
"category_label": "Kategori",
"rule_label": "Atura(n)",
"list_label": "Daftar",
@@ -366,18 +366,18 @@
"install_devices_router": "Router",
"install_devices_router_desc": "Penyiapan ini secara otomatis mencakup semua perangkat yang terhubung ke router rumah Anda, tidak perlu mengkonfigurasi masing-masing perangkat secara manual.",
"install_devices_address": "Server DNS AdGuard Home akan menggunakan alamat berikut",
"install_devices_router_list_1": "Buka preferensi untuk router Anda. Biasanya, Anda dapat mengaksesnya dari peramban Anda melalui URL, seperti http://192.168.0.1/ atau http://192.168.1.1/. Anda mungkin diminta untuk memasukkan kata sandi. Jika Anda tidak mengingatnya, Anda sering kali dapat mengatur ulang kata sandi dengan menekan tombol pada router itu sendiri, tetapi perlu diketahui bahwa jika prosedur ini dipilih, Anda mungkin akan kehilangan seluruh konfigurasi router. Jika router Anda memerlukan aplikasi untuk menyiapkannya, pasang aplikasi tersebut di ponsel atau PC Anda dan gunakan untuk mengakses pengaturan router.",
"install_devices_router_list_1": "Buka preferensi untuk router Anda. Biasanya, Anda dapat mengaksesnya dari browser Anda melalui URL, seperti http://192.168.0.1/ atau http://192.168.1.1/. Anda mungkin diminta untuk memasukkan kata sandi. Jika Anda tidak mengingatnya, Anda sering kali dapat mengatur ulang kata sandi dengan menekan tombol pada perute itu sendiri, tetapi perlu diketahui bahwa jika prosedur ini dipilih, Anda mungkin akan kehilangan seluruh konfigurasi perute. Jika router Anda memerlukan aplikasi untuk menyiapkannya, instal aplikasi tersebut di ponsel atau PC Anda dan gunakan untuk mengakses pengaturan router.",
"install_devices_router_list_2": "Temukan pengaturan DHCP / DNS. Cari huruf DNS di sebelah kolom yang memungkinkan dua atau tiga set angka, masing-masing dipecah menjadi empat kelompok dengan satu hingga tiga digit.",
"install_devices_router_list_3": "Masukkan alamat server AdGuard Home disana",
"install_devices_router_list_4": "Anda tidak dapat menyetel server DNS kustom pada beberapa tipe router. Dalam hal ini mungkin membantu jika Anda mengatur AdGuard Home sebagai <0>server DHCP</0>. Jika tidak, Anda harus mencari petunjuk tentang cara mengkustomisasi server DNS untuk model router khusus Anda.",
"install_devices_windows_list_1": "Buka Panel Kontrol melalui menu Start atau pencarian Windows.",
"install_devices_windows_list_2": "Masuk ke kategori Jaringan dan Internet (Network and Internet) dan kemudian ke Pusat Jaringan dan Berbagi (Network and Sharing Center).",
"install_devices_windows_list_3": "Di panel kiri, klik \"Ubah pengaturan adaptor\".",
"install_devices_windows_list_4": "Klik kanan koneksi aktif Anda dan pilih Properti.",
"install_devices_windows_list_5": "Temukan \"Protokol Internet Versi 4 (TCP/IPv4)\" (atau, untuk IPv6, \"Protokol Internet Versi 6 (TCP/IPv6)\") dalam daftar, pilih dan kemudian klik Properti lagi.",
"install_devices_windows_list_4": "Klik kanan koneksi aktif Anda dan pilih Properties.",
"install_devices_windows_list_5": "Temukan \"Internet Protocol Version 4 (TCP/IPv4)\" (atau, untuk IPv6, \"Internet Protocol Version 6 (TCP/IPv6)\") dalam daftar, pilih dan kemudian klik Properties lagi.",
"install_devices_windows_list_6": "Pilih \"Gunakan alamat server DNS berikut\" dan masukkan alamat server Beranda AdGuard Anda.",
"install_devices_macos_list_1": "Klik ikon Apple dan buka Preferensi Sistem.",
"install_devices_macos_list_2": "Klik Jaringan.",
"install_devices_macos_list_1": "Klik ikon Apple dan pergi ke System Preferences.",
"install_devices_macos_list_2": "Klik Network.",
"install_devices_macos_list_3": "Pilih koneksi pertama dalam daftar dan klik Advanced.",
"install_devices_macos_list_4": "Pilih tab DNS dan masukkan alamat server AdGuard Anda.",
"install_devices_android_list_1": "Dari layar beranda Menu Android, ketuk Pengaturan.",
@@ -394,7 +394,7 @@
"open_dashboard": "Buka Beranda",
"install_saved": "Berhasil disimpan",
"encryption_title": "Enkripsi",
"encryption_desc": "Dukungan enkripsi (HTTPS/QUIC/TLS) untuk DNS dan antarmuka web admin",
"encryption_desc": "Enkripsi (HTTPS/QUIC/TLS) untuk DNS dan antarmuka admin",
"encryption_config_saved": "Pengaturan enkripsi telah tersimpan",
"encryption_server": "Nama server",
"encryption_server_enter": "Masukkan nama domain anda",
@@ -406,7 +406,7 @@
"encryption_dot": "Port DNS-over-TLS",
"encryption_dot_desc": "Jika port ini terkonfigurasi, AdGuard Home akan menjalankan server DNS-over-TLS dalam port ini",
"encryption_doq": "Port DNS-over-QUIC ",
"encryption_doq_desc": "Jika port ini dikonfigurasi, AdGuard Home akan menjalankan server DNS melalui QUIC pada port ini.",
"encryption_doq_desc": "Jika port ini diatur secara sepesifik, AdGuard Home akan menjalankan server DNS-lewat-QUIC pada port ini.",
"encryption_certificates": "Sertifikat",
"encryption_certificates_desc": "Untuk menggunakan enkripsi, Anda perlu memberikan rantai sertifikat SSL yang valid untuk domain Anda. Anda bisa mendapatkan sertifikat gratis di <0>{{link}}</0> atau Anda dapat membelinya dari salah satu Otoritas Sertifikat tepercaya.",
"encryption_certificates_input": "Salin / rekatkan sertifikat PEM yang disandikan di sini.",
@@ -431,8 +431,8 @@
"topline_expiring_certificate": "Sertifikat SSL Anda hampir kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"topline_expired_certificate": "Sertifikat SSL Anda kedaluwarsa. Perbarui <0>Pengaturan enkripsi</0>.",
"form_error_port_range": "Masukkan nomor port di kisaran 80-65535",
"form_error_port_unsafe": "Port tidak aman",
"form_error_equal": "Tidak boleh sama",
"form_error_port_unsafe": "Ini adalah port yang tidak aman",
"form_error_equal": "Seharusnya tidak sama",
"form_error_password": "Kata sandi tidak cocok",
"reset_settings": "Setel ulang pengaturan",
"update_announcement": "AdGuard Home {{version}} sekarang tersedia! <0>Klik di sini</0> untuk info lebih lanjut.",
@@ -447,7 +447,7 @@
"update_failed": "Pembaruan otomatis gagal. Silakan <a>ikuti langkah-langkah berikut</a> untuk memperbarui secara manual.",
"manual_update": "Silakan <a>mengikuti langkah berikut</a> untuk memperbarui secara manual.",
"processing_update": "Silahkan tunggu, AdGuard Home sedang diperbarui",
"clients_title": "Klien persisten",
"clients_title": "Klien yang gigih",
"clients_desc": "Konfigurasikan catatan klien persisten untuk perangkat yang terhubung ke AdGuard Home",
"settings_global": "Global",
"settings_custom": "Kustom",
@@ -459,7 +459,7 @@
"client_edit": "Ubah Klien",
"client_identifier": "Identifikasi",
"ip_address": "Alamat IP",
"client_identifier_desc": "Klien dapat diidentifikasi berdasarkan alamat IP, CIDR, alamat MAC, atau ClientID (dapat digunakan untuk DoT/DoH/DoQ). Pelajari lebih lanjut tentang cara mengidentifikasi klien <0>di sini</0>.",
"client_identifier_desc": "Klien dapat diidentifikasi oleh alamat IP, CIDR, alamat MAC atau ClientID (dapat digunakan untuk DoT/DoH/DoQ). <0>Di sini</0> Anda dapat mempelajari lebih lanjut tentang cara mengidentifikasi klien.",
"form_enter_ip": "Masukkan IP",
"form_enter_subnet_ip": "Masukkan alamat IP di subnet \"{{cidr}}\"",
"form_enter_mac": "Masukkan MAC",
@@ -475,7 +475,7 @@
"clients_not_found": "Tidak ada klien ditemukan",
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
"list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
"auto_clients_title": "Klien runtime",
"auto_clients_title": "Klien (waktu berjalan)",
"auto_clients_desc": "Informasi tentang alamat IP perangkat yang menggunakan atau mungkin menggunakan AdGuard Home. Informasi ini dikumpulkan dari beberapa sumber, termasuk berkas host, DNS terbalik, dll.",
"access_title": "Pengaturan akses",
"access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS",
@@ -484,9 +484,9 @@
"access_disallowed_title": "Klien yang tidak diizinkan",
"access_disallowed_desc": "Daftar CIDR, alamat IP, atau <a>ClientID</a>. Jika daftar ini memiliki entri, AdGuard Home akan membatalkan permintaan dari klien ini. Kolom ini diabaikan jika ada entri di daftar putih klien.",
"access_blocked_title": "Domain yang diblokir",
"access_blocked_desc": "Jangan dikelirukan dengan filter. AdGuard Home membuang kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di catatan kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, misalnya \"example.org\", \"*.example.org\", atau \"||example.org^\" secara bersamaan.",
"access_blocked_desc": "Jangan bingung dengan filter. AdGuard Home menghapus kueri DNS yang cocok dengan domain ini, dan kueri ini bahkan tidak muncul di log kueri. Anda dapat menentukan nama domain, karakter pengganti, atau aturan filter URL yang tepat, mis. \"example.org\", \"*.example.org\", atau \"||example.org^\" yang sesuai.",
"access_settings_saved": "Pengaturan akses berhasil disimpan",
"updates_checked": "Versi baru AdGuard Home tersedia",
"updates_checked": "Versi baru AdGuard Home tersedia\n",
"updates_version_equal": "AdGuard Home sudah tebaru",
"check_updates_now": "Periksa pembaruan sekarang",
"version_request_error": "Pemeriksaan pembaruan gagal. Harap periksa koneksi internet anda.",
@@ -563,7 +563,7 @@
"ignore_domains": "Domain yang diabaikan (dipisahkan oleh baris baru)",
"ignore_domains_title": "Domain yang diabaikan",
"ignore_domains_desc_stats": "Kueri yang cocok dengan aturan ini tidak ditulis ke statistik",
"ignore_domains_desc_query": "Kueri yang cocok dengan aturan ini tidak ditulis ke catatan kueri",
"ignore_domains_desc_query": "Kueri yang cocok dengan aturan ini tidak ditulis ke log kueri",
"interval_hours": "{{count}} jam",
"interval_hours_plural": "{{count}} jam",
"filters_configuration": "Konfigurasi filter",
@@ -593,8 +593,8 @@
"example_rewrite_wildcard": "tulis ulang respon untuk semua subdomain <0>contoh.org</0>.",
"rewrite_ip_address": "Alamat IP: pakai IP ini dalam respons A atau AAAA",
"rewrite_domain_name": "Nama domain: tambah ke rekaman CNAME",
"rewrite_A": "<0>A</0>: nilai khusus, biarkan <0>A</0> merekam dari hulu",
"rewrite_AAAA": "<0>AAAA</0>: nilai khusus, biarkan <0>AAAA</0> merekam dari hulu",
"rewrite_A": "<0>A</0>: nilai khusus, biarkan <0>A</0> merekam dari upstream",
"rewrite_AAAA": "<0>AAAA</0>: nilai khusus, biarkan <0>AAAA</0> merekam dari upstream",
"disable_ipv6": "Nonaktifkan penyelesaian alamat IPv6",
"disable_ipv6_desc": "Hapus semua kueri DNS untuk alamat IPv6 (ketik AAAA) dan hapus petunjuk IPv6 dari respons HTTPS.",
"fastest_addr": "Alamat IP tercepat",
@@ -655,8 +655,8 @@
"enter_cache_size": "Masukkan ukuran cache (bytes)",
"enter_cache_ttl_min_override": "Masukkan TTL minimum (detik)",
"enter_cache_ttl_max_override": "Masukkan TTL maksimum (detik)",
"cache_ttl_min_override_desc": "Perpanjang nilai time-to-live (detik) yang diterima dari server hulu saat menyimpan respons DNS.",
"cache_ttl_max_override_desc": "Tetapkan nilai maksimum time-to-live (detik) untuk entri dalam cache DNS.",
"cache_ttl_min_override_desc": "Perpanjang nilai waktu untuk hidup (detik) yang diterima dari server hulu saat menyimpan respons DNS.",
"cache_ttl_max_override_desc": "Tetapkan nilai waktu-online maksimum (detik) untuk entri di cache DNS.",
"ttl_cache_validation": "Nilai TTL cache minimum harus kurang dari atau sama dengan nilai maksimum",
"cache_optimistic": "Caching yang optimis",
"cache_optimistic_desc": "Buat AdGuard Home merespons dari cache bahkan ketika entri telah kedaluwarsa dan juga mencoba untuk menyegarkannya.",
@@ -678,7 +678,7 @@
"use_saved_key": "Gunakan kunci yang disimpan sebelumnya",
"parental_control": "Pengawasan Orang Tua",
"safe_browsing": "Penjelajahan Aman",
"served_from_cache_label": "Disajikan dari cache",
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
"form_error_password_length": "Kata sandi harus terdiri dari {{min}} hingga {{max}}",
"anonymizer_notification": "<0>Catatan:</0> Anonimisasi IP diaktifkan. Anda dapat menonaktifkannya di <1>Pengaturan umum</1> .",
"confirm_dns_cache_clear": "Apakah Anda yakin ingin menghapus cache DNS?",
@@ -688,11 +688,11 @@
"theme_auto_desc": "Otomatis (berdasarkan skema warna perangkat anda)",
"theme_dark_desc": "Tema gelap",
"theme_light_desc": "Tema terang",
"disable_for_seconds": "Selama {{count}} detik",
"disable_for_seconds_plural": "Selama {{count}} detik",
"disable_for_minutes": "Selama {{count}} menit",
"disable_for_minutes_plural": "Selama {{count}} menit",
"disable_for_hours": "Selama {{count}} jam",
"disable_for_seconds": "Untuk {{count}} detik",
"disable_for_seconds_plural": "Untuk {{count}} detik",
"disable_for_minutes": "Untuk {{count}} menit",
"disable_for_minutes_plural": "Untuk {{count}} menit",
"disable_for_hours": "Untuk {{count}} jam",
"disable_for_hours_plural": "Untuk {{count}} jam",
"disable_until_tomorrow": "Sampai besok",
"disable_notify_for_seconds": "Hentikan perlindungan selama {{count}} detik",
@@ -706,10 +706,10 @@
"custom_retention_input": "Masukkan retensi dalam hitungan jam",
"custom_rotation_input": "Masukkan rotasi dalam hitungan jam",
"protection_section_label": "Perlindungan",
"log_and_stats_section_label": "Catatan kueri dan statistik",
"ignore_query_log": "Abaikan klien ini di catatan kueri",
"log_and_stats_section_label": "Log kueri dan statistik",
"ignore_query_log": "Abaikan klien ini di log kueri",
"ignore_statistics": "Abaikan klien ini di statistik",
"schedule_services": "Jeda pemblokiran layanan",
"schedule_services": "Menjeda pemblokiran layanan",
"schedule_services_desc": "Mengonfigurasi jadwal jeda filter pemblokiran layanan",
"schedule_services_desc_client": "Mengonfigurasi jadwal jeda filter pemblokiran layanan untuk klien ini",
"schedule_desc": "Tetapkan periode tidak aktif untuk layanan yang diblokir",
@@ -741,7 +741,7 @@
"thursday_short": "Kam",
"friday_short": "Jum",
"saturday_short": "Sab",
"upstream_dns_cache_configuration": "Konfigurasi cache DNS hulu",
"upstream_dns_cache_configuration": "Konfigurasi cache DNS upstream",
"enable_upstream_dns_cache": "Aktifkan cache DNS untuk konfigurasi hulu kustom pada klien ini",
"dns_cache_size": "Ukuran cache DNS, dalam byte"
}

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
"parallel_requests": "Richieste parallele",
"load_balancing": "Bilanciamento del carico",
"load_balancing_desc": "Esegui una query su un server upstream alla volta.<br/>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.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Risposta TTL bloccata",
"blocked_response_ttl_desc": "Specifica per quanti secondi i client devono tenere nella cache una risposta filtrata",
"form_enter_blocked_response_ttl": "Inserisci tempo di vita (TTL) della risposta bloccata (secondi)",
"upstream_timeout": "Timeout upstream",
"upstream_timeout_desc": "Specifica il numero di secondi da attendere per una risposta dal server upstream",
"form_enter_upstream_timeout": "Inserisci la durata del timeout del server upstream in secondi",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS su HTTPS",
"dns_over_tls": "DNS su TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Disattiva risoluzione indirizzi IPv6",
"disable_ipv6_desc": "Eliminare tutte le query DNS per gli indirizzi IPv6 (tipo AAAA) e rimuovere i suggerimenti IPv6 dalle risposte HTTPS.",
"fastest_addr": "Indirizzo IP più veloce",
"fastest_addr_desc": "Attendi le risposte da <b>tutti i</b> server DNS, misura la velocità di connessione TCP per ogni server e restituisci l'indirizzo IP del server con la velocità di connessione più elevata.<br/>Questa modalità può rallentare notevolmente le query DNS, se uno o più server upstream non rispondono. Assicurati che i tuoi server upstream siano stabili e che il timeout upstream sia basso.",
"fastest_addr_desc": "Interroga tutti i server DNS e restituisci l'indirizzo IP più veloce tra tutte le risposte. Ciò rallenterà le richieste DNS poiché AdGuard Home dovrà attendere le risposte da tutti i server DNS, ma ciò migliorerà complessivamente la connettività.",
"autofix_warning_text": "Se fai clic su \"Correggi\", AdGuardHome configurerà il tuo sistema per utilizzare il server DNS AdGuardHome.",
"autofix_warning_list": "Eseguirà queste attività: <0> Disattiva DNSStubListener di sistema </0> <0> Imposta l'indirizzo del server DNS su 127.0.0.1 </0> <0> Sostituisci la destinazione del collegamento simbolico di /etc/resolv.conf su / run / systemd /resolve/resolv.conf </0> <0> Arresta DNSStubListener (ricarica il servizio systemd-resolved) </0>",
"autofix_warning_result": "Di conseguenza, tutte le richieste DNS dal sistema verranno elaborate da AdGuardHome per impostazione predefinita.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
"parallel_requests": "並列リクエスト",
"load_balancing": "ロードバランシング",
"load_balancing_desc": "一度に1つのアップストリームサーバーをクエリします。<br/>AdGuard Home は、重み付き乱択アルゴリズムを使用して、ルックアップに失敗した回数が最も少なく、平均ルックアップ時間が最も短いサーバーを選択します。",
"load_balancing_desc": "一度に1つのアップストリームサーバーをクエリします。AdGuard Home は、重み付き乱択アルゴリズムを使用して、ルックアップに失敗した回数が最も少なく、平均ルックアップ時間が最も短いサーバーを選択します。",
"bootstrap_dns": "ブートストラップDNSサーバ",
"bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません",
"fallback_dns_title": "フォールバックDNSサーバー",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Blocked Response TTLブロック済み応答のTTL",
"blocked_response_ttl_desc": "フィルタリングされた応答をクライアントがキャッシュしておく時間(秒)を指定します。",
"form_enter_blocked_response_ttl": "ブロック済み応答のTTL秒単位を入力してください",
"upstream_timeout": "Upstream timeoutアップストリームタイムアウト",
"upstream_timeout_desc": "アップストリームサーバーからの応答を待つ秒数を指定します。",
"form_enter_upstream_timeout": "アップストリームサーバーのタイムアウト時間を秒単位で入力します。",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "IPv6アドレスの解決を無効にする",
"disable_ipv6_desc": "IPv6アドレスタイプAAAAに対するDNSクエリをすべて破棄し、HTTPS応答から IPv6 hint を削除します。",
"fastest_addr": "最速のIPアドレス",
"fastest_addr_desc": "<b>すべての</b>DNSサーバーからの応答を待ち、各サーバーのTCP接続速度を測定し、最も接続速度の速いサーバーのIPアドレスを返します。<br/>※このモードでは、1つまたは複数のアップストリームサーバーが応答しない場合、DNSクエリが大幅に遅くなることがあります。アップストリームサーバーが安定していることを確認し、アップストリームタイムアウトは小さくしておいてください。",
"fastest_addr_desc": "すべてのDNSサーバーに処理要求し、全応答の中で最速のIPアドレスを返します。これにより、AdGuard HomeがすべてのDNSサーバーからの応答を待つ必要があるため、DNSクエリが遅くなりますが、全体的な接続性は向上します。",
"autofix_warning_text": "「修正」をクリックすると、AdGuardHomeはAdGuardHome DNSサーバを使用するようにシステムを構成します。",
"autofix_warning_list": "次のタスクを実行します:<0>システムDNSStubListenerを非アクティブ化します</0> <0>DNSサーバのアドレスを127.0.0.1に設定します</0> <0>/etc/resolv.confのシンボリックリンクの対象を/run/systemd/resolve/resolv.confに置換します</0> <0>DNSStubListenerを停止しますsystemd-resolvedサービスをリロードします</0>",
"autofix_warning_result": "その結果、システムからのすべてのDNSリクエストは、デフォルトでAdGuard Homeによって処理されます。",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
"parallel_requests": "병렬 처리 요청",
"load_balancing": "로드 밸런싱",
"load_balancing_desc": "한 번에 하나의 업스트림 서버를 쿼리합니다.<br/>AdGuard Home은 가중 무작위 알고리즘을 사용하여 조회 실패 횟수가 가장 적고 평균 조회 시간이 가장 짧은 서버를 선택합니다.",
"load_balancing_desc": "한 번에 하나의 업스트림 서버를 쿼리합니다. AdGuard Home은 가중 무작위 알고리즘을 사용하여 조회 실패 횟수가 가장 적고 평균 조회 시간이 가장 짧은 서버를 선택합니다.",
"bootstrap_dns": "부트스트랩 DNS 서버",
"bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.",
"fallback_dns_title": "폴백 DNS 서버",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "차단된 TTL 응답",
"blocked_response_ttl_desc": "클라이언트가 필터링된 응답을 캐시해야 하는 시간(초)을 지정합니다.",
"form_enter_blocked_response_ttl": "차단된 응답 TTL(초)을 입력하세요.",
"upstream_timeout": "업스트림 제한 시간",
"upstream_timeout_desc": "업스트림 서버의 응답을 기다리는 시간(초)을 지정합니다.",
"form_enter_upstream_timeout": "업스트림 서버 응답 제한 시간을 초 단위로 입력하세요.",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "IPv6 주소 확인 비활성화",
"disable_ipv6_desc": "IPv6 주소(AAAA 유형)에 대한 모든 DNS 쿼리를 무시하고 HTTPS 유형 응답에서 IPv6 데이터를 제거합니다.",
"fastest_addr": "가장 빠른 IP 주소",
"fastest_addr_desc": "<b>모든</b> DNS 서버의 응답을 기다렸다가 각 서버의 TCP 연결 속도를 측정하여 연결 속도가 가장 빠른 서버의 IP 주소를 반합니다.<br/>이 모드는 하나 이상의 업스트림 서버 응답하지 않는 경우, DNS 쿼리 속도가 상당히 느려질 수 있습니다. 업스트림 서버가 안정적이고 업스트림 타임아웃이 짧은지 확인하세요.",
"fastest_addr_desc": "모든 DNS 서버에 쿼리를 수행한 다음 반응이 가장 빠른 IP주소를 반합니다. AdGuard Home이 모든 DNS 서버 응답을 기다려야 하기 때문에 DNS 쿼리 속도가 느려지지만 전반적인 연결이 향상됩니다.",
"autofix_warning_text": "'수정'을 클릭하면 AdGuard Home이 AdGuard Home DNS 서버를 사용하도록 시스템을 설정합니다.",
"autofix_warning_list": "다음 작업을 진행합니다: <0>DNSStubListener 시스템 비활성화</0> <0>DNS 서버 주소를 127.0.0.1로 설정</0> <0>/etc/resolv.conf의 심볼릭 링크 타겟을 /run/systemd/resolve/resolv.conf로 변경</0> <0>DNSStubListener 중지 (systemd-resolved 서비스 새로고침)</0>",
"autofix_warning_result": "결과적으로 시스템의 모든 DNS 요청은 기본적으로 AdGuard Home에 의해 처리됩니다.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
"parallel_requests": "Parallelle verzoeken",
"load_balancing": "Volume balanceren",
"load_balancing_desc": "Voer zoekopdrachten uit op één upstream-server tegelijk.<br/>AdGuard Home gebruikt een gewogen willekeurig algoritme om servers te selecteren met het laagste aantal mislukte zoekopdrachten en de laagste gemiddelde opzoektijd.",
"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_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",
@@ -166,7 +166,7 @@
"encryption_settings": "Encryptie instellingen",
"dhcp_settings": "DHCP instellingen",
"upstream_dns": "Upstream DNS-servers",
"upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer informatie</a> over het configureren van upstream DNS-servers.",
"upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer weten</a> over het configureren van upstream DNS-servers.",
"upstream_dns_configured_in_file": "Geconfigureerd in {{path}}",
"test_upstream_btn": "Test upstream",
"upstreams": "Upstreams",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Geblokkeerde reactie TTL",
"blocked_response_ttl_desc": "Hiermee geef je op hoeveel seconden de clients een gefilterd antwoord in de cache moeten opslaan",
"form_enter_blocked_response_ttl": "Voer geblokkeerd antwoord TTL in (seconden)",
"upstream_timeout": "Upstream time-out",
"upstream_timeout_desc": "Geeft het aantal seconden aan dat moet worden gewacht op een reactie van de upstream-server",
"form_enter_upstream_timeout": "Voer de time-outduur van de upstream-server in seconden in",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-via-HTTPS",
"dns_over_tls": "DNS-via-TLS",
@@ -327,10 +324,10 @@
"rate_limit_whitelist_placeholder": "Voer één IP-adres per regel in",
"blocking_ipv4_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
"blocking_ipv6_desc": "IP-adres dat moet worden teruggegeven voor een geblokkeerd A-verzoek",
"blocking_mode_default": "Standaard: Reageer met een nul IP-adres (0.0.0.0 for A; :: voor AAAA) wanneer geblokkeerd door een Adblock-type regel; reageer met het IP-adres dat is opgegeven in de regel wanneer geblokkeerd door een /etc/hosts type regel",
"blocking_mode_default": "Standaard: Reageer met een nul IP adres (0.0.0.0 for A; :: voor AAAA) wanneer geblokkeerd door een Adblock-type regel; reageer met het IP-adres dat is opgegeven in de regel wanneer geblokkeerd door een /etc/hosts type regel",
"blocking_mode_refused": "REFUSED: Antwoorden met REFUSED code",
"blocking_mode_nxdomain": "NXDOMAIN: Reageer met NXDOMAIN code",
"blocking_mode_null_ip": "Nul IP: Reageer met een nul IP-adres (0.0.0.0 voor A; :: voor AAAA)",
"blocking_mode_null_ip": "Nul IP: Reageer met een nul IP address (0.0.0.0 voor A; :: voor AAAA)",
"blocking_mode_custom_ip": "Aangepast IP: Reageer met een handmatige ingesteld IP adres",
"theme_auto": "Automatisch",
"theme_light": "Licht",
@@ -601,7 +598,7 @@
"disable_ipv6": "Oplossen IPv6-adressen uitschakelen",
"disable_ipv6_desc": "Alle DNS-query's voor IPv6-adressen (type AAAA) verwijderen en IPv6-hints uit HTTPS-antwoorden verwijderen.",
"fastest_addr": "Snelste IP adres",
"fastest_addr_desc": "Wacht op reacties van <b>alle</b> DNS-servers, meet de TCP-verbindingssnelheid voor elke server en retourneer het IP-adres van de server met de hoogste verbindingssnelheid.<br/>Deze modus kan DNS-query's aanzienlijk vertragen als een of meer upstream-servers niet reageren. Zorg ervoor dat je upstream-servers stabiel zijn en dat je upstream-time-out laag is.",
"fastest_addr_desc": "Alle DNS-servers bevragen en het snelste IP adres terugkoppelen. Dit zal de DNS verzoeken vertragen omdat AdGuard Home moet wachten op de antwoorden van alles DNS-servers, maar verbetert wel de connectiviteit.",
"autofix_warning_text": "Als je op \"Repareren\" klikt, configureert AdGuard Home jouw systeem om de AdGuard Home DNS-server te gebruiken.",
"autofix_warning_list": "De volgende taken worden uitgevoerd: <0> Deactiveren van Systeem DNSStubListener</0> <0> DNS-serveradres instellen op 127.0.0.1 </0> <0> Symbolisch koppelingsdoel van /etc/resolv.conf vervangen door /run/systemd/resolve/resolv.conf </0> <0> Stop DNSStubListener (herlaad systemd-resolved service) </0>",
"autofix_warning_result": "Als gevolg hiervan worden alle DNS-aanvragen van je systeem standaard door AdGuard Home verwerkt.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
"parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulte um servidor upstream por vez.<br/>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.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Resposta bloqueada TTL",
"blocked_response_ttl_desc": "Especifica por quantos segundos os clientes devem armazenar em cache uma resposta filtrada",
"form_enter_blocked_response_ttl": "Insira o TTL da resposta bloqueada (segundos)",
"upstream_timeout": "Tempo limite de upstream",
"upstream_timeout_desc": "Especifica o número de segundos para esperar por uma resposta do servidor upstream",
"form_enter_upstream_timeout": "Insira a duração do tempo limite do servidor upstream em segundos",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-sobre-HTTPS",
"dns_over_tls": "DNS-sobre-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Desativar resolução de endereços IPv6",
"disable_ipv6_desc": "Descarta todas as consultas DNS para endereços IPv6 (tipo AAAA) e remove dicas de IPv6 das respostas HTTPS.",
"fastest_addr": "Endereço de IP mais rápido",
"fastest_addr_desc": "Aguarde as respostas de <b>todos</b> os servidores DNS, meça a velocidade da conexão TCP para cada servidor e retorne o endereço de IP do servidor com a velocidade de conexão mais rápida.<br/>Esse modo pode retardar significativamente as consultas de DNS, se um ou mais servidores DNS primários não estiverem respondendo. Certifique-se de que seus servidores DNS primários sejam estáveis e que seu tempo de espera para DNS seja baixo.",
"fastest_addr_desc": "Consulta todos os servidores DNS e retorna o endereço IP mais rápido entre todas as respostas. Isso torna as consultas DNS mais lentas, pois o AdGuard Home tem que esperar pelas respostas de todos os servidores DNS, mas melhora a conectividade geral.",
"autofix_warning_text": "Se clicar em \"Corrigir\", o AdGuardHome irá configurar o seu sistema para utilizar o servidor DNS do AdGuardHome.",
"autofix_warning_list": "Ele irá realizar estas tarefas: <0>Desativar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
"autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por padrão.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
"parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulta um servidor upstream de cada vez. <br/>O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de pesquisas falhadas e o menor tempo médio de pesquisa.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Resposta bloqueada TTL",
"blocked_response_ttl_desc": "Especifica por quantos segundos os clientes devem armazenar em cache uma resposta filtrada",
"form_enter_blocked_response_ttl": "Insira o TTL da resposta bloqueada (segundos)",
"upstream_timeout": "Tempo esgotado de upstream",
"upstream_timeout_desc": "Especifica o número de segundos a aguardar por uma resposta do servidor upstream",
"form_enter_upstream_timeout": "Insira a duração do tempo esgotado do servidor upstream em segundos",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-sobre-HTTPS",
"dns_over_tls": "DNS-sobre-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Desativar resolução de endereços IPv6",
"disable_ipv6_desc": "Descarte todas as consultas DNS para endereços IPv6 (tipo AAAA) e remova as dicas IPv6 das respostas HTTPS.",
"fastest_addr": "Endereço de IP mais rápido",
"fastest_addr_desc": "Aguarda por respostas de <b>todos</b> os servidores DNS, mede a velocidade da ligação TCP para cada servidor e devolva o endereço IP do servidor com a velocidade de ligação mais rápida.<br/>Este modo pode abrandar significativamente as consultas DNS, se um ou mais servidores upstream não estiverem a responder. Certifique-se de que os seus servidores upstream são estáveis e que o tempo esgotado de upstream é baixo.",
"fastest_addr_desc": "Consulta todos os servidores DNS e retorna o endereço IP mais rápido entre todas as respostas. Isso torna as consultas DNS mais lentas, pois o AdGuard Home tem que esperar pelas respostas de todos os servidores DNS, mas melhora a conectividade geral.",
"autofix_warning_text": "Se clicar em \"Corrigir\", o AdGuardHome irá configurar o seu sistema para utilizar o servidor DNS do AdGuardHome.",
"autofix_warning_list": "Irá realizar estas tarefas: <0>Desativar sistema DNSStubListener</0> <0>Definir endereço do servidor DNS para 127.0.0.1</0> <0>Substituir o alvo simbólico do link /etc/resolv.conf para /run/systemd/resolv.conf</0> <0>Parar DNSStubListener (recarregar serviço resolvido pelo sistema)</0>",
"autofix_warning_result": "Como resultado, todos as solicitações DNS do seu sistema serão processadas pelo AdGuard Home por predefinição.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
"parallel_requests": "Параллельные запросы",
"load_balancing": "Распределение нагрузки\n",
"load_balancing_desc": "Запрашивать по одному upstream-серверу.<br/>AdGuard Home использует алгоритм случайной выборки с учётом веса для выбора серверов с наименьшим количеством неудачных запросов и наименьшим средним временем выполнения запроса.",
"load_balancing_desc": "Запрашивайте по одному серверу за раз. AdGuard Home использует алгоритм случайной выборки с учётом веса для выбора серверов с наименьшим количеством неудачных запросов и наименьшим средним временем выполнения запроса.",
"bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.",
"fallback_dns_title": "Резервные DNS-серверы",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "TTL заблокированного ответа",
"blocked_response_ttl_desc": "Указывает, в течение скольких секунд клиенты должны кешировать отфильтрованный ответ",
"form_enter_blocked_response_ttl": "Введите TTL заблокированного ответа (в секундах)",
"upstream_timeout": "Время ожидания ответов от upstream-серверов",
"upstream_timeout_desc": "Длительность ожидания ответа от upstream-серверов в секундах",
"form_enter_upstream_timeout": "Введите время ожидания для upstream-сервера в секундах",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Отключить обработку IPv6-адресов",
"disable_ipv6_desc": "Игнорировать все DNS-запросы адресов IPv6 (тип AAAA) и удалять IPv6-данные из ответов типа HTTPS.",
"fastest_addr": "Самый быстрый IP-адрес",
"fastest_addr_desc": "Дождаться ответов от <b>всех</b> DNS-серверов, измерить скорость TCP-соединения для каждого сервера и вернуть IP-адрес сервера с самой высокой скоростью соединения.<br/>Этот режим может значительно замедлить выполнение DNS-запросов, если один или несколько серверов не отвечают. Убедитесь, что ваши серверы работают стабильно, а время ожидания серверов мало.",
"fastest_addr_desc": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов. Это замедлит DNS-запросы, так как нужно будет дождаться ответов со всех DNS-серверов, но улучшит соединение.",
"autofix_warning_text": "При нажатии «Исправить» AdGuard Home настроит вашу систему на использование DNS-сервера AdGuard Home.",
"autofix_warning_list": "Будут выполняться следующие задачи: <0>Деактивировать системный DNSStubListener</0> <0>Установить адрес сервера DNS на 127.0.0.1</0> <0>Создать символическую ссылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Остановить DNSStubListener (перезагрузить системную службу)</0>.",
"autofix_warning_result": "В результате все DNS-запросы от вашей системы будут по умолчанию обрабатываться AdGuard Home.\n",

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.",
"parallel_requests": "Paralelné dopyty",
"load_balancing": "Vyrovnávanie záťaže",
"load_balancing_desc": "Dopytuje sa súčasne len jeden upstream server.<br/>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.",
"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_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",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Blokovaná odozva TTL",
"blocked_response_ttl_desc": "Určuje, na koľko sekúnd by mali klienti uložiť filtrovanú odozvu do vyrovnávacej pamäte",
"form_enter_blocked_response_ttl": "Zadajte TTL blokovanej odozve (sekundy)",
"upstream_timeout": "Časový limit pre upstream",
"upstream_timeout_desc": "Určuje počet sekúnd čakania na odpoveď z upstream servera",
"form_enter_upstream_timeout": "Zadajte trvanie časového limitu upstream servera v sekundách",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "Vypnúť rozlišovanie IPv6 adries",
"disable_ipv6_desc": "Ignorovať všetky dotazy DNS na adresy IPv6 (typ AAAA) a odstrániť IPv6 údaje z HTTPS odpovedí.",
"fastest_addr": "Najrýchlejšia IP adresa",
"fastest_addr_desc": "Čaká na odpovede od <b>všetkých</b> DNS serverov, zmeria rýchlosť pripojenia TCP pre každý server a vráti adresu IP servera s najväčšou rýchlosťou pripojenia.<br/>Tento režim môže výrazne spomal DNS dopyty, ak jeden alebo viac upstream serverov neodpovedá. Uistite sa, že Vaše upstream servery sú stabilné a upstream upstream je nízky.",
"fastest_addr_desc": "Dopytovať všetky servery DNS a vrátiť najrýchlejšiu IP adresu zo všetkých odpovedí. Toto spomalí DNS dopyty, pretože AdGuard Home musí čakať na odpovede zo všetkých serverov DNS, ale zlepší sa celkové pripojenie.",
"autofix_warning_text": "Ak kliknete na „Opraviť“, AdGuardHome nakonfiguruje Váš systém tak, aby používal DNS server AdGuardHome.",
"autofix_warning_list": "Bude vykonávať tieto úlohy: <0>Deaktivovať systém DNSStubListener</0> <0>Nastaviť adresu servera DNS na 127.0.0.1</0> <0>Nahradiť cieľový symbolický odkaz /etc/resolv.conf na /run/systemd/resolve/resolv.conf</0> <0>Zastaviť službu DNSStubListener (znova načítať službu systemd-resolved)</0>",
"autofix_warning_result": "Výsledkom bude, že všetky DNS dopyty z Vášho systému budú štandardne spracované službou AdGuard Home.",

View File

@@ -40,11 +40,11 @@
"dhcp_ipv4_settings": "DHCP IPv4 Ayarları",
"dhcp_ipv6_settings": "DHCP IPv6 Ayarları",
"form_error_required": "Gerekli alan",
"form_error_ip4_format": "IPv4 adresi geçersiz",
"form_error_ip4_gateway_format": "Ağ geçidi IPv4 adresi geçersiz",
"form_error_ip6_format": "IPv6 adresi geçersiz",
"form_error_ip_format": "IP adresi geçersiz",
"form_error_mac_format": "MAC adresi geçersiz",
"form_error_ip4_format": "Geçersiz IPv4 adresi",
"form_error_ip4_gateway_format": "Geçersiz ağ geçidi IPv4 adresi",
"form_error_ip6_format": "Geçersiz IPv6 adresi",
"form_error_ip_format": "Geçersiz IP adresi",
"form_error_mac_format": "Geçersiz MAC adresi",
"form_error_client_id_format": "İstemci Kimliği yalnızca sayılar, küçük harfler ve kısa çizgiler içermelidir",
"form_error_server_name": "Sunucu adı geçersiz",
"form_error_subnet": "\"{{cidr}}\" alt ağı, \"{{ip}}\" IP adresini içermiyor",
@@ -68,7 +68,7 @@
"ip": "IP",
"dhcp_table_hostname": "Ana makine Adı",
"dhcp_table_expires": "Bitiş tarihi",
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka bir aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların internet bağlantısı kesilebilir!",
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların internet bağlantısı kesilebilir!",
"dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi",
"dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
"dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}}</0> arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}</0>. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.",
@@ -147,13 +147,13 @@
"average_upstream_response_time": "Ortalama üst kaynak yanıt süresi",
"response_time": "Yanıt süresi",
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
"block_domain_use_filters_and_hosts": "Filtre ve ana bilgisayar dosyalarını kullanarak alan adlarını engelle",
"block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle",
"filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
"use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
"use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.",
"use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan",
"use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.",
"enforce_safe_search": "Güvenli aramayı kullan",
"enforce_safe_search": "Güvenli Aramayı kullan",
"enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.",
"no_servers_specified": "Sunucu belirtilmedi",
"general_settings": "Genel ayarlar",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "Engellenen yanıt kullanım süresi",
"blocked_response_ttl_desc": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir",
"form_enter_blocked_response_ttl": "Engellenen yanıt kullanım süresini girin (saniye)",
"upstream_timeout": "Üst kaynak zaman aşımı",
"upstream_timeout_desc": "Üst kaynak sunucusundan yanıt almak için kaç saniye bekleneceğini belirtir",
"form_enter_upstream_timeout": "Üst kaynak sunucusu zaman aşımı süresini saniye cinsinden girin",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -311,7 +308,7 @@
"form_enter_rate_limit": "Sıklık limitini girin",
"rate_limit": "Sıklık limiti",
"edns_enable": "EDNS istemci alt ağını etkinleştir",
"edns_cs_desc": "Üst sunucu isteklerine ECS (EDNS İstemci Alt Ağı) seçeneğini ekler ve istemciler tarafından gönderilen değerleri sorgu günlüğünde kaydeder.",
"edns_cs_desc": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı seçeneğini (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.",
"edns_use_custom_ip": "EDNS için özel IP kullan",
"edns_use_custom_ip_desc": "EDNS için özel IP kullanımına izin ver",
"rate_limit_desc": "İstemci başına izin verilen saniyedeki istek sayısı. 0 olarak ayarlamak, sınır olmadığı anlamına gelir.",
@@ -345,17 +342,17 @@
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
"known_tracker": "Bilinen izleyici",
"install_welcome_title": "AdGuard Home'a hoş geldiniz!",
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyici engelleyen bir DNS sunucusudur. Tüm ağınızı ve cihazlarınızı kontrol etmenizi sağlar ve istemci tarafında ek bir yazılım kullanmanıza gerek duymaz.",
"install_welcome_desc": "AdGuard Home, ağ genelinde reklamları ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenizi sağlar, istemci tarafında herhangi bir program kullanmanıza gerek duymaz.",
"install_settings_title": "Yönetici Web Arayüzü",
"install_settings_listen": "Dinleme arayüzü",
"install_settings_port": "Bağlantı noktası",
"install_settings_interface_link": "AdGuard Home yönetici web arayüzüne aşağıdaki adreslerden erişebilirsiniz:",
"install_settings_interface_link": "AdGuard Home yönetici web arayüzünüz aşağıdaki adreslerde bulunacaktır:",
"form_error_port": "Geçerli bir bağlantı noktası değeri girin",
"install_settings_dns": "DNS sunucusu",
"install_settings_dns_desc": "Cihazlarınızı veya yönlendiricinizi aşağıdaki adreslerdeki DNS sunucusunu kullanacak şekilde yapılandırmanız gerekir:",
"install_settings_dns_desc": "Aşağıdaki adreslerde DNS sunucusunu kullanmak için cihazlarınızı veya yönlendiricinizi yapılandırmanız gerekir:",
"install_settings_all_interfaces": "Tüm arayüzler",
"install_auth_title": "Kimlik Doğrulama",
"install_auth_desc": "AdGuard Home yönetici web arayüzüne parola ile kimlik doğrulama yapılandırılmalıdır. AdGuard Home yalnızca yerel ağınızdan erişilebilir olsa bile, yine de yetkisiz erişime karşı korunması önemlidir.",
"install_auth_desc": "AdGuard Home yönetim web arayüzü için şifre doğrulaması yapılandırılmalıdır. AdGuard Home'a yalnızca yerel ağınızdan erişilebilir olsa bile, onu sınırsız erişimden korumak yine de önemlidir.",
"install_auth_username": "Kullanıcı adı",
"install_auth_password": "Parola",
"install_auth_confirm": "Parolayı onayla",
@@ -369,10 +366,10 @@
"install_devices_router": "Yönlendirici",
"install_devices_router_desc": "Bu kurulum, ev yönlendiricinize bağlı tüm cihazları otomatik olarak kapsar ve her birini elle yapılandırmanıza gerek yoktur.",
"install_devices_address": "AdGuard Home DNS sunucusu aşağıdaki adresleri dinliyor",
"install_devices_router_list_1": "Yönlendiricinizin ayarlarına gidin. Genellikle, tarayıcınızdan http://192.168.0.1/ veya http://192.168.1.1/ gibi bir URL üzerinden erişebilirsiniz. Giriş yaparken bir parola girmeniz istenebilir. Parolanızı hatırlamıyorsanız, genellikle yönlendiricinin üzerindeki bir düğmeye basarak parolayı sıfırlayabilirsiniz, ancak bu işlemi seçerseniz yönlendiricinin tüm yapılandırmasını kaybedebileceğinizi unutmayın. Yönlendiricinizin kurulumu için bir uygulama gerekiyorsa, lütfen uygulamayı telefonunuza veya bilgisayarınıza yükleyin ve yönlendiricinin ayarlarına erişmek için bu uygulamayı kullanın.",
"install_devices_router_list_1": "Yönlendiricinizin ayarlarına gidin. Genellikle tarayıcınızdan http://192.168.0.1/ veya http://192.168.1.1/ gibi bir URL aracılığıyla erişebilirsiniz. Bir parola girmeniz istenebilir. Hatırlamıyorsanız, genellikle yönlendiricinin üzerindeki bir düğmeye basarak parolayı sıfırlayabilirsiniz, ancak bu işlemin seçilmesi durumunda yüksek ihtimalle tüm yönlendirici yapılandırmasını kaybedeceğinizi unutmayın. Yönlendiricinizin kurulumu için bir uygulama gerekiyorsa, lütfen uygulamayı telefonunuza veya PC'nize yükleyin ve yönlendiricinin ayarlarına erişmek için kullanın.",
"install_devices_router_list_2": "DHCP/DNS ayarlarını bulun. DNS satırlarını arayın, genelde iki veya üç tanedir, üç rakam girilebilen dört ayrı grup içeren satırdır.",
"install_devices_router_list_3": "AdGuard Home sunucu adreslerinizi oraya girin.",
"install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu yapılandırılamaz. Bu durumda, AdGuard Home'u bir <0>DHCP sunucusu</0> olarak yapılandırmak yardımcı olabilir. Aksi takdirde, yönlendirici modelinizde DNS sunucularını nasıl özelleştireceğinizi öğrenmek için yönlendirici kılavuzunu kontrol etmelisiniz.",
"install_devices_router_list_4": "Bazı yönlendirici türlerinde özel bir DNS sunucusu ayarlanamaz. Bu durumda, AdGuard Home'u <0>DHCP sunucusu</0> olarak ayarlamak yardımcı olabilir. Aksi takdirde, yönlendirici modeliniz için DNS sunucularını nasıl ayarlayacağınız konusunda yönlendirici kılavuzuna bakmalısınız.",
"install_devices_windows_list_1": "Başlat menüsünden veya Windows araması aracılığıyla Denetim Masası'nıın.",
"install_devices_windows_list_2": "Ağ ve İnternet kategorisine girin ve ardından Ağ ve Paylaşım Merkezi'ne girin.",
"install_devices_windows_list_3": "Sol panelde \"Bağdaştırıcı ayarlarını değiştirin\" öğesine tıklayın.",
@@ -392,7 +389,7 @@
"install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (telefon ağlar için özel DNS sunucusu ayarlanamaz).",
"install_devices_ios_list_3": "O anda aktif olan ağın adına dokunun.",
"install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.",
"get_started": "Başla",
"get_started": "Başlayın",
"next": "Sonraki",
"open_dashboard": "Panoyu Aç",
"install_saved": "Başarıyla kaydedildi",
@@ -455,14 +452,14 @@
"settings_global": "Genel",
"settings_custom": "Özel",
"table_client": "İstemci",
"table_name": "Ad",
"table_name": "AdAdı",
"save_btn": "Kaydet",
"client_add": "İstemci Ekle",
"client_new": "Yeni İstemci",
"client_edit": "İstemciyi Düzenle",
"client_identifier": "Tanımlayıcı",
"ip_address": "IP adresi",
"client_identifier_desc": "İstemciler, IP adresi, CIDR, MAC adresi veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiye <0>buradan</0> ulaşabilirsiniz.",
"client_identifier_desc": "İstemciler IP adresleri, CIDR, MAC adresleri veya ClientID (DoT/DoH/DoQ için kullanılabilir) ile tanımlanabilir. İstemcileri nasıl tanımlayacağınız hakkında daha fazla bilgiyi <0>buradan</0> edinebilirsiniz.",
"form_enter_ip": "IP girin",
"form_enter_subnet_ip": "\"{{cidr}}\" alt ağına bir IP adresi girin",
"form_enter_mac": "MAC adresi girin",
@@ -479,7 +476,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"auto_clients_title": "Çalışma zamanı istemcileri",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, ana bilgisayar dosyaları, ters DNS sorguları ve çeşitli diğer kaynaklardan toplanmaktadır.",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dâhil olmak üzere çeşitli kaynaklardan toplanır.",
"access_title": "Erişim ayarları",
"access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler",
@@ -601,12 +598,12 @@
"disable_ipv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak",
"disable_ipv6_desc": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.",
"fastest_addr": "En hızlı IP adresi",
"fastest_addr_desc": "<b>Tüm</b> DNS sunucularından yanıt bekler, her sunucu için TCP bağlantı hızını ölçer ve en hızlı bağlantı hızına sahip sunucunun IP adresini döndürür.<br/>Bu yapılandırma, bir veya daha fazla üst kaynak sunucusu yanıt vermediğinde, DNS sorgularını önemli ölçüde yavaşlatabilir. Üst kaynak sunucularınızın kararlı olduğundan ve üst kaynak zaman aşım sürenizin düşük olduğundan emin olun.",
"fastest_addr_desc": "Tüm DNS sunucularını sorgulayın ve tüm yanıtlar arasından en hızlı olan IP adresini döndürün. AdGuard Home'un tüm DNS sunucularından yanıt beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
"autofix_warning_text": "\"Düzelt\" seçeneğine tıklarsanız, AdGuard Home, sisteminizi AdGuard Home DNS sunucusunu kullanacak şekilde yapılandırır.",
"autofix_warning_list": "Bu görevleri gerçekleştirir: <0>Sistem DNSStubListener'ı devre dışı bırakın</0> <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın</0> <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)</0>",
"autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.",
"tags_title": "Etiketler",
"tags_desc": "İstemciyi tanımlayan etiketleri seçebilirsiniz. Filtreleme kurallarına etiketleri dahil ederek daha hassas bir şekilde uygulayabilirsiniz. <0>Daha fazla bilgi edinin</0>.",
"tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dâhil edin. <0>Daha fazla bilgi edinin</0>.",
"form_select_tags": "İstemci etiketlerini seçin",
"check_title": "Filtrelemeyi denetleyin",
"check_desc": "Ana makine adının filtreleme durumunu kontrol edin.",
@@ -627,11 +624,11 @@
"client_blocked": "\"{{ip}}\" istemcisi başarıyla engellendi",
"client_unblocked": "\"{{ip}}\" istemcinin engellemesi başarıyla kaldırıldı",
"static_ip": "Sabit IP adresi",
"static_ip_desc": "AdGuard Home bir sunucudur, bu nedenle düzgün çalışabilmesi için sabit bir IP adresine ihtiyaç duyar. Aksi takdirde, yönlendiriciniz bu cihaza farklı bir IP adresi atayabilir.",
"static_ip_desc": "AdGuard Home bir sunucudur, bu nedenle düzgün çalışması için sabit bir IP adresine ihtiyacı vardır. Aksi takdirde, yönlendiriciniz bir zaman sonra bu cihaza farklı bir IP adresi atayabilir.",
"set_static_ip": "Sabit IP adresi ayarla",
"install_static_ok": "Güzel haber! Sabit IP adresi zaten yapılandırılmış",
"install_static_error": "AdGuard Home, bu ağ arayüzü için otomatik olarak yapılandıramıyor. Lütfen bunu elle nasıl yapacağınızla ilgili talimatlara bakın.",
"install_static_configure": "AdGuard Home, <0>{{ip}}</0> sabit IP adresinin kullanıldığını tespit etti. Sabit adresiniz olarak ayarlanmasını ister misiniz?",
"install_static_configure": "AdGuard Home, <0>{{ip}}</0> dinamik IP adresinin kullanıldığını tespit etti. Sabit adresiniz olarak ayarlanmasını ister misiniz?",
"confirm_static_ip": "AdGuard Home, {{ip}} adresini sabit IP adresiniz olacak şekilde yapılandırır. Devam etmek istiyor musunuz?",
"list_updated": "{{count}} liste güncellendi",
"list_updated_plural": "{{count}} liste güncellendi",
@@ -710,8 +707,8 @@
"custom_rotation_input": "Rotasyonu saat cinsinden girin",
"protection_section_label": "Koruma",
"log_and_stats_section_label": "Sorgu günlüğü ve istatistikler",
"ignore_query_log": "Sorgu günlüğünde bu istemciyi gösterme",
"ignore_statistics": "İstatistiklerde bu istemciyi gösterme",
"ignore_query_log": "Sorgu günlüğünde bu istemciyi yoksay",
"ignore_statistics": "İstatistiklerde bu istemciyi yoksay",
"schedule_services": "Hizmet engellemeyi duraklat",
"schedule_services_desc": "Hizmet engelleme filtresinin duraklatma planını yapılandırın",
"schedule_services_desc_client": "Bu istemci için hizmet engelleme filtresinin duraklatma planını yapılandırın",
@@ -745,6 +742,6 @@
"friday_short": "Cum",
"saturday_short": "Cmt",
"upstream_dns_cache_configuration": "Üst kaynak DNS önbellek yapılandırması",
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğini etkinleştir",
"enable_upstream_dns_cache": "Bu istemcinin özel üst kaynak yapılandırması için DNS önbelleğe almayı etkinleştir",
"dns_cache_size": "DNS önbellek boyutu, bayt cinsinden"
}

View File

@@ -20,7 +20,7 @@
"resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів",
"resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
"use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
"use_private_ptr_resolvers_desc": "Розвʼязувати запити PTR, SOA та NS для доменів ARPA, що містять приватні IP-адреси, через приватні вихідні сервери, DHCP, /etc/hosts тощо. Якщо вимкнено, AdGuard Home відповідатиме на всі такі запити з NXDOMAIN.",
"use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі завдяки DHCP, /etc/hosts тощо.",
"check_dhcp_servers": "Перевірити DHCP-сервери",
"save_config": "Зберегти конфігурацію",
"enabled_dhcp": "DHCP-сервер увімкнено",
@@ -343,10 +343,10 @@
"known_tracker": "Відомі трекери",
"install_welcome_title": "Вітаємо в AdGuard Home!",
"install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.",
"install_settings_title": "Вебінтерфейс адміністратора",
"install_settings_title": "Веб-інтерфейс адміністратора",
"install_settings_listen": "Мережевий інтерфейс",
"install_settings_port": "Порт",
"install_settings_interface_link": "Вебінтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
"install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
"form_error_port": "Уведіть правильне значення порту",
"install_settings_dns": "DNS-сервер",
"install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
"parallel_requests": "并行请求",
"load_balancing": "负载均衡",
"load_balancing_desc": "一次查询一台上游服务器。<br/>AdGuard Home 使用加权随机算法来选择具有最少失败查找和最低平均查找时间的服务器。",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择具有最少失败查找和最低平均查找时间的服务器。",
"bootstrap_dns": "Bootstrap DNS 服务器",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。",
"fallback_dns_title": "后备 DNS 服务器",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "屏蔽的 TTL 应答",
"blocked_response_ttl_desc": "指定客户端应缓存已过滤响应的秒数",
"form_enter_blocked_response_ttl": "输入拦截的 TTL 应答(秒)",
"upstream_timeout": "上游超时",
"upstream_timeout_desc": "指定等待上游服务器响应的秒数",
"form_enter_upstream_timeout": "输入上游服务器超时时间(以秒为单位)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -601,7 +598,7 @@
"disable_ipv6": "禁用 IPv6 地址的解析",
"disable_ipv6_desc": "丢弃对 IPv6 地址(类型 AAAA的所有 DNS 查询,并从 HTTPS 响应中删除 IPv6 相关的信息。",
"fastest_addr": "最快的 IP 地址",
"fastest_addr_desc": "等待<b>所有</b> DNS 服务器的响应,测量每个服务器的 TCP 连接速度,并返回连接速度最快的服务器的 IP 地址。<br/>如果一个或多个上游服务器没有响应,此模式会显著减慢 DNS 查询速度。确保您的上游服务器稳定且上游超时时间短。",
"fastest_addr_desc": "查询所有 DNS 服务器并返回所有响应中速度最快的 IP 地址。因 AdGuard Home 必须等待全部 DNS 服务器响应,这会降低 DNS 查询速度,但此举将会在总体上改善连接速度。",
"autofix_warning_text": "若您单击「修复」AdGuard Home 将会配置您的系统以使用 AdGuard Home 的 DNS 服务器。",
"autofix_warning_list": "其将会进行如下工作:<0>停用系统DNSStubListener</0><0>设置DNS服务器地址为127.0.0.1</0><0>将/etc/resolv.conf的符号链接目标替换为/run/systemd/resolv/resolv.conf</0><0>停止DNSStubListener重新加载系统解析服务</0>",
"autofix_warning_result": "因此,默认情况下所有来自系统的 DNS 请求都将由 AdGuard Home 处理。",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
"parallel_requests": "並行的請求",
"load_balancing": "負載平衡",
"load_balancing_desc": "一次查詢一台上游伺服器。<br/>AdGuard Home 使用加權隨機演算法來選擇具有最少失敗查詢和最低平均查詢時間的伺服器。",
"load_balancing_desc": "一次查詢一台伺服器。AdGuard Home 使用加權隨機演算法來選擇具有最少失敗查詢和最低平均查詢時間的伺服器。",
"bootstrap_dns": "自我啟動BootstrapDNS 伺服器",
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
"fallback_dns_title": "應變 DNS 伺服器",
@@ -20,17 +20,17 @@
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
"resolve_clients_desc": "透過傳送指標PTR查詢到對應的解析器私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
"use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域的 PTR、SOA 和 NS 請求。如果AdGuard Home 將對所有此類請求以 NXDOMAIN 回應。",
"check_dhcp_servers": "檢查動態主機設定協定DHCP伺服器",
"save_config": "儲存配置",
"enabled_dhcp": "動態主機設定協定DHCP伺服器被啟用",
"disabled_dhcp": "DHCP 伺服器已停用",
"disabled_dhcp": "動態主機設定協定(DHCP伺服器被禁用",
"unavailable_dhcp": "DHCP 為不可用的",
"unavailable_dhcp_desc": "AdGuard Home 無法於您的作業系統上執行 DHCP 伺服器",
"dhcp_title": "動態主機設定協定DHCP伺服器實驗性的",
"dhcp_description": "如果您的路由器未提供動態主機設定協定DHCP設定您可使用 AdGuard 自身內建的 DHCP 伺服器。",
"dhcp_enable": "啟用動態主機設定協定DHCP伺服器",
"dhcp_disable": "停用 DHCP 伺服器",
"dhcp_disable": "禁用動態主機設定協定(DHCP伺服器",
"dhcp_not_found": "因為 AdGuard Home 於該網路上未發現任何現行的 DHCP 伺服器啟用內建的動態主機設定協定DHCP伺服器為安全的。然而您應手動地重新檢查那個因為自動的探查目前不予 100 保證。",
"dhcp_found": "於該網路上一個現行的動態主機設定協定DHCP伺服器被發現。啟用內建的 DHCP 伺服器為不安全的。",
"dhcp_leases": "動態主機設定協定DHCP租約",
@@ -294,9 +294,6 @@
"blocked_response_ttl": "已封鎖的回應之存活時間TTL",
"blocked_response_ttl_desc": "對用戶端應快取受過濾的回應,指定多少秒數",
"form_enter_blocked_response_ttl": "請輸入已封鎖回應的存活時間(秒)",
"upstream_timeout": "上游超時",
"upstream_timeout_desc": "指定等待來自此上游伺服器回應的秒數",
"form_enter_upstream_timeout": "輸入上游伺服器超時時間(以秒為單位)",
"dnscrypt": "DNSCrypt",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
@@ -482,7 +479,7 @@
"auto_clients_desc": "AdGuard Home 使用或可能使用的裝置的 IP 地址資訊。這些資訊來自多個來源,包括主機檔案、反向 DNS 等。",
"access_title": "存取設定",
"access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則",
"access_allowed_title": "允許的用戶端",
"access_allowed_title": "允許的用戶端",
"access_allowed_desc": "無類別網域間路由CIDRs、IP 位址或<a>用戶端 IDs</a> 之清單。如果此清單有項目AdGuard Home 將接受僅來自這些用戶端的請求。",
"access_disallowed_title": "未被允許的用戶端",
"access_disallowed_desc": "無類別網域間路由CIDRs、IP 位址或<a>用戶端 IDs</a> 之清單。如果此清單有項目AdGuard Home 將排除來自這些用戶端的請求。如果在已允許的用戶端中有項目,此欄位被忽略。",
@@ -563,7 +560,7 @@
"statistics_retention_confirm": "您確定您想要更改統計資料保留嗎?如果您減少該間隔值,某些資料將被丟失",
"statistics_cleared": "統計資料被成功地清除",
"statistics_enable": "啟用統計資料",
"ignore_domains": "忽略的網域(換行分隔)",
"ignore_domains": "忽略的網域(換行分隔)",
"ignore_domains_title": "被忽略的網域",
"ignore_domains_desc_stats": "符合這些規則的查詢不會被記錄在統計資料中",
"ignore_domains_desc_query": "符合這些規則的查詢不會被寫入查詢記錄中",
@@ -601,7 +598,7 @@
"disable_ipv6": "禁用 IPv6 位址之解析",
"disable_ipv6_desc": "停止所有對於 IPv6 位址(類型 AAAA的 DNS 查詢,並從 HTTPS 回應中移除 IPv6 的提示。",
"fastest_addr": "最快的 IP 位址",
"fastest_addr_desc": "等待<b>所有</b> DNS 伺服器的回應,測量每個伺服器的 TCP 連線速度,並返回連線速度最快的伺服器的 IP 位址。<br/>如果一個或多個上游伺服器沒有回應,此模式會顯著減慢 DNS 查詢速度。確保您的上游伺服器穩定且上游超時時間短。",
"fastest_addr_desc": "查詢所有的 DNS 伺服器並返回在所有的回應之中最快的 IP 位址。因為 AdGuard Home 必須等待來自所有的 DNS 伺服器回應,這使 DNS 查詢變慢,但改善總體的連線。",
"autofix_warning_text": "如果您點擊\"修復\"AdGuard Home 將配置您的系統使用 AdGuard Home DNS 伺服器。",
"autofix_warning_list": "它將執行這些任務:<0>撤銷系統 DNSStubListener</0> <0>設定 DNS 伺服器位址為 127.0.0.1</0> <0>用 /run/systemd/resolve/resolv.conf 取代 /etc/resolv.conf 的符號連結目標</0> <0>停止 DNSStubListener重新載入 systemd-resolved 服務)</0>",
"autofix_warning_result": "因此,預設下,來自您的系統之所有的 DNS 請求將被 AdGuard Home 處理。",
@@ -640,7 +637,7 @@
"validated_with_dnssec": "已用網域名稱系統安全性擴充功能DNSSEC驗證",
"all_queries": "所有的查詢",
"show_blocked_responses": "已封鎖的",
"show_whitelisted_responses": "允許的",
"show_whitelisted_responses": "允許的",
"show_processed_responses": "已處理的",
"blocked_safebrowsing": "被安全瀏覽封鎖",
"blocked_adult_websites": "被家長控制封鎖",
@@ -676,8 +673,8 @@
"click_to_view_queries": "點擊以檢視查詢",
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。",
"adg_will_drop_dns_queries": "AdGuard Home 將持續排除來自此用戶端之所有的 DNS 查詢。",
"filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在允許的用戶端的清單之外。",
"last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"允許的用戶端\"清單,無法不允許此用戶端。",
"filter_allowlist": "警告:此動作也將把 \"{{disallowed_rule}}\" 規則排除在允許的用戶端的清單之外。",
"last_rule_in_allowlist": "因為排除 \"{{disallowed_rule}}\" 規則將禁用\"允許的用戶端\"清單,無法不允許此用戶端。",
"use_saved_key": "使用該先前已儲存的金鑰",
"parental_control": "家長控制",
"safe_browsing": "安全瀏覽",

View File

@@ -1,5 +1,3 @@
import { describe, expect, test, afterEach, vi, beforeEach, it } from 'vitest';
import { sortIp, countClientsStatistics, findAddressType, subnetMaskToBitMask } from '../helpers/helpers';
import { ADDRESS_TYPES } from '../helpers/constants';
@@ -261,7 +259,7 @@ describe('sortIp', () => {
const originalWarn = console.warn;
beforeEach(() => {
console.warn = vi.fn();
console.warn = jest.fn();
});
afterEach(() => {
@@ -349,15 +347,15 @@ describe('sortIp', () => {
});
describe('findAddressType', () => {
it('should return IP type for IP addresses', () => {
describe('ip', () => {
expect(findAddressType('127.0.0.1')).toStrictEqual(ADDRESS_TYPES.IP);
});
it('should return CIDR type for CIDR addresses', () => {
describe('cidr', () => {
expect(findAddressType('127.0.0.1/8')).toStrictEqual(ADDRESS_TYPES.CIDR);
});
it('should return UNKNOWN type for MAC addresses', () => {
describe('mac', () => {
expect(findAddressType('00:1B:44:11:3A:B7')).toStrictEqual(ADDRESS_TYPES.UNKNOWN);
});
});

View File

@@ -19,6 +19,7 @@ import {
CHECK_TIMEOUT,
STATUS_RESPONSE,
SETTINGS_NAMES,
FORM_NAME,
MANUAL_UPDATE_LINK,
DISABLE_PROTECTION_TIMINGS,
} from '../helpers/constants';
@@ -423,9 +424,10 @@ export const testUpstream =
}
};
export const testUpstreamWithFormValues = (formValues: any) => async (dispatch: any, getState: any) => {
export const testUpstreamWithFormValues = () => async (dispatch: any, getState: any) => {
const { upstream_dns_file } = getState().dnsConfig;
const { bootstrap_dns, upstream_dns, local_ptr_upstreams, fallback_dns } = formValues;
const { bootstrap_dns, upstream_dns, local_ptr_upstreams, fallback_dns } =
getState().form[FORM_NAME.UPSTREAM].values;
return dispatch(
testUpstream(
@@ -510,15 +512,16 @@ export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = (selectedInterface: any) => async (dispatch: any, getState: any) => {
export const findActiveDhcp = (name: any) => async (dispatch: any, getState: any) => {
dispatch(findActiveDhcpRequest());
try {
const req = {
interface: selectedInterface,
interface: name,
};
const activeDhcp = await apiClient.findActiveDhcp(req);
dispatch(findActiveDhcpSuccess(activeDhcp));
const { check, interface_name, interfaces } = getState().dhcp;
const selectedInterface = getState().form[FORM_NAME.DHCP_INTERFACES].values.interface_name;
const v4 = check?.v4 ?? { static_ip: {}, other_server: {} };
const v6 = check?.v6 ?? { other_server: {} };

View File

@@ -27,8 +27,7 @@ export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
export const setAllSettings = (values: any) => async (dispatch: any) => {
dispatch(setAllSettingsRequest());
try {
const config = { ...values };
delete config.confirm_password;
const { confirm_password, ...config } = values;
await apiClient.setAllSettings(config);
dispatch(setAllSettingsSuccess());
@@ -49,11 +48,7 @@ export const checkConfig = (values: any) => async (dispatch: any) => {
dispatch(checkConfigRequest());
try {
const check = await apiClient.checkConfig(values);
dispatch(checkConfigSuccess({
web: { ...values.web, ...check.web },
dns: { ...values.dns, ...check.dns },
static_ip: check.static_ip,
}));
dispatch(checkConfigSuccess(check));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(checkConfigFailure());

View File

@@ -3,9 +3,8 @@ import { createAction } from 'redux-actions';
import apiClient from '../api/Api';
import { normalizeLogs } from '../helpers/helpers';
import { DEFAULT_LOGS_FILTER, QUERY_LOGS_PAGE_LIMIT } from '../helpers/constants';
import { DEFAULT_LOGS_FILTER, FORM_NAME, QUERY_LOGS_PAGE_LIMIT } from '../helpers/constants';
import { addErrorToast, addSuccessToast } from './toasts';
import { SearchFormValues } from '../components/Logs';
const getLogsWithParams = async (config: any) => {
const { older_than, filter, ...values } = config;
@@ -28,10 +27,12 @@ export const getAdditionalLogsRequest = createAction('GET_ADDITIONAL_LOGS_REQUES
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
const shortPollQueryLogs = async (data: any, filter: any, dispatch: any, currentQuery?: string, total?: any) => {
const shortPollQueryLogs = async (data: any, filter: any, dispatch: any, getState: any, total?: any) => {
const { logs, oldest } = data;
const totalData = total || { logs };
const queryForm = getState().form[FORM_NAME.LOGS_FILTER];
const currentQuery = queryForm && queryForm.values.search;
const previousQuery = filter?.search;
const isQueryTheSame =
typeof previousQuery === 'string' && typeof currentQuery === 'string' && previousQuery === currentQuery;
@@ -50,7 +51,7 @@ const shortPollQueryLogs = async (data: any, filter: any, dispatch: any, current
filter,
});
if (additionalLogs.oldest.length > 0) {
return await shortPollQueryLogs(additionalLogs, filter, dispatch, currentQuery, {
return await shortPollQueryLogs(additionalLogs, filter, dispatch, getState, {
logs: [...totalData.logs, ...additionalLogs.logs],
oldest: additionalLogs.oldest,
});
@@ -90,18 +91,17 @@ export const updateLogs = () => async (dispatch: any, getState: any) => {
}
};
export const getLogs = (currentQuery?: string) => async (dispatch: any, getState: any) => {
export const getLogs = () => async (dispatch: any, getState: any) => {
dispatch(getLogsRequest());
try {
const { isFiltered, filter, oldest } = getState().queryLogs;
const data = await getLogsWithParams({
older_than: oldest,
filter,
});
if (isFiltered) {
const additionalData = await shortPollQueryLogs(data, filter, dispatch, currentQuery);
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
dispatch(getLogsSuccess(updatedData));
} else {
@@ -122,13 +122,13 @@ export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
* @param {string} filter.response_status 'QUERY' field of RESPONSE_FILTER object
* @returns function
*/
export const setLogsFilter = (filter: SearchFormValues) => setLogsFilterRequest(filter);
export const setLogsFilter = (filter: any) => setLogsFilterRequest(filter);
export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST');
export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
export const setFilteredLogs = (filter?: SearchFormValues) => async (dispatch: any) => {
export const setFilteredLogs = (filter?: any) => async (dispatch: any, getState: any) => {
dispatch(setFilteredLogsRequest());
try {
const data = await getLogsWithParams({
@@ -136,9 +136,7 @@ export const setFilteredLogs = (filter?: SearchFormValues) => async (dispatch: a
filter,
});
const currentQuery = filter?.search;
const additionalData = await shortPollQueryLogs(data, filter, dispatch, currentQuery);
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
dispatch(

View File

@@ -46,7 +46,7 @@ export const getStats = () => async (dispatch: any) => {
const normalizedTopClients = normalizeTopStats(stats.top_clients);
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
const clients = await apiClient.searchClients(clientsParams);
const clients = await apiClient.findClients(clientsParams);
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
const normalizedStats = {

View File

@@ -415,7 +415,7 @@ class Api {
// Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' };
SEARCH_CLIENTS = { path: 'clients/search', method: 'POST' };
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
@@ -453,12 +453,11 @@ class Api {
return this.makeRequest(path, method, parameters);
}
searchClients(config: any) {
const { path, method } = this.SEARCH_CLIENTS;
const parameters = {
data: config,
};
return this.makeRequest(path, method, parameters);
findClients(params: any) {
const { path, method } = this.FIND_CLIENTS;
const url = getPathWithQueryString(path, params);
return this.makeRequest(url, method);
}
// DNS access settings

View File

@@ -23,7 +23,7 @@ interface RowProps {
const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => {
const content = response_status ? (
<LogsSearchLink response_status={response_status}>{count}</LogsSearchLink>
<LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
) : (
count
);
@@ -77,16 +77,16 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
? t('number_of_dns_query_hours', { count: msToHours(interval) })
: t('number_of_dns_query_days', { count: msToDays(interval) });
const rows: RowProps[] = [
const rows = [
{
label: 'dns_query',
count: formatNumber(numDnsQueries),
count: numDnsQueries.toString(),
tooltipTitle: dnsQueryTooltip,
response_status: RESPONSE_FILTER.ALL.QUERY,
},
{
label: 'blocked_by',
count: formatNumber(numBlockedFiltering),
count: numBlockedFiltering.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
response_status: RESPONSE_FILTER.BLOCKED.QUERY,
@@ -98,19 +98,19 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
},
{
label: 'stats_malware_phishing',
count: formatNumber(numReplacedSafebrowsing),
count: numReplacedSafebrowsing.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
},
{
label: 'stats_adult',
count: formatNumber(numReplacedParental),
count: numReplacedParental.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
},
{
label: 'enforced_save_search',
count: formatNumber(numReplacedSafesearch),
count: numReplacedSafesearch.toString(),
tooltipTitle: 'number_of_dns_query_to_safe_search',
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
},

View File

@@ -10,7 +10,6 @@ import Card from '../ui/Card';
import DomainCell from './DomainCell';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
import { formatNumber } from '../../helpers/helpers';
interface TimeCellProps {
value?: string | number;
@@ -21,7 +20,7 @@ const TimeCell = ({ value }: TimeCellProps) => {
return '';
}
const valueInMilliseconds = formatNumber(round(Number(value) * 1000));
const valueInMilliseconds = round(Number(value) * 1000);
return (
<div className="logs__row o-hidden">

View File

@@ -154,7 +154,7 @@ const Dashboard = ({
}}
disabled={processingProtection}>
{protectionDisabledDuration
? `${t('enable_protection_timer', { time: getRemaningTimeText(protectionDisabledDuration) })}`
? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
: getProtectionBtnText(protectionEnabled)}
</button>

View File

@@ -1,112 +1,62 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Field, reduxForm } from 'redux-form';
import { useSelector } from 'react-redux';
import { Controller, useForm } from 'react-hook-form';
import Card from '../../ui/Card';
import { renderInputField } from '../../../helpers/form';
import Info from './Info';
import { FORM_NAME } from '../../../helpers/constants';
import { RootState } from '../../../initialState';
import { validateRequiredValue } from '../../../helpers/validators';
import { Input } from '../../ui/Controls/Input';
import { DNS_RECORD_TYPES } from '../../../helpers/constants';
import { Select } from '../../ui/Controls/Select';
export type FilteringCheckFormValues = {
name: string;
client_id?: string;
dns_record_type?: string;
interface CheckProps {
handleSubmit: (...args: unknown[]) => string;
pristine: boolean;
invalid: boolean;
}
type Props = {
onSubmit?: (data: FilteringCheckFormValues) => void;
};
const Check = (props: CheckProps) => {
const { pristine, invalid, handleSubmit } = props;
const Check = ({ onSubmit }: Props) => {
const { t } = useTranslation();
const processingCheck = useSelector((state: RootState) => state.filtering.processingCheck);
const hostname = useSelector((state: RootState) => state.filtering.check.hostname);
const {
control,
handleSubmit,
formState: { isValid },
} = useForm<FilteringCheckFormValues>({
mode: 'onBlur',
defaultValues: {
name: '',
client_id: '',
dns_record_type: DNS_RECORD_TYPES[0],
},
});
const hostname = useSelector((state: RootState) => state.filtering.check.hostname);
return (
<Card title={t('check_title')} subtitle={t('check_desc')}>
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-md-6">
<Controller
name="name"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
label={t('check_hostname')}
data-testid="check_domain_name"
placeholder="example.com"
error={fieldState.error?.message}
/>
)}
/>
<div className="input-group">
<Field
id="name"
name="name"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_host')}
/>
<Controller
name="client_id"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="check_client_id"
label={t('check_client_id')}
placeholder={t('check_enter_client_id')}
error={fieldState.error?.message}
/>
)}
/>
<Controller
name="dns_record_type"
control={control}
render={({ field }) => (
<Select
{...field}
label={t('check_dns_record')}
data-testid="check_dns_record_type"
>
{DNS_RECORD_TYPES.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</Select>
)}
/>
<button
className="btn btn-success btn-standard btn-large"
type="submit"
data-testid="check_domain_submit"
disabled={!isValid || processingCheck}
>
{t('check')}
</button>
<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 />
</>
)}
@@ -117,4 +67,4 @@ const Check = ({ onSubmit }: Props) => {
);
};
export default Check;
export default reduxForm({ form: FORM_NAME.DOMAIN_CHECK })(Check);

View File

@@ -7,7 +7,7 @@ import PageTitle from '../ui/PageTitle';
import Examples from './Examples';
import Check, { FilteringCheckFormValues } from './Check';
import Check from './Check';
import { getTextareaCommentsHighlight, syncScroll } from '../../helpers/highlightTextareaComments';
import { COMMENT_LINE_DEFAULT_TOKEN } from '../../helpers/constants';
@@ -48,7 +48,7 @@ class CustomRules extends Component<CustomRulesProps> {
this.props.setRules(this.props.filtering.userRules);
};
handleCheck = (values: FilteringCheckFormValues) => {
handleCheck = (values: any) => {
this.props.checkHost(values);
};

View File

@@ -1,94 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { Checkbox } from '../ui/Controls/Checkbox';
const getIconsData = (homepage: string, source: string) => [
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
];
const renderIcons = (iconsData: { iconName: string; href: string; className?: string }[]) =>
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>
));
type Filter = {
categoryId: string;
homepage: string;
source: string;
name: string;
};
type Category = {
name: string;
description: string;
};
type Props = {
categories: Record<string, Category>;
filters: Record<string, Filter>;
selectedSources: Record<string, boolean>;
};
export const FiltersList = ({ categories, filters, selectedSources }: Props) => {
const { t } = useTranslation();
const { control } = useFormContext();
return (
<>
{Object.entries(categories).map(([categoryId, category]) => {
const categoryFilters = Object.entries(filters)
.filter(([, filter]) => filter.categoryId === categoryId)
.map(([key, filter]) => ({ ...filter, id: key }));
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, id } = filter;
const isSelected = selectedSources[source];
const iconsData = getIconsData(homepage, source);
return (
<div key={name} className="d-flex align-items-center pb-1">
<Controller
name={id}
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid={`filters_${id}`}
title={name}
disabled={isSelected}
/>
)}
/>
{renderIcons(iconsData)}
</div>
);
})}
</div>
);
})}
</>
);
};

View File

@@ -1,152 +1,208 @@
import React from 'react';
import { useForm, Controller, FormProvider } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
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 { MODAL_OPEN_TIMEOUT, MODAL_TYPE } from '../../helpers/constants';
import { CheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
import filtersCatalog from '../../helpers/filters/filters';
import { FiltersList } from './FiltersList';
import { Input } from '../ui/Controls/Input';
type FormValues = {
enabled: boolean;
name: string;
url: string;
};
const getIconsData = (homepage: any, source: any) => [
{
iconName: 'dashboard',
href: homepage,
className: 'ml-1',
},
{
iconName: 'info',
href: source,
},
];
const defaultValues: FormValues = {
enabled: true,
name: '',
url: '',
};
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>
));
type Props = {
closeModal: () => void;
onSubmit: (values: FormValues) => void;
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: ({ type }: { type?: keyof typeof MODAL_TYPE }) => void;
selectedSources?: Record<string, boolean>;
initialValues?: FormValues;
};
toggleFilteringModal: (...args: unknown[]) => unknown;
selectedSources?: object;
}
export const Form = ({
closeModal,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
onSubmit,
initialValues,
}: Props) => {
const { t } = useTranslation();
const Form = (props: FormProps) => {
const {
t,
closeModal,
handleSubmit,
processingAddFilter,
processingConfigFilter,
whitelist,
modalType,
toggleFilteringModal,
selectedSources,
} = props;
const methods = useForm({
defaultValues: {
...defaultValues,
...initialValues,
},
mode: 'onBlur',
});
const { handleSubmit, control } = methods;
const openModal = (modalType: keyof typeof MODAL_TYPE, timeout = MODAL_OPEN_TIMEOUT) => {
toggleFilteringModal(undefined);
const openModal = (modalType: any, timeout = MODAL_OPEN_TIMEOUT) => {
toggleFilteringModal();
setTimeout(() => toggleFilteringModal({ type: modalType }), timeout);
};
const openFilteringListModal = () => openModal('CHOOSE_FILTERING_LIST');
const openFilteringListModal = () => openModal(MODAL_TYPE.CHOOSE_FILTERING_LIST);
const openAddFiltersModal = () => openModal('ADD_FILTERS');
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<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 && (
<FiltersList
categories={filtersCatalog.categories}
filters={filtersCatalog.filters}
selectedSources={selectedSources}
/>
)}
{modalType !== MODAL_TYPE.CHOOSE_FILTERING_LIST && modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
<>
<div className="form__group">
<Controller
name="name"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="filters_name"
placeholder={t('enter_name_hint')}
error={fieldState.error?.message}
trimOnBlur
/>
)}
/>
</div>
<div className="form__group">
<Controller
name="url"
control={control}
rules={{ validate: { validateRequiredValue, validatePath } }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="filters_url"
placeholder={t('enter_url_or_path_hint')}
error={fieldState.error?.message}
trimOnBlur
/>
)}
/>
</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 && (
<form onSubmit={handleSubmit}>
<div className="modal-body modal-body--filters">
{modalType === MODAL_TYPE.SELECT_MODAL_TYPE && (
<div className="d-flex justify-content-around">
<button
type="submit"
data-testid="filters_save"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}>
{t('save_btn')}
onClick={openFilteringListModal}
className="btn btn-success btn-standard mr-2 btn-large">
{t('choose_from_list')}
</button>
)}
</div>
</form>
</FormProvider>
<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

@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
import { MODAL_TYPE } from '../../helpers/constants';
import { Form } from './Form';
import Form from './Form';
import '../ui/Modal.css';
import { getMap } from '../../helpers/helpers';
@@ -75,15 +75,25 @@ class Modal extends Component<ModalProps> {
render() {
const {
isOpen,
processingAddFilter,
processingConfigFilter,
handleSubmit,
modalType,
currentFilterData,
whitelist,
toggleFilteringModal,
filters,
t,
filtersCatalog,
} = this.props;

View File

@@ -1,69 +1,42 @@
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { renderInputField } from '../../../helpers/form';
import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators';
import { Input } from '../../ui/Controls/Input';
import { FORM_NAME } from '../../../helpers/constants';
interface RewriteFormValues {
domain: string;
answer: string;
interface FormProps {
pristine: boolean;
handleSubmit: (...args: unknown[]) => string;
reset: (...args: unknown[]) => string;
toggleRewritesModal: (...args: unknown[]) => unknown;
submitting: boolean;
processingAdd: boolean;
t: (...args: unknown[]) => string;
initialValues?: object;
}
type Props = {
processingAdd: boolean;
currentRewrite?: RewriteFormValues;
toggleRewritesModal: () => void;
onSubmit?: (data: RewriteFormValues) => Promise<void> | void;
};
const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }: Props) => {
const { t } = useTranslation();
const {
handleSubmit,
reset,
control,
formState: { isDirty, isSubmitting },
} = useForm<RewriteFormValues>({
mode: 'onBlur',
defaultValues: {
domain: currentRewrite?.domain || '',
answer: currentRewrite?.answer || '',
},
});
const handleFormSubmit = async (data: RewriteFormValues) => {
if (onSubmit) {
await onSubmit(data);
}
};
const Form = (props: FormProps) => {
const { t, handleSubmit, reset, pristine, submitting, toggleRewritesModal, processingAdd } = props;
return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__desc form__desc--top">
<Trans>domain_desc</Trans>
</div>
<div className="form__group">
<Controller
<Field
id="domain"
name="domain"
control={control}
rules={{
validate: {
validate: validateDomain,
required: validateRequiredValue,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="rewrites_domain"
placeholder={t('form_domain')}
error={fieldState.error?.message}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_domain')}
validate={[validateRequiredValue, validateDomain]}
/>
</div>
<Trans>examples_title</Trans>:
@@ -71,6 +44,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
<li>
<code>example.org</code> <Trans>example_rewrite_domain</Trans>
</li>
<li>
<code>*.example.org</code> &nbsp;
<span>
@@ -79,24 +53,14 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
</li>
</ol>
<div className="form__group">
<Controller
<Field
id="answer"
name="answer"
control={control}
rules={{
validate: {
validate: validateAnswer,
required: validateRequiredValue,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="rewrites_answer"
placeholder={t('form_answer')}
error={fieldState.error?.message}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_answer')}
validate={[validateRequiredValue, validateAnswer]}
/>
</div>
</div>
@@ -113,9 +77,8 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
<div className="btn-list">
<button
type="button"
data-testid="rewrites_cancel"
className="btn btn-secondary btn-standard"
disabled={isSubmitting || processingAdd}
disabled={submitting || processingAdd}
onClick={() => {
reset();
toggleRewritesModal();
@@ -125,9 +88,8 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
<button
type="submit"
data-testid="rewrites_save"
className="btn btn-success btn-standard"
disabled={isSubmitting || !isDirty || processingAdd}>
disabled={submitting || pristine || processingAdd}>
<Trans>save_btn</Trans>
</button>
</div>
@@ -136,4 +98,10 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
);
};
export default Form;
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.REWRITES,
enableReinitialize: true,
}),
])(Form);

View File

@@ -14,7 +14,7 @@ interface ModalProps {
processingAdd: boolean;
processingDelete: boolean;
modalType: string;
currentRewrite?: { answer: string, domain: string; };
currentRewrite?: object;
}
const Modal = (props: ModalProps) => {
@@ -23,6 +23,7 @@ const Modal = (props: ModalProps) => {
handleSubmit,
toggleRewritesModal,
processingAdd,
processingDelete,
modalType,
currentRewrite,
} = props;
@@ -49,10 +50,11 @@ const Modal = (props: ModalProps) => {
</div>
<Form
initialValues={{ ...currentRewrite }}
onSubmit={handleSubmit}
toggleRewritesModal={toggleRewritesModal}
processingAdd={processingAdd}
currentRewrite={currentRewrite}
processingDelete={processingDelete}
/>
</div>
</ReactModal>

View File

@@ -1,55 +1,38 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { Controller, useForm } from 'react-hook-form';
import { toggleAllServices } from '../../../helpers/helpers';
import { ServiceField } from './ServiceField';
export type BlockedService = {
id: string;
name: string;
icon_svg: string;
};
type FormValues = {
blocked_services: Record<string, boolean>;
};
import { renderServiceField } from '../../../helpers/form';
import { FORM_NAME } from '../../../helpers/constants';
interface FormProps {
initialValues: Record<string, boolean>;
blockedServices: BlockedService[];
onSubmit: (values: FormValues) => void;
blockedServices: unknown[];
pristine: boolean;
handleSubmit: (...args: unknown[]) => string;
change: (...args: unknown[]) => unknown;
submitting: boolean;
processing: boolean;
processingSet: boolean;
t: (...args: unknown[]) => string;
}
export const Form = ({ initialValues, blockedServices, processing, processingSet, onSubmit }: FormProps) => {
const {
handleSubmit,
control,
setValue,
formState: { isSubmitting },
} = useForm<FormValues>({
mode: 'onBlur',
defaultValues: initialValues,
});
const handleToggleAllServices = async (isSelected: boolean) => {
blockedServices.forEach((service: BlockedService) => setValue(`blocked_services.${service.id}`, isSelected));
};
const Form = (props: FormProps) => {
const { blockedServices, handleSubmit, change, pristine, submitting, processing, processingSet } = props;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="form__group">
<div className="row mb-4">
<div className="col-6">
<button
type="button"
data-testid="blocked_services_block_all"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => handleToggleAllServices(true)}>
onClick={() => toggleAllServices(blockedServices, change, true)}>
<Trans>block_all</Trans>
</button>
</div>
@@ -57,30 +40,24 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
<div className="col-6">
<button
type="button"
data-testid="blocked_services_unblock_all"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => handleToggleAllServices(false)}>
onClick={() => toggleAllServices(blockedServices, change, false)}>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{blockedServices.map((service: BlockedService) => (
<Controller
{blockedServices.map((service: any) => (
<Field
key={service.id}
icon={service.icon_svg}
name={`blocked_services.${service.id}`}
control={control}
render={({ field }) => (
<ServiceField
{...field}
data-testid={`blocked_services_${service.id}`}
placeholder={service.name}
disabled={processing || processingSet}
icon={service.icon_svg}
/>
)}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={processing || processingSet}
/>
))}
</div>
@@ -89,12 +66,19 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
<div className="btn-list">
<button
type="submit"
data-testid="blocked_services_save"
className="btn btn-success btn-standard btn-large"
disabled={isSubmitting || processing || processingSet}>
disabled={submitting || pristine || processing || processingSet}>
<Trans>save_btn</Trans>
</button>
</div>
</form>
);
};
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.SERVICES,
enableReinitialize: true,
}),
])(Form);

View File

@@ -1,42 +0,0 @@
import React from 'react';
import cn from 'classnames';
import { FieldValues, ControllerRenderProps } from 'react-hook-form';
type Props = ControllerRenderProps<FieldValues> & {
placeholder: string;
disabled?: boolean;
className?: string;
icon?: string;
error?: string;
};
export const ServiceField = React.forwardRef<HTMLInputElement, Props>(
({ name, value, onChange, onBlur, placeholder, disabled, className, icon, error, ...rest }, ref) => (
<>
<label className={cn('service custom-switch', className)}>
<input
name={name}
type="checkbox"
className="custom-switch-input"
checked={!!value}
onChange={onChange}
onBlur={onBlur}
ref={ref}
disabled={disabled}
{...rest}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text" title={placeholder}>
{placeholder}
</span>
{icon && <div dangerouslySetInnerHTML={{ __html: window.atob(icon) }} className="service__icon" />}
</label>
{!disabled && error && <span className="form__message form__message--error">{error}</span>}
</>
),
);
ServiceField.displayName = 'ServiceField';

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Form } from './Form';
import Form from './Form';
import Card from '../../ui/Card';
import { getBlockedServices, getAllBlockedServices, updateBlockedServices } from '../../../actions/services';
@@ -86,8 +86,7 @@ const Services = () => {
<Card
title={t('schedule_services')}
subtitle={t('schedule_services_desc')}
bodyType="card-body box-body--settings"
>
bodyType="card-body box-body--settings">
<ScheduleForm schedule={services.list.schedule} onScheduleSubmit={handleScheduleSubmit} />
</Card>
</>

View File

@@ -1,71 +1,158 @@
import React, { useEffect } from 'react';
import { Field, type InjectedFormProps, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import classNames from 'classnames';
import { useFormContext } from 'react-hook-form';
import {
DEBOUNCE_FILTER_TIMEOUT,
DEFAULT_LOGS_FILTER,
FORM_NAME,
RESPONSE_FILTER,
RESPONSE_FILTER_QUERIES,
} from '../../../helpers/constants';
import { setLogsFilter } from '../../../actions/queryLogs';
import useDebounce from '../../../helpers/useDebounce';
import { getLogsUrlParams } from '../../../helpers/helpers';
import { createOnBlurHandler, getLogsUrlParams } from '../../../helpers/helpers';
import { SearchField } from './SearchField';
import { SearchFormValues } from '..';
import Tooltip from '../../ui/Tooltip';
import { RootState } from '../../../initialState';
type Props = {
interface renderFilterFieldProps {
input: {
value: string;
};
id: string;
onClearInputClick: (...args: unknown[]) => unknown;
className?: string;
setIsLoading: (value: boolean) => void;
placeholder?: string;
type?: string;
disabled?: boolean;
autoComplete?: string;
tooltip?: string;
onKeyDown?: (...args: unknown[]) => unknown;
normalizeOnBlur?: (...args: unknown[]) => unknown;
meta: {
touched?: boolean;
error?: object;
};
}
const renderFilterField = ({
input,
id,
className,
placeholder,
type,
disabled,
autoComplete,
tooltip,
meta: { touched, error },
onClearInputClick,
onKeyDown,
normalizeOnBlur,
}: renderFilterFieldProps) => {
const onBlur = (event: any) => createOnBlurHandler(event, input, normalizeOnBlur);
return (
<>
<div className="input-group-search input-group-search__icon--magnifier">
<svg className="icons icon--24 icon--gray">
<use xlinkHref="#magnifier" />
</svg>
</div>
<input
{...input}
id={id}
placeholder={placeholder}
type={type}
className={className}
disabled={disabled}
autoComplete={autoComplete}
aria-label={placeholder}
onKeyDown={onKeyDown}
onBlur={onBlur}
/>
<div
className={classNames('input-group-search input-group-search__icon--cross', {
invisible: input.value.length < 1,
})}>
<svg className="icons icon--20 icon--gray" onClick={onClearInputClick}>
<use xlinkHref="#cross" />
</svg>
</div>
<span className="input-group-search input-group-search__icon--tooltip">
<Tooltip content={tooltip} className="tooltip-container">
<svg className="icons icon--20 icon--gray">
<use xlinkHref="#question" />
</svg>
</Tooltip>
</span>
{!disabled && touched && error && <span className="form__message form__message--error">{error}</span>}
</>
);
};
export const Form = ({ className, setIsLoading }: Props) => {
const FORM_NAMES = {
search: 'search',
response_status: 'response_status',
};
type FiltersFormProps = {
className?: string;
responseStatusClass?: string;
setIsLoading: (...args: unknown[]) => unknown;
};
const Form = (props: FiltersFormProps & InjectedFormProps) => {
const { className = '', responseStatusClass, setIsLoading, change } = props;
const { t } = useTranslation();
const dispatch = useDispatch();
const history = useHistory();
const { register, watch, setValue } = useFormContext<SearchFormValues>();
const { response_status, search } = useSelector(
(state: RootState) => state?.form[FORM_NAME.LOGS_FILTER].values,
shallowEqual,
);
const searchValue = watch('search');
const responseStatusValue = watch('response_status');
const [debouncedSearch, setDebouncedSearch] = useDebounce(searchValue.trim(), DEBOUNCE_FILTER_TIMEOUT);
const [debouncedSearch, setDebouncedSearch] = useDebounce(search.trim(), DEBOUNCE_FILTER_TIMEOUT);
useEffect(() => {
dispatch(
setLogsFilter({
response_status: responseStatusValue,
response_status,
search: debouncedSearch,
}),
);
history.replace(`${getLogsUrlParams(debouncedSearch, responseStatusValue)}`);
}, [responseStatusValue, debouncedSearch]);
history.replace(`${getLogsUrlParams(debouncedSearch, response_status)}`);
}, [response_status, debouncedSearch]);
useEffect(() => {
if (responseStatusValue && !(responseStatusValue in RESPONSE_FILTER_QUERIES)) {
setValue('response_status', DEFAULT_LOGS_FILTER.response_status);
}
}, [responseStatusValue, setValue]);
if (response_status && !(response_status in RESPONSE_FILTER_QUERIES)) {
change(FORM_NAMES.response_status, DEFAULT_LOGS_FILTER[FORM_NAMES.response_status]);
}
const onInputClear = async () => {
setIsLoading(true);
setValue('search', DEFAULT_LOGS_FILTER.search);
change(FORM_NAMES.search, DEFAULT_LOGS_FILTER[FORM_NAMES.search]);
setIsLoading(false);
};
const onEnterPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const onEnterPress = (e: any) => {
if (e.key === 'Enter') {
setDebouncedSearch(searchValue);
setDebouncedSearch(search);
}
};
const normalizeOnBlur = (data: any) => data.trim();
return (
<form
className="d-flex flex-wrap form-control--container"
@@ -73,28 +160,40 @@ export const Form = ({ className, setIsLoading }: Props) => {
e.preventDefault();
}}>
<div className="field__search">
<SearchField
value={searchValue}
handleChange={(val) => setValue('search', val)}
onKeyDown={onEnterPress}
onClear={onInputClear}
<Field
id={FORM_NAMES.search}
name={FORM_NAMES.search}
component={renderFilterField}
type="text"
className={classNames('form-control form-control--search form-control--transparent', className)}
placeholder={t('domain_or_client')}
tooltip={t('query_log_strict_search')}
className={classNames('form-control form-control--search form-control--transparent', className)}
onClearInputClick={onInputClear}
onKeyDown={onEnterPress}
normalizeOnBlur={normalizeOnBlur}
/>
</div>
<div className="field__select">
<select
{...register('response_status')}
className="form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent d-sm-block">
<Field
name={FORM_NAMES.response_status}
component="select"
className={classNames(
'form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent',
responseStatusClass,
)}>
{Object.values(RESPONSE_FILTER).map(({ QUERY, LABEL, disabled }: any) => (
<option key={LABEL} value={QUERY} disabled={disabled}>
{t(LABEL)}
</option>
))}
</select>
</Field>
</div>
</form>
);
};
export const FiltersForm = reduxForm<Record<string, any>, FiltersFormProps>({
form: FORM_NAME.LOGS_FILTER,
enableReinitialize: true,
})(Form);

View File

@@ -1,62 +0,0 @@
import React, { ComponentProps } from 'react';
import Tooltip from '../../ui/Tooltip';
interface Props extends ComponentProps<'input'> {
handleChange: (newValue: string) => void;
onClear: () => void;
tooltip?: string;
}
export const SearchField = ({
handleChange,
onClear,
value,
tooltip,
className,
...rest
}: Props) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
handleChange(e.target.value);
};
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
e.target.value = e.target.value.trim();
handleChange(e.target.value)
}
return (
<>
<div className="input-group-search input-group-search__icon--magnifier">
<svg className="icons icon--24 icon--gray">
<use xlinkHref="#magnifier" />
</svg>
</div>
<input
className={className}
value={value}
onChange={handleInputChange}
onBlur={handleBlur}
{...rest}
/>
{typeof value === 'string' && value.length > 0 && (
<div
className="input-group-search input-group-search__icon--cross"
onClick={onClear}
>
<svg className="icons icon--20 icon--gray">
<use xlinkHref="#cross" />
</svg>
</div>
)}
{tooltip && (
<span className="input-group-search input-group-search__icon--tooltip">
<Tooltip content={tooltip} className="tooltip-container">
<svg className="icons icon--20 icon--gray">
<use xlinkHref="#question" />
</svg>
</Tooltip>
</span>
)}
</>
);
};

View File

@@ -2,16 +2,17 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { Form } from './Form';
import { FiltersForm } from './Form';
import { refreshFilteredLogs } from '../../../actions/queryLogs';
import { addSuccessToast } from '../../../actions/toasts';
interface FiltersProps {
filter: object;
processingGetLogs: boolean;
setIsLoading: (...args: unknown[]) => unknown;
}
const Filters = ({ setIsLoading }: FiltersProps) => {
const Filters = ({ filter, setIsLoading }: FiltersProps) => {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -37,9 +38,7 @@ const Filters = ({ setIsLoading }: FiltersProps) => {
</svg>
</button>
</h1>
<Form
setIsLoading={setIsLoading}
/>
<FiltersForm responseStatusClass="d-sm-block" setIsLoading={setIsLoading} initialValues={filter} />
</div>
);
};

View File

@@ -18,7 +18,6 @@ interface InfiniteTableProps {
isLoading: boolean;
items: unknown[];
isSmallScreen: boolean;
currentQuery: string;
setDetailedDataCurrent: Dispatch<SetStateAction<any>>;
setButtonType: (...args: unknown[]) => unknown;
setModalOpened: (...args: unknown[]) => unknown;
@@ -28,7 +27,6 @@ const InfiniteTable = ({
isLoading,
items,
isSmallScreen,
currentQuery,
setDetailedDataCurrent,
setButtonType,
setModalOpened,
@@ -45,7 +43,7 @@ const InfiniteTable = ({
const listener = useCallback(() => {
if (!loadingRef.current && loader.current && isScrolledIntoView(loader.current)) {
dispatch(getLogs(currentQuery));
dispatch(getLogs());
}
}, []);

View File

@@ -7,8 +7,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import queryString from 'query-string';
import classNames from 'classnames';
import { FormProvider, useForm } from 'react-hook-form';
import { BLOCK_ACTIONS, DEFAULT_LOGS_FILTER, MEDIUM_SCREEN_SIZE } from '../../helpers/constants';
import { BLOCK_ACTIONS, MEDIUM_SCREEN_SIZE } from '../../helpers/constants';
import Loading from '../ui/Loading';
@@ -30,12 +29,7 @@ import { BUTTON_PREFIX } from './Cells/helpers';
import AnonymizerNotification from './AnonymizerNotification';
import { RootState } from '../../initialState';
export type SearchFormValues = {
search: string;
response_status: string;
};
const processContent = (data: any, _buttonType: string) =>
const processContent = (data: any, buttonType: string) =>
Object.entries(data).map(([key, value]) => {
if (!value) {
return null;
@@ -82,6 +76,7 @@ const Logs = () => {
const {
enabled,
processingGetConfig,
// processingAdditionalLogs,
processingGetLogs,
anonymize_client_ip: anonymizeClientIp,
} = useSelector((state: RootState) => state.queryLogs, shallowEqual);
@@ -93,17 +88,6 @@ const Logs = () => {
const search = search_url_param || filter?.search || '';
const response_status = response_status_url_param || filter?.response_status || '';
const formMethods = useForm<SearchFormValues>({
mode: 'onBlur',
defaultValues: {
search: search || DEFAULT_LOGS_FILTER.search,
response_status: response_status || DEFAULT_LOGS_FILTER.response_status,
},
});
const { watch } = formMethods;
const currentQuery = watch('search');
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth <= MEDIUM_SCREEN_SIZE);
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
@@ -190,12 +174,15 @@ const Logs = () => {
const renderPage = () => (
<>
<FormProvider {...formMethods}>
<Filters
setIsLoading={setIsLoading}
processingGetLogs={processingGetLogs}
/>
</FormProvider>
<Filters
filter={{
response_status,
search,
}}
setIsLoading={setIsLoading}
processingGetLogs={processingGetLogs}
// processingAdditionalLogs={processingAdditionalLogs}
/>
<InfiniteTable
isLoading={isLoading}
@@ -204,7 +191,6 @@ const Logs = () => {
setDetailedDataCurrent={setDetailedDataCurrent}
setButtonType={setButtonType}
setModalOpened={setModalOpened}
currentQuery={currentQuery}
/>
<Modal

View File

@@ -12,7 +12,7 @@ import whoisCell from './whoisCell';
import LogsSearchLink from '../../ui/LogsSearchLink';
import { sortIp, formatNumber } from '../../../helpers/helpers';
import { sortIp } from '../../../helpers/helpers';
import { LocalStorageHelper, LOCAL_STORAGE_KEYS } from '../../../helpers/localStorageHelper';
import { TABLES_MIN_ROWS } from '../../../helpers/constants';
@@ -66,7 +66,7 @@ class AutoClients extends Component<AutoClientsProps> {
return (
<div className="logs__row">
<div className="logs__text" title={clientStats}>
<LogsSearchLink search={row.original.ip}>{formatNumber(clientStats)}</LogsSearchLink>
<LogsSearchLink search={row.original.ip}>{clientStats}</LogsSearchLink>
</div>
</div>
);

View File

@@ -12,7 +12,7 @@ import ReactTable from 'react-table';
import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services';
import { initSettings } from '../../../../actions';
import { splitByNewLine, countClientsStatistics, sortIp, getService, formatNumber } from '../../../../helpers/helpers';
import { splitByNewLine, countClientsStatistics, sortIp, getService } from '../../../../helpers/helpers';
import { MODAL_TYPE, LOCAL_TIMEZONE_VALUE, TABLES_MIN_ROWS } from '../../../../helpers/constants';
import Card from '../../../ui/Card';
@@ -111,12 +111,6 @@ const ClientsTable = ({
config.tags = [];
}
if (values.ids) {
config.ids = values.ids.map((id) => id.name);
} else {
config.ids = [];
}
if (typeof values.upstreams_cache_size === 'string') {
config.upstreams_cache_size = 0;
}
@@ -306,16 +300,13 @@ const ClientsTable = ({
sortMethod: (a: any, b: any) => b - a,
minWidth: 120,
Cell: (row: any) => {
let content = row.value;
if (typeof content === "number") {
content = formatNumber(content);
} else {
content = CellWrap(row);
}
if (!content) {
const content = CellWrap(row);
if (!row.value) {
return content;
}
return <LogsSearchLink search={row.original.name}>{content}</LogsSearchLink>;
return <LogsSearchLink search={row.original.ids[0]}>{content}</LogsSearchLink>;
},
},
{

View File

@@ -0,0 +1,514 @@
import React, { useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { Field, FieldArray, reduxForm, formValueSelector, FormErrors } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import Select from 'react-select';
import i18n from '../../../i18n';
import Tabs from '../../ui/Tabs';
import Examples from '../Dns/Upstream/Examples';
import { ScheduleForm } from '../../Filters/Services/ScheduleForm';
import { toggleAllServices, trimLinesAndRemoveEmpty, captitalizeWords } from '../../../helpers/helpers';
import {
toNumber,
renderInputField,
renderGroupField,
CheckboxField,
renderServiceField,
renderTextareaField,
} from '../../../helpers/form';
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
import { CLIENT_ID_LINK, FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
import './Service.css';
import { RootState } from '../../../initialState';
const settingsCheckboxes = [
{
name: 'use_global_settings',
placeholder: 'client_global_settings',
},
{
name: 'filtering_enabled',
placeholder: 'block_domain_use_filters_and_hosts',
},
{
name: 'safebrowsing_enabled',
placeholder: 'use_adguard_browsing_sec',
},
{
name: 'parental_enabled',
placeholder: 'use_adguard_parental',
},
];
const logAndStatsCheckboxes = [
{
name: 'ignore_querylog',
placeholder: 'ignore_query_log',
},
{
name: 'ignore_statistics',
placeholder: 'ignore_statistics',
},
];
const validate = (values: any): FormErrors<any, string> => {
const errors: {
name?: string;
ids?: string[];
} = {};
const { name, ids } = values;
errors.name = validateRequiredValue(name);
if (ids && ids.length) {
const idArrayErrors: any = [];
ids.forEach((id: any, idx: any) => {
idArrayErrors[idx] = validateRequiredValue(id) || validateClientId(id);
});
if (idArrayErrors.length) {
errors.ids = idArrayErrors;
}
}
// @ts-expect-error FIXME: ts migration
return errors;
};
const renderFieldsWrapper = (placeholder: any, buttonTitle: any) =>
function cell(row: any) {
const { fields } = row;
return (
<div className="form__group">
{fields.map((ip: any, index: any) => (
<div key={index} className="mb-1">
<Field
name={ip}
component={renderGroupField}
type="text"
className="form-control"
placeholder={placeholder}
isActionAvailable={index !== 0}
removeField={() => fields.remove(index)}
normalizeOnBlur={(data: any) => data.trim()}
/>
</div>
))}
<button
type="button"
className="btn btn-link btn-block btn-sm"
onClick={() => fields.push()}
title={buttonTitle}>
<svg className="icon icon--24">
<use xlinkHref="#plus" />
</svg>
</button>
</div>
);
};
// Should create function outside of component to prevent component re-renders
const renderFields = renderFieldsWrapper(i18n.t('form_enter_id'), i18n.t('form_add_id'));
interface renderMultiselectProps {
input: {
name: string;
value: string;
checked: boolean;
onChange: (...args: unknown[]) => unknown;
onBlur: (...args: unknown[]) => unknown;
};
placeholder?: string;
options?: unknown[];
}
const renderMultiselect = (props: renderMultiselectProps) => {
const { input, placeholder, options } = props;
return (
<Select
{...input}
options={options}
className="basic-multi-select"
classNamePrefix="select"
onChange={(value: any) => input.onChange(value)}
onBlur={() => input.onBlur(input.value)}
placeholder={placeholder}
blurInputOnSelect={false}
isMulti
/>
);
};
interface FormProps {
pristine: boolean;
handleSubmit: (...args: unknown[]) => string;
reset: (...args: unknown[]) => string;
change: (...args: unknown[]) => unknown;
submitting: boolean;
handleClose: (...args: unknown[]) => unknown;
useGlobalSettings?: boolean;
useGlobalServices?: boolean;
blockedServicesSchedule?: {
time_zone: string;
};
t: (...args: unknown[]) => string;
processingAdding: boolean;
processingUpdating: boolean;
invalid: boolean;
tagsOptions: unknown[];
initialValues?: {
safe_search: any;
};
}
let Form = (props: FormProps) => {
const {
t,
handleSubmit,
reset,
change,
submitting,
useGlobalSettings,
useGlobalServices,
blockedServicesSchedule,
handleClose,
processingAdding,
processingUpdating,
invalid,
tagsOptions,
initialValues,
} = props;
const services = useSelector((store: RootState) => store?.services);
const { safe_search } = initialValues;
const safeSearchServices = { ...safe_search };
delete safeSearchServices.enabled;
const [activeTabLabel, setActiveTabLabel] = useState('settings');
const handleScheduleSubmit = (values: any) => {
change('blocked_services_schedule', { ...values });
};
const tabs = {
settings: {
title: 'settings',
component: (
<div title={props.t('main_settings')}>
<div className="form__label--bot form__label--bold">{t('protection_section_label')}</div>
{settingsCheckboxes.map((setting) => (
<div className="form__group" key={setting.name}>
<Field
name={setting.name}
type="checkbox"
component={CheckboxField}
placeholder={t(setting.placeholder)}
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
/>
</div>
))}
<div className="form__group">
<Field
name="safe_search.enabled"
type="checkbox"
component={CheckboxField}
placeholder={t('enforce_safe_search')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group--inner">
{Object.keys(safeSearchServices).map((searchKey) => (
<div key={searchKey}>
<Field
name={`safe_search.${searchKey}`}
type="checkbox"
component={CheckboxField}
placeholder={captitalizeWords(searchKey)}
disabled={useGlobalSettings}
/>
</div>
))}
</div>
<div className="form__label--bold form__label--top form__label--bot">
{t('log_and_stats_section_label')}
</div>
{logAndStatsCheckboxes.map((setting) => (
<div className="form__group" key={setting.name}>
<Field
name={setting.name}
type="checkbox"
component={CheckboxField}
placeholder={t(setting.placeholder)}
/>
</div>
))}
</div>
),
},
block_services: {
title: 'block_services',
component: (
<div title={props.t('block_services')}>
<div className="form__group">
<Field
name="use_global_blocked_services"
type="checkbox"
component={renderServiceField}
placeholder={t('blocked_services_global')}
modifier="service--global"
/>
<div className="row mb-4">
<div className="col-6">
<button
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(services.allServices, change, true)}>
<Trans>block_all</Trans>
</button>
</div>
<div className="col-6">
<button
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(services.allServices, change, false)}>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
{services.allServices.length > 0 && (
<div className="services">
{services.allServices.map((service: any) => (
<Field
key={service.id}
icon={service.icon_svg}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={useGlobalServices}
/>
))}
</div>
)}
</div>
</div>
),
},
schedule_services: {
title: 'schedule_services',
component: (
<>
<div className="form__desc mb-4">
<Trans>schedule_services_desc_client</Trans>
</div>
<ScheduleForm
schedule={blockedServicesSchedule}
onScheduleSubmit={handleScheduleSubmit}
clientForm
/>
</>
),
},
upstream_dns: {
title: 'upstream_dns',
component: (
<div title={props.t('upstream_dns')}>
<div className="form__desc mb-3">
<Trans
components={[
<a href="#dns" key="0">
link
</a>,
]}>
upstream_dns_client_desc
</Trans>
</div>
<Field
id="upstreams"
name="upstreams"
component={renderTextareaField}
type="text"
className="form-control form-control--textarea mb-5"
placeholder={t('upstream_dns')}
normalizeOnBlur={trimLinesAndRemoveEmpty}
/>
<Examples />
<div className="form__label--bold mt-5 mb-3">{t('upstream_dns_cache_configuration')}</div>
<div className="form__group mb-2">
<Field
name="upstreams_cache_enabled"
type="checkbox"
component={CheckboxField}
placeholder={t('enable_upstream_dns_cache')}
/>
</div>
<div className="form__group form__group--settings">
<label htmlFor="upstreams_cache_size" className="form__label">
{t('dns_cache_size')}
</label>
<Field
name="upstreams_cache_size"
type="number"
component={renderInputField}
placeholder={t('enter_cache_size')}
className="form-control"
normalize={toNumber}
min={0}
max={UINT32_RANGE.MAX}
/>
</div>
</div>
),
},
};
const activeTab = tabs[activeTabLabel].component;
return (
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group mb-0">
<div className="form__group">
<Field
id="name"
name="name"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_client_name')}
normalizeOnBlur={(data: any) => data.trim()}
/>
</div>
<div className="form__group mb-4">
<div className="form__label">
<strong className="mr-3">
<Trans>tags_title</Trans>
</strong>
</div>
<div className="form__desc mt-0 mb-2">
<Trans
components={[
<a
target="_blank"
rel="noopener noreferrer"
href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax_ctag&from=ui&app=home"
key="0">
link
</a>,
]}>
tags_desc
</Trans>
</div>
<Field
name="tags"
component={renderMultiselect}
placeholder={t('form_select_tags')}
options={tagsOptions}
/>
</div>
<div className="form__group">
<div className="form__label">
<strong className="mr-3">
<Trans>client_identifier</Trans>
</strong>
</div>
<div className="form__desc mt-0">
<Trans
components={[
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" key="0">
text
</a>,
]}>
client_identifier_desc
</Trans>
</div>
</div>
<div className="form__group">
<FieldArray name="ids" component={renderFields} />
</div>
</div>
<Tabs
controlClass="form"
tabs={tabs}
activeTabLabel={activeTabLabel}
setActiveTabLabel={setActiveTabLabel}>
{activeTab}
</Tabs>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
handleClose();
}}>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid || processingAdding || processingUpdating}>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
const selector = formValueSelector(FORM_NAME.CLIENT);
Form = connect((state) => {
const useGlobalSettings = selector(state, 'use_global_settings');
const useGlobalServices = selector(state, 'use_global_blocked_services');
const blockedServicesSchedule = selector(state, 'blocked_services_schedule');
return {
useGlobalSettings,
useGlobalServices,
blockedServicesSchedule,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.CLIENT,
enableReinitialize: true,
validate,
}),
])(Form);

View File

@@ -1,84 +0,0 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Controller, useFormContext } from 'react-hook-form';
import { ClientForm } from '../types';
import { BlockedService } from '../../../../Filters/Services/Form';
import { ServiceField } from '../../../../Filters/Services/ServiceField';
type Props = {
services: BlockedService[];
};
export const BlockedServices = ({ services }: Props) => {
const { t } = useTranslation();
const { watch, setValue, control } = useFormContext<ClientForm>();
const useGlobalServices = watch('use_global_blocked_services');
const handleToggleAllServices = (isSelected: boolean) => {
services.forEach((service: BlockedService) => setValue(`blocked_services.${service.id}`, isSelected));
};
return (
<div title={t('block_services')}>
<div className="form__group">
<Controller
name="use_global_blocked_services"
control={control}
render={({ field }) => (
<ServiceField
{...field}
data-testid="clients_use_global_blocked_services"
placeholder={t('blocked_services_global')}
className="service--global"
/>
)}
/>
<div className="row mb-4">
<div className="col-6">
<button
type="button"
data-testid="clients_block_all"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => handleToggleAllServices(true)}>
<Trans>block_all</Trans>
</button>
</div>
<div className="col-6">
<button
type="button"
data-testid="clients_unblock_all"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => handleToggleAllServices(false)}>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
{services.length > 0 && (
<div className="services">
{services.map((service: BlockedService) => (
<Controller
key={service.id}
name={`blocked_services.${service.id}`}
control={control}
render={({ field }) => (
<ServiceField
{...field}
data-testid={`clients_service_${service.id}`}
placeholder={service.name}
disabled={useGlobalServices}
icon={service.icon_svg}
/>
)}
/>
))}
</div>
)}
</div>
</div>
);
};

View File

@@ -1,74 +0,0 @@
import React from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { ClientForm } from '../types';
import { Input } from '../../../../ui/Controls/Input';
import { validateClientId, validateRequiredValue } from '../../../../../helpers/validators';
export const ClientIds = () => {
const { t } = useTranslation();
const { control } = useFormContext<ClientForm>();
const { fields, append, remove } = useFieldArray<ClientForm>({
control,
name: 'ids',
});
return (
<div className="form__group">
{fields.map((field, index) => (
<div key={field.id} className="mb-1">
<Controller
name={`ids.${index}.name`}
control={control}
rules={{
validate: {
required: (value) => validateRequiredValue(value),
validId: (value) => validateClientId(value),
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid={`clients_id_${index}`}
placeholder={t('form_enter_id')}
error={fieldState.error?.message}
onBlur={(event) => {
const trimmedValue = event.target.value.trim();
field.onBlur();
field.onChange(trimmedValue);
}}
rightAddon={
index !== 0 && (
<span className="input-group-append">
<button
type="button"
data-testid={`clients_id_remove_${index}`}
className="btn btn-secondary btn-icon btn-icon--green"
onClick={() => remove(index)}>
<svg className="icon icon--24">
<use xlinkHref="#cross" />
</svg>
</button>
</span>
)
}
/>
)}
/>
</div>
))}
<button
type="button"
data-testid="clients_id_add"
className="btn btn-link btn-block btn-sm"
onClick={() => append({ name: '' })}
title={t('form_add_id')}>
<svg className="icon icon--24">
<use xlinkHref="#plus" />
</svg>
</button>
</div>
);
};

View File

@@ -1,126 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Controller, useFormContext } from 'react-hook-form';
import i18next from 'i18next';
import { captitalizeWords } from '../../../../../helpers/helpers';
import { ClientForm } from '../types';
import { Checkbox } from '../../../../ui/Controls/Checkbox';
type ProtectionSettings = 'use_global_settings' | 'filtering_enabled' | 'safebrowsing_enabled' | 'parental_enabled';
const settingsCheckboxes: {
name: ProtectionSettings;
placeholder: string;
}[] = [
{
name: 'use_global_settings',
placeholder: i18next.t('client_global_settings'),
},
{
name: 'filtering_enabled',
placeholder: i18next.t('block_domain_use_filters_and_hosts'),
},
{
name: 'safebrowsing_enabled',
placeholder: i18next.t('use_adguard_browsing_sec'),
},
{
name: 'parental_enabled',
placeholder: i18next.t('use_adguard_parental'),
},
];
type LogsStatsSettings = 'ignore_querylog' | 'ignore_statistics';
const logAndStatsCheckboxes: { name: LogsStatsSettings; placeholder: string }[] = [
{
name: 'ignore_querylog',
placeholder: i18next.t('ignore_query_log'),
},
{
name: 'ignore_statistics',
placeholder: i18next.t('ignore_statistics'),
},
];
type Props = {
safeSearchServices: Record<string, boolean>;
};
export const MainSettings = ({ safeSearchServices }: Props) => {
const { t } = useTranslation();
const { watch, control } = useFormContext<ClientForm>();
const useGlobalSettings = watch('use_global_settings');
return (
<div title={t('main_settings')}>
<div className="form__label--bot form__label--bold">{t('protection_section_label')}</div>
{settingsCheckboxes.map((setting) => (
<div className="form__group" key={setting.name}>
<Controller
name={setting.name}
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid={`clients_${setting.name}`}
title={setting.placeholder}
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
/>
)}
/>
</div>
))}
<div className="form__group">
<Controller
name="safe_search.enabled"
control={control}
render={({ field }) => (
<Checkbox
data-testid="clients_safe_search"
{...field}
title={t('enforce_safe_search')}
disabled={useGlobalSettings}
/>
)}
/>
</div>
<div className="form__group--inner">
{Object.keys(safeSearchServices).map((searchKey) => (
<div key={searchKey}>
<Controller
name={`safe_search.${searchKey}`}
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid={`clients_safe_search_${searchKey}`}
title={captitalizeWords(searchKey)}
disabled={useGlobalSettings}
/>
)}
/>
</div>
))}
</div>
<div className="form__label--bold form__label--top form__label--bot">
{t('log_and_stats_section_label')}
</div>
{logAndStatsCheckboxes.map((setting) => (
<div className="form__group" key={setting.name}>
<Controller
name={setting.name}
control={control}
render={({ field }) => (
<Checkbox {...field} data-testid={`clients_${setting.name}`} title={setting.placeholder} />
)}
/>
</div>
))}
</div>
);
};

View File

@@ -1,25 +0,0 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { useFormContext } from 'react-hook-form';
import { ScheduleForm } from '../../../../Filters/Services/ScheduleForm';
import { ClientForm } from '../types';
export const ScheduleServices = () => {
const { watch, setValue } = useFormContext<ClientForm>();
const blockedServicesSchedule = watch('blocked_services_schedule');
const handleScheduleSubmit = (values: any) => {
setValue('blocked_services_schedule', values);
};
return (
<>
<div className="form__desc mb-4">
<Trans>schedule_services_desc_client</Trans>
</div>
<ScheduleForm schedule={blockedServicesSchedule} onScheduleSubmit={handleScheduleSubmit} clientForm />
</>
);
};

View File

@@ -1,83 +0,0 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Controller, useFormContext } from 'react-hook-form';
import Examples from '../../../Dns/Upstream/Examples';
import { UINT32_RANGE } from '../../../../../helpers/constants';
import { Textarea } from '../../../../ui/Controls/Textarea';
import { ClientForm } from '../types';
import { Checkbox } from '../../../../ui/Controls/Checkbox';
import { Input } from '../../../../ui/Controls/Input';
import { toNumber } from '../../../../../helpers/form';
export const UpstreamDns = () => {
const { t } = useTranslation();
const { control } = useFormContext<ClientForm>();
return (
<div title={t('upstream_dns')}>
<div className="form__desc mb-3">
<Trans components={[<a href="#dns" key="0" />]}>upstream_dns_client_desc</Trans>
</div>
<Controller
name="upstreams"
control={control}
render={({ field }) => (
<Textarea
{...field}
data-testid="clients_upstreams"
className="form-control form-control--textarea mb-5"
placeholder={t('upstream_dns')}
trimOnBlur
/>
)}
/>
<Examples />
<div className="form__label--bold mt-5 mb-3">{t('upstream_dns_cache_configuration')}</div>
<div className="form__group mb-2">
<Controller
name="upstreams_cache_enabled"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="clients_upstreams_cache_enabled"
title={t('enable_upstream_dns_cache')}
/>
)}
/>
</div>
<div className="form__group form__group--settings">
<label htmlFor="upstreams_cache_size" className="form__label">
{t('dns_cache_size')}
</label>
<Controller
name="upstreams_cache_size"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="clients_upstreams_cache_size"
placeholder={t('enter_cache_size')}
error={fieldState.error?.message}
min={0}
max={UINT32_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/>
</div>
</div>
);
};

View File

@@ -1,5 +0,0 @@
export { BlockedServices } from './BlockedServices';
export { ClientIds } from './ClientIds';
export { ScheduleServices } from './ScheduleServices';
export { MainSettings } from './MainSettings';
export { UpstreamDns } from './UpstreamDns';

View File

@@ -1,223 +0,0 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { Trans, useTranslation } from 'react-i18next';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import Select from 'react-select';
import Tabs from '../../../ui/Tabs';
import { CLIENT_ID_LINK, LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
import { RootState } from '../../../../initialState';
import { Input } from '../../../ui/Controls/Input';
import { validateRequiredValue } from '../../../../helpers/validators';
import { ClientForm } from './types';
import { BlockedServices, ClientIds, MainSettings, ScheduleServices, UpstreamDns } from './components';
import '../Service.css';
const defaultFormValues: ClientForm = {
ids: [{ name: '' }],
name: '',
tags: [],
use_global_settings: false,
filtering_enabled: false,
safebrowsing_enabled: false,
parental_enabled: false,
ignore_querylog: false,
ignore_statistics: false,
blocked_services: {},
safe_search: { enabled: false },
upstreams: '',
upstreams_cache_enabled: false,
upstreams_cache_size: 0,
use_global_blocked_services: false,
blocked_services_schedule: {
time_zone: LOCAL_TIMEZONE_VALUE,
},
};
type Props = {
onSubmit: (values: ClientForm) => void;
onClose: () => void;
useGlobalSettings?: boolean;
useGlobalServices?: boolean;
blockedServicesSchedule?: {
time_zone: string;
};
processingAdding: boolean;
processingUpdating: boolean;
tagsOptions: { label: string; value: string }[];
initialValues?: ClientForm;
};
export const Form = ({
onSubmit,
onClose,
processingAdding,
processingUpdating,
tagsOptions,
initialValues,
}: Props) => {
const { t } = useTranslation();
const methods = useForm<ClientForm>({
defaultValues: {
...defaultFormValues,
...initialValues,
},
mode: 'onBlur',
});
const {
handleSubmit,
reset,
control,
formState: { isSubmitting, isValid },
} = methods;
const services = useSelector((store: RootState) => store?.services);
const { safe_search } = initialValues;
const safeSearchServices = { ...safe_search };
delete safeSearchServices.enabled;
const [activeTabLabel, setActiveTabLabel] = useState('settings');
const tabs = {
settings: {
title: 'settings',
component: <MainSettings safeSearchServices={safeSearchServices} />,
},
block_services: {
title: 'block_services',
component: <BlockedServices services={services?.allServices} />,
},
schedule_services: {
title: 'schedule_services',
component: <ScheduleServices />,
},
upstream_dns: {
title: 'upstream_dns',
component: <UpstreamDns />,
},
};
const activeTab = tabs[activeTabLabel].component;
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="modal-body">
<div className="form__group mb-0">
<div className="form__group">
<Controller
name="name"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="clients_name"
placeholder={t('form_client_name')}
error={fieldState.error?.message}
onBlur={(event) => {
const trimmedValue = event.target.value.trim();
field.onBlur();
field.onChange(trimmedValue);
}}
/>
)}
/>
</div>
<div className="form__group mb-4">
<div className="form__label">
<strong className="mr-3">
<Trans>tags_title</Trans>
</strong>
</div>
<div className="form__desc mt-0 mb-2">
<Trans
components={[
<a
target="_blank"
rel="noopener noreferrer"
href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax_ctag&from=ui&app=home"
key="0"
/>,
]}>
tags_desc
</Trans>
</div>
<Controller
name="tags"
control={control}
render={({ field }) => (
<Select
{...field}
data-testid="clients_tags"
options={tagsOptions}
className="basic-multi-select"
classNamePrefix="select"
isMulti
/>
)}
/>
</div>
<div className="form__group">
<div className="form__label">
<strong className="mr-3">
<Trans>client_identifier</Trans>
</strong>
</div>
<div className="form__desc mt-0">
<Trans
components={[
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" key="0" />,
]}>
client_identifier_desc
</Trans>
</div>
</div>
<div className="form__group">
<ClientIds />
</div>
</div>
<Tabs
controlClass="form"
tabs={tabs}
activeTabLabel={activeTabLabel}
setActiveTabLabel={setActiveTabLabel}>
{activeTab}
</Tabs>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={isSubmitting}
onClick={() => {
reset();
onClose();
}}>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={isSubmitting || !isValid || processingAdding || processingUpdating}>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
</FormProvider>
);
};

View File

@@ -1,28 +0,0 @@
export type ClientForm = {
name: string;
tags: { value: string; label: string }[];
ids: { name: string }[];
use_global_settings: boolean;
use_global_blocked_services: boolean;
blocked_services_schedule: {
time_zone: string;
};
safe_search: {
enabled: boolean;
[key: string]: boolean;
};
upstreams: string;
upstreams_cache_enabled: boolean;
upstreams_cache_size: number;
blocked_services: Record<string, boolean>;
filtering_enabled: boolean;
safebrowsing_enabled: boolean;
parental_enabled: boolean;
ignore_querylog: boolean;
ignore_statistics: boolean;
};
export type SubmitClientForm = Omit<ClientForm, 'ids' | 'tags'> & {
ids: string[];
tags: string[];
};

View File

@@ -4,15 +4,8 @@ import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import { Form } from './Form';
const normalizeIds = (initialIds?: string[]): { name: string }[] => {
if (!initialIds || initialIds.length === 0) {
return [{ name: '' }];
}
return initialIds.map((id: string) => ({ name: id }));
};
import Form from './Form';
const getInitialData = ({ initial, modalType, clientId, clientName }: any) => {
if (initial && initial.blocked_services) {
@@ -26,7 +19,6 @@ const getInitialData = ({ initial, modalType, clientId, clientName }: any) => {
return {
...initial,
blocked_services: blocked,
ids: normalizeIds(initial.ids),
};
}
@@ -34,14 +26,11 @@ const getInitialData = ({ initial, modalType, clientId, clientName }: any) => {
return {
...initial,
name: clientName,
ids: [{ name: clientId }],
ids: [clientId],
};
}
return {
...initial,
ids: normalizeIds(initial.ids),
};
return initial;
};
interface ModalProps {
@@ -52,7 +41,7 @@ interface ModalProps {
handleClose: (...args: unknown[]) => unknown;
processingAdding: boolean;
processingUpdating: boolean;
tagsOptions: { label: string; value: string }[];
tagsOptions: unknown[];
t: (...args: unknown[]) => string;
clientId?: string;
}
@@ -96,7 +85,7 @@ const Modal = ({
<Form
initialValues={{ ...initialData }}
onSubmit={handleSubmit}
onClose={handleClose}
handleClose={handleClose}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}

View File

@@ -1,22 +1,28 @@
import React, { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import React, { useCallback } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
import { UINT32_RANGE } from '../../../helpers/constants';
import { renderInputField, toNumber } from '../../../helpers/form';
import { FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
import {
validateIpv4,
validateRequiredValue,
validateIpv4RangeEnd,
validateGatewaySubnetMask,
validateIpForGatewaySubnetMask,
validateIpv4,
validateIpv4RangeEnd,
validateNotInRange,
validateRequiredValue,
} from '../../../helpers/validators';
import { DhcpFormValues } from '.';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { RootState } from '../../../initialState';
type FormDHCPv4Props = {
interface FormDHCPv4Props {
handleSubmit: (...args: unknown[]) => string;
submitting: boolean;
initialValues: { v4?: any };
processingConfig?: boolean;
change: (field: string, value: any) => void;
reset: () => void;
ipv4placeholders?: {
gateway_ip: string;
subnet_mask: string;
@@ -24,179 +30,127 @@ type FormDHCPv4Props = {
range_end: string;
lease_duration: string;
};
interfaces: any;
onSubmit?: (data: DhcpFormValues) => void;
};
}
const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }: FormDHCPv4Props) => {
const FormDHCPv4 = ({ handleSubmit, submitting, processingConfig, ipv4placeholders }: FormDHCPv4Props) => {
const { t } = useTranslation();
const {
handleSubmit,
formState: { errors, isSubmitting },
control,
watch,
} = useFormContext<DhcpFormValues>();
const dhcp = useSelector((state: RootState) => state.form[FORM_NAME.DHCPv4], shallowEqual);
const interfaceName = watch('interface_name');
const isInterfaceIncludesIpv4 = interfaces?.[interfaceName]?.ipv4_addresses;
const interfaces = useSelector((state: RootState) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
const interface_name = interfaces?.values?.interface_name;
const formValues = watch('v4');
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
const hasV4Errors = errors.v4 && Object.keys(errors.v4).length > 0;
const isInterfaceIncludesIpv4 = useSelector(
(state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
);
const isDisabled = useMemo(() => {
return isSubmitting || hasV4Errors || processingConfig || !isInterfaceIncludesIpv4 || isEmptyConfig;
}, [isSubmitting, hasV4Errors, processingConfig, isInterfaceIncludesIpv4, isEmptyConfig]);
const isEmptyConfig = !Object.values(dhcp?.values?.v4 ?? {}).some(Boolean);
const invalid =
dhcp?.syncErrors ||
interfaces?.syncErrors ||
!isInterfaceIncludesIpv4 ||
isEmptyConfig ||
submitting ||
processingConfig;
const validateRequired = useCallback(
(value) => {
if (isEmptyConfig) {
return undefined;
}
return validateRequiredValue(value);
},
[isEmptyConfig],
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<Controller
<label>{t('dhcp_form_gateway_input')}</label>
<Field
name="v4.gateway_ip"
control={control}
rules={{
validate: {
ipv4: validateIpv4,
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
notInRange: validateNotInRange,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v4_gateway_ip"
label={t('dhcp_form_gateway_input')}
placeholder={t(ipv4placeholders.gateway_ip)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv4}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.gateway_ip)}
validate={[validateIpv4, validateRequired, validateNotInRange]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
<div className="form__group form__group--settings">
<Controller
<label>{t('dhcp_form_subnet_input')}</label>
<Field
name="v4.subnet_mask"
control={control}
rules={{
validate: {
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
subnet: validateGatewaySubnetMask,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v4_subnet_mask"
label={t('dhcp_form_subnet_input')}
placeholder={t(ipv4placeholders.subnet_mask)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv4}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.subnet_mask)}
validate={[validateRequired, validateGatewaySubnetMask]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
<div className="col-lg-6">
<div className="form__group mb-0">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Controller
<Field
name="v4.range_start"
control={control}
rules={{
validate: {
ipv4: validateIpv4,
gateway: validateIpForGatewaySubnetMask,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v4_range_start"
placeholder={t(ipv4placeholders.range_start)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv4}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.range_start)}
validate={[validateIpv4, validateIpForGatewaySubnetMask]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
<div className="col">
<Controller
<Field
name="v4.range_end"
control={control}
rules={{
validate: {
ipv4: validateIpv4,
rangeEnd: validateIpv4RangeEnd,
gateway: validateIpForGatewaySubnetMask,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v4_range_end"
placeholder={t(ipv4placeholders.range_end)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv4}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.range_end)}
validate={[validateIpv4, validateIpv4RangeEnd, validateIpForGatewaySubnetMask]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
</div>
<div className="form__group form__group--settings">
<Controller
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="v4.lease_duration"
control={control}
rules={{
validate: {
required: (value) => (isEmptyConfig ? undefined : validateRequiredValue(value)),
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="v4_lease_duration"
label={t('dhcp_form_lease_title')}
placeholder={t(ipv4placeholders.lease_duration)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv4}
min={1}
max={UINT32_RANGE.MAX}
value={field.value ?? ''}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
component={renderInputField}
type="number"
className="form-control"
placeholder={t(ipv4placeholders.lease_duration)}
validate={validateRequired}
normalize={toNumber}
min={1}
max={UINT32_RANGE.MAX}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
</div>
<div className="btn-list">
<button
data-testid="v4_save"
type="submit"
className="btn btn-success btn-standard"
disabled={isDisabled}>
<button type="submit" className="btn btn-success btn-standard" disabled={invalid}>
{t('save_config')}
</button>
</div>
@@ -204,4 +158,9 @@ const FormDHCPv4 = ({ processingConfig, ipv4placeholders, interfaces, onSubmit }
);
};
export default FormDHCPv4;
export default reduxForm<
Record<string, any>,
Omit<FormDHCPv4Props, 'submitting' | 'handleSubmit' | 'reset' | 'change'>
>({
form: FORM_NAME.DHCPv4,
})(FormDHCPv4);

View File

@@ -1,92 +1,93 @@
import React, { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import React, { useCallback } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
import { UINT32_RANGE } from '../../../helpers/constants';
import { renderInputField, toNumber } from '../../../helpers/form';
import { FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
import { validateIpv6, validateRequiredValue } from '../../../helpers/validators';
import { DhcpFormValues } from '.';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { RootState } from '../../../initialState';
type FormDHCPv6Props = {
interface FormDHCPv6Props {
handleSubmit: (...args: unknown[]) => string;
submitting: boolean;
initialValues: {
v6?: any;
};
change: (field: string, value: any) => void;
reset: () => void;
processingConfig?: boolean;
ipv6placeholders?: {
range_start: string;
range_end: string;
lease_duration: string;
};
interfaces: any;
onSubmit?: (data: DhcpFormValues) => Promise<void> | void;
};
}
const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }: FormDHCPv6Props) => {
const FormDHCPv6 = ({ handleSubmit, submitting, processingConfig, ipv6placeholders }: FormDHCPv6Props) => {
const { t } = useTranslation();
const {
handleSubmit,
formState: { isSubmitting, isValid },
control,
watch,
} = useFormContext<DhcpFormValues>();
const interfaceName = watch('interface_name');
const isInterfaceIncludesIpv6 = interfaces?.[interfaceName]?.ipv6_addresses;
const dhcp = useSelector((state: RootState) => state.form[FORM_NAME.DHCPv6], shallowEqual);
const formValues = watch('v6');
const isEmptyConfig = !Object.values(formValues || {}).some(Boolean);
const interfaces = useSelector((state: RootState) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
const interface_name = interfaces?.values?.interface_name;
const isDisabled = useMemo(() => {
return isSubmitting || !isValid || processingConfig || !isInterfaceIncludesIpv6 || isEmptyConfig;
}, [isSubmitting, isValid, processingConfig, isInterfaceIncludesIpv6, isEmptyConfig]);
const isInterfaceIncludesIpv6 = useSelector(
(state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv6_addresses,
);
const isEmptyConfig = !Object.values(dhcp?.values?.v6 ?? {}).some(Boolean);
const invalid =
dhcp?.syncErrors ||
interfaces?.syncErrors ||
!isInterfaceIncludesIpv6 ||
isEmptyConfig ||
submitting ||
processingConfig;
const validateRequired = useCallback(
(value) => {
if (isEmptyConfig) {
return undefined;
}
return validateRequiredValue(value);
},
[isEmptyConfig],
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-lg-6">
<div className="form__group mb-0">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Controller
<Field
name="v6.range_start"
control={control}
rules={{
validate: isInterfaceIncludesIpv6
? {
ipv6: validateIpv6,
required: validateRequiredValue,
}
: undefined,
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v6_range_start"
placeholder={t(ipv6placeholders.range_start)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv6}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv6placeholders.range_start)}
validate={[validateIpv6, validateRequired]}
disabled={!isInterfaceIncludesIpv6}
/>
</div>
<div className="col">
<Controller
<Field
name="v6.range_end"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="v6_range_end"
placeholder={t(ipv6placeholders.range_end)}
error={fieldState.error?.message}
disabled
/>
)}
component="input"
type="text"
className="form-control disabled cursor--not-allowed"
placeholder={t(ipv6placeholders.range_end)}
value={t(ipv6placeholders.range_end)}
disabled
/>
</div>
</div>
@@ -96,43 +97,25 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
<div className="row">
<div className="col-lg-6 form__group form__group--settings">
<Controller
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="v6.lease_duration"
control={control}
rules={{
validate: isInterfaceIncludesIpv6
? {
required: validateRequiredValue,
}
: undefined,
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
data-testid="v6_lease_duration"
label={t('dhcp_form_lease_title')}
placeholder={t(ipv6placeholders.lease_duration)}
error={fieldState.error?.message}
disabled={!isInterfaceIncludesIpv6}
min={1}
max={UINT32_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
component={renderInputField}
type="number"
className="form-control"
placeholder={t(ipv6placeholders.lease_duration)}
validate={validateRequired}
normalizeOnBlur={toNumber}
min={1}
max={UINT32_RANGE.MAX}
disabled={!isInterfaceIncludesIpv6}
/>
</div>
</div>
<div className="btn-list">
<button
data-testid="v6_save"
type="submit"
className="btn btn-success btn-standard"
disabled={isDisabled}>
<button type="submit" className="btn btn-success btn-standard" disabled={invalid}>
{t('save_config')}
</button>
</div>
@@ -140,4 +123,9 @@ const FormDHCPv6 = ({ processingConfig, ipv6placeholders, interfaces, onSubmit }
);
};
export default FormDHCPv6;
export default reduxForm<
Record<string, any>,
Omit<FormDHCPv6Props, 'handleSubmit' | 'change' | 'submitting' | 'reset'>
>({
form: FORM_NAME.DHCPv6,
})(FormDHCPv6);

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { useFormContext } from 'react-hook-form';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import { renderSelectField } from '../../../helpers/form';
import { validateRequiredValue } from '../../../helpers/validators';
import { FORM_NAME } from '../../../helpers/constants';
import { RootState } from '../../../initialState';
import { DhcpFormValues } from '.';
const renderInterfaces = (interfaces: any) =>
Object.keys(interfaces).map((item) => {
@@ -45,13 +47,13 @@ const getInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: any)
},
];
interface RenderInterfaceValuesProps {
interface renderInterfaceValuesProps {
gateway_ip: string;
hardware_address: string;
ip_addresses: string[];
}
const renderInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: RenderInterfaceValuesProps) => (
const renderInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: renderInterfaceValuesProps) => (
<div className="d-flex align-items-end dhcp__interfaces-info">
<ul className="list-unstyled m-0">
{getInterfaceValues({
@@ -75,15 +77,11 @@ const renderInterfaceValues = ({ gateway_ip, hardware_address, ip_addresses }: R
const Interfaces = () => {
const { t } = useTranslation();
const {
register,
watch,
formState: { errors },
} = useFormContext<DhcpFormValues>();
const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp);
const { processingInterfaces, interfaces, enabled } = useSelector((store: RootState) => store.dhcp, shallowEqual);
const interface_name = watch('interface_name');
const interface_name =
useSelector((store: RootState) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name);
if (processingInterfaces || !interfaces) {
return null;
@@ -94,34 +92,27 @@ const Interfaces = () => {
return (
<div className="row dhcp__interfaces">
<div className="col col__dhcp">
<label htmlFor="interface_name" className="form__label">
{t('dhcp_interface_select')}
</label>
<select
id="interface_name"
data-testid="interface_name"
<Field
name="interface_name"
component={renderSelectField}
className="form-control custom-select pl-4 col-md"
disabled={enabled}
{...register('interface_name', {
validate: validateRequiredValue,
})}>
validate={[validateRequiredValue]}
label="dhcp_interface_select">
<option value="" disabled={enabled}>
{t('dhcp_interface_select')}
</option>
{renderInterfaces(interfaces)}
</select>
{errors.interface_name && (
<div className="form__message form__message--error">{t(errors.interface_name.message)}</div>
)}
</Field>
</div>
{interfaceValue &&
renderInterfaceValues({
gateway_ip: interfaceValue.gateway_ip,
hardware_address: interfaceValue.hardware_address,
ip_addresses: interfaceValue.ip_addresses,
})}
{interfaceValue && renderInterfaceValues({
gateway_ip: interfaceValue.gateway_ip,
hardware_address: interfaceValue.hardware_address,
ip_addresses: interfaceValue.ip_addresses
})}
</div>
);
};
export default Interfaces;
export default reduxForm({
form: FORM_NAME.DHCP_INTERFACES,
})(Interfaces);

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { normalizeMac } from '../../../../helpers/form';
import { renderInputField, normalizeMac } from '../../../../helpers/form';
import {
validateIpv4,
validateMac,
@@ -11,12 +12,12 @@ import {
validateIpv4InCidr,
validateIpGateway,
} from '../../../../helpers/validators';
import { FORM_NAME } from '../../../../helpers/constants';
import { toggleLeaseModal } from '../../../../actions';
import { RootState } from '../../../../initialState';
import { Input } from '../../../ui/Controls/Input';
type Props = {
interface FormStaticLeaseProps {
initialValues?: {
mac?: string;
ip?: string;
@@ -24,26 +25,20 @@ type Props = {
cidr?: string;
gatewayIp?: string;
};
pristine: boolean;
handleSubmit: (...args: unknown[]) => string;
reset: () => void;
submitting: boolean;
processingAdding?: boolean;
cidr?: string;
isEdit?: boolean;
onSubmit: (data: any) => void;
};
}
export const Form = ({ initialValues, processingAdding, cidr, isEdit, onSubmit }: Props) => {
const Form = ({ handleSubmit, reset, pristine, submitting, processingAdding, cidr, isEdit }: FormStaticLeaseProps) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const dynamicLease = useSelector((store: RootState) => store.dhcp.leaseModalConfig, shallowEqual);
const {
handleSubmit,
control,
reset,
formState: { isSubmitting, isDirty },
} = useForm({
defaultValues: initialValues,
mode: 'onBlur',
});
const dynamicLease = useSelector((store: RootState) => store.dhcp.leaseModalConfig, shallowEqual);
const onClick = () => {
reset();
@@ -51,64 +46,42 @@ export const Form = ({ initialValues, processingAdding, cidr, isEdit, onSubmit }
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<Controller
<Field
id="mac"
name="mac"
control={control}
rules={{ validate: { required: validateRequiredValue, mac: validateMac } }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="static_lease_mac"
placeholder={t('form_enter_mac')}
disabled={isEdit}
error={fieldState.error?.message}
onChange={(e) => field.onChange(normalizeMac(e.target.value))}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
normalize={normalizeMac}
validate={[validateRequiredValue, validateMac]}
disabled={isEdit}
/>
</div>
<div className="form__group">
<Controller
<Field
id="ip"
name="ip"
control={control}
rules={{
validate: {
required: validateRequiredValue,
ipv4: validateIpv4,
inCidr: validateIpv4InCidr,
gateway: validateIpGateway,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="static_lease_ip"
error={fieldState.error?.message}
placeholder={t('form_enter_subnet_ip', { cidr })}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_subnet_ip', { cidr })}
validate={[validateRequiredValue, validateIpv4, validateIpv4InCidr, validateIpGateway]}
/>
</div>
<div className="form__group">
<Controller
<Field
id="hostname"
name="hostname"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
data-testid="static_lease_hostname"
error={fieldState.error?.message}
placeholder={t('form_enter_hostname')}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('form_enter_hostname')}
/>
</div>
</div>
@@ -117,18 +90,16 @@ export const Form = ({ initialValues, processingAdding, cidr, isEdit, onSubmit }
<div className="btn-list">
<button
type="button"
data-testid="static_lease_cancel"
className="btn btn-secondary btn-standard"
disabled={isSubmitting}
disabled={submitting}
onClick={onClick}>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
data-testid="static_lease_save"
className="btn btn-success btn-standard"
disabled={isSubmitting || processingAdding || (!isDirty && !dynamicLease)}>
disabled={submitting || processingAdding || (pristine && !dynamicLease)}>
<Trans>save_btn</Trans>
</button>
</div>
@@ -136,3 +107,8 @@ export const Form = ({ initialValues, processingAdding, cidr, isEdit, onSubmit }
</form>
);
};
export default reduxForm<
Record<string, any>,
Omit<FormStaticLeaseProps, 'submitting' | 'handleSubmit' | 'reset' | 'pristine'>
>({ form: FORM_NAME.LEASE })(Form);

View File

@@ -4,7 +4,7 @@ import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { Form } from './Form';
import Form from './Form';
import { toggleLeaseModal } from '../../../../actions';
import { MODAL_TYPE } from '../../../../helpers/constants';

View File

@@ -4,8 +4,8 @@ import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames';
import { FormProvider, useForm } from 'react-hook-form';
import { DHCP_DESCRIPTION_PLACEHOLDERS, STATUS_RESPONSE } from '../../../helpers/constants';
import { destroy } from 'redux-form';
import { DHCP_DESCRIPTION_PLACEHOLDERS, DHCP_FORM_NAMES, STATUS_RESPONSE, FORM_NAME } from '../../../helpers/constants';
import Leases from './Leases';
@@ -40,55 +40,6 @@ import {
import './index.css';
import { RootState } from '../../../initialState';
type IPv4FormValues = {
gateway_ip?: string;
subnet_mask?: string;
range_start?: string;
range_end?: string;
lease_duration?: number;
}
type IPv6FormValues = {
range_start?: string;
range_end?: string;
lease_duration?: number;
}
const getDefaultV4Values = (v4: IPv4FormValues) => {
const emptyForm = Object.entries(v4).every(
([key, value]) => key === 'lease_duration' || value === ''
);
if (emptyForm) {
return {
...v4,
lease_duration: undefined,
}
}
return v4;
}
export type DhcpFormValues = {
v4?: IPv4FormValues;
v6?: IPv6FormValues;
interface_name?: string;
};
const DEFAULT_V4_VALUES = {
gateway_ip: '',
subnet_mask: '',
range_start: '',
range_end: '',
lease_duration: undefined,
};
const DEFAULT_V6_VALUES = {
range_start: '',
range_end: '',
lease_duration: undefined,
};
const Dhcp = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -114,21 +65,12 @@ const Dhcp = () => {
modalType,
} = useSelector((state: RootState) => state.dhcp, shallowEqual);
const methods = useForm<DhcpFormValues>({
mode: 'onBlur',
defaultValues: {
v4: getDefaultV4Values(v4),
v6,
interface_name: interfaceName || '',
},
});
const { watch, reset } = methods;
const interface_name =
useSelector((state: RootState) => state.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name);
const isInterfaceIncludesIpv4 =
useSelector((state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses);
const interface_name = watch('interface_name');
const isInterfaceIncludesIpv4 = useSelector(
(state: RootState) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
);
const ipv4Config = watch('v4');
const dhcp = useSelector((state: RootState) => state.form[FORM_NAME.DHCPv4], shallowEqual);
const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4);
const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6);
@@ -143,22 +85,6 @@ const Dhcp = () => {
}
}, [dhcp_available]);
useEffect(() => {
if (v4 || v6 || interfaceName) {
reset({
v4: {
...DEFAULT_V4_VALUES,
...getDefaultV4Values(v4),
},
v6: {
...DEFAULT_V6_VALUES,
...v6,
},
interface_name: interfaceName || '',
});
}
}, [v4, v6, interfaceName, reset]);
useEffect(() => {
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
@@ -177,17 +103,13 @@ const Dhcp = () => {
const clear = () => {
// eslint-disable-next-line no-alert
if (window.confirm(t('dhcp_reset'))) {
reset({
v4: DEFAULT_V4_VALUES,
v6: DEFAULT_V6_VALUES,
interface_name: '',
});
Object.values(DHCP_FORM_NAMES).forEach((formName: any) => dispatch(destroy(formName)));
dispatch(resetDhcp());
dispatch(getDhcpStatus());
}
};
const handleSubmit = (values: DhcpFormValues) => {
const handleSubmit = (values: any) => {
dispatch(
setDhcpConfig({
interface_name,
@@ -208,7 +130,12 @@ const Dhcp = () => {
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
const getToggleDhcpButton = () => {
const filledConfig = interface_name && (Object.values(v4).every(Boolean) || Object.values(v6).every(Boolean));
const filledConfig =
interface_name &&
(Object.values(v4)
.every(Boolean) ||
Object.values(v6).every(Boolean));
const className = classNames('btn btn-sm', {
'btn-gray': enabled,
@@ -246,6 +173,9 @@ const Dhcp = () => {
const toggleModal = () => dispatch(toggleLeaseModal());
const initialV4 = enteredSomeV4Value ? v4 : {};
const initialV6 = enteredSomeV6Value ? v6 : {};
if (processing || processingInterfaces) {
return <Loading />;
}
@@ -266,13 +196,19 @@ const Dhcp = () => {
const toggleDhcpButton = getToggleDhcpButton();
const inputtedIPv4values = ipv4Config.gateway_ip && ipv4Config.subnet_mask;
const inputtedIPv4values = dhcp?.values?.v4?.gateway_ip && dhcp?.values?.v4?.subnet_mask;
const isEmptyConfig = !Object.values(ipv4Config).some(Boolean);
const isEmptyConfig = !Object.values(dhcp?.values?.v4 ?? {}).some(Boolean);
const disabledLeasesButton = Boolean(
!isInterfaceIncludesIpv4 || isEmptyConfig || processingConfig || !inputtedIPv4values,
dhcp?.syncErrors ||
!isInterfaceIncludesIpv4 ||
isEmptyConfig ||
processingConfig ||
!inputtedIPv4values,
);
const cidr = inputtedIPv4values ? `${ipv4Config.gateway_ip}/${subnetMaskToBitMask(ipv4Config.subnet_mask)}` : '';
const cidr = inputtedIPv4values
? `${dhcp?.values?.v4?.gateway_ip}/${subnetMaskToBitMask(dhcp?.values?.v4?.subnet_mask)}`
: '';
return (
<>
@@ -310,30 +246,29 @@ const Dhcp = () => {
</div>
)}
<FormProvider {...methods}>
<Interfaces />
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
<div>
<FormDHCPv4
onSubmit={handleSubmit}
processingConfig={processingConfig}
ipv4placeholders={ipv4placeholders}
interfaces={interfaces}
/>
</div>
</Card>
<Card title={t('dhcp_ipv6_settings')} bodyType="card-body box-body--settings">
<div>
<FormDHCPv6
onSubmit={handleSubmit}
processingConfig={processingConfig}
ipv6placeholders={ipv6placeholders}
interfaces={interfaces}
/>
</div>
</Card>
</FormProvider>
<Interfaces initialValues={{ interface_name: interfaceName }} />
<Card title={t('dhcp_ipv4_settings')} bodyType="card-body box-body--settings">
<div>
<FormDHCPv4
onSubmit={handleSubmit}
initialValues={{ v4: initialV4 }}
processingConfig={processingConfig}
ipv4placeholders={ipv4placeholders}
/>
</div>
</Card>
<Card title={t('dhcp_ipv6_settings')} bodyType="card-body box-body--settings">
<div>
<FormDHCPv6
onSubmit={handleSubmit}
initialValues={{ v6: initialV6 }}
processingConfig={processingConfig}
ipv6placeholders={ipv6placeholders}
/>
</div>
</Card>
{enabled && (
<Card title={t('dhcp_leases')} bodyType="card-body box-body--settings">
<div className="row">
@@ -355,7 +290,7 @@ const Dhcp = () => {
processingDeleting={processingDeleting}
processingUpdating={processingUpdating}
cidr={cidr}
gatewayIp={ipv4Config.gateway_ip}
gatewayIp={dhcp?.values?.v4?.gateway_ip}
/>
<div className="btn-list mt-2">

View File

@@ -1,140 +1,118 @@
import React, { ReactNode } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import React from 'react';
import { connect } from 'react-redux';
import i18next from 'i18next';
import { CLIENT_ID_LINK } from '../../../../helpers/constants';
import { removeEmptyLines, trimMultilineString } from '../../../../helpers/helpers';
import { Textarea } from '../../../ui/Controls/Textarea';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
type FormData = {
allowed_clients: string;
disallowed_clients: string;
blocked_hosts: string;
};
import { renderTextareaField } from '../../../../helpers/form';
import { trimMultilineString, removeEmptyLines } from '../../../../helpers/helpers';
import { CLIENT_ID_LINK, FORM_NAME } from '../../../../helpers/constants';
const fields: {
id: keyof FormData;
title: string;
subtitle: ReactNode;
normalizeOnBlur: (value: string) => string;
}[] = [
const fields = [
{
id: 'allowed_clients',
title: i18next.t('access_allowed_title'),
subtitle: (
<Trans
components={{
a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
}}>
access_allowed_desc
</Trans>
),
title: 'access_allowed_title',
subtitle: 'access_allowed_desc',
normalizeOnBlur: removeEmptyLines,
},
{
id: 'disallowed_clients',
title: i18next.t('access_disallowed_title'),
subtitle: (
<Trans
components={{
a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
}}>
access_disallowed_desc
</Trans>
),
title: 'access_disallowed_title',
subtitle: 'access_disallowed_desc',
normalizeOnBlur: trimMultilineString,
},
{
id: 'blocked_hosts',
title: i18next.t('access_blocked_title'),
subtitle: i18next.t('access_blocked_desc'),
title: 'access_blocked_title',
subtitle: 'access_blocked_desc',
normalizeOnBlur: removeEmptyLines,
},
];
type FormProps = {
initialValues?: {
allowed_clients?: string;
disallowed_clients?: string;
blocked_hosts?: string;
};
onSubmit: (data: FormData) => void;
interface FormProps {
handleSubmit: (...args: unknown[]) => string;
submitting: boolean;
invalid: boolean;
initialValues: object;
processingSet: boolean;
};
t: (...args: unknown[]) => string;
textarea?: boolean;
allowedClients?: string;
}
const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
const { t } = useTranslation();
interface renderFieldProps {
id?: string;
title?: string;
subtitle?: string;
disabled?: boolean;
processingSet?: boolean;
normalizeOnBlur?: (...args: unknown[]) => unknown;
}
const {
control,
handleSubmit,
watch,
formState: { isSubmitting, isDirty },
} = useForm<FormData>({
mode: 'onBlur',
defaultValues: {
allowed_clients: initialValues?.allowed_clients || '',
disallowed_clients: initialValues?.disallowed_clients || '',
blocked_hosts: initialValues?.blocked_hosts || '',
},
});
const allowedClients = watch('allowed_clients');
let Form = (props: FormProps) => {
const { allowedClients, handleSubmit, submitting, invalid, processingSet } = props;
const renderField = ({
id,
title,
subtitle,
disabled = false,
processingSet,
normalizeOnBlur,
}: {
id: keyof FormData;
title: string;
subtitle: ReactNode;
normalizeOnBlur: (value: string) => string;
}) => {
const disabled = allowedClients && id === 'disallowed_clients';
}: renderFieldProps) => (
<div key={id} className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor={id}>
<Trans>{title}</Trans>
return (
<div key={id} className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor={id}>
{title}
{disabled && <>&nbsp;({t('disabled')})</>}
</label>
{disabled && (
<>
<span> </span>(<Trans>disabled</Trans>)
</>
)}
</label>
<div className="form__desc form__desc--top">{subtitle}</div>
<Controller
name={id}
control={control}
render={({ field }) => (
<Textarea
{...field}
id={id}
data-testid={id}
disabled={disabled || processingSet}
onBlur={(e) => {
field.onChange(normalizeOnBlur(e.target.value));
}}
/>
)}
/>
<div className="form__desc form__desc--top">
<Trans
components={{
a: (
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer">
text
</a>
),
}}>
{subtitle}
</Trans>
</div>
);
};
<Field
id={id}
name={id}
component={renderTextareaField}
type="text"
className="form-control form-control--textarea font-monospace"
disabled={disabled || processingSet}
normalizeOnBlur={normalizeOnBlur}
/>
</div>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{fields.map((f) => renderField(f))}
<form onSubmit={handleSubmit}>
{fields.map((f) => {
return renderField({
...f,
disabled: allowedClients && f.id === 'disallowed_clients' || false
});
})}
<div className="card-actions">
<div className="btn-list">
<button
type="submit"
data-testid="access_save"
className="btn btn-success btn-standard"
disabled={isSubmitting || !isDirty || processingSet}>
{t('save_config')}
disabled={submitting || invalid || processingSet}>
<Trans>save_config</Trans>
</button>
</div>
</div>
@@ -142,4 +120,18 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
);
};
export default Form;
const selector = formValueSelector(FORM_NAME.ACCESS);
Form = connect((state) => {
const allowedClients = selector(state, 'allowed_clients');
return {
allowedClients,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.ACCESS,
}),
])(Form);

View File

@@ -1,72 +1,52 @@
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import i18next from 'i18next';
import { clearDnsCache } from '../../../../actions/dnsConfig';
import { CACHE_CONFIG_FIELDS, UINT32_RANGE } from '../../../../helpers/constants';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { renderInputField, toNumber, CheckboxField } from '../../../../helpers/form';
import { CACHE_CONFIG_FIELDS, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
import { replaceZeroWithEmptyString } from '../../../../helpers/helpers';
import { clearDnsCache } from '../../../../actions/dnsConfig';
import { RootState } from '../../../../initialState';
import { Checkbox } from '../../../ui/Controls/Checkbox';
const INPUTS_FIELDS = [
{
name: CACHE_CONFIG_FIELDS.cache_size,
title: i18next.t('cache_size'),
description: i18next.t('cache_size_desc'),
placeholder: i18next.t('enter_cache_size'),
title: 'cache_size',
description: 'cache_size_desc',
placeholder: 'enter_cache_size',
},
{
name: CACHE_CONFIG_FIELDS.cache_ttl_min,
title: i18next.t('cache_ttl_min_override'),
description: i18next.t('cache_ttl_min_override_desc'),
placeholder: i18next.t('enter_cache_ttl_min_override'),
title: 'cache_ttl_min_override',
description: 'cache_ttl_min_override_desc',
placeholder: 'enter_cache_ttl_min_override',
},
{
name: CACHE_CONFIG_FIELDS.cache_ttl_max,
title: i18next.t('cache_ttl_max_override'),
description: i18next.t('cache_ttl_max_override_desc'),
placeholder: i18next.t('enter_cache_ttl_max_override'),
title: 'cache_ttl_max_override',
description: 'cache_ttl_max_override_desc',
placeholder: 'enter_cache_ttl_max_override',
},
];
type FormData = {
cache_size: number;
cache_ttl_min: number;
cache_ttl_max: number;
cache_optimistic: boolean;
};
interface CacheFormProps {
handleSubmit: (...args: unknown[]) => string;
submitting: boolean;
invalid: boolean;
}
type CacheFormProps = {
initialValues?: Partial<FormData>;
onSubmit: (data: FormData) => void;
};
const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
const Form = ({ handleSubmit, submitting, invalid }: CacheFormProps) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const { processingSetConfig } = useSelector((state: RootState) => state.dnsConfig);
const {
register,
handleSubmit,
watch,
control,
formState: { isSubmitting, isDirty },
} = useForm<FormData>({
mode: 'onBlur',
defaultValues: {
cache_size: initialValues?.cache_size || 0,
cache_ttl_min: initialValues?.cache_ttl_min || 0,
cache_ttl_max: initialValues?.cache_ttl_max || 0,
cache_optimistic: initialValues?.cache_optimistic || false,
},
});
const cache_ttl_min = watch('cache_ttl_min');
const cache_ttl_max = watch('cache_ttl_max');
const { processingSetConfig } = useSelector((state: RootState) => state.dnsConfig, shallowEqual);
const { cache_ttl_max, cache_ttl_min } = useSelector(
(state: RootState) => state.form[FORM_NAME.CACHE].values,
shallowEqual,
);
const minExceedsMax = cache_ttl_min > 0 && cache_ttl_max > 0 && cache_ttl_min > cache_ttl_max;
@@ -77,30 +57,29 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
{INPUTS_FIELDS.map(({ name, title, description, placeholder }) => (
<div className="col-12" key={name}>
<div className="col-12 col-md-7 p-0">
<div className="form__group form__group--settings">
<label htmlFor={name} className="form__label form__label--with-desc">
{title}
{t(title)}
</label>
<div className="form__desc form__desc--top">{description}</div>
<div className="form__desc form__desc--top">{t(description)}</div>
<input
<Field
name={name}
type="number"
data-testid={`dns_${name}`}
className="form-control"
placeholder={placeholder}
component={renderInputField}
placeholder={t(placeholder)}
disabled={processingSetConfig}
className="form-control"
normalizeOnBlur={replaceZeroWithEmptyString}
normalize={toNumber}
min={0}
max={UINT32_RANGE.MAX}
{...register(name as keyof FormData, {
valueAsNumber: true,
setValueAs: (value) => replaceZeroWithEmptyString(value),
})}
/>
</div>
</div>
@@ -112,18 +91,13 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
<div className="row">
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<Field
name="cache_optimistic"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="dns_cache_optimistic"
title={t('cache_optimistic')}
subtitle={t('cache_optimistic_desc')}
disabled={processingSetConfig}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('cache_optimistic')}
disabled={processingSetConfig}
subtitle={t('cache_optimistic_desc')}
/>
</div>
</div>
@@ -131,21 +105,19 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
<button
type="submit"
data-testid="dns_save"
className="btn btn-success btn-standard btn-large"
disabled={isSubmitting || !isDirty || processingSetConfig || minExceedsMax}>
{t('save_btn')}
disabled={submitting || invalid || processingSetConfig || minExceedsMax}>
<Trans>save_btn</Trans>
</button>
<button
type="button"
data-testid="dns_clear"
className="btn btn-outline-secondary btn-standard form__button"
onClick={handleClearCache}>
{t('clear_cache')}
<Trans>clear_cache</Trans>
</button>
</form>
);
};
export default Form;
export default reduxForm({ form: FORM_NAME.CACHE })(Form);

View File

@@ -1,279 +1,211 @@
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useSelector } from 'react-redux';
import i18next from 'i18next';
import { validateIp, validateIpv4, validateIpv6, validateRequiredValue } from '../../../../helpers/validators';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import {
renderInputField,
renderRadioField,
renderTextareaField,
CheckboxField,
toNumber,
} from '../../../../helpers/form';
import {
validateIpv4,
validateIpv6,
validateRequiredValue,
validateIp,
validateIPv4Subnet,
validateIPv6Subnet,
} from '../../../../helpers/validators';
import { BLOCKING_MODES, UINT32_RANGE } from '../../../../helpers/constants';
import { Checkbox } from '../../../ui/Controls/Checkbox';
import { Input } from '../../../ui/Controls/Input';
import { toNumber } from '../../../../helpers/form';
import { Textarea } from '../../../ui/Controls/Textarea';
import { Radio } from '../../../ui/Controls/Radio';
import { removeEmptyLines } from '../../../../helpers/helpers';
import { BLOCKING_MODES, FORM_NAME, UINT32_RANGE } from '../../../../helpers/constants';
import { RootState } from '../../../../initialState';
const checkboxes: {
name: 'dnssec_enabled' | 'disable_ipv6';
placeholder: string;
subtitle: string;
}[] = [
const checkboxes = [
{
name: 'dnssec_enabled',
placeholder: i18next.t('dnssec_enable'),
subtitle: i18next.t('dnssec_enable_desc'),
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: i18next.t('disable_ipv6'),
subtitle: i18next.t('disable_ipv6_desc'),
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
},
];
const customIps: {
name: 'blocking_ipv4' | 'blocking_ipv6';
label: string;
description: string;
validateIp: (value: string) => string;
}[] = [
const customIps = [
{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
label: i18next.t('blocking_ipv4'),
description: i18next.t('blocking_ipv4_desc'),
validateIp: validateIpv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
label: i18next.t('blocking_ipv6'),
description: i18next.t('blocking_ipv6_desc'),
validateIp: validateIpv6,
},
];
const blockingModeOptions = [
{
value: BLOCKING_MODES.default,
label: i18next.t('default'),
},
{
value: BLOCKING_MODES.refused,
label: i18next.t('refused'),
},
{
value: BLOCKING_MODES.nxdomain,
label: i18next.t('nxdomain'),
},
{
value: BLOCKING_MODES.null_ip,
label: i18next.t('null_ip'),
},
{
value: BLOCKING_MODES.custom_ip,
label: i18next.t('custom_ip'),
},
];
const getFields = (processing: any, t: any) =>
Object.values(BLOCKING_MODES)
const blockingModeDescriptions = [
i18next.t(`blocking_mode_default`),
i18next.t(`blocking_mode_refused`),
i18next.t(`blocking_mode_nxdomain`),
i18next.t(`blocking_mode_null_ip`),
i18next.t(`blocking_mode_custom_ip`),
];
.map((mode: any) => (
<Field
key={mode}
name="blocking_mode"
type="radio"
component={renderRadioField}
value={mode}
placeholder={t(mode)}
disabled={processing}
/>
));
type FormData = {
ratelimit: number;
ratelimit_subnet_len_ipv4: number;
ratelimit_subnet_len_ipv6: number;
ratelimit_whitelist: string;
edns_cs_enabled: boolean;
edns_cs_use_custom: boolean;
edns_cs_custom_ip?: string;
dnssec_enabled: boolean;
disable_ipv6: boolean;
blocking_mode: string;
blocking_ipv4?: string;
blocking_ipv6?: string;
blocked_response_ttl: number;
};
type Props = {
interface ConfigFormProps {
handleSubmit: (...args: unknown[]) => string;
submitting: boolean;
invalid: boolean;
processing?: boolean;
initialValues?: Partial<FormData>;
onSubmit: (data: FormData) => void;
};
}
const Form = ({ processing, initialValues, onSubmit }: Props) => {
const Form = ({ handleSubmit, submitting, invalid, processing }: ConfigFormProps) => {
const { t } = useTranslation();
const {
handleSubmit,
watch,
control,
formState: { isSubmitting, isDirty },
} = useForm<FormData>({
mode: 'onBlur',
defaultValues: initialValues,
});
const blocking_mode = watch('blocking_mode');
const edns_cs_enabled = watch('edns_cs_enabled');
const edns_cs_use_custom = watch('edns_cs_use_custom');
const { blocking_mode, edns_cs_enabled, edns_cs_use_custom } = useSelector(
(state: RootState) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {},
shallowEqual,
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<label htmlFor="ratelimit" className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
<Field
name="ratelimit"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_ratelimit"
type="number"
label={t('rate_limit')}
desc={t('rate_limit_desc')}
error={fieldState.error?.message}
min={UINT32_RANGE.MIN}
max={UINT32_RANGE.MAX}
disabled={processing}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={validateRequiredValue}
min={UINT32_RANGE.MIN}
max={UINT32_RANGE.MAX}
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<label htmlFor="ratelimit_subnet_len_ipv4" className="form__label form__label--with-desc">
<Trans>rate_limit_subnet_len_ipv4</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_subnet_len_ipv4_desc</Trans>
</div>
<Field
name="ratelimit_subnet_len_ipv4"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_subnet_ipv4"
type="number"
label={t('rate_limit_subnet_len_ipv4')}
desc={t('rate_limit_subnet_len_ipv4_desc')}
error={fieldState.error?.message}
min={0}
max={32}
disabled={processing}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit_subnet_len')}
normalize={toNumber}
validate={[validateRequiredValue, validateIPv4Subnet]}
min={0}
max={32}
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<label htmlFor="ratelimit_subnet_len_ipv6" className="form__label form__label--with-desc">
<Trans>rate_limit_subnet_len_ipv6</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_subnet_len_ipv6_desc</Trans>
</div>
<Field
name="ratelimit_subnet_len_ipv6"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_subnet_ipv6"
type="number"
label={t('rate_limit_subnet_len_ipv6')}
desc={t('rate_limit_subnet_len_ipv6_desc')}
error={fieldState.error?.message}
min={0}
max={128}
disabled={processing}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit_subnet_len')}
normalize={toNumber}
validate={[validateRequiredValue, validateIPv6Subnet]}
min={0}
max={128}
/>
</div>
</div>
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<label htmlFor="ratelimit_whitelist" className="form__label form__label--with-desc">
<Trans>rate_limit_whitelist</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_whitelist_desc</Trans>
</div>
<Field
name="ratelimit_whitelist"
control={control}
render={({ field, fieldState }) => (
<Textarea
{...field}
data-testid="dns_config_subnet_ipv6"
label={t('rate_limit_whitelist')}
desc={t('rate_limit_whitelist_desc')}
error={fieldState.error?.message}
disabled={processing}
trimOnBlur
/>
)}
component={renderTextareaField}
type="text"
className="form-control"
placeholder={t('rate_limit_whitelist_placeholder')}
normalizeOnBlur={removeEmptyLines}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Controller
<Field
name="edns_cs_enabled"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="dns_config_edns_cs_enabled"
title={t('edns_enable')}
disabled={processing}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('edns_enable')}
disabled={processing}
subtitle={t('edns_cs_desc')}
/>
</div>
</div>
<div className="col-12 form__group form__group--inner">
<div className="form__group">
<Controller
<div className="form__group ">
<Field
name="edns_cs_use_custom"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="dns_config_edns_use_custom_ip"
title={t('edns_use_custom_ip')}
disabled={processing || !edns_cs_enabled}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('edns_use_custom_ip')}
disabled={processing || !edns_cs_enabled}
subtitle={t('edns_use_custom_ip_desc')}
/>
</div>
{edns_cs_use_custom && (
<Controller
<Field
name="edns_cs_custom_ip"
control={control}
rules={{
validate: {
required: validateRequiredValue,
id: validateIp,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_edns_cs_custom_ip"
error={fieldState.error?.message}
disabled={processing || !edns_cs_enabled}
/>
)}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
)}
</div>
@@ -281,18 +213,13 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
{checkboxes.map(({ name, placeholder, subtitle }) => (
<div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Controller
<Field
name={name}
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid={`dns_config_${name}`}
title={placeholder}
subtitle={subtitle}
disabled={processing}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>
@@ -300,50 +227,42 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">{t('blocking_mode')}</label>
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{blockingModeDescriptions.map((desc: string) => (
<li key={desc}>{desc}</li>
))}
{Object.values(BLOCKING_MODES)
.map((mode: any) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
<Controller
name="blocking_mode"
control={control}
render={({ field }) => (
<Radio {...field} options={blockingModeOptions} disabled={processing} />
)}
/>
</div>
<div className="custom-controls-stacked">{getFields(processing, t)}</div>
</div>
</div>
{blocking_mode === BLOCKING_MODES.custom_ip && (
<>
{customIps.map(({ label, description, name, validateIp }) => (
{customIps.map(({ description, name, validateIp }) => (
<div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<Controller
<label className="form__label form__label--with-desc" htmlFor={name}>
<Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
control={control}
rules={{
validate: {
required: validateRequiredValue,
ip: validateIp,
},
}}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_blocked_response_ttl"
type="text"
label={label}
desc={description}
error={fieldState.error?.message}
disabled={processing}
/>
)}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
</div>
</div>
@@ -353,27 +272,24 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
<div className="col-12 col-md-7">
<div className="form__group form__group--settings">
<Controller
<label htmlFor="blocked_response_ttl" className="form__label form__label--with-desc">
<Trans>blocked_response_ttl</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>blocked_response_ttl_desc</Trans>
</div>
<Field
name="blocked_response_ttl"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="dns_config_blocked_response_ttl"
type="number"
label={t('blocked_response_ttl')}
desc={t('blocked_response_ttl_desc')}
error={fieldState.error?.message}
min={UINT32_RANGE.MIN}
max={UINT32_RANGE.MAX}
disabled={processing}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_blocked_response_ttl')}
normalize={toNumber}
validate={validateRequiredValue}
min={UINT32_RANGE.MIN}
max={UINT32_RANGE.MAX}
/>
</div>
</div>
@@ -381,13 +297,14 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
<button
type="submit"
data-testid="dns_config_save"
className="btn btn-success btn-standard btn-large"
disabled={isSubmitting || !isDirty || processing}>
{t('save_btn')}
disabled={submitting || invalid || processing}>
<Trans>save_btn</Trans>
</button>
</form>
);
};
export default Form;
export default reduxForm<Record<string, any>, Omit<ConfigFormProps, 'invalid' | 'submitting' | 'handleSubmit'>>({
form: FORM_NAME.BLOCKING_MODE,
})(Form);

View File

@@ -1,110 +1,173 @@
import React, { useRef } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import i18next from 'i18next';
import clsx from 'clsx';
import { testUpstreamWithFormValues } from '../../../../actions';
import { DNS_REQUEST_OPTIONS, UINT32_RANGE, UPSTREAM_CONFIGURATION_WIKI_LINK } from '../../../../helpers/constants';
import { removeEmptyLines } from '../../../../helpers/helpers';
import { getTextareaCommentsHighlight, syncScroll } from '../../../../helpers/highlightTextareaComments';
import { RootState } from '../../../../initialState';
import '../../../ui/texareaCommentsHighlight.css';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import classnames from 'classnames';
import Examples from './Examples';
import { Checkbox } from '../../../ui/Controls/Checkbox';
import { Textarea } from '../../../ui/Controls/Textarea';
import { Radio } from '../../../ui/Controls/Radio';
import { Input } from '../../../ui/Controls/Input';
import { validateRequiredValue } from '../../../../helpers/validators';
import { toNumber } from '../../../../helpers/form';
import { renderRadioField, renderTextareaField, CheckboxField } from '../../../../helpers/form';
import { DNS_REQUEST_OPTIONS, FORM_NAME, UPSTREAM_CONFIGURATION_WIKI_LINK } from '../../../../helpers/constants';
import { testUpstreamWithFormValues } from '../../../../actions';
import { removeEmptyLines, trimLinesAndRemoveEmpty } from '../../../../helpers/helpers';
import { getTextareaCommentsHighlight, syncScroll } from '../../../../helpers/highlightTextareaComments';
import '../../../ui/texareaCommentsHighlight.css';
import { RootState } from '../../../../initialState';
const UPSTREAM_DNS_NAME = 'upstream_dns';
const UPSTREAM_MODE_NAME = 'upstream_mode';
type FormData = {
upstream_dns: string;
upstream_mode: string;
fallback_dns: string;
bootstrap_dns: string;
local_ptr_upstreams: string;
use_private_ptr_resolvers: boolean;
resolve_clients: boolean;
upstream_timeout: number;
interface renderFieldProps {
name: string;
component: any;
type: string;
className?: string;
placeholder: string;
subtitle?: string;
value?: string;
normalizeOnBlur?: (...args: unknown[]) => unknown;
containerClass?: string;
onScroll?: (...args: unknown[]) => unknown;
}
const renderField = ({
name,
component,
type,
className,
placeholder,
subtitle,
value,
normalizeOnBlur,
containerClass,
onScroll,
}: renderFieldProps) => {
const { t } = useTranslation();
const processingTestUpstream = useSelector((state: RootState) => state.settings.processingTestUpstream);
const processingSetConfig = useSelector((state: RootState) => state.dnsConfig.processingSetConfig);
return (
<div key={placeholder} className={classnames('col-12 mb-4', containerClass)}>
<Field
id={name}
value={value}
name={name}
component={component}
type={type}
className={className}
placeholder={t(placeholder)}
subtitle={t(subtitle)}
disabled={processingSetConfig || processingTestUpstream}
normalizeOnBlur={normalizeOnBlur}
onScroll={onScroll}
/>
</div>
);
};
type FormProps = {
initialValues?: Partial<FormData>;
onSubmit: (data: FormData) => void;
interface renderTextareaWithHighlightFieldProps {
className: string;
disabled?: boolean;
id: string;
input?: object;
meta?: object;
normalizeOnBlur?: (...args: unknown[]) => unknown;
onScroll?: (...args: unknown[]) => unknown;
placeholder: string;
type: string;
}
const renderTextareaWithHighlightField = (props: renderTextareaWithHighlightFieldProps) => {
const upstream_dns = useSelector((store: RootState) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
const upstream_dns_file = useSelector((state: RootState) => state.dnsConfig.upstream_dns_file);
const ref = useRef(null);
const onScroll = (e: any) => syncScroll(e, ref);
return (
<>
{renderTextareaField({
...props,
disabled: !!upstream_dns_file,
onScroll,
normalizeOnBlur: trimLinesAndRemoveEmpty,
})}
{getTextareaCommentsHighlight(ref, upstream_dns)}
</>
);
};
const upstreamModeOptions = [
const INPUT_FIELDS = [
{
label: i18next.t('load_balancing'),
desc: <Trans components={{ br: <br />, b: <b /> }}>load_balancing_desc</Trans>,
name: UPSTREAM_MODE_NAME,
type: 'radio',
value: DNS_REQUEST_OPTIONS.LOAD_BALANCING,
component: renderRadioField,
subtitle: 'load_balancing_desc',
placeholder: 'load_balancing',
},
{
label: i18next.t('parallel_requests'),
desc: <Trans components={{ br: <br />, b: <b /> }}>upstream_parallel</Trans>,
name: UPSTREAM_MODE_NAME,
type: 'radio',
value: DNS_REQUEST_OPTIONS.PARALLEL,
component: renderRadioField,
subtitle: 'upstream_parallel',
placeholder: 'parallel_requests',
},
{
label: i18next.t('fastest_addr'),
desc: <Trans components={{ br: <br />, b: <b /> }}>fastest_addr_desc</Trans>,
name: UPSTREAM_MODE_NAME,
type: 'radio',
value: DNS_REQUEST_OPTIONS.FASTEST_ADDR,
component: renderRadioField,
subtitle: 'fastest_addr_desc',
placeholder: 'fastest_addr',
},
];
const Form = ({ initialValues, onSubmit }: FormProps) => {
const { t } = useTranslation();
interface FormProps {
handleSubmit?: (...args: unknown[]) => string;
submitting?: boolean;
invalid?: boolean;
initialValues?: object;
upstream_dns?: string;
fallback_dns?: string;
bootstrap_dns?: string;
}
const Form = ({ submitting, invalid, handleSubmit }: FormProps) => {
const dispatch = useDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const {
control,
handleSubmit,
watch,
formState: { isSubmitting, isDirty },
} = useForm<FormData>({
mode: 'onBlur',
defaultValues: {
upstream_dns: initialValues?.upstream_dns || '',
upstream_mode: initialValues?.upstream_mode || DNS_REQUEST_OPTIONS.LOAD_BALANCING,
fallback_dns: initialValues?.fallback_dns || '',
bootstrap_dns: initialValues?.bootstrap_dns || '',
local_ptr_upstreams: initialValues?.local_ptr_upstreams || '',
use_private_ptr_resolvers: initialValues?.use_private_ptr_resolvers || false,
resolve_clients: initialValues?.resolve_clients || false,
upstream_timeout: initialValues?.upstream_timeout || 0,
},
});
const upstream_dns = useSelector((store: RootState) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
const upstream_dns = watch('upstream_dns');
const processingTestUpstream = useSelector((state: RootState) => state.settings.processingTestUpstream);
const processingSetConfig = useSelector((state: RootState) => state.dnsConfig.processingSetConfig);
const defaultLocalPtrUpstreams = useSelector((state: RootState) => state.dnsConfig.default_local_ptr_upstreams);
const upstream_dns_file = useSelector((state: RootState) => state.dnsConfig.upstream_dns_file);
const handleUpstreamTest = () => {
const formValues = {
bootstrap_dns: watch('bootstrap_dns'),
upstream_dns: watch('upstream_dns'),
local_ptr_upstreams: watch('local_ptr_upstreams'),
fallback_dns: watch('fallback_dns'),
};
dispatch(testUpstreamWithFormValues(formValues));
const handleUpstreamTest = () => dispatch(testUpstreamWithFormValues());
const testButtonClass = classnames('btn btn-primary btn-standard mr-2', {
'btn-loading': processingTestUpstream,
});
const components = {
a: <a href={UPSTREAM_CONFIGURATION_WIKI_LINK} target="_blank" rel="noopener noreferrer" />,
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="form--upstream">
<form onSubmit={handleSubmit} className="form--upstream">
<div className="row">
<label className="col form__label" htmlFor="upstream_dns">
<Trans
components={{
a: <a href={UPSTREAM_CONFIGURATION_WIKI_LINK} target="_blank" rel="noopener noreferrer" />,
}}>
upstream_dns_help
</Trans>{' '}
<label className="col form__label" htmlFor={UPSTREAM_DNS_NAME}>
<Trans components={components}>upstream_dns_help</Trans>{' '}
<Trans
components={[
<a
@@ -121,69 +184,44 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
<div className="col-12 mb-4">
<div className="text-edit-container">
<Controller
name="upstream_dns"
control={control}
render={({ field }) => (
<>
<Textarea
{...field}
id={UPSTREAM_DNS_NAME}
data-testid="upstream_dns"
className="form-control--textarea-large text-input"
wrapperClassName="mb-0"
placeholder={t('upstream_dns')}
disabled={!!upstream_dns_file || processingSetConfig || processingTestUpstream}
onScroll={(e) => syncScroll(e, textareaRef)}
trimOnBlur
/>
{getTextareaCommentsHighlight(textareaRef, upstream_dns)}
</>
)}
<Field
id={UPSTREAM_DNS_NAME}
name={UPSTREAM_DNS_NAME}
component={renderTextareaWithHighlightField}
type="text"
className="form-control form-control--textarea font-monospace text-input"
placeholder={t('upstream_dns')}
disabled={processingSetConfig || processingTestUpstream}
normalizeOnBlur={removeEmptyLines}
/>
</div>
</div>
{INPUT_FIELDS.map(renderField)}
<div className="col-12">
<Examples />
<hr />
</div>
<div className="col-12 mb-4">
<Controller
name="upstream_mode"
control={control}
render={({ field }) => (
<Radio
{...field}
options={upstreamModeOptions}
disabled={processingSetConfig || processingTestUpstream}
/>
)}
/>
<hr />
</div>
<div className="col-12">
<label className="form__label form__label--with-desc" htmlFor="fallback_dns">
{t('fallback_dns_title')}
<Trans>fallback_dns_title</Trans>
</label>
<div className="form__desc form__desc--top">{t('fallback_dns_desc')}</div>
<div className="form__desc form__desc--top">
<Trans>fallback_dns_desc</Trans>
</div>
<Controller
<Field
id="fallback_dns"
name="fallback_dns"
control={control}
render={({ field }) => (
<Textarea
{...field}
id="fallback_dns"
data-testid="fallback_dns"
wrapperClassName="mb-0"
placeholder={t('fallback_dns_placeholder')}
disabled={processingSetConfig}
trimOnBlur
/>
)}
component={renderTextareaField}
type="text"
className="form-control form-control--textarea form-control--textarea-small font-monospace"
placeholder={t('fallback_dns_placeholder')}
disabled={processingSetConfig}
normalizeOnBlur={removeEmptyLines}
/>
</div>
@@ -191,30 +229,24 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
<hr />
</div>
<div className="col-12">
<div className="col-12 mb-2">
<label className="form__label form__label--with-desc" htmlFor="bootstrap_dns">
{t('bootstrap_dns')}
<Trans>bootstrap_dns</Trans>
</label>
<div className="form__desc form__desc--top">{t('bootstrap_dns_desc')}</div>
<div className="form__desc form__desc--top">
<Trans>bootstrap_dns_desc</Trans>
</div>
<Controller
<Field
id="bootstrap_dns"
name="bootstrap_dns"
control={control}
render={({ field }) => (
<Textarea
{...field}
id="bootstrap_dns"
data-testid="bootstrap_dns"
placeholder={t('bootstrap_dns')}
wrapperClassName="mb-0"
disabled={processingSetConfig}
onBlur={(e) => {
const value = removeEmptyLines(e.target.value);
field.onChange(value);
}}
/>
)}
component={renderTextareaField}
type="text"
className="form-control form-control--textarea form-control--textarea-small font-monospace"
placeholder={t('bootstrap_dns')}
disabled={processingSetConfig}
normalizeOnBlur={removeEmptyLines}
/>
</div>
@@ -224,47 +256,43 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
<div className="col-12">
<label className="form__label form__label--with-desc" htmlFor="local_ptr">
{t('local_ptr_title')}
<Trans>local_ptr_title</Trans>
</label>
<div className="form__desc form__desc--top">{t('local_ptr_desc')}</div>
<div className="form__desc form__desc--top">
{defaultLocalPtrUpstreams?.length > 0
? t('local_ptr_default_resolver', {
ip: defaultLocalPtrUpstreams.map((s: any) => `"${s}"`).join(', '),
})
: t('local_ptr_no_default_resolver')}
<Trans>local_ptr_desc</Trans>
</div>
<Controller
name="local_ptr_upstreams"
control={control}
render={({ field }) => (
<Textarea
{...field}
id="local_ptr_upstreams"
data-testid="local_ptr_upstreams"
placeholder={t('local_ptr_placeholder')}
disabled={processingSetConfig}
trimOnBlur
/>
<div className="form__desc form__desc--top">
{/** TODO: Add internazionalization for "" */}
{defaultLocalPtrUpstreams?.length > 0 ? (
<Trans values={{ ip: defaultLocalPtrUpstreams.map((s: any) => `"${s}"`).join(', ') }}>
local_ptr_default_resolver
</Trans>
) : (
<Trans>local_ptr_no_default_resolver</Trans>
)}
</div>
<Field
id="local_ptr_upstreams"
name="local_ptr_upstreams"
component={renderTextareaField}
type="text"
className="form-control form-control--textarea form-control--textarea-small font-monospace"
placeholder={t('local_ptr_placeholder')}
disabled={processingSetConfig}
normalizeOnBlur={removeEmptyLines}
/>
<div className="mt-4">
<Controller
<Field
name="use_private_ptr_resolvers"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="dns_use_private_ptr_resolvers"
title={t('use_private_ptr_resolvers_title')}
subtitle={t('use_private_ptr_resolvers_desc')}
disabled={processingSetConfig}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('use_private_ptr_resolvers_title')}
subtitle={t('use_private_ptr_resolvers_desc')}
disabled={processingSetConfig}
/>
</div>
</div>
@@ -274,79 +302,32 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
</div>
<div className="col-12 mb-4">
<Controller
<Field
name="resolve_clients"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="dns_resolve_clients"
title={t('resolve_clients_title')}
subtitle={t('resolve_clients_desc')}
disabled={processingSetConfig}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('resolve_clients_title')}
subtitle={t('resolve_clients_desc')}
disabled={processingSetConfig}
/>
</div>
<div className="col-12">
<hr />
</div>
<div className="col-12 col-md-7">
<div className="form__group">
<label htmlFor="upstream_timeout" className="form__label form__label--with-desc">
<Trans>upstream_timeout</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>upstream_timeout_desc</Trans>
</div>
<Controller
name="upstream_timeout"
control={control}
rules={{ validate: validateRequiredValue }}
render={({ field }) => (
<Input
{...field}
type="number"
id="upstream_timeout"
data-testid="upstream_timeout"
placeholder={t('form_enter_upstream_timeout')}
disabled={processingSetConfig}
min={1}
max={UINT32_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
/>
</div>
</div>
</div>
<div className="card-actions">
<div className="btn-list">
<button
type="button"
data-testid="dns_upstream_test"
className={clsx('btn btn-primary btn-standard mr-2', {
'btn-loading': processingTestUpstream,
})}
className={testButtonClass}
onClick={handleUpstreamTest}
disabled={!upstream_dns || processingTestUpstream}>
{t('test_upstream_btn')}
<Trans>test_upstream_btn</Trans>
</button>
<button
type="submit"
data-testid="dns_upstream_save"
className="btn btn-success btn-standard"
disabled={isSubmitting || !isDirty || processingSetConfig || processingTestUpstream}>
{t('apply_btn')}
disabled={submitting || invalid || processingSetConfig || processingTestUpstream}>
<Trans>apply_btn</Trans>
</button>
</div>
</div>
@@ -354,4 +335,4 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
);
};
export default Form;
export default reduxForm({ form: FORM_NAME.UPSTREAM })(Form);

View File

@@ -19,7 +19,6 @@ const Upstream = () => {
resolve_clients,
local_ptr_upstreams,
use_private_ptr_resolvers,
upstream_timeout,
} = useSelector((state: RootState) => state.dnsConfig, shallowEqual);
const upstream_dns_file = useSelector((state: RootState) => state.dnsConfig.upstream_dns_file);
@@ -33,7 +32,6 @@ const Upstream = () => {
resolve_clients,
local_ptr_upstreams,
use_private_ptr_resolvers,
upstream_timeout,
} = values;
const dnsConfig = {
@@ -43,7 +41,6 @@ const Upstream = () => {
resolve_clients,
local_ptr_upstreams,
use_private_ptr_resolvers,
upstream_timeout,
...(upstream_dns_file ? null : { upstream_dns }),
};
@@ -67,7 +64,6 @@ const Upstream = () => {
resolve_clients,
local_ptr_upstreams,
use_private_ptr_resolvers,
upstream_timeout,
}}
onSubmit={handleSubmit}
/>

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import { Trans, useTranslation } from 'react-i18next';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { Controller, useForm } from 'react-hook-form';
import i18next from 'i18next';
import { renderInputField, CheckboxField, renderRadioField, toNumber } from '../../../helpers/form';
import {
validateServerName,
validateIsSafePort,
@@ -12,6 +14,7 @@ import {
validatePortTLS,
validatePlainDns,
} from '../../../helpers/validators';
import i18n from '../../../i18n';
import KeyStatus from './KeyStatus';
@@ -19,39 +22,51 @@ import CertificateStatus from './CertificateStatus';
import {
DNS_OVER_QUIC_PORT,
DNS_OVER_TLS_PORT,
FORM_NAME,
STANDARD_HTTPS_PORT,
ENCRYPTION_SOURCE,
} from '../../../helpers/constants';
import { Checkbox } from '../../ui/Controls/Checkbox';
import { Radio } from '../../ui/Controls/Radio';
import { Input } from '../../ui/Controls/Input';
import { Textarea } from '../../ui/Controls/Textarea';
import { EncryptionData } from '../../../initialState';
import { toNumber } from '../../../helpers/form';
const certificateSourceOptions = [
{
label: i18next.t('encryption_certificates_source_path'),
value: ENCRYPTION_SOURCE.PATH,
},
{
label: i18next.t('encryption_certificates_source_content'),
value: ENCRYPTION_SOURCE.CONTENT,
},
];
const validate = (values: any) => {
const errors: { port_dns_over_tls?: string; port_https?: string } = {};
const keySourceOptions = [
{
label: i18next.t('encryption_key_source_path'),
value: ENCRYPTION_SOURCE.PATH,
},
{
label: i18next.t('encryption_key_source_content'),
value: ENCRYPTION_SOURCE.CONTENT,
},
];
if (values.port_dns_over_tls && values.port_https) {
if (values.port_dns_over_tls === values.port_https) {
errors.port_dns_over_tls = i18n.t('form_error_equal');
const validationMessage = (warningValidation: string, isWarning: boolean) => {
errors.port_https = i18n.t('form_error_equal');
}
}
return errors;
};
const clearFields = (change: any, setTlsConfig: any, validateTlsConfig: any, t: any) => {
const fields = {
private_key: '',
certificate_chain: '',
private_key_path: '',
certificate_path: '',
port_https: STANDARD_HTTPS_PORT,
port_dns_over_tls: DNS_OVER_TLS_PORT,
port_dns_over_quic: DNS_OVER_QUIC_PORT,
server_name: '',
force_https: false,
enabled: false,
private_key_saved: false,
serve_plain_dns: true,
};
// eslint-disable-next-line no-alert
if (window.confirm(t('encryption_reset'))) {
Object.keys(fields)
.forEach((field) => change(field, fields[field]));
setTlsConfig(fields);
validateTlsConfig(fields);
}
};
const validationMessage = (warningValidation: any, isWarning: any) => {
if (!warningValidation) {
return null;
}
@@ -73,60 +88,56 @@ const validationMessage = (warningValidation: string, isWarning: boolean) => {
);
};
export type EncryptionFormValues = {
enabled?: boolean;
serve_plain_dns?: boolean;
server_name?: string;
force_https?: boolean;
port_https?: number;
port_dns_over_tls?: number;
port_dns_over_quic?: number;
certificate_chain?: string;
private_key?: string;
certificate_path?: string;
private_key_path?: string;
certificate_source?: string;
key_source?: string;
private_key_saved?: boolean;
};
type Props = {
initialValues: EncryptionFormValues;
encryption: EncryptionData;
onSubmit: (values: EncryptionFormValues) => void;
debouncedConfigValidation: (values: EncryptionFormValues) => void;
setTlsConfig: (values: Partial<EncryptionData>) => void;
validateTlsConfig: (values: Partial<EncryptionData>) => void;
};
const defaultValues = {
enabled: false,
serve_plain_dns: true,
server_name: '',
force_https: false,
port_https: STANDARD_HTTPS_PORT,
port_dns_over_tls: DNS_OVER_TLS_PORT,
port_dns_over_quic: DNS_OVER_QUIC_PORT,
certificate_chain: '',
private_key: '',
certificate_path: '',
private_key_path: '',
certificate_source: ENCRYPTION_SOURCE.PATH,
key_source: ENCRYPTION_SOURCE.PATH,
private_key_saved: false,
};
export const Form = ({
initialValues,
encryption,
onSubmit,
setTlsConfig,
debouncedConfigValidation,
validateTlsConfig,
}: Props) => {
const { t } = useTranslation();
interface FormProps {
handleSubmit: (...args: unknown[]) => string;
handleChange?: (...args: unknown[]) => unknown;
isEnabled: boolean;
servePlainDns: boolean;
certificateChain: string;
privateKey: string;
certificatePath: string;
privateKeyPath: string;
change: (...args: unknown[]) => unknown;
submitting: boolean;
invalid: boolean;
initialValues: object;
processingConfig: boolean;
processingValidate: boolean;
status_key?: string;
not_after?: string;
warning_validation?: string;
valid_chain?: boolean;
valid_key?: boolean;
valid_cert?: boolean;
valid_pair?: boolean;
dns_names?: string[];
key_type?: string;
issuer?: string;
subject?: string;
t: (...args: unknown[]) => string;
setTlsConfig: (...args: unknown[]) => unknown;
validateTlsConfig: (...args: unknown[]) => unknown;
certificateSource?: string;
privateKeySource?: string;
privateKeySaved?: boolean;
}
let Form = (props: FormProps) => {
const {
t,
handleSubmit,
handleChange,
isEnabled,
servePlainDns,
certificateChain,
privateKey,
certificatePath,
privateKeyPath,
change,
invalid,
submitting,
processingConfig,
processingValidate,
not_after,
valid_chain,
valid_key,
@@ -137,100 +148,37 @@ export const Form = ({
issuer,
subject,
warning_validation,
processingConfig,
processingValidate,
} = encryption;
const {
control,
handleSubmit,
watch,
reset,
setValue,
setError,
getValues,
formState: { isSubmitting, isValid },
} = useForm<EncryptionFormValues>({
defaultValues: {
...defaultValues,
...initialValues,
},
mode: 'onBlur',
});
const {
enabled: isEnabled,
serve_plain_dns: servePlainDns,
certificate_chain: certificateChain,
private_key: privateKey,
private_key_path: privateKeyPath,
key_source: privateKeySource,
private_key_saved: privateKeySaved,
certificate_path: certificatePath,
certificate_source: certificateSource,
} = watch();
const handleBlur = () => {
debouncedConfigValidation(getValues());
};
setTlsConfig,
validateTlsConfig,
certificateSource,
privateKeySource,
privateKeySaved,
} = props;
const isSavingDisabled = () => {
const processing = isSubmitting || processingConfig || processingValidate;
const processing = submitting || processingConfig || processingValidate;
if (servePlainDns && !isEnabled) {
return !isValid || processing;
return invalid || processing;
}
return !isValid || processing || !valid_key || !valid_cert || !valid_pair;
};
const clearFields = () => {
if (window.confirm(t('encryption_reset'))) {
reset();
setTlsConfig(defaultValues);
validateTlsConfig(defaultValues);
}
};
const validatePorts = (values: EncryptionFormValues) => {
const errors: { port_dns_over_tls?: string; port_https?: string } = {};
if (values.port_dns_over_tls && values.port_https) {
if (values.port_dns_over_tls === values.port_https) {
errors.port_dns_over_tls = i18next.t('form_error_equal');
errors.port_https = i18next.t('form_error_equal');
}
}
return errors;
};
const onFormSubmit = (data: EncryptionFormValues) => {
const validationErrors = validatePorts(data);
if (Object.keys(validationErrors).length > 0) {
Object.entries(validationErrors).forEach(([field, message]) => {
setError(field as keyof EncryptionFormValues, { type: 'manual', message });
});
} else {
onSubmit(data);
}
return invalid || processing || !valid_key || !valid_cert || !valid_pair;
};
const isDisabled = isSavingDisabled();
const isWarning = valid_key && valid_cert && valid_pair;
return (
<form onSubmit={handleSubmit(onFormSubmit)}>
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings mb-3">
<Controller
<Field
name="enabled"
control={control}
render={({ field }) => (
<Checkbox {...field} title={t('encryption_enable')} onBlur={handleBlur} />
)}
type="checkbox"
component={CheckboxField}
placeholder={t('encryption_enable')}
onChange={handleChange}
/>
</div>
@@ -239,13 +187,13 @@ export const Form = ({
</div>
<div className="form__group mb-3 mt-5">
<Controller
<Field
name="serve_plain_dns"
control={control}
rules={{
validate: (value) => validatePlainDns(value, getValues()),
}}
render={({ field }) => <Checkbox {...field} title={t('encryption_plain_dns_enable')} />}
type="checkbox"
component={CheckboxField}
placeholder={t('encryption_plain_dns_enable')}
onChange={handleChange}
validate={validatePlainDns}
/>
</div>
@@ -264,20 +212,16 @@ export const Form = ({
<div className="col-lg-6">
<div className="form__group form__group--settings">
<Controller
<Field
id="server_name"
name="server_name"
control={control}
rules={{ validate: validateServerName }}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('encryption_server_enter')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('encryption_server_enter')}
onChange={handleChange}
disabled={!isEnabled}
validate={validateServerName}
/>
<div className="form__desc">
@@ -288,12 +232,13 @@ export const Form = ({
<div className="col-lg-6">
<div className="form__group form__group--settings">
<Controller
<Field
name="force_https"
control={control}
render={({ field }) => (
<Checkbox {...field} title={t('encryption_redirect')} disabled={!isEnabled} />
)}
type="checkbox"
component={CheckboxField}
placeholder={t('encryption_redirect')}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
@@ -310,24 +255,17 @@ export const Form = ({
<Trans>encryption_https</Trans>
</label>
<Controller
<Field
id="port_https"
name="port_https"
control={control}
rules={{ validate: { validatePort, validateIsSafePort } }}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
placeholder={t('encryption_https')}
error={fieldState.error?.message}
disabled={!isEnabled}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="number"
className="form-control"
placeholder={t('encryption_https')}
validate={[validatePort, validateIsSafePort]}
normalize={toNumber}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
@@ -342,24 +280,17 @@ export const Form = ({
<Trans>encryption_dot</Trans>
</label>
<Controller
<Field
id="port_dns_over_tls"
name="port_dns_over_tls"
control={control}
rules={{ validate: validatePortTLS }}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
placeholder={t('encryption_dot')}
error={fieldState.error?.message}
disabled={!isEnabled}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="number"
className="form-control"
placeholder={t('encryption_dot')}
validate={[validatePortTLS]}
normalize={toNumber}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
@@ -374,24 +305,17 @@ export const Form = ({
<Trans>encryption_doq</Trans>
</label>
<Controller
<Field
id="port_dns_over_quic"
name="port_dns_over_quic"
control={control}
rules={{ validate: validatePortQuic }}
render={({ field, fieldState }) => (
<Input
{...field}
type="number"
placeholder={t('encryption_doq')}
error={fieldState.error?.message}
disabled={!isEnabled}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="number"
className="form-control"
placeholder={t('encryption_doq')}
validate={[validatePortQuic]}
normalize={toNumber}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
@@ -428,44 +352,50 @@ export const Form = ({
<div className="form__inline mb-2">
<div className="custom-controls-stacked">
<Controller
<Field
name="certificate_source"
control={control}
render={({ field }) => (
<Radio {...field} options={certificateSourceOptions} disabled={!isEnabled} />
)}
component={renderRadioField}
type="radio"
className="form-control mr-2"
value="path"
placeholder={t('encryption_certificates_source_path')}
disabled={!isEnabled}
/>
<Field
name="certificate_source"
component={renderRadioField}
type="radio"
className="form-control mr-2"
value="content"
placeholder={t('encryption_certificates_source_content')}
disabled={!isEnabled}
/>
</div>
</div>
{certificateSource === ENCRYPTION_SOURCE.CONTENT ? (
<Controller
{certificateSource === ENCRYPTION_SOURCE.CONTENT && (
<Field
id="certificate_chain"
name="certificate_chain"
control={control}
render={({ field, fieldState }) => (
<Textarea
{...field}
placeholder={t('encryption_certificates_input')}
disabled={!isEnabled}
error={fieldState.error?.message}
onBlur={handleBlur}
/>
)}
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder={t('encryption_certificates_input')}
onChange={handleChange}
disabled={!isEnabled}
/>
) : (
<Controller
)}
{certificateSource === ENCRYPTION_SOURCE.PATH && (
<Field
id="certificate_path"
name="certificate_path"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('encryption_certificate_path')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('encryption_certificate_path')}
onChange={handleChange}
disabled={!isEnabled}
/>
)}
</div>
@@ -494,67 +424,70 @@ export const Form = ({
<div className="form__inline mb-2">
<div className="custom-controls-stacked">
<Controller
<Field
name="key_source"
control={control}
render={({ field }) => (
<Radio {...field} options={keySourceOptions} disabled={!isEnabled} />
)}
component={renderRadioField}
type="radio"
className="form-control mr-2"
value={ENCRYPTION_SOURCE.PATH}
placeholder={t('encryption_key_source_path')}
disabled={!isEnabled}
/>
<Field
name="key_source"
component={renderRadioField}
type="radio"
className="form-control mr-2"
value={ENCRYPTION_SOURCE.CONTENT}
placeholder={t('encryption_key_source_content')}
disabled={!isEnabled}
/>
</div>
</div>
{privateKeySource === ENCRYPTION_SOURCE.CONTENT ? (
<>
<Controller
name="private_key_saved"
control={control}
render={({ field }) => (
<Checkbox
{...field}
title={t('use_saved_key')}
disabled={!isEnabled}
onChange={(checked: boolean) => {
if (checked) {
setValue('private_key', '');
}
field.onChange(checked);
}}
onBlur={handleBlur}
/>
)}
/>
<Controller
name="private_key"
control={control}
render={({ field, fieldState }) => (
<Textarea
{...field}
placeholder={t('encryption_key_input')}
disabled={!isEnabled || privateKeySaved}
error={fieldState.error?.message}
onBlur={handleBlur}
/>
)}
/>
</>
) : (
<Controller
{privateKeySource === ENCRYPTION_SOURCE.PATH && (
<Field
name="private_key_path"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
type="text"
placeholder={t('encryption_private_key_path')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
component={renderInputField}
type="text"
className="form-control"
placeholder={t('encryption_private_key_path')}
onChange={handleChange}
disabled={!isEnabled}
/>
)}
{privateKeySource === ENCRYPTION_SOURCE.CONTENT && [
<Field
key="private_key_saved"
name="private_key_saved"
type="checkbox"
className="form__group form__group--settings mb-2"
component={CheckboxField}
disabled={!isEnabled}
placeholder={t('use_saved_key')}
onChange={(event: any) => {
if (event.target.checked) {
change('private_key', '');
}
if (handleChange) {
handleChange(event);
}
}}
/>,
<Field
id="private_key"
key="private_key"
name="private_key"
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder={t('encryption_key_input')}
onChange={handleChange}
disabled={!isEnabled || privateKeySaved}
/>,
]}
</div>
<div className="form__status">
@@ -572,11 +505,44 @@ export const Form = ({
<button
type="button"
className="btn btn-secondary btn-standart"
disabled={isSubmitting || processingConfig}
onClick={clearFields}>
disabled={submitting || processingConfig}
onClick={() => clearFields(change, setTlsConfig, validateTlsConfig, t)}>
<Trans>reset_settings</Trans>
</button>
</div>
</form>
);
};
const selector = formValueSelector(FORM_NAME.ENCRYPTION);
Form = connect((state) => {
const isEnabled = selector(state, 'enabled');
const servePlainDns = selector(state, 'serve_plain_dns');
const certificateChain = selector(state, 'certificate_chain');
const privateKey = selector(state, 'private_key');
const certificatePath = selector(state, 'certificate_path');
const privateKeyPath = selector(state, 'private_key_path');
const certificateSource = selector(state, 'certificate_source');
const privateKeySource = selector(state, 'key_source');
const privateKeySaved = selector(state, 'private_key_saved');
return {
isEnabled,
servePlainDns,
certificateChain,
privateKey,
certificatePath,
privateKeyPath,
certificateSource,
privateKeySource,
privateKeySaved,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.ENCRYPTION,
validate,
}),
])(Form);

View File

@@ -1,60 +1,61 @@
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { debounce } from 'lodash';
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import debounce from 'lodash/debounce';
import { DEBOUNCE_TIMEOUT, ENCRYPTION_SOURCE } from '../../../helpers/constants';
import { EncryptionFormValues, Form } from './Form';
import Form from './Form';
import Card from '../../ui/Card';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import { EncryptionData } from '../../../initialState';
type Props = {
interface EncryptionProps {
setTlsConfig: (...args: unknown[]) => unknown;
validateTlsConfig: (...args: unknown[]) => unknown;
encryption: EncryptionData;
setTlsConfig: (values: Partial<EncryptionData>) => void;
validateTlsConfig: (values: Partial<EncryptionData>) => void;
};
t: (...args: unknown[]) => string;
}
export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Props) => {
const { t } = useTranslation();
class Encryption extends Component<EncryptionProps> {
componentDidMount() {
const { validateTlsConfig, encryption } = this.props;
const initialValues = useMemo((): EncryptionFormValues => {
const {
enabled,
serve_plain_dns,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
} = encryption;
if (encryption.enabled) {
validateTlsConfig(encryption);
}
}
handleFormSubmit = (values: any) => {
const submitValues = this.getSubmitValues(values);
this.props.setTlsConfig(submitValues);
};
handleFormChange = debounce((values) => {
const submitValues = this.getSubmitValues(values);
if (submitValues.enabled) {
this.props.validateTlsConfig(submitValues);
}
}, DEBOUNCE_TIMEOUT);
getInitialValues = (data: any) => {
const { certificate_chain, private_key, private_key_saved } = data;
const certificate_source = certificate_chain ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
const key_source = private_key || private_key_saved ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
return {
enabled,
serve_plain_dns,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
...data,
certificate_source,
key_source,
};
}, [encryption]);
};
const getSubmitValues = useCallback((values: any) => {
getSubmitValues = (values: any) => {
const { certificate_source, key_source, private_key_saved, ...config } = values;
if (certificate_source === ENCRYPTION_SOURCE.PATH) {
@@ -75,47 +76,63 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
}
return config;
}, []);
};
const handleFormSubmit = useCallback(
(values: any) => {
const submitValues = getSubmitValues(values);
setTlsConfig(submitValues);
},
[getSubmitValues, setTlsConfig],
);
render() {
const { encryption, t } = this.props;
const {
enabled,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
serve_plain_dns,
} = encryption;
const validateConfig = useCallback((values) => {
const submitValues = getSubmitValues(values);
const initialValues = this.getInitialValues({
enabled,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
serve_plain_dns,
});
if (submitValues.enabled) {
validateTlsConfig(submitValues);
}
}, []);
return (
<div className="encryption">
<PageTitle title={t('encryption_settings')} />
const debouncedConfigValidation = useMemo(() => debounce(validateConfig, DEBOUNCE_TIMEOUT), [validateConfig]);
{encryption.processing && <Loading />}
{!encryption.processing && (
<Card
title={t('encryption_title')}
subtitle={t('encryption_desc')}
bodyType="card-body box-body--settings">
<Form
initialValues={initialValues}
onSubmit={this.handleFormSubmit}
onChange={this.handleFormChange}
setTlsConfig={this.props.setTlsConfig}
validateTlsConfig={this.props.validateTlsConfig}
{...this.props.encryption}
/>
</Card>
)}
</div>
);
}
}
return (
<div className="encryption">
<PageTitle title={t('encryption_settings')} />
{encryption.processing ? (
<Loading />
) : (
<Card
title={t('encryption_title')}
subtitle={t('encryption_desc')}
bodyType="card-body box-body--settings">
<Form
initialValues={initialValues}
onSubmit={handleFormSubmit}
debouncedConfigValidation={debouncedConfigValidation}
setTlsConfig={setTlsConfig}
validateTlsConfig={validateTlsConfig}
encryption={encryption}
/>
</Card>
)}
</div>
);
};
export default withTranslation()(Encryption);

View File

@@ -0,0 +1,85 @@
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { CheckboxField, toNumber } from '../../../helpers/form';
import { FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK, FORM_NAME } from '../../../helpers/constants';
const getTitleForInterval = (interval: any, t: any) => {
if (interval === 0) {
return t('disabled');
}
if (interval === 72 || interval === 168) {
return t('interval_days', { count: interval / 24 });
}
return t('interval_hours', { count: interval });
};
const getIntervalSelect = (processing: any, t: any, handleChange: any, toNumber: any) => (
<Field
name="interval"
className="custom-select"
component="select"
onChange={handleChange}
normalize={toNumber}
disabled={processing}>
{FILTERS_INTERVALS_HOURS.map((interval) => (
<option value={interval} key={interval}>
{getTitleForInterval(interval, t)}
</option>
))}
</Field>
);
interface FormProps {
handleSubmit: (...args: unknown[]) => string;
handleChange?: (...args: unknown[]) => unknown;
change: (...args: unknown[]) => unknown;
submitting: boolean;
invalid: boolean;
processing: boolean;
t: (...args: unknown[]) => string;
}
const Form = (props: FormProps) => {
const { handleSubmit, handleChange, processing, t } = props;
const components = {
a: <a href={FILTERS_RELATIVE_LINK} rel="noopener noreferrer" />,
};
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="enabled"
type="checkbox"
modifier="checkbox--settings"
component={CheckboxField}
placeholder={t('block_domain_use_filters_and_hosts')}
subtitle={<Trans components={components}>filters_block_toggle_hint</Trans>}
onChange={handleChange}
disabled={processing}
/>
</div>
</div>
<div className="col-12 col-md-5">
<div className="form__group form__group--inner mb-5">
<label className="form__label">
<Trans>filters_interval</Trans>
</label>
{getIntervalSelect(processing, t, handleChange, toNumber)}
</div>
</div>
</div>
</form>
);
};
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.FILTER_CONFIG })])(Form);

View File

@@ -1,115 +1,39 @@
import React, { useEffect, useRef } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import React from 'react';
import { withTranslation } from 'react-i18next';
import debounce from 'lodash/debounce';
import i18next from 'i18next';
import { toNumber } from '../../../helpers/form';
import { DAY, FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants';
import { Checkbox } from '../../ui/Controls/Checkbox';
import { Select } from '../../ui/Controls/Select';
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
const THREE_DAYS_INTERVAL = DAY * 3;
const SEVEN_DAYS_INTERVAL = DAY * 7;
import Form from './Form';
const getTitleForInterval = (interval: number) => {
if (interval === 0) {
return i18next.t('disabled');
}
import { getObjDiff } from '../../../helpers/helpers';
if (interval === THREE_DAYS_INTERVAL || interval === SEVEN_DAYS_INTERVAL) {
return i18next.t('interval_days', { count: interval / DAY });
}
return i18next.t('interval_hours', { count: interval });
};
export type FormValues = {
enabled: boolean;
interval: number;
};
type Props = {
initialValues: FormValues;
setFiltersConfig: (values: FormValues) => void;
interface FiltersConfigProps {
initialValues: object;
processing: boolean;
};
setFiltersConfig: (...args: unknown[]) => unknown;
t: (...args: unknown[]) => string;
}
export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: Props) => {
const { t } = useTranslation();
const prevFormValuesRef = useRef<FormValues>(initialValues);
const FiltersConfig = (props: FiltersConfigProps) => {
const { initialValues, processing } = props;
const { watch, control } = useForm({
mode: 'onBlur',
defaultValues: initialValues,
});
const handleFormChange = debounce((values) => {
const diff = getObjDiff(initialValues, values);
const formValues = watch();
useEffect(() => {
const prevFormValues = prevFormValuesRef.current;
if (JSON.stringify(prevFormValues) !== JSON.stringify(formValues)) {
setFiltersConfig(formValues);
prevFormValuesRef.current = formValues;
if (Object.values(diff).length > 0) {
props.setFiltersConfig(values);
}
}, [formValues]);
const components = {
a: <a href={FILTERS_RELATIVE_LINK} rel="noopener noreferrer" />,
};
}, DEBOUNCE_TIMEOUT);
return (
<>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<Controller
name="enabled"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="filters_enabled"
title={t('block_domain_use_filters_and_hosts')}
disabled={processing}
/>
)}
/>
<p>
<Trans components={components}>filters_block_toggle_hint</Trans>
</p>
</div>
</div>
<div className="col-12 col-md-5">
<div className="form__group form__group--inner mb-5">
<label className="form__label">
<Trans>filters_interval</Trans>
</label>
<Controller
name="interval"
control={control}
render={({ field }) => (
<Select
{...field}
data-testid="filters_interval"
disabled={processing}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}>
{FILTERS_INTERVALS_HOURS.map((interval) => (
<option value={interval} key={interval}>
{getTitleForInterval(interval)}
</option>
))}
</Select>
)}
/>
</div>
</div>
</div>
</>
<Form
initialValues={initialValues}
onSubmit={handleFormChange}
onChange={handleFormChange}
processing={processing}
/>
);
};
export default withTranslation()(FiltersConfig);

View File

@@ -1,182 +1,147 @@
import React, { useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next';
import { Controller, useForm } from 'react-hook-form';
import { change, Field, formValueSelector, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import {
CheckboxField,
toFloatNumber,
renderTextareaField,
renderInputField,
renderRadioField,
} from '../../../helpers/form';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import { QUERY_LOG_INTERVALS_DAYS, HOUR, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
import {
FORM_NAME,
QUERY_LOG_INTERVALS_DAYS,
HOUR,
DAY,
RETENTION_CUSTOM,
RETENTION_CUSTOM_INPUT,
RETENTION_RANGE,
CUSTOM_INTERVAL,
} from '../../../helpers/constants';
import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { Textarea } from '../../ui/Controls/Textarea';
const getIntervalTitle = (interval: number) => {
const getIntervalTitle = (interval: any, t: any) => {
switch (interval) {
case RETENTION_CUSTOM:
return i18next.t('settings_custom');
return t('settings_custom');
case 6 * HOUR:
return i18next.t('interval_6_hour');
return t('interval_6_hour');
case DAY:
return i18next.t('interval_24_hour');
return t('interval_24_hour');
default:
return i18next.t('interval_days', { count: interval / DAY });
return t('interval_days', { count: interval / DAY });
}
};
export type FormValues = {
enabled: boolean;
anonymize_client_ip: boolean;
interval: number;
customInterval?: number | null;
ignored: string;
};
const getIntervalFields = (processing: any, t: any, toNumber: any) =>
QUERY_LOG_INTERVALS_DAYS.map((interval) => (
<Field
key={interval}
name="interval"
type="radio"
component={renderRadioField}
value={interval}
placeholder={getIntervalTitle(interval, t)}
normalize={toNumber}
disabled={processing}
/>
));
type Props = {
initialValues: Partial<FormValues>;
interface FormProps {
handleSubmit: (...args: unknown[]) => string;
handleClear: (...args: unknown[]) => unknown;
submitting: boolean;
invalid: boolean;
processing: boolean;
processingReset: boolean;
onSubmit: (values: FormValues) => void;
onReset: () => void;
};
export const Form = ({ initialValues, processing, processingReset, onSubmit, onReset }: Props) => {
const { t } = useTranslation();
processingClear: boolean;
t: (...args: unknown[]) => string;
interval?: number;
customInterval?: number;
dispatch: (...args: unknown[]) => unknown;
}
let Form = (props: FormProps) => {
const {
handleSubmit,
watch,
setValue,
control,
formState: { isSubmitting },
} = useForm<FormValues>({
mode: 'onBlur',
defaultValues: {
enabled: initialValues.enabled || false,
anonymize_client_ip: initialValues.anonymize_client_ip || false,
interval: initialValues.interval || DAY,
customInterval: initialValues.customInterval || null,
ignored: initialValues.ignored || '',
},
});
const intervalValue = watch('interval');
const customIntervalValue = watch('customInterval');
submitting,
invalid,
processing,
processingClear,
handleClear,
t,
interval,
customInterval,
dispatch,
} = props;
useEffect(() => {
if (QUERY_LOG_INTERVALS_DAYS.includes(intervalValue)) {
setValue('customInterval', null);
if (QUERY_LOG_INTERVALS_DAYS.includes(interval)) {
dispatch(change(FORM_NAME.LOG_CONFIG, CUSTOM_INTERVAL, null));
}
}, [intervalValue]);
const onSubmitForm = (data: FormValues) => {
onSubmit(data);
};
const handleIgnoredBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
const trimmed = trimLinesAndRemoveEmpty(e.target.value);
setValue('ignored', trimmed);
};
const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
}, [interval]);
return (
<form onSubmit={handleSubmit(onSubmitForm)}>
<form onSubmit={handleSubmit}>
<div className="form__group form__group--settings">
<Controller
<Field
name="enabled"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="logs_enabled"
title={t('query_log_enable')}
disabled={processing}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('query_log_enable')}
disabled={processing}
/>
</div>
<div className="form__group form__group--settings">
<Controller
<Field
name="anonymize_client_ip"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="logs_anonymize_client_ip"
title={t('anonymize_client_ip')}
subtitle={t('anonymize_client_ip_desc')}
disabled={processing}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('anonymize_client_ip')}
subtitle={t('anonymize_client_ip_desc')}
disabled={processing}
/>
</div>
<div className="form__label">
<label className="form__label">
<Trans>query_log_retention</Trans>
</div>
</label>
<div className="form__group form__group--settings">
<div className="custom-controls-stacked">
<label className="custom-control custom-radio">
<input
type="radio"
data-testid="logs_config_interval"
className="custom-control-input"
disabled={processing}
checked={!QUERY_LOG_INTERVALS_DAYS.includes(intervalValue)}
value={RETENTION_CUSTOM}
onChange={(e) => {
setValue('interval', parseInt(e.target.value, 10));
}}
/>
<span className="custom-control-label">{getIntervalTitle(RETENTION_CUSTOM)}</span>
</label>
{!QUERY_LOG_INTERVALS_DAYS.includes(intervalValue) && (
<Field
key={RETENTION_CUSTOM}
name="interval"
type="radio"
component={renderRadioField}
value={QUERY_LOG_INTERVALS_DAYS.includes(interval) ? RETENTION_CUSTOM : interval}
placeholder={getIntervalTitle(RETENTION_CUSTOM, t)}
normalize={toFloatNumber}
disabled={processing}
/>
{!QUERY_LOG_INTERVALS_DAYS.includes(interval) && (
<div className="form__group--input">
<div className="form__desc form__desc--top">{t('custom_rotation_input')}</div>
<Controller
name="customInterval"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="logs_config_custom_interval"
disabled={processing}
error={fieldState.error?.message}
min={RETENTION_RANGE.MIN}
max={RETENTION_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
<Field
key={RETENTION_CUSTOM_INPUT}
name={CUSTOM_INTERVAL}
type="number"
className="form-control"
component={renderInputField}
disabled={processing}
normalize={toFloatNumber}
min={RETENTION_RANGE.MIN}
max={RETENTION_RANGE.MAX}
/>
</div>
)}
{QUERY_LOG_INTERVALS_DAYS.map((interval) => (
<label key={interval} className="custom-control custom-radio">
<input
type="radio"
className="custom-control-input"
data-testid={`logs_config_${interval}`}
disabled={processing}
value={interval}
checked={intervalValue === interval}
onChange={(e) => {
setValue('interval', parseInt(e.target.value, 10));
}}
/>
<span className="custom-control-label">{getIntervalTitle(interval)}</span>
</label>
))}
{getIntervalFields(processing, t, toFloatNumber)}
</div>
</div>
@@ -189,41 +154,51 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
</div>
<div className="form__group form__group--settings">
<Controller
<Field
name="ignored"
control={control}
render={({ field, fieldState }) => (
<Textarea
{...field}
data-testid="logs_config_ingored"
placeholder={t('ignore_domains')}
className="text-input"
disabled={processing}
error={fieldState.error?.message}
onBlur={handleIgnoredBlur}
/>
)}
type="textarea"
className="form-control form-control--textarea font-monospace text-input"
component={renderTextareaField}
placeholder={t('ignore_domains')}
disabled={processing}
normalizeOnBlur={trimLinesAndRemoveEmpty}
/>
</div>
<div className="mt-5">
<button
type="submit"
data-testid="logs_config_save"
className="btn btn-success btn-standard btn-large"
disabled={disableSubmit}>
disabled={
submitting ||
invalid ||
processing ||
(!QUERY_LOG_INTERVALS_DAYS.includes(interval) && !customInterval)
}>
<Trans>save_btn</Trans>
</button>
<button
type="button"
data-testid="logs_config_clear"
className="btn btn-outline-secondary btn-standard form__button"
onClick={onReset}
disabled={processingReset}>
onClick={() => handleClear()}
disabled={processingClear}>
<Trans>query_log_clear</Trans>
</button>
</div>
</form>
);
};
const selector = formValueSelector(FORM_NAME.LOG_CONFIG);
Form = connect((state) => {
const interval = selector(state, 'interval');
const customInterval = selector(state, CUSTOM_INTERVAL);
return {
interval,
customInterval,
};
})(Form);
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.LOG_CONFIG })])(Form);

View File

@@ -3,7 +3,7 @@ import { withTranslation } from 'react-i18next';
import Card from '../../ui/Card';
import { Form, FormValues } from './Form';
import Form from './Form';
import { HOUR } from '../../../helpers/constants';
interface LogsConfigProps {
@@ -20,7 +20,7 @@ interface LogsConfigProps {
}
class LogsConfig extends Component<LogsConfigProps> {
handleFormSubmit = (values: FormValues) => {
handleFormSubmit = (values: any) => {
const { t, interval: prevInterval } = this.props;
const { interval, customInterval, ...rest } = values;
@@ -53,12 +53,19 @@ class LogsConfig extends Component<LogsConfigProps> {
render() {
const {
t,
enabled,
interval,
processing,
processingClear,
anonymize_client_ip,
ignored,
customInterval,
} = this.props;
@@ -73,10 +80,10 @@ class LogsConfig extends Component<LogsConfigProps> {
anonymize_client_ip,
ignored: ignored?.join('\n'),
}}
processing={processing}
processingReset={processingClear}
onSubmit={this.handleFormSubmit}
onReset={this.handleClear}
processing={processing}
processingClear={processingClear}
handleClear={this.handleClear}
/>
</div>
</Card>

View File

@@ -63,7 +63,6 @@
}
.form__message {
margin-top: 4px;
font-size: 11px;
}
@@ -98,10 +97,6 @@
margin: 0 0 8px;
}
.form__label {
margin-bottom: 8px;
}
.form__label--bold {
font-weight: 700;
}

View File

@@ -1,101 +1,90 @@
import React, { useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import i18next from 'i18next';
import { change, Field, formValueSelector, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { connect } from 'react-redux';
import { Controller, useForm } from 'react-hook-form';
import { STATS_INTERVALS_DAYS, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
import {
renderRadioField,
toNumber,
CheckboxField,
renderTextareaField,
toFloatNumber,
renderInputField,
} from '../../../helpers/form';
import {
FORM_NAME,
STATS_INTERVALS_DAYS,
DAY,
RETENTION_CUSTOM,
RETENTION_CUSTOM_INPUT,
CUSTOM_INTERVAL,
RETENTION_RANGE,
} from '../../../helpers/constants';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
import '../FormButton.css';
import { Checkbox } from '../../ui/Controls/Checkbox';
import { Input } from '../../ui/Controls/Input';
import { toNumber } from '../../../helpers/form';
import { Textarea } from '../../ui/Controls/Textarea';
const getIntervalTitle = (interval: any) => {
switch (interval) {
const getIntervalTitle = (intervalMs: any, t: any) => {
switch (intervalMs) {
case RETENTION_CUSTOM:
return i18next.t('settings_custom');
return t('settings_custom');
case DAY:
return i18next.t('interval_24_hour');
return t('interval_24_hour');
default:
return i18next.t('interval_days', { count: interval / DAY });
return t('interval_days', { count: intervalMs / DAY });
}
};
export type FormValues = {
enabled: boolean;
interval: number;
customInterval?: number | null;
ignored: string;
};
const defaultFormValues = {
enabled: false,
interval: DAY,
customInterval: null,
ignored: '',
};
type Props = {
initialValues: FormValues;
interface FormProps {
handleSubmit: (...args: unknown[]) => string;
handleReset: (...args: unknown[]) => string;
change: (...args: unknown[]) => unknown;
submitting: boolean;
invalid: boolean;
processing: boolean;
processingReset: boolean;
onSubmit: (values: FormValues) => void;
onReset: () => void;
};
export const Form = ({ initialValues, processing, processingReset, onSubmit, onReset }: Props) => {
const { t } = useTranslation();
t: (...args: unknown[]) => string;
interval?: number;
customInterval?: number;
dispatch: (...args: unknown[]) => unknown;
}
let Form = (props: FormProps) => {
const {
handleSubmit,
watch,
setValue,
control,
formState: { isSubmitting },
} = useForm<FormValues>({
mode: 'onBlur',
defaultValues: {
...defaultFormValues,
...initialValues,
},
});
const intervalValue = watch('interval');
const customIntervalValue = watch('customInterval');
processing,
submitting,
invalid,
handleReset,
processingReset,
t,
interval,
customInterval,
dispatch,
} = props;
useEffect(() => {
if (STATS_INTERVALS_DAYS.includes(intervalValue)) {
setValue('customInterval', null);
if (STATS_INTERVALS_DAYS.includes(interval)) {
dispatch(change(FORM_NAME.STATS_CONFIG, CUSTOM_INTERVAL, null));
}
}, [intervalValue]);
const onSubmitForm = (data: FormValues) => {
onSubmit(data);
};
const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
}, [interval]);
return (
<form onSubmit={handleSubmit(onSubmitForm)}>
<form onSubmit={handleSubmit}>
<div className="form__group form__group--settings">
<Controller
<Field
name="enabled"
control={control}
render={({ field }) => (
<Checkbox
{...field}
data-testid="stats_config_enabled"
title={t('statistics_enable')}
disabled={processing}
/>
)}
type="checkbox"
component={CheckboxField}
placeholder={t('statistics_enable')}
disabled={processing}
/>
</div>
<div className="form__label form__label--with-desc">
<label className="form__label form__label--with-desc">
<Trans>statistics_retention</Trans>
</div>
</label>
<div className="form__desc form__desc--top">
<Trans>statistics_retention_desc</Trans>
@@ -103,105 +92,85 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
<div className="form__group form__group--settings mt-2">
<div className="custom-controls-stacked">
<label className="custom-control custom-radio">
<input
type="radio"
data-testid="stats_config_interval"
className="custom-control-input"
disabled={processing}
checked={!STATS_INTERVALS_DAYS.includes(intervalValue)}
value={RETENTION_CUSTOM}
onChange={(e) => {
setValue('interval', parseInt(e.target.value, 10));
}}
/>
<span className="custom-control-label">{getIntervalTitle(RETENTION_CUSTOM)}</span>
</label>
{!STATS_INTERVALS_DAYS.includes(intervalValue) && (
<Field
key={RETENTION_CUSTOM}
name="interval"
type="radio"
component={renderRadioField}
value={STATS_INTERVALS_DAYS.includes(interval) ? RETENTION_CUSTOM : interval}
placeholder={getIntervalTitle(RETENTION_CUSTOM, t)}
normalize={toFloatNumber}
disabled={processing}
/>
{!STATS_INTERVALS_DAYS.includes(interval) && (
<div className="form__group--input">
<div className="form__desc form__desc--top">{i18next.t('custom_retention_input')}</div>
<div className="form__desc form__desc--top">{t('custom_retention_input')}</div>
<Controller
name="customInterval"
control={control}
render={({ field, fieldState }) => (
<Input
{...field}
data-testid="stats_config_custom_interval"
disabled={processing}
error={fieldState.error?.message}
min={RETENTION_RANGE.MIN}
max={RETENTION_RANGE.MAX}
onChange={(e) => {
const { value } = e.target;
field.onChange(toNumber(value));
}}
/>
)}
<Field
key={RETENTION_CUSTOM_INPUT}
name={CUSTOM_INTERVAL}
type="number"
className="form-control"
component={renderInputField}
disabled={processing}
normalize={toFloatNumber}
min={RETENTION_RANGE.MIN}
max={RETENTION_RANGE.MAX}
/>
</div>
)}
{STATS_INTERVALS_DAYS.map((interval) => (
<label key={interval} className="custom-control custom-radio">
<input
type="radio"
className="custom-control-input"
disabled={processing}
value={interval}
checked={intervalValue === interval}
onChange={(e) => {
setValue('interval', parseInt(e.target.value, 10));
}}
/>
<span className="custom-control-label">{getIntervalTitle(interval)}</span>
</label>
<Field
key={interval}
name="interval"
type="radio"
component={renderRadioField}
value={interval}
placeholder={getIntervalTitle(interval, t)}
normalize={toNumber}
disabled={processing}
/>
))}
</div>
</div>
<div className="form__label form__label--with-desc">
<label className="form__label form__label--with-desc">
<Trans>ignore_domains_title</Trans>
</div>
</label>
<div className="form__desc form__desc--top">
<Trans>ignore_domains_desc_stats</Trans>
</div>
<div className="form__group form__group--settings">
<Controller
<Field
name="ignored"
control={control}
render={({ field, fieldState }) => (
<Textarea
{...field}
data-testid="stats_config_ignored"
placeholder={t('ignore_domains')}
className="text-input"
disabled={processing}
error={fieldState.error?.message}
trimOnBlur
/>
)}
type="textarea"
className="form-control form-control--textarea font-monospace text-input"
component={renderTextareaField}
placeholder={t('ignore_domains')}
disabled={processing}
normalizeOnBlur={trimLinesAndRemoveEmpty}
/>
</div>
<div className="mt-5">
<button
type="submit"
data-testid="stats_config_save"
className="btn btn-success btn-standard btn-large"
disabled={disableSubmit}>
disabled={
submitting ||
invalid ||
processing ||
(!STATS_INTERVALS_DAYS.includes(interval) && !customInterval)
}>
<Trans>save_btn</Trans>
</button>
<button
type="button"
data-testid="stats_config_clear"
className="btn btn-outline-secondary btn-standard form__button"
onClick={onReset}
onClick={() => handleReset()}
disabled={processingReset}>
<Trans>statistics_clear</Trans>
</button>
@@ -209,3 +178,16 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
</form>
);
};
const selector = formValueSelector(FORM_NAME.STATS_CONFIG);
Form = connect((state) => {
const interval = selector(state, 'interval');
const customInterval = selector(state, CUSTOM_INTERVAL);
return {
interval,
customInterval,
};
})(Form);
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.STATS_CONFIG })])(Form);

View File

@@ -3,7 +3,7 @@ import { withTranslation } from 'react-i18next';
import Card from '../../ui/Card';
import { Form, FormValues } from './Form';
import Form from './Form';
import { HOUR } from '../../../helpers/constants';
interface StatsConfigProps {
@@ -19,7 +19,7 @@ interface StatsConfigProps {
}
class StatsConfig extends Component<StatsConfigProps> {
handleFormSubmit = ({ enabled, interval, ignored, customInterval }: FormValues) => {
handleFormSubmit = ({ enabled, interval, ignored, customInterval }: any) => {
const { t, interval: prevInterval } = this.props;
const newInterval = customInterval ? customInterval * HOUR : interval;
@@ -49,11 +49,17 @@ class StatsConfig extends Component<StatsConfigProps> {
render() {
const {
t,
interval,
customInterval,
processing,
processingReset,
ignored,
enabled,
} = this.props;
@@ -67,10 +73,10 @@ class StatsConfig extends Component<StatsConfigProps> {
enabled,
ignored: ignored.join('\n'),
}}
onSubmit={this.handleFormSubmit}
processing={processing}
processingReset={processingReset}
onSubmit={this.handleFormSubmit}
onReset={this.handleReset}
handleReset={this.handleReset}
/>
</div>
</Card>

View File

@@ -1,14 +1,13 @@
import React, { Component, Fragment } from 'react';
import { withTranslation } from 'react-i18next';
import i18next from 'i18next';
import StatsConfig from './StatsConfig';
import LogsConfig from './LogsConfig';
import { FiltersConfig } from './FiltersConfig';
import FiltersConfig from './FiltersConfig';
import { Checkbox } from '../ui/Controls/Checkbox';
import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading';
@@ -25,14 +24,14 @@ const ORDER_KEY = 'order';
const SETTINGS = {
safebrowsing: {
enabled: false,
title: i18next.t('use_adguard_browsing_sec'),
subtitle: i18next.t('use_adguard_browsing_sec_hint'),
title: 'use_adguard_browsing_sec',
subtitle: 'use_adguard_browsing_sec_hint',
[ORDER_KEY]: 0,
},
parental: {
enabled: false,
title: i18next.t('use_adguard_parental'),
subtitle: i18next.t('use_adguard_parental_hint'),
title: 'use_adguard_parental',
subtitle: 'use_adguard_parental_hint',
[ORDER_KEY]: 1,
},
};
@@ -90,18 +89,9 @@ class Settings extends Component<SettingsProps> {
renderSettings = (settings: any) =>
getObjectKeysSorted(SETTINGS, ORDER_KEY).map((key: any) => {
const setting = settings[key];
const { enabled, title, subtitle } = setting;
const { enabled } = setting;
return (
<div key={key} className="form__group form__group--checkbox">
<Checkbox
value={enabled}
title={title}
subtitle={subtitle}
onChange={(checked) => this.props.toggleSetting(key, !checked)}
/>
</div>
);
return <Checkbox {...setting} key={key} handleChange={() => this.props.toggleSetting(key, enabled)} />;
});
renderSafeSearch = () => {
@@ -116,29 +106,27 @@ class Settings extends Component<SettingsProps> {
return (
<>
<div className="form__group form__group--checkbox">
<Checkbox
value={enabled}
title={i18next.t('enforce_safe_search')}
subtitle={i18next.t('enforce_save_search_hint')}
onChange={(checked) =>
this.props.toggleSetting('safesearch', { ...safesearch, enabled: checked })
}
/>
</div>
<Checkbox
enabled={enabled}
title="enforce_safe_search"
subtitle="enforce_save_search_hint"
handleChange={({ target: { checked: enabled } }) =>
this.props.toggleSetting('safesearch', { ...safesearch, enabled })
}
/>
<div className="form__group--inner">
{Object.keys(searches).map((searchKey) => (
<div key={searchKey} className="form__group form__group--checkbox">
<Checkbox
value={searches[searchKey]}
title={captitalizeWords(searchKey)}
disabled={!safesearch.enabled}
onChange={(checked) =>
this.props.toggleSetting('safesearch', { ...safesearch, [searchKey]: checked })
}
/>
</div>
<Checkbox
key={searchKey}
enabled={searches[searchKey]}
title={captitalizeWords(searchKey)}
subtitle=""
disabled={!safesearch.enabled}
handleChange={({ target: { checked } }: any) =>
this.props.toggleSetting('safesearch', { ...safesearch, [searchKey]: checked })
}
/>
))}
</div>
</>
@@ -148,14 +136,23 @@ class Settings extends Component<SettingsProps> {
render() {
const {
settings,
setStatsConfig,
resetStats,
stats,
queryLogs,
setLogsConfig,
clearLogs,
filtering,
setFiltersConfig,
t,
} = this.props;
@@ -166,7 +163,6 @@ class Settings extends Component<SettingsProps> {
<PageTitle title={t('general_settings')} />
{!isDataReady && <Loading />}
{isDataReady && (
<div className="content">
<div className="row">

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Trans, withTranslation } from 'react-i18next';
import { Guide } from '../ui/Guide';
import Guide from '../ui/Guide';
import Card from '../ui/Card';
@@ -14,7 +14,10 @@ interface SetupGuideProps {
t: (id: string) => string;
}
const SetupGuide = ({ t, dashboard: { dnsAddresses } }: SetupGuideProps) => (
const SetupGuide = ({
t,
dashboard: { dnsAddresses },
}: SetupGuideProps) => (
<div className="guide">
<PageTitle title={t('setup_guide')} />

View File

@@ -0,0 +1,59 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import './Checkbox.css';
interface CheckboxProps {
title: string;
subtitle: string;
enabled: boolean;
handleChange: (...args: unknown[]) => unknown;
disabled?: boolean;
t?: (...args: unknown[]) => string;
}
class Checkbox extends Component<CheckboxProps> {
render() {
const {
title,
subtitle,
enabled,
handleChange,
disabled,
t,
} = this.props;
return (
<div className="form__group form__group--checkbox">
<label className="checkbox checkbox--settings">
<span className="checkbox__marker" />
<input
type="checkbox"
className="checkbox__input"
onChange={handleChange}
checked={enabled}
disabled={disabled}
/>
<span className="checkbox__label">
<span className="checkbox__label-text">
<span className="checkbox__label-title">{t(title)}</span>
<span
className="checkbox__label-subtitle"
dangerouslySetInnerHTML={{ __html: t(subtitle) }}
/>
</span>
</span>
</label>
</div>
);
}
}
export default withTranslation()(Checkbox);

View File

@@ -1,50 +0,0 @@
import React, { forwardRef, ReactNode } from 'react';
import clsx from 'clsx';
import './checkbox.css';
type Props = {
title: string;
subtitle?: ReactNode;
value: boolean;
name?: string;
disabled?: boolean;
className?: string;
error?: string;
onChange: (value: boolean) => void;
onBlur?: () => void;
};
export const Checkbox = forwardRef<HTMLInputElement, Props>(
(
{ title, subtitle, value, name, disabled, error, className = 'checkbox--form', onChange, onBlur, ...rest },
ref,
) => (
<>
<label className={clsx('checkbox', className)}>
<span className="checkbox__marker" />
<input
name={name}
type="checkbox"
className="checkbox__input"
disabled={disabled}
checked={value}
onChange={(e) => onChange(e.target.checked)}
onBlur={onBlur}
ref={ref}
{...rest}
/>
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{title}</span>
{subtitle && <span className="checkbox__label-subtitle">{subtitle}</span>}
</span>
</span>
</label>
{error && <div className="form__message form__message--error">{error}</div>}
</>
),
);
Checkbox.displayName = 'Checkbox';

View File

@@ -1,45 +0,0 @@
import React, { ComponentProps, forwardRef, ReactNode } from 'react';
import clsx from 'clsx';
type Props = ComponentProps<'input'> & {
label?: string;
desc?: string;
leftAddon?: ReactNode;
rightAddon?: ReactNode;
error?: string;
trimOnBlur?: boolean;
};
export const Input = forwardRef<HTMLInputElement, Props>(
({ name, label, desc, className, leftAddon, rightAddon, error, trimOnBlur, onBlur, ...rest }, ref) => (
<div className={clsx('form-group', { 'has-error': !!error })}>
{label && (
<label className={clsx('form__label', { 'form__label--with-desc': !!desc })} htmlFor={name}>
{label}
</label>
)}
{desc && <div className="form__desc form__desc--top">{desc}</div>}
<div className="input-group">
{leftAddon && <div>{leftAddon}</div>}
<input
className={clsx('form-control', { 'is-invalid': !!error }, className)}
ref={ref}
onBlur={(e) => {
if (trimOnBlur) {
e.target.value = e.target.value.trim();
rest.onChange(e);
}
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{rightAddon && <div>{rightAddon}</div>}
</div>
{error && <div className="form__message form__message--error mt-1">{error}</div>}
</div>
),
);
Input.displayName = 'Input';

View File

@@ -1,50 +0,0 @@
import React, { forwardRef, ReactNode } from 'react';
type Props<T> = {
name: string;
value: T;
onChange: (e: T) => void;
options: { label: string; desc?: ReactNode; value: T }[];
disabled?: boolean;
error?: string;
};
export const Radio = forwardRef<HTMLInputElement, Props<string | boolean | number | undefined>>(
({ disabled, onChange, value, options, name, error, ...rest }, ref) => {
const getId = (label: string) => (name ? `${label}_${name}` : label);
return (
<div>
{options.map((o) => {
const checked = value === o.value;
return (
<label
key={`${getId(o.label)}`}
htmlFor={getId(o.label)}
className="custom-control custom-radio">
<input
id={getId(o.label)}
data-testid={o.value}
type="radio"
className="custom-control-input"
onChange={() => onChange(o.value)}
checked={checked}
disabled={disabled}
ref={ref}
{...rest}
/>
<span className="custom-control-label">{o.label}</span>
{o.desc && <span className="checkbox__label-subtitle">{o.desc}</span>}
</label>
);
})}
{!disabled && error && <span className="form__message form__message--error">{error}</span>}
</div>
);
},
);
Radio.displayName = 'Radio';

View File

@@ -1,27 +0,0 @@
import React, { ComponentProps, forwardRef } from 'react';
import clsx from 'clsx';
type SelectProps = ComponentProps<'select'> & {
label?: string;
error?: string;
};
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ name, label, className, error, children, ...rest }, ref) => (
<div className={clsx('form-group', { 'has-error': !!error })}>
{label && (
<label className="form__label" htmlFor={name}>
{label}
</label>
)}
<div className="input-group">
<select className={clsx('form-control custom-select', className)} ref={ref} {...rest}>
{children}
</select>
</div>
{error && <div className="form__message form__message--error mt-1">{error}</div>}
</div>
),
);
Select.displayName = 'Select';

View File

@@ -1,45 +0,0 @@
import React, { ComponentProps, forwardRef } from 'react';
import clsx from 'clsx';
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
type Props = ComponentProps<'textarea'> & {
className?: string;
wrapperClassName?: string;
label?: string;
desc?: string;
error?: string;
trimOnBlur?: boolean;
};
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(
({ name, label, desc, className, wrapperClassName, error, trimOnBlur, onBlur, ...rest }, ref) => (
<div className={clsx('form-group', wrapperClassName, { 'has-error': !!error })}>
{label && (
<label className={clsx('form__label', { 'form__label--with-desc': !!desc })} htmlFor={name}>
{label}
</label>
)}
{desc && <div className="form__desc form__desc--top">{desc}</div>}
<textarea
className={clsx(
'form-control form-control--textarea form-control--textarea-small font-monospace',
className,
)}
ref={ref}
onBlur={(e) => {
if (trimOnBlur) {
const normalizedValue = trimLinesAndRemoveEmpty(e.target.value);
rest.onChange(normalizedValue);
}
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{error && <div className="form__message form__message--error">{error}</div>}
</div>
),
);
Textarea.displayName = 'Textarea';

View File

@@ -7,7 +7,7 @@ import { MOBILE_CONFIG_LINKS } from '../../../helpers/constants';
import Tabs from '../Tabs';
import { MobileConfigForm } from './MobileConfigForm';
import MobileConfigForm from './MobileConfigForm';
import { RootState } from '../../../initialState';
interface renderLiProps {
@@ -346,7 +346,7 @@ interface GuideProps {
dnsAddresses?: unknown[];
}
export const Guide = ({ dnsAddresses }: GuideProps) => {
const Guide = ({ dnsAddresses }: GuideProps) => {
const { t } = useTranslation();
const serverName = useSelector((state: RootState) => state.encryption?.server_name);
@@ -381,3 +381,5 @@ export const Guide = ({ dnsAddresses }: GuideProps) => {
Guide.defaultProps = {
dnsAddresses: [],
};
export default Guide;

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