Compare commits
689 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1973901802 | ||
|
|
79a5c920a4 | ||
|
|
0fb42e5c71 | ||
|
|
1c5b613048 | ||
|
|
55a4536997 | ||
|
|
1b45dc45fc | ||
|
|
87ccd192c3 | ||
|
|
2c91de73af | ||
|
|
94f3bf44d7 | ||
|
|
27006f58c5 | ||
|
|
4326a2c945 | ||
|
|
375e410aa3 | ||
|
|
cc8633ed7d | ||
|
|
a79643f23e | ||
|
|
c81c79aad7 | ||
|
|
e2b518339f | ||
|
|
57c510631e | ||
|
|
d4bbc45a39 | ||
|
|
9eb6da05ad | ||
|
|
3f796a5d05 | ||
|
|
0a1d7fd707 | ||
|
|
26db906e54 | ||
|
|
3a5f9a7ad3 | ||
|
|
bcbfa43ea2 | ||
|
|
2520a62e24 | ||
|
|
fce551dcaf | ||
|
|
cf4616cbee | ||
|
|
f0c9ffcbeb | ||
|
|
71c1157ef5 | ||
|
|
2fe9819150 | ||
|
|
4af635e58a | ||
|
|
b0cfd7228e | ||
|
|
e03efbcdd1 | ||
|
|
2897bb983f | ||
|
|
387783cf91 | ||
|
|
d4bd53a824 | ||
|
|
f1a6912092 | ||
|
|
531ee20988 | ||
|
|
5c7c9964b8 | ||
|
|
425f3c87d0 | ||
|
|
ad7c5cb9dc | ||
|
|
124d73bd32 | ||
|
|
1445940473 | ||
|
|
df30248870 | ||
|
|
b419a1e3d8 | ||
|
|
98ff11e1c7 | ||
|
|
134d9275bb | ||
|
|
e2675e9a3b | ||
|
|
dc43ad9910 | ||
|
|
ceac4cbdd5 | ||
|
|
131aa4c93c | ||
|
|
5abf0b5a53 | ||
|
|
5cddde53c3 | ||
|
|
1c9abd6107 | ||
|
|
8e3f05e538 | ||
|
|
64f66cfb5d | ||
|
|
e616d843bf | ||
|
|
6406202888 | ||
|
|
b3c2b3a21b | ||
|
|
b45e8e80fb | ||
|
|
885b660808 | ||
|
|
bdc9a0b906 | ||
|
|
c631a6832f | ||
|
|
db7efc24d3 | ||
|
|
b4b11406cf | ||
|
|
eb8c531ae1 | ||
|
|
d1987e711d | ||
|
|
e50b4fd185 | ||
|
|
d2258cb66d | ||
|
|
42b76ada9d | ||
|
|
25da23497a | ||
|
|
efaaeb58eb | ||
|
|
0b3ba82242 | ||
|
|
eff23f3b62 | ||
|
|
0e9df33a40 | ||
|
|
6a1edc45be | ||
|
|
5d60bb05ab | ||
|
|
2307f55715 | ||
|
|
f1e6a30931 | ||
|
|
4ddae72faf | ||
|
|
082354204b | ||
|
|
6187871e3b | ||
|
|
dc682763ff | ||
|
|
9fe34818e3 | ||
|
|
9a77bb3a0a | ||
|
|
86890a8609 | ||
|
|
1fd0f78612 | ||
|
|
1fcb69d3a9 | ||
|
|
07db927246 | ||
|
|
f9807e4011 | ||
|
|
5647bc1fc9 | ||
|
|
0f7235f217 | ||
|
|
db67fb6c6a | ||
|
|
087d2f68c2 | ||
|
|
02fa39226c | ||
|
|
edfa104710 | ||
|
|
7a124213e5 | ||
|
|
0a2a7ca630 | ||
|
|
1f164c7005 | ||
|
|
44f224d69e | ||
|
|
22469bb83b | ||
|
|
10a0873bc8 | ||
|
|
a165410c9f | ||
|
|
ec5e2be31f | ||
|
|
6d3099acd3 | ||
|
|
66c670c6ff | ||
|
|
c2a31f9503 | ||
|
|
466f553bbe | ||
|
|
ddb1bc0fee | ||
|
|
a36630e5a8 | ||
|
|
3958330560 | ||
|
|
86ba6d4332 | ||
|
|
b2364e465f | ||
|
|
a3b8d4d923 | ||
|
|
3454bf9243 | ||
|
|
64a4443d0c | ||
|
|
d24b78db0e | ||
|
|
f7150e6a19 | ||
|
|
85046abb15 | ||
|
|
454e26db7f | ||
|
|
b74438bf83 | ||
|
|
7d40d3bfea | ||
|
|
6261fb79ab | ||
|
|
27bffef940 | ||
|
|
450e2ac549 | ||
|
|
6ac466e430 | ||
|
|
f7d88f6976 | ||
|
|
7f5ac19b59 | ||
|
|
54f6710b8f | ||
|
|
757bb7285a | ||
|
|
af041bcbd7 | ||
|
|
cf53653cfa | ||
|
|
1d09ff0562 | ||
|
|
c93cb43db8 | ||
|
|
276d87a218 | ||
|
|
fcf609ac1e | ||
|
|
e4532a27cd | ||
|
|
302a11a6a3 | ||
|
|
b8d9ca942c | ||
|
|
df9864ec00 | ||
|
|
3baa6919dc | ||
|
|
02db488b30 | ||
|
|
821ad3edd9 | ||
|
|
d18c222b1a | ||
|
|
36ffcf7d22 | ||
|
|
147344afa3 | ||
|
|
1abd9da27d | ||
|
|
d9e70f5244 | ||
|
|
a1ceb83da0 | ||
|
|
a12f01793f | ||
|
|
b1fbd7c40c | ||
|
|
2976726f99 | ||
|
|
6f2503a09f | ||
|
|
a8384c004e | ||
|
|
49b91b4fc9 | ||
|
|
fa47fa3f9c | ||
|
|
763b986955 | ||
|
|
342699d933 | ||
|
|
fd593f5282 | ||
|
|
725aeeb910 | ||
|
|
564a41d598 | ||
|
|
c3204664c3 | ||
|
|
626c1ae753 | ||
|
|
cc366495d3 | ||
|
|
0d405c0af8 | ||
|
|
c038e4cf14 | ||
|
|
a83bc5eeeb | ||
|
|
702db84e39 | ||
|
|
9cc824d852 | ||
|
|
8a8c7329f7 | ||
|
|
cbef338592 | ||
|
|
bd2c4269db | ||
|
|
f40141bbbc | ||
|
|
c7b5830336 | ||
|
|
bb34381a0d | ||
|
|
68a4cc597f | ||
|
|
22d3c38df2 | ||
|
|
22c7efd2d1 | ||
|
|
eb159e6997 | ||
|
|
8bf76c331d | ||
|
|
4bb7b654ab | ||
|
|
3f89335ed2 | ||
|
|
8f7aff93d7 | ||
|
|
5fb7e44e79 | ||
|
|
6a7b1aba8b | ||
|
|
218f51092c | ||
|
|
9f75146eab | ||
|
|
6ab8aa4da1 | ||
|
|
386886cec2 | ||
|
|
5b29cae133 | ||
|
|
4df8868787 | ||
|
|
517ebc0251 | ||
|
|
f25639f1fc | ||
|
|
f23507a554 | ||
|
|
b9df476c5d | ||
|
|
e2579c72bd | ||
|
|
ac8f703407 | ||
|
|
9ad4bba9ab | ||
|
|
452c930dd0 | ||
|
|
fdd0f594fb | ||
|
|
00e1b6ca08 | ||
|
|
dece393d6a | ||
|
|
aa2d942783 | ||
|
|
e3ee7a0c3e | ||
|
|
70e3299567 | ||
|
|
967517316f | ||
|
|
24f582d36d | ||
|
|
9cffe865ec | ||
|
|
cb3f7f2834 | ||
|
|
096a959987 | ||
|
|
5ec747b30b | ||
|
|
829415da5b | ||
|
|
3396d68019 | ||
|
|
bd68bf2e25 | ||
|
|
9644f79a03 | ||
|
|
36e273dfd5 | ||
|
|
068072bc5a | ||
|
|
b72ca4d127 | ||
|
|
28440fc3ac | ||
|
|
6d14ec18ac | ||
|
|
5fd35254a8 | ||
|
|
3ee8051e97 | ||
|
|
2dd6ea5161 | ||
|
|
788e91a51e | ||
|
|
d4fcef8d04 | ||
|
|
392c7b6ee1 | ||
|
|
7bb40bca0f | ||
|
|
f20cb65189 | ||
|
|
d5f6dd1a46 | ||
|
|
0f28a989e9 | ||
|
|
d918e5b418 | ||
|
|
3a0f608402 | ||
|
|
cd2dd00da3 | ||
|
|
07ffcbec3d | ||
|
|
b3461d37ca | ||
|
|
2178546e7b | ||
|
|
24ae61de3e | ||
|
|
68f9ec70fb | ||
|
|
17aa46c4d2 | ||
|
|
a45f0c519e | ||
|
|
2cb2b3585f | ||
|
|
d24f208f98 | ||
|
|
6ac9509d64 | ||
|
|
7d2df26335 | ||
|
|
ae403fb137 | ||
|
|
e1bb89c393 | ||
|
|
c4e67690f4 | ||
|
|
f6023b395e | ||
|
|
cedab695c2 | ||
|
|
1c339e5fcd | ||
|
|
4eb910c35e | ||
|
|
c6957bed64 | ||
|
|
8e6f7be5b8 | ||
|
|
528c1a72ca | ||
|
|
3087c54a15 | ||
|
|
5c65d0cabe | ||
|
|
4231920ee8 | ||
|
|
d5f46f51b8 | ||
|
|
a860c8e6ff | ||
|
|
0794704f74 | ||
|
|
173ab2a3c1 | ||
|
|
68dc8a1341 | ||
|
|
043e89a1a4 | ||
|
|
c5ed6da5bd | ||
|
|
69c5f175e8 | ||
|
|
79b0fac01a | ||
|
|
bc81a0ecff | ||
|
|
1ac9419c94 | ||
|
|
5f88abb322 | ||
|
|
33db419384 | ||
|
|
ceabad0fd0 | ||
|
|
f76b7c3d94 | ||
|
|
9e68a522cb | ||
|
|
faa7c7b2d4 | ||
|
|
d326d1bc8b | ||
|
|
73fbe8b95a | ||
|
|
87147ac89f | ||
|
|
8d936b5756 | ||
|
|
4ca24b7707 | ||
|
|
133dd75ec3 | ||
|
|
00c128f0a4 | ||
|
|
e4b53db558 | ||
|
|
1611057852 | ||
|
|
00ba63341b | ||
|
|
a1b1877667 | ||
|
|
bebdc1b5bc | ||
|
|
58868b75af | ||
|
|
1836e56e6e | ||
|
|
f83d026c33 | ||
|
|
61554ba4e0 | ||
|
|
c82887d3aa | ||
|
|
faeda3f075 | ||
|
|
afeadbb454 | ||
|
|
0e031a4921 | ||
|
|
d0942c88c8 | ||
|
|
794d302ce5 | ||
|
|
7f1f85b08c | ||
|
|
c8e4f61534 | ||
|
|
3f404bc37e | ||
|
|
7911a24dc8 | ||
|
|
7746a3e6a9 | ||
|
|
08bedacf0a | ||
|
|
74a0938038 | ||
|
|
828d3121be | ||
|
|
b6ae539c36 | ||
|
|
fa5ff053b7 | ||
|
|
6bf57ae84e | ||
|
|
472dc0b77d | ||
|
|
4b821d67f5 | ||
|
|
24fc2957c5 | ||
|
|
6ba0e4686a | ||
|
|
b92fb34f37 | ||
|
|
ffd9f1aaa9 | ||
|
|
d43290fe31 | ||
|
|
ef22a31a93 | ||
|
|
0ed619c9c8 | ||
|
|
8b6e9ef5f9 | ||
|
|
cca61a33c6 | ||
|
|
4d217583b0 | ||
|
|
c78cee3396 | ||
|
|
b453d9f41d | ||
|
|
e231230f1b | ||
|
|
850e856e6e | ||
|
|
d6b83d4a63 | ||
|
|
0c973334be | ||
|
|
23ac1726b7 | ||
|
|
1f1e26f67b | ||
|
|
91af0cddce | ||
|
|
6f014fa53d | ||
|
|
6f2e852e09 | ||
|
|
fd82e7c26a | ||
|
|
2bb5b24d4e | ||
|
|
92e70515ae | ||
|
|
51aec7cbbc | ||
|
|
3367b9fb2a | ||
|
|
dfa39293a1 | ||
|
|
65364930f7 | ||
|
|
53ea2d28cf | ||
|
|
395ddb7b3c | ||
|
|
1448b7ab16 | ||
|
|
abd58004b8 | ||
|
|
800cb177f3 | ||
|
|
31a0dde515 | ||
|
|
6edfe1bb8e | ||
|
|
7ebfaccc6e | ||
|
|
bc0b0af06b | ||
|
|
5210d214ec | ||
|
|
ed942f3e31 | ||
|
|
5b417d9f17 | ||
|
|
f7860c893d | ||
|
|
a01ba5dd4d | ||
|
|
b6d0d94990 | ||
|
|
9ea5c1abe1 | ||
|
|
91ec996ffb | ||
|
|
9bc5a4570e | ||
|
|
8defb3b39e | ||
|
|
d5e57248a0 | ||
|
|
0647f3fe86 | ||
|
|
d664a9de1d | ||
|
|
b54f540f71 | ||
|
|
0884116de3 | ||
|
|
eefdf8449a | ||
|
|
ae2c7d00a9 | ||
|
|
afa54a1339 | ||
|
|
56271819ea | ||
|
|
a9b329daf6 | ||
|
|
61b1a30aa1 | ||
|
|
b4732c83c5 | ||
|
|
783ac967a1 | ||
|
|
c091d10a41 | ||
|
|
756c5ac0d3 | ||
|
|
ef789acee4 | ||
|
|
b1c11a718e | ||
|
|
d7b1825cf5 | ||
|
|
b5eb840d22 | ||
|
|
6f56eb4c12 | ||
|
|
6b223e2992 | ||
|
|
43e5d42070 | ||
|
|
a58bf0e24e | ||
|
|
f432eb3609 | ||
|
|
6e16654344 | ||
|
|
ef637e1313 | ||
|
|
9494b87ca5 | ||
|
|
d68600c5d0 | ||
|
|
8fa2f48136 | ||
|
|
542a67b84e | ||
|
|
d832d7ce95 | ||
|
|
92cf7c1aca | ||
|
|
b5f0d48e7f | ||
|
|
6f69fb73af | ||
|
|
67014c40f7 | ||
|
|
a2e9d69452 | ||
|
|
e164cff02b | ||
|
|
08bf9b0acb | ||
|
|
60fa3b2e95 | ||
|
|
3f93cb3397 | ||
|
|
10daf29e2f | ||
|
|
70c56f7a18 | ||
|
|
e9d20651e9 | ||
|
|
466807638c | ||
|
|
991717e150 | ||
|
|
b1f707d18c | ||
|
|
f857ed74ec | ||
|
|
2f497cf5d0 | ||
|
|
21db166be0 | ||
|
|
d06cc0f8ee | ||
|
|
b74eded414 | ||
|
|
ecf0b0c5c1 | ||
|
|
64c2f2d89c | ||
|
|
0b96bd17c4 | ||
|
|
392c16cd27 | ||
|
|
10fcfefcfd | ||
|
|
2bd9923691 | ||
|
|
c8c663f3f0 | ||
|
|
bf82ccede9 | ||
|
|
982f8dc1e1 | ||
|
|
83877fb871 | ||
|
|
ac131923a2 | ||
|
|
bc4c2e2ff7 | ||
|
|
1b15bee2b0 | ||
|
|
8e09424774 | ||
|
|
89b6323f03 | ||
|
|
cdc568d8d9 | ||
|
|
bda6f777c4 | ||
|
|
bf2781d465 | ||
|
|
f2e547a54e | ||
|
|
ceaa1e4ebf | ||
|
|
5d6c980ac7 | ||
|
|
e973c4b174 | ||
|
|
d9d641941c | ||
|
|
d318545913 | ||
|
|
7ec4aa91ea | ||
|
|
681fb4e705 | ||
|
|
c443672e35 | ||
|
|
53d680a5df | ||
|
|
6b7a8078b0 | ||
|
|
91f8ab0549 | ||
|
|
a8812908c1 | ||
|
|
b8595b87c4 | ||
|
|
acb4a98466 | ||
|
|
3929f0da44 | ||
|
|
224c2a891d | ||
|
|
81e88472cb | ||
|
|
6b2baba3c7 | ||
|
|
967a1e6b87 | ||
|
|
241e7ca20c | ||
|
|
bc325de13f | ||
|
|
ffa4429818 | ||
|
|
24edf7eeb6 | ||
|
|
99c8cd06c9 | ||
|
|
9a6266588d | ||
|
|
f21daae023 | ||
|
|
7b64f9ff42 | ||
|
|
1626b6bd5a | ||
|
|
eb22d9cdd9 | ||
|
|
1ed3a9673d | ||
|
|
5ad9f8ead2 | ||
|
|
523c5ef10a | ||
|
|
a9839e95a0 | ||
|
|
1223965cd4 | ||
|
|
141b14c94a | ||
|
|
3a9d436f8a | ||
|
|
01548c236e | ||
|
|
5cb6d97cd7 | ||
|
|
f4a6ca726c | ||
|
|
766fbab071 | ||
|
|
87c8114291 | ||
|
|
d218e047a3 | ||
|
|
bf893d488a | ||
|
|
bd31151c1f | ||
|
|
4476262357 | ||
|
|
2a93e45b67 | ||
|
|
5ba43a59f4 | ||
|
|
dc05556c5a | ||
|
|
5bc6d00aa0 | ||
|
|
2dc2a0946a | ||
|
|
aa30728cda | ||
|
|
71ab95f12f | ||
|
|
c71d6ed433 | ||
|
|
77348e746f | ||
|
|
27c33b2fa9 | ||
|
|
86279f19b0 | ||
|
|
3d901a82ad | ||
|
|
d351ed82c1 | ||
|
|
8e13f22aa5 | ||
|
|
d0f4f22e0d | ||
|
|
4bbc503709 | ||
|
|
1b305d94a7 | ||
|
|
a7478255a1 | ||
|
|
84604e292b | ||
|
|
a04923a4f3 | ||
|
|
1da954fa97 | ||
|
|
ad4b58472f | ||
|
|
4e1c1618cb | ||
|
|
3916f1073d | ||
|
|
623c3bba09 | ||
|
|
e8898811fe | ||
|
|
71df659dc9 | ||
|
|
158f2f6100 | ||
|
|
8e993cd788 | ||
|
|
12f8590228 | ||
|
|
2814c393ad | ||
|
|
37431735fd | ||
|
|
251beb24d3 | ||
|
|
37a1a98c49 | ||
|
|
5ac775aa4a | ||
|
|
c53a132072 | ||
|
|
8e7ceec1a1 | ||
|
|
89446fccd5 | ||
|
|
4f45f2c3e3 | ||
|
|
9c8e4c64ea | ||
|
|
2c2295c161 | ||
|
|
a2dd7c32d5 | ||
|
|
b3f33b4b0b | ||
|
|
de08b53ae1 | ||
|
|
a60eeb55f1 | ||
|
|
9d4b829fb6 | ||
|
|
1515c353f8 | ||
|
|
f0536b6347 | ||
|
|
340a4fb58e | ||
|
|
e873149bee | ||
|
|
77793e5f21 | ||
|
|
24154f0033 | ||
|
|
8c406427af | ||
|
|
885e4e16c8 | ||
|
|
0b7f0396de | ||
|
|
cca6998efe | ||
|
|
3c374b5940 | ||
|
|
ba103f9825 | ||
|
|
2748d4c889 | ||
|
|
b8c0ed9335 | ||
|
|
ff012cf0a3 | ||
|
|
2b0addd505 | ||
|
|
2de0f82bbc | ||
|
|
1fc5f15aaa | ||
|
|
954d923975 | ||
|
|
05cce8b107 | ||
|
|
d44f68e844 | ||
|
|
cb97c221fd | ||
|
|
81bb4aea78 | ||
|
|
8da90a7f4a | ||
|
|
b4b800565c | ||
|
|
e8280c60d8 | ||
|
|
571be68733 | ||
|
|
bdec98f18e | ||
|
|
28df187012 | ||
|
|
f0569af367 | ||
|
|
e2956cae82 | ||
|
|
110434c2d5 | ||
|
|
f417f6257f | ||
|
|
1d2958f4aa | ||
|
|
3e67c8d79a | ||
|
|
57a33654f7 | ||
|
|
30050bf278 | ||
|
|
5cbaeb82a8 | ||
|
|
876bec5a65 | ||
|
|
4b4faad9e8 | ||
|
|
c061bec6d8 | ||
|
|
229ef78085 | ||
|
|
0aeca6bbf5 | ||
|
|
35b5f4b48b | ||
|
|
0d3aa00956 | ||
|
|
cb9ffe4de9 | ||
|
|
351673c060 | ||
|
|
4a14c199d8 | ||
|
|
1dd548c36c | ||
|
|
d42718465d | ||
|
|
93847bd309 | ||
|
|
4da55dc2aa | ||
|
|
3d3e0784ea | ||
|
|
3898309778 | ||
|
|
c19416bf8e | ||
|
|
c025c845d2 | ||
|
|
c5b1105fc1 | ||
|
|
38869b22a6 | ||
|
|
ab11c912db | ||
|
|
7451eb1346 | ||
|
|
8725c1df7a | ||
|
|
0820983d81 | ||
|
|
a5b61459cc | ||
|
|
dd3621bcf6 | ||
|
|
571370ab16 | ||
|
|
e33c8a3cde | ||
|
|
0d5f24927c | ||
|
|
27ea739cfd | ||
|
|
899b26725e | ||
|
|
26f2207b5c | ||
|
|
6d7d10ec38 | ||
|
|
c1f6da2b52 | ||
|
|
a40ddb094b | ||
|
|
b477b67428 | ||
|
|
cd9db6440b | ||
|
|
9ff420bb52 | ||
|
|
9a03190a62 | ||
|
|
6b6eacaa2b | ||
|
|
5ca33e44d8 | ||
|
|
68c8a4d484 | ||
|
|
6e5731ab02 | ||
|
|
548f539566 | ||
|
|
853582dade | ||
|
|
3a94080491 | ||
|
|
ba161e9a6f | ||
|
|
91eaf72051 | ||
|
|
826529e73e | ||
|
|
c466f8cc73 | ||
|
|
f9d1948f6a | ||
|
|
bb8d7c37bb | ||
|
|
f2d7f8161b | ||
|
|
39b2e345c3 | ||
|
|
a7a38413fe | ||
|
|
fe671152c2 | ||
|
|
ba678ffa82 | ||
|
|
672ff33879 | ||
|
|
398312cd80 | ||
|
|
06a28a461d | ||
|
|
31b855f9ab | ||
|
|
f379d34813 | ||
|
|
5abe5af707 | ||
|
|
daae040f9c | ||
|
|
f2b3c3a14c | ||
|
|
d3e81c47f6 | ||
|
|
c14aff3dba | ||
|
|
d97c426646 | ||
|
|
34e14930de | ||
|
|
924afea22b | ||
|
|
302c3a767a | ||
|
|
c494e17df5 | ||
|
|
7c25c0febe | ||
|
|
5f7fc0f041 | ||
|
|
beb94741cf | ||
|
|
24be7ce4ed | ||
|
|
6e41897323 | ||
|
|
b5e7237169 | ||
|
|
7e95ce9136 | ||
|
|
a7416f9c34 | ||
|
|
2bd4840ba5 | ||
|
|
5349ec76fd | ||
|
|
71259c5f19 | ||
|
|
f21aebd1cf | ||
|
|
c36a7895ad | ||
|
|
5fed5c0718 | ||
|
|
f437d53c1c | ||
|
|
bfe25ba014 | ||
|
|
a8cdc5b01c | ||
|
|
0fbfa057b1 | ||
|
|
93ea27077f | ||
|
|
aab8da4c7c | ||
|
|
448a6caeb8 | ||
|
|
a4dc4c61d8 | ||
|
|
277415124e | ||
|
|
b216475c20 | ||
|
|
c776ad21b7 | ||
|
|
b56dcc9de1 | ||
|
|
05cab6fde0 | ||
|
|
09b49d0145 | ||
|
|
98bfb82787 | ||
|
|
d238e1feb3 | ||
|
|
911250cfbe | ||
|
|
4b4cb99b30 | ||
|
|
0161509b5f | ||
|
|
ec6b1f7c42 | ||
|
|
69a75fbcaa | ||
|
|
a0157e39c6 | ||
|
|
d078851246 | ||
|
|
c9d627ea71 | ||
|
|
297a1c7fa5 | ||
|
|
f1c3fecfb2 | ||
|
|
79eff5f260 | ||
|
|
8d5d37c281 | ||
|
|
e1bb428a6a | ||
|
|
f1b6da93cf | ||
|
|
61f4b6f1ae | ||
|
|
f678eaf9c0 | ||
|
|
607089cd25 | ||
|
|
df94d76a8b | ||
|
|
8294bb1c7c | ||
|
|
ec157ac4ea | ||
|
|
c4ba284964 | ||
|
|
f3a97ed7ab | ||
|
|
d90da5d540 | ||
|
|
6cd93139fd | ||
|
|
246f726115 | ||
|
|
5a6dc34ec0 | ||
|
|
ddcfe7c4bf | ||
|
|
eb71f3ed8f | ||
|
|
fd629be6e6 | ||
|
|
ce1aaea4ca | ||
|
|
fa8c038bc1 | ||
|
|
9fdf946fc0 |
8
.codecov.yml
Normal file
8
.codecov.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 40%
|
||||
threshold: null
|
||||
patch: false
|
||||
changes: false
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,15 +2,19 @@
|
||||
/.vscode
|
||||
/.idea
|
||||
/AdGuardHome
|
||||
/AdGuardHome.exe
|
||||
/AdGuardHome.yaml
|
||||
/AdGuardHome.log
|
||||
/data/
|
||||
/build/
|
||||
/dist/
|
||||
/client/node_modules/
|
||||
/querylog.json
|
||||
/querylog.json.1
|
||||
/scripts/translations/node_modules
|
||||
/scripts/translations/oneskyapp.json
|
||||
/a_main-packr.go
|
||||
|
||||
# Test output
|
||||
dnsfilter/dnsfilter.TestLotsOfRules*.pprof
|
||||
tests/top-1m.csv
|
||||
dnsfilter/tests/top-1m.csv
|
||||
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||
56
.golangci.yml
Normal file
56
.golangci.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 2m
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- ".*generated.*"
|
||||
- dnsfilter/rule_to_regexp.go
|
||||
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
lll:
|
||||
line-length: 200
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- interfacer
|
||||
- gocritic
|
||||
- scopelint
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- prealloc
|
||||
- maligned
|
||||
- goconst # disabled until it's possible to configure
|
||||
fast: true
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
# structcheck cannot detect usages while they're there
|
||||
- .parentalServer. is unused
|
||||
- .safeBrowsingServer. is unused
|
||||
# errcheck
|
||||
- Error return value of .s.closeConn. is not checked
|
||||
- Error return value of ..*.Shutdown.
|
||||
# goconst
|
||||
- string .forcesafesearch.google.com. has 3 occurrences
|
||||
29
.gometalinter.json
Normal file
29
.gometalinter.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Vendor": true,
|
||||
"Test": true,
|
||||
"Deadline": "2m",
|
||||
"Sort": ["linter", "severity", "path", "line"],
|
||||
"Exclude": [
|
||||
".*generated.*",
|
||||
"dnsfilter/rule_to_regexp.go"
|
||||
],
|
||||
"EnableGC": true,
|
||||
"Linters": {
|
||||
"nakedret": {
|
||||
"Command": "nakedret",
|
||||
"Pattern": "^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$"
|
||||
}
|
||||
},
|
||||
"WarnUnmatchedDirective": true,
|
||||
|
||||
"EnableAll": true,
|
||||
"DisableAll": false,
|
||||
"Disable": [
|
||||
"maligned",
|
||||
"goconst",
|
||||
"vetshadow"
|
||||
],
|
||||
|
||||
"Cyclo": 20,
|
||||
"LineLength": 200
|
||||
}
|
||||
75
.travis.yml
75
.travis.yml
@@ -1,8 +1,19 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.x
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_install:
|
||||
- nvm install node
|
||||
- npm install -g npm
|
||||
|
||||
install:
|
||||
- npm --prefix client install
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@@ -10,15 +21,61 @@ cache:
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/Library/Caches/go-build
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
install:
|
||||
- npm --prefix client install
|
||||
|
||||
script:
|
||||
- go test ./...
|
||||
- node -v
|
||||
- npm -v
|
||||
# Run tests
|
||||
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
# Make
|
||||
- make build/static/index.html
|
||||
- make
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Release build configuration
|
||||
- name: release
|
||||
go:
|
||||
- 1.12.x
|
||||
os:
|
||||
- linux
|
||||
|
||||
script:
|
||||
- node -v
|
||||
- npm -v
|
||||
# Run tests just in case
|
||||
- go test -race -v -bench=. ./...
|
||||
# Prepare releases
|
||||
- ./release.sh
|
||||
- ls -l dist
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
file:
|
||||
- dist/AdGuardHome_*
|
||||
on:
|
||||
repo: AdguardTeam/AdGuardHome
|
||||
tags: true
|
||||
draft: true
|
||||
file_glob: true
|
||||
skip_cleanup: true
|
||||
|
||||
- name: docker
|
||||
if: type != pull_request AND (branch = master OR tag IS present)
|
||||
go:
|
||||
- 1.12.x
|
||||
os:
|
||||
- linux
|
||||
services:
|
||||
- docker
|
||||
before_script:
|
||||
- nvm install node
|
||||
- npm install -g npm
|
||||
script:
|
||||
- docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD"
|
||||
- ./build_docker.sh
|
||||
after_script:
|
||||
- docker images
|
||||
|
||||
684
AGHTechDoc.md
Normal file
684
AGHTechDoc.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# AdGuard Home Technical Document
|
||||
|
||||
The document describes technical details and internal algorithms of AdGuard Home.
|
||||
|
||||
Contents:
|
||||
* First startup
|
||||
* Installation wizard
|
||||
* "Get install settings" command
|
||||
* "Check configuration" command
|
||||
* Disable DNSStubListener
|
||||
* "Apply configuration" command
|
||||
* Updating
|
||||
* Get version command
|
||||
* Update command
|
||||
* Device Names and Per-client Settings
|
||||
* Per-client settings
|
||||
* Get list of clients
|
||||
* Add client
|
||||
* Update client
|
||||
* Delete client
|
||||
* Enable DHCP server
|
||||
* "Show DHCP status" command
|
||||
* "Check DHCP" command
|
||||
* "Enable DHCP" command
|
||||
* Static IP check/set
|
||||
* Add a static lease
|
||||
* DNS access settings
|
||||
* List access settings
|
||||
* Set access settings
|
||||
|
||||
|
||||
## First startup
|
||||
|
||||
The first application startup is detected when there's no .yaml configuration file.
|
||||
|
||||
We check if the user is root, otherwise we fail with an error.
|
||||
|
||||
Web server is started up on port 3000 and automatically redirects requests to `/` to Installation wizard.
|
||||
|
||||
After Installation wizard steps are completed, we write configuration to a file and start normal operation.
|
||||
|
||||
|
||||
## Installation wizard
|
||||
|
||||
This is the collection of UI screens that are shown to a user on first application startup.
|
||||
|
||||
The screens are:
|
||||
|
||||
1. Welcome
|
||||
2. Set up network interface and listening ports for Web and DNS servers
|
||||
3. Set up administrator username and password
|
||||
4. Configuration complete
|
||||
5. Done
|
||||
|
||||
Algorithm:
|
||||
|
||||
Screen 2:
|
||||
* UI asks server for initial information and shows it
|
||||
* User edits the default settings, clicks on "Next" button
|
||||
* UI asks server to check new settings
|
||||
* Server searches for the known issues
|
||||
* UI shows information about the known issues and the means to fix them
|
||||
* Server applies automatic fixes of the known issues on command from UI
|
||||
|
||||
Screen 3:
|
||||
* UI asks server to apply the configuration
|
||||
* Server restarts DNS server
|
||||
|
||||
|
||||
### "Get install settings" command
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/install/get_addresses
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"web_port":80,
|
||||
"dns_port":53,
|
||||
"interfaces":{
|
||||
"enp2s0":{"name":"enp2s0","mtu":1500,"hardware_address":"","ip_addresses":["",""],"flags":"up|broadcast|multicast"},
|
||||
"lo":{"name":"lo","mtu":65536,"hardware_address":"","ip_addresses":["127.0.0.1","::1"],"flags":"up|loopback"},
|
||||
}
|
||||
}
|
||||
|
||||
If `interfaces.flags` doesn't contain `up` flag, UI must show `(Down)` status next to its IP address in interfaces selector.
|
||||
|
||||
|
||||
### "Check configuration" command
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/install/check_config
|
||||
|
||||
{
|
||||
"web":{"port":80,"ip":"192.168.11.33"},
|
||||
"dns":{"port":53,"ip":"127.0.0.1","autofix":false},
|
||||
}
|
||||
|
||||
Server should check whether a port is available only in case it itself isn't already listening on that port.
|
||||
|
||||
Server replies on success:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"web":{"status":""},
|
||||
"dns":{"status":""},
|
||||
}
|
||||
|
||||
Server replies on error:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"web":{"status":"ERROR MESSAGE"},
|
||||
"dns":{"status":"ERROR MESSAGE", "can_autofix": true|false},
|
||||
}
|
||||
|
||||
|
||||
### Disable DNSStubListener
|
||||
|
||||
On Linux, if 53 port is not available, server performs several additional checks to determine if the issue can be fixed automatically.
|
||||
|
||||
#### Phase 1
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/install/check_config
|
||||
|
||||
{
|
||||
"dns":{"port":53,"ip":"127.0.0.1","autofix":false}
|
||||
}
|
||||
|
||||
Check if DNSStubListener is enabled:
|
||||
|
||||
systemctl is-enabled systemd-resolved
|
||||
|
||||
Check if DNSStubListener is active:
|
||||
|
||||
grep -E '#?DNSStubListener=yes' /etc/systemd/resolved.conf
|
||||
|
||||
If the issue can be fixed automatically, server replies with `"can_autofix":true`
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"dns":{"status":"ERROR MESSAGE", "can_autofix":true},
|
||||
}
|
||||
|
||||
In this case UI shows "Fix" button next to error message.
|
||||
|
||||
#### Phase 2
|
||||
|
||||
If user clicks on "Fix" button, UI sends request to perform an automatic fix
|
||||
|
||||
POST /control/install/check_config
|
||||
|
||||
{
|
||||
"dns":{"port":53,"ip":"127.0.0.1","autofix":true},
|
||||
}
|
||||
|
||||
Deactivate (save backup as `resolved.conf.orig`) and stop DNSStubListener:
|
||||
|
||||
sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
|
||||
systemctl reload-or-restart systemd-resolved
|
||||
|
||||
Server replies:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"dns":{"status":""},
|
||||
}
|
||||
|
||||
|
||||
### "Apply configuration" command
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/install/configure
|
||||
|
||||
{
|
||||
"web":{"port":80,"ip":"192.168.11.33"},
|
||||
"dns":{"port":53,"ip":"127.0.0.1"},
|
||||
"username":"u",
|
||||
"password":"p",
|
||||
}
|
||||
|
||||
Server checks the parameters once again, restarts DNS server, replies:
|
||||
|
||||
200 OK
|
||||
|
||||
On error, server responds with code 400 or 500. In this case UI should show error message and reset to the beginning.
|
||||
|
||||
400 Bad Request
|
||||
|
||||
ERROR MESSAGE
|
||||
|
||||
|
||||
## Updating
|
||||
|
||||
Algorithm of an update by command:
|
||||
|
||||
* UI requests the latest version information from Server
|
||||
* Server requests information from Internet; stores the data in cache for several hours; sends data to UI
|
||||
* If UI sees that a new version is available, it shows notification message and "Update Now" button
|
||||
* When user clicks on "Update Now" button, UI sends Update command to Server
|
||||
* UI shows "Please wait, AGH is being updated..." message
|
||||
* Server performs an update:
|
||||
* Use working directory from `--work-dir` if necessary
|
||||
* Download new package for the current OS and CPU
|
||||
* Unpack the package to a temporary directory `update-vXXX`
|
||||
* Copy the current configuration file to the directory we unpacked new AGH to
|
||||
* Check configuration compatibility by executing `./AGH --check-config`. If this command fails, we won't be able to update.
|
||||
* Create `backup-vXXX` directory and copy the current configuration file there
|
||||
* Copy supporting files (README, LICENSE, etc.) to backup directory
|
||||
* Copy supporting files from the update directory to the current directory
|
||||
* Move the current binary file to backup directory
|
||||
* Note: if power fails here, AGH won't be able to start at system boot. Administrator has to fix it manually
|
||||
* Move new binary file to the current directory
|
||||
* Send response to UI
|
||||
* Stop all tasks, including DNS server, DHCP server, HTTP server
|
||||
* If AGH is running as a service, use service control functionality to restart
|
||||
* If AGH is not running as a service, use the current process arguments to start a new process
|
||||
* Exit process
|
||||
* UI resends Get Status command until Server responds to it with the new version. This means that Server is successfully restarted after update.
|
||||
* UI reloads itself
|
||||
|
||||
|
||||
### Get version command
|
||||
|
||||
On receiving this request server downloads version.json data from github and stores it in cache for several hours.
|
||||
|
||||
Example of version.json data:
|
||||
|
||||
{
|
||||
"version": "v0.95-hotfix",
|
||||
"announcement": "AdGuard Home v0.95-hotfix is now available!",
|
||||
"announcement_url": "",
|
||||
"download_windows_amd64": "",
|
||||
"download_windows_386": "",
|
||||
"download_darwin_amd64": "",
|
||||
"download_linux_amd64": "",
|
||||
"download_linux_386": "",
|
||||
"download_linux_arm": "",
|
||||
"download_linux_arm64": "",
|
||||
"download_linux_mips": "",
|
||||
"download_linux_mipsle": "",
|
||||
"selfupdate_min_version": "v0.0"
|
||||
}
|
||||
|
||||
Server can only auto-update if the current version is equal or higher than `selfupdate_min_version`.
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/version.json
|
||||
|
||||
{
|
||||
"recheck_now": true | false // if false, server will check for a new version data only once in several hours
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"new_version": "v0.95",
|
||||
"announcement": "AdGuard Home v0.95 is now available!",
|
||||
"announcement_url": "http://...",
|
||||
"can_autoupdate": true
|
||||
}
|
||||
|
||||
If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
|
||||
|
||||
Response with empty body:
|
||||
|
||||
200 OK
|
||||
|
||||
It means that update check is disabled by user. UI should do nothing.
|
||||
|
||||
|
||||
### Update command
|
||||
|
||||
Perform an update procedure to the latest available version
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/update
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
Error response:
|
||||
|
||||
500
|
||||
|
||||
UI shows error message "Auto-update has failed"
|
||||
|
||||
|
||||
## Enable DHCP server
|
||||
|
||||
Algorithm:
|
||||
|
||||
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
|
||||
* User clicks on "Check DHCP"; UI sends request to server
|
||||
* Server may fail to detect whether there is another DHCP server working in the network. In this case UI shows a warning.
|
||||
* Server may detect that a dynamic IP configuration is used for this interface. In this case UI shows a warning.
|
||||
* UI enables "Enable DHCP" button
|
||||
* User clicks on "Enable DHCP"; UI sends request to server
|
||||
* Server sets a static IP (if necessary), enables DHCP server, sends the status back to UI
|
||||
* UI shows the status
|
||||
|
||||
|
||||
### "Show DHCP status" command
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/dhcp/status
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"config":{
|
||||
"enabled":false,
|
||||
"interface_name":"...",
|
||||
"gateway_ip":"...",
|
||||
"subnet_mask":"...",
|
||||
"range_start":"...",
|
||||
"range_end":"...",
|
||||
"lease_duration":60,
|
||||
"icmp_timeout_msec":0
|
||||
},
|
||||
"leases":[
|
||||
{"ip":"...","mac":"...","hostname":"...","expires":"..."}
|
||||
...
|
||||
],
|
||||
"static_leases":[
|
||||
{"ip":"...","mac":"...","hostname":"..."}
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
### "Check DHCP" command
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/find_active_dhcp
|
||||
|
||||
vboxnet0
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"other_server": {
|
||||
"found": "yes|no|error",
|
||||
"error": "Error message", // set if found=error
|
||||
},
|
||||
"static_ip": {
|
||||
"static": "yes|no|error",
|
||||
"ip": "<Current dynamic IP address>", // set if static=no
|
||||
}
|
||||
}
|
||||
|
||||
If `other_server.found` is:
|
||||
* `no`: everything is fine - there is no other DHCP server
|
||||
* `yes`: we found another DHCP server. UI shows a warning.
|
||||
* `error`: we failed to determine whether there's another DHCP server. `other_server.error` contains error details. UI shows a warning.
|
||||
|
||||
If `static_ip.static` is:
|
||||
* `yes`: everything is fine - server uses static IP address.
|
||||
|
||||
* `no`: `static_ip.ip` contains the current dynamic IP address which we may set as static. In this case UI shows a warning:
|
||||
|
||||
Your system uses dynamic IP address configuration for interface <CURRENT INTERFACE NAME>. In order to use DHCP server a static IP address must be set. Your current IP address is <static_ip.ip>. We will automatically set this IP address as static if you press Enable DHCP button.
|
||||
|
||||
* `error`: this means that the server failed to check for a static IP. In this case UI shows a warning:
|
||||
|
||||
In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.
|
||||
|
||||
|
||||
### "Enable DHCP" command
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/set_config
|
||||
|
||||
{
|
||||
"enabled":true,
|
||||
"interface_name":"vboxnet0",
|
||||
"gateway_ip":"192.169.56.1",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"range_start":"192.169.56.3",
|
||||
"range_end":"192.169.56.3",
|
||||
"lease_duration":60,
|
||||
"icmp_timeout_msec":0
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
OK
|
||||
|
||||
|
||||
### Static IP check/set
|
||||
|
||||
Before enabling DHCP server we have to make sure the network interface we use has a static IP configured.
|
||||
|
||||
#### Phase 1
|
||||
|
||||
On Debian systems DHCP is configured by `/etc/dhcpcd.conf`.
|
||||
|
||||
To detect if a static IP is used currently we search for line
|
||||
|
||||
interface eth0
|
||||
|
||||
and then look for line
|
||||
|
||||
static ip_address=...
|
||||
|
||||
If the interface already has a static IP, everything is set up, we don't have to change anything.
|
||||
|
||||
To get the current IP address along with netmask we execute
|
||||
|
||||
ip -oneline -family inet address show eth0
|
||||
|
||||
which will print:
|
||||
|
||||
2: eth0 inet 192.168.0.1/24 brd 192.168.0.255 scope global eth0\ valid_lft forever preferred_lft forever
|
||||
|
||||
To get the current gateway address:
|
||||
|
||||
ip route show dev enp2s0
|
||||
|
||||
which will print:
|
||||
|
||||
default via 192.168.0.1 proto dhcp metric 100
|
||||
|
||||
|
||||
#### Phase 2
|
||||
|
||||
This method only works on Raspbian.
|
||||
|
||||
On Ubuntu DHCP for a network interface can't be disabled via `dhcpcd.conf`. This must be configured in `/etc/netplan/01-netcfg.yaml`.
|
||||
|
||||
Fedora doesn't use `dhcpcd.conf` configuration at all.
|
||||
|
||||
Step 1.
|
||||
|
||||
To set a static IP address we add these lines to `dhcpcd.conf`:
|
||||
|
||||
interface eth0
|
||||
static ip_address=192.168.0.1/24
|
||||
static routers=192.168.0.1
|
||||
static domain_name_servers=192.168.0.1
|
||||
|
||||
* Don't set 'routers' if we couldn't find gateway IP
|
||||
* Set 'domain_name_servers' equal to our IP
|
||||
|
||||
Step 2.
|
||||
|
||||
If we would set a different IP address, we'd need to replace the IP address for the current network configuration. But currently this step isn't necessary.
|
||||
|
||||
ip addr replace dev eth0 192.168.0.1/24
|
||||
|
||||
|
||||
### Add a static lease
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/add_static_lease
|
||||
|
||||
{
|
||||
"mac":"...",
|
||||
"ip":"...",
|
||||
"hostname":"..."
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
|
||||
### Remove a static lease
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/remove_static_lease
|
||||
|
||||
{
|
||||
"mac":"...",
|
||||
"ip":"...",
|
||||
"hostname":"..."
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
|
||||
## Device Names and Per-client Settings
|
||||
|
||||
When a client requests information from DNS server, he's identified by IP address.
|
||||
Administrator can set a name for a client with a known IP and also override global settings for this client. The name is used to improve readability of DNS logs: client's name is shown in UI next to its IP address. The names are loaded from 3 sources:
|
||||
* automatically from "/etc/hosts" file. It's a list of `IP<->Name` entries which is loaded once on AGH startup from "/etc/hosts" file.
|
||||
* automatically using rDNS. It's a list of `IP<->Name` entries which is added in runtime using rDNS mechanism when a client first makes a DNS request.
|
||||
* manually configured via UI. It's a list of client's names and their settings which is loaded from configuration file and stored on disk.
|
||||
|
||||
### Per-client settings
|
||||
|
||||
UI provides means to manage the list of known clients (List/Add/Update/Delete) and their settings. These settings are stored in configuration file as an array of objects.
|
||||
|
||||
Notes:
|
||||
|
||||
* `name`, `ip` and `mac` values are unique.
|
||||
|
||||
* `ip` & `mac` values can't be set both at the same time.
|
||||
|
||||
* If `mac` is set and DHCP server is enabled, IP is taken from DHCP lease table.
|
||||
|
||||
* If `use_global_settings` is true, then DNS responses for this client are processed and filtered using global settings.
|
||||
|
||||
* If `use_global_settings` is false, then the client-specific settings are used to override (enable or disable) global settings.
|
||||
|
||||
|
||||
### Get list of clients
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/clients
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
clients: [
|
||||
{
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safesearch_enabled: false
|
||||
}
|
||||
]
|
||||
auto_clients: [
|
||||
{
|
||||
name: "host"
|
||||
ip: "..."
|
||||
source: "etc/hosts" || "rDNS"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
### Add client
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/clients/add
|
||||
|
||||
{
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safesearch_enabled: false
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
Error response (Client already exists):
|
||||
|
||||
400
|
||||
|
||||
|
||||
### Update client
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/clients/update
|
||||
|
||||
{
|
||||
name: "client1"
|
||||
data: {
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safesearch_enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
Error response (Client not found):
|
||||
|
||||
400
|
||||
|
||||
|
||||
### Delete client
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/clients/delete
|
||||
|
||||
{
|
||||
name: "client1"
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
Error response (Client not found):
|
||||
|
||||
400
|
||||
|
||||
|
||||
## DNS access settings
|
||||
|
||||
There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request.
|
||||
|
||||
There are 3 types of access settings:
|
||||
* allowed_clients: Only these clients are allowed to make DNS requests.
|
||||
* disallowed_clients: These clients are not allowed to make DNS requests.
|
||||
* blocked_hosts: These hosts are not allowed to be resolved by a DNS request.
|
||||
|
||||
|
||||
### List access settings
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/access/list
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
allowed_clients: ["127.0.0.1", ...]
|
||||
disallowed_clients: ["127.0.0.1", ...]
|
||||
blocked_hosts: ["host.com", ...]
|
||||
}
|
||||
|
||||
|
||||
### Set access settings
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/access/set
|
||||
|
||||
{
|
||||
allowed_clients: ["127.0.0.1", ...]
|
||||
disallowed_clients: ["127.0.0.1", ...]
|
||||
blocked_hosts: ["host.com", ...]
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM golang:alpine AS build
|
||||
|
||||
RUN apk add --update git make build-base npm && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /src/AdGuardHome
|
||||
COPY . /src/AdGuardHome
|
||||
RUN make
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
|
||||
|
||||
# Update CA certs
|
||||
RUN apk --no-cache --update add ca-certificates libcap && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
|
||||
chown -R nobody: /opt/adguardhome
|
||||
|
||||
COPY --from=build --chown=nobody:nogroup /src/AdGuardHome/AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
||||
|
||||
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
|
||||
|
||||
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
|
||||
|
||||
WORKDIR /opt/adguardhome/work
|
||||
|
||||
#USER nobody
|
||||
|
||||
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
|
||||
CMD ["-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM easypi/alpine-arm:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_arm"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_386"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_amd64"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
23
Dockerfile.travis
Normal file
23
Dockerfile.travis
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
|
||||
|
||||
# Update CA certs
|
||||
RUN apk --no-cache --update add ca-certificates libcap && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
|
||||
chown -R nobody: /opt/adguardhome
|
||||
|
||||
COPY --chown=nobody:nogroup ./AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
|
||||
|
||||
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
|
||||
|
||||
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
|
||||
|
||||
WORKDIR /opt/adguardhome/work
|
||||
|
||||
#USER nobody
|
||||
|
||||
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
|
||||
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
|
||||
6
Makefile
6
Makefile
@@ -4,6 +4,7 @@ NATIVE_GOARCH = $(shell unset GOARCH; go env GOARCH)
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js')
|
||||
STATIC = build/static/index.html
|
||||
CHANNEL ?= release
|
||||
|
||||
TARGET=AdGuardHome
|
||||
|
||||
@@ -19,11 +20,10 @@ client/node_modules: client/package.json client/package-lock.json
|
||||
$(STATIC): $(JSFILES) client/node_modules
|
||||
npm --prefix client run build-prod
|
||||
|
||||
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
go get -d .
|
||||
$(TARGET): $(STATIC) *.go home/*.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr -z
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VersionString=$(GIT_VERSION)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(GIT_VERSION) -X main.channel=$(CHANNEL)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
|
||||
clean:
|
||||
|
||||
215
README.md
215
README.md
@@ -11,11 +11,21 @@
|
||||
<a href="https://adguard.com/">AdGuard.com</a> |
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
|
||||
<a href="https://reddit.com/r/Adguard">Reddit</a> |
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a>
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a> |
|
||||
<a href="https://t.me/adguard_en">Telegram</a>
|
||||
<br /><br />
|
||||
<a href="https://travis-ci.org/AdguardTeam/AdGuardHome">
|
||||
<img src="https://travis-ci.org/AdguardTeam/AdGuardHome.svg" alt="Build status" />
|
||||
</a>
|
||||
<a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
|
||||
<img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage" />
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
|
||||
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card" />
|
||||
</a>
|
||||
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
|
||||
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
|
||||
</a>
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
|
||||
</a>
|
||||
@@ -29,122 +39,96 @@
|
||||
|
||||
<hr />
|
||||
|
||||
# AdGuard Home
|
||||
|
||||
AdGuard Home is a network-wide software for blocking ads & tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that.
|
||||
|
||||
## How does AdGuard Home work?
|
||||
It operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
|
||||
|
||||
AdGuard Home operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
|
||||
* [Getting Started](#getting-started)
|
||||
* [Comparing AdGuard Home to other solutions](#comparison)
|
||||
* [How is this different from public AdGuard DNS servers?](#comparison-adguard-dns)
|
||||
* [How does AdGuard Home compare to Pi-Hole](#comparison-pi-hole)
|
||||
* [How does AdGuard Home compare to traditional ad blockers](#comparison-adblock)
|
||||
* [How to build from source](#how-to-build)
|
||||
* [Contributing](#contributing)
|
||||
* [Test unstable versions](#test-unstable-versions)
|
||||
* [Reporting issues](#reporting-issues)
|
||||
* [Help with translations](#translate)
|
||||
* [Acknowledgments](#acknowledgments)
|
||||
|
||||
## How is this different from public AdGuard DNS servers?
|
||||
<a id="getting-started"></a>
|
||||
## Getting Started
|
||||
|
||||
Running your own AdGuard Home server allows you to do much more than using a public DNS server.
|
||||
Please read the [Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started) article on our Wiki to learn how to install AdGuard Home, and how to configure your devices to use it.
|
||||
|
||||
* Choose what exactly will the server block or not block;
|
||||
* Monitor your network activity;
|
||||
* Add your own custom filtering rules;
|
||||
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
|
||||
|
||||
In the future, AdGuard Home is supposed to become more than just a DNS server.
|
||||
### Guides
|
||||
|
||||
## Installation
|
||||
* [Configuration](https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration)
|
||||
* [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption)
|
||||
* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi)
|
||||
* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS)
|
||||
|
||||
### Mac
|
||||
### API
|
||||
|
||||
Download this file: [AdGuardHome_v0.92_MacOS.zip](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92/AdGuardHome_v0.92_MacOS.zip), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
If you want to integrate with AdGuard Home, you can use our [REST API](https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi).
|
||||
Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://community.home-assistant.io/t/community-hass-io-add-on-adguard-home).
|
||||
|
||||
### Windows 64-bit
|
||||
<a id="comparison"></a>
|
||||
## Comparing AdGuard Home to other solutions
|
||||
|
||||
Download this file: [AdGuardHome_v0.92_Windows.zip](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92/AdGuardHome_v0.92_Windows.zip), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
<a id="comparison-adguard-dns"></a>
|
||||
### How is this different from public AdGuard DNS servers?
|
||||
|
||||
### Linux 64-bit Intel
|
||||
Running your own AdGuard Home server allows you to do much more than using a public DNS server. It's a completely different level. See for yourself:
|
||||
|
||||
Download this file: [AdGuardHome_v0.92_linux_amd64.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92/AdGuardHome_v0.92_linux_amd64.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
* Choose what exactly will the server block or not block.
|
||||
* Monitor your network activity.
|
||||
* Add your own custom filtering rules.
|
||||
* **Most importantly, this is your own server, and you are the only one who's in control.**
|
||||
|
||||
### Linux 32-bit Intel
|
||||
<a id="comparison-pi-hole"></a>
|
||||
### How does AdGuard Home compare to Pi-Hole
|
||||
|
||||
Download this file: [AdGuardHome_v0.92_linux_386.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92/AdGuardHome_v0.92_linux_386.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
At this point, AdGuard Home has a lot in common with Pi-Hole. Both block ads and trackers using "DNS sinkholing" method, and both allow customizing what's blocked.
|
||||
|
||||
### Raspberry Pi (32-bit ARM)
|
||||
> We're not going to stop here. DNS sinkholing is not a bad starting point, but this is just the beginning.
|
||||
|
||||
Download this file: [AdGuardHome_v0.92_linux_arm.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92/AdGuardHome_v0.92_linux_arm.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
AdGuard Home provides a lot of features out-of-the-box with no need to install and configure additional software. We want it to be simple to the point when even casual users can set it up with minimal effort.
|
||||
|
||||
## How to update
|
||||
> Disclaimer: some of the listed features can be added to Pi-Hole by installing additional software or by manually using SSH terminal and reconfiguring one of the utilities Pi-Hole consists of. However, in our opinion, this cannot be legitimately counted as a Pi-Hole's feature.
|
||||
|
||||
We have not yet implemented an auto-update of AdGuard Home, but it is planned for future versions: #448.
|
||||
| Feature | AdGuard Home | Pi-Hole |
|
||||
|-------------------------------------------------------------------------|--------------|--------------------------------------------------------|
|
||||
| Blocking ads and trackers | ✅ | ✅ |
|
||||
| Customizing blocklists | ✅ | ✅ |
|
||||
| Built-in DHCP server | ✅ | ✅ |
|
||||
| HTTPS for the Admin interface | ✅ | Kind of, but you'll need to manually configure lighthttp |
|
||||
| Encrypted DNS upstream servers (DNS-over-HTTPS, DNS-over-TLS, DNSCrypt) | ✅ | ❌ (requires additional software) |
|
||||
| Cross-platform | ✅ | ❌ (not natively, only via Docker) |
|
||||
| Running as a DNS-over-HTTPS or DNS-over-TLS server | ✅ | ❌ (requires additional software) |
|
||||
| Blocking phishing and malware domains | ✅ | ❌ |
|
||||
| Parental control (blocking adult domains) | ✅ | ❌ |
|
||||
| Force Safe search on search engines | ✅ | ❌ |
|
||||
| Per-client (device) configuration | ✅ | ❌ |
|
||||
| Access settings (choose who can use AGH DNS) | ✅ | ❌ |
|
||||
|
||||
At the moment, the update procedure is manual:
|
||||
<a id="comparison-adblock"></a>
|
||||
### How does AdGuard Home compare to traditional ad blockers
|
||||
|
||||
1. Download the new AdGuard Home binary.
|
||||
2. Replace the old file with the new one.
|
||||
3. Restart AdGuard Home.
|
||||
It depends.
|
||||
|
||||
## How to run
|
||||
|
||||
DNS works on port 53, which requires superuser privileges. Therefore, you need to run it with `sudo` in terminal:
|
||||
|
||||
```bash
|
||||
sudo ./AdGuardHome
|
||||
```
|
||||
|
||||
Now open the browser and navigate to http://localhost:3000/ to control your AdGuard Home service.
|
||||
|
||||
### Running without superuser
|
||||
|
||||
You can run AdGuard Home without superuser privileges, but you need to instruct it to use a different port rather than 53. You can do that by editing `AdGuardHome.yaml` and finding these two lines:
|
||||
|
||||
```yaml
|
||||
dns:
|
||||
port: 53
|
||||
```
|
||||
|
||||
You can change port 53 to anything above 1024 to avoid requiring superuser privileges.
|
||||
|
||||
If the file does not exist, create it in the same folder, type these two lines down and save.
|
||||
|
||||
### Additional configuration
|
||||
|
||||
Upon the first execution, a file named `AdGuardHome.yaml` will be created, with default values written in it. You can modify the file while your AdGuard Home service is not running. Otherwise, any changes to the file will be lost because the running program will overwrite them.
|
||||
|
||||
Settings are stored in [YAML format](https://en.wikipedia.org/wiki/YAML), possible parameters that you can configure are listed below:
|
||||
|
||||
* `bind_host` — Web interface IP address to listen on.
|
||||
* `bind_port` — Web interface IP port to listen on.
|
||||
* `auth_name` — Web interface optional authorization username.
|
||||
* `auth_pass` — Web interface optional authorization password.
|
||||
* `dns` — DNS configuration section.
|
||||
* `port` — DNS server port to listen on.
|
||||
* `protection_enabled` — Whether any kind of filtering and protection should be done, when off it works as a plain dns forwarder.
|
||||
* `filtering_enabled` — Filtering of DNS requests based on filter lists.
|
||||
* `blocked_response_ttl` — For how many seconds the clients should cache a filtered response. Low values are useful on LAN if you change filters very often, high values are useful to increase performance and save traffic.
|
||||
* `querylog_enabled` — Query logging (also used to calculate top 50 clients, blocked domains and requested domains for statistical purposes).
|
||||
* `ratelimit` — DDoS protection, specifies in how many packets per second a client should receive. Anything above that is silently dropped. To disable set 0, default is 20. Safe to disable if DNS server is not available from internet.
|
||||
* `ratelimit_whitelist` — If you want exclude some IP addresses from ratelimiting but keep ratelimiting on for others, put them here.
|
||||
* `refuse_any` — Another DDoS protection mechanism. Requests of type ANY are rarely needed, so refusing to serve them mitigates against attackers trying to use your DNS as a reflection. Safe to disable if DNS server is not available from internet.
|
||||
* `bootstrap_dns` — DNS server used for initial hostname resolution in case if upstream server name is a hostname.
|
||||
* `parental_sensitivity` — Age group for parental control-based filtering, must be either 3, 10, 13 or 17 if enabled.
|
||||
* `parental_enabled` — Parental control-based DNS requests filtering.
|
||||
* `safesearch_enabled` — Enforcing "Safe search" option for search engines, when possible.
|
||||
* `safebrowsing_enabled` — Filtering of DNS requests based on safebrowsing.
|
||||
* `upstream_dns` — List of upstream DNS servers.
|
||||
* `filters` — List of filters, each filter has the following values:
|
||||
* `enabled` — Current filter's status (enabled/disabled).
|
||||
* `url` — URL pointing to the filter contents (filtering rules).
|
||||
* `name` — Name of the filter. If it's an adguard syntax filter it will get updated automatically, otherwise it stays unchanged.
|
||||
* `last_updated` — Time when the filter was last updated from server.
|
||||
* `ID` - filter ID (must be unique).
|
||||
* `user_rules` — User-specified filtering rules.
|
||||
|
||||
Removing an entry from settings file will reset it to the default value. Deleting the file will reset all settings to the default values.
|
||||
"DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities). However, this level of protection is enough for some users.
|
||||
|
||||
<a id="how-to-build"></a>
|
||||
## How to build from source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need:
|
||||
|
||||
* [go](https://golang.org/dl/) v1.11 or later.
|
||||
* [node.js](https://nodejs.org/en/download/)
|
||||
* [go](https://golang.org/dl/) v1.12 or later.
|
||||
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
||||
|
||||
You can either install it via the provided links or use [brew.sh](https://brew.sh/) if you're on Mac:
|
||||
|
||||
@@ -162,11 +146,48 @@ cd AdGuardHome
|
||||
make
|
||||
```
|
||||
|
||||
#### (For devs) Upload translations
|
||||
```
|
||||
node upload.js
|
||||
```
|
||||
|
||||
#### (For devs) Download translations
|
||||
```
|
||||
node download.js
|
||||
```
|
||||
|
||||
<a id="contributing"></a>
|
||||
## Contributing
|
||||
|
||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||
|
||||
### How to update translations
|
||||
<a id="test-unstable-versions"></a>
|
||||
### Test unstable versions
|
||||
|
||||
There are two options how you can install an unstable version.
|
||||
You can either install a beta version of AdGuard Home which we update periodically,
|
||||
or you can use the Docker image from the `edge` tag, which is synced with the repo master branch.
|
||||
|
||||
* [Docker Hub](https://hub.docker.com/r/adguard/adguardhome)
|
||||
* Beta builds
|
||||
* [Rapsberry Pi (32-bit ARM)](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm.tar.gz)
|
||||
* [MacOS](https://static.adguard.com/adguardhome/beta/AdGuardHome_MacOS.zip)
|
||||
* [Windows 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_amd64.zip)
|
||||
* [Windows 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_386.zip)
|
||||
* [Linux 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz)
|
||||
* [Linux 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
|
||||
* [FreeBSD 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz)
|
||||
* [64-bit ARM](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz)
|
||||
* [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.tar.gz)
|
||||
* [MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle.tar.gz)
|
||||
|
||||
<a id="reporting-issues"></a>
|
||||
### Report issues
|
||||
|
||||
If you run into any problem or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdGuardHome/issues) and click on the `New issue` button.
|
||||
|
||||
<a id="translate"></a>
|
||||
### Help with translations
|
||||
|
||||
If you want to help with AdGuard Home translations, please learn more about translating AdGuard products here: https://kb.adguard.com/en/general/adguard-translations
|
||||
|
||||
@@ -190,20 +211,7 @@ Example of `oneskyapp.json`
|
||||
}
|
||||
```
|
||||
|
||||
#### Upload translations
|
||||
```
|
||||
node upload.js
|
||||
```
|
||||
|
||||
#### Download translations
|
||||
```
|
||||
node download.js
|
||||
```
|
||||
|
||||
## Reporting issues
|
||||
|
||||
If you run into any problem or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdGuardHome/issues) and click on the `New issue` button.
|
||||
|
||||
<a id="acknowledgments"></a>
|
||||
## Acknowledgments
|
||||
|
||||
This software wouldn't have been possible without:
|
||||
@@ -213,6 +221,9 @@ This software wouldn't have been possible without:
|
||||
* [gcache](https://github.com/bluele/gcache)
|
||||
* [miekg's dns](https://github.com/miekg/dns)
|
||||
* [go-yaml](https://github.com/go-yaml/yaml)
|
||||
* [service](https://godoc.org/github.com/kardianos/service)
|
||||
* [dnsproxy](https://github.com/AdguardTeam/dnsproxy)
|
||||
* [urlfilter](https://github.com/AdguardTeam/urlfilter)
|
||||
* [Node.js](https://nodejs.org/) and it's libraries:
|
||||
* [React.js](https://reactjs.org)
|
||||
* [Tabler](https://github.com/tabler/tabler)
|
||||
|
||||
289
app.go
289
app.go
@@ -1,289 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/hmage/golibs/log"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// VersionString will be set through ldflags, contains current version
|
||||
var VersionString = "undefined"
|
||||
|
||||
func main() {
|
||||
log.Printf("AdGuard Home web interface backend, version %s\n", VersionString)
|
||||
box := packr.NewBox("build/static")
|
||||
{
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
executableName := filepath.Base(executable)
|
||||
if executableName == "AdGuardHome" {
|
||||
// Binary build
|
||||
config.ourBinaryDir = filepath.Dir(executable)
|
||||
} else {
|
||||
// Most likely we're debugging -- using current working directory in this case
|
||||
workDir, _ := os.Getwd()
|
||||
config.ourBinaryDir = workDir
|
||||
}
|
||||
log.Printf("Current working directory is %s", config.ourBinaryDir)
|
||||
}
|
||||
|
||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
// therefore, we must do it manually instead of using a lib
|
||||
{
|
||||
var printHelp func()
|
||||
var configFilename *string
|
||||
var bindHost *string
|
||||
var bindPort *int
|
||||
var opts = []struct {
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callbackWithValue func(value string)
|
||||
callbackNoValue func()
|
||||
}{
|
||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }, nil},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }, nil},
|
||||
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic("Got port that is not a number")
|
||||
}
|
||||
bindPort = &v
|
||||
}, nil},
|
||||
{"verbose", "v", "enable verbose output", nil, func() { log.Verbose = true }},
|
||||
{"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }},
|
||||
}
|
||||
printHelp = func() {
|
||||
fmt.Printf("Usage:\n\n")
|
||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
||||
fmt.Printf("Options:\n")
|
||||
for _, opt := range opts {
|
||||
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
v := os.Args[i]
|
||||
knownParam := false
|
||||
for _, opt := range opts {
|
||||
if v == "--"+opt.longName || v == "-"+opt.shortName {
|
||||
if opt.callbackWithValue != nil {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callbackWithValue(os.Args[i])
|
||||
} else if opt.callbackNoValue != nil {
|
||||
opt.callbackNoValue()
|
||||
}
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !knownParam {
|
||||
log.Printf("ERROR: unknown option %v\n", v)
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
}
|
||||
if configFilename != nil {
|
||||
config.ourConfigFilename = *configFilename
|
||||
}
|
||||
|
||||
err := askUsernamePasswordIfPossible()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Do the upgrade if necessary
|
||||
err = upgradeConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// parse from config file
|
||||
err = parseConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
if bindHost != nil {
|
||||
config.BindHost = *bindHost
|
||||
}
|
||||
if bindPort != nil {
|
||||
config.BindPort = *bindPort
|
||||
}
|
||||
}
|
||||
|
||||
// Load filters from the disk
|
||||
// And if any filter has zero ID, assign a new one
|
||||
for i := range config.Filters {
|
||||
filter := &config.Filters[i] // otherwise we're operating on a copy
|
||||
if filter.ID == 0 {
|
||||
filter.ID = assignUniqueFilterID()
|
||||
}
|
||||
err := filter.load()
|
||||
if err != nil {
|
||||
// This is okay for the first start, the filter will be loaded later
|
||||
log.Printf("Couldn't load filter %d contents due to %s", filter.ID, err)
|
||||
// clear LastUpdated so it gets fetched right away
|
||||
}
|
||||
if len(filter.Rules) == 0 {
|
||||
filter.LastUpdated = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// Update filters we've just loaded right away, don't wait for periodic update timer
|
||||
go func() {
|
||||
refreshFiltersIfNeccessary(false)
|
||||
// Save the updated config
|
||||
err := config.write()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
signalChannel := make(chan os.Signal)
|
||||
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
go func() {
|
||||
<-signalChannel
|
||||
cleanup()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// Save the updated config
|
||||
err := config.write()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
|
||||
go periodicallyRefreshFilters()
|
||||
|
||||
http.Handle("/", optionalAuthHandler(http.FileServer(box)))
|
||||
registerControlHandlers()
|
||||
|
||||
err = startDNSServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = startDHCPServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf("http://%s", address)
|
||||
log.Println("Go to " + URL)
|
||||
log.Fatal(http.ListenAndServe(address, nil))
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
err := stopDNSServer()
|
||||
if err != nil {
|
||||
log.Printf("Couldn't stop DNS server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getInput() (string, error) {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
text := scanner.Text()
|
||||
err := scanner.Err()
|
||||
return text, err
|
||||
}
|
||||
|
||||
func promptAndGet(prompt string) (string, error) {
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
input, err := getInput()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get input, aborting: %s", err)
|
||||
return "", err
|
||||
}
|
||||
if len(input) != 0 {
|
||||
return input, nil
|
||||
}
|
||||
// try again
|
||||
}
|
||||
}
|
||||
|
||||
func promptAndGetPassword(prompt string) (string, error) {
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Print("\n")
|
||||
if err != nil {
|
||||
log.Printf("Failed to get input, aborting: %s", err)
|
||||
return "", err
|
||||
}
|
||||
if len(password) != 0 {
|
||||
return string(password), nil
|
||||
}
|
||||
// try again
|
||||
}
|
||||
}
|
||||
|
||||
func askUsernamePasswordIfPossible() error {
|
||||
configfile := config.ourConfigFilename
|
||||
if !filepath.IsAbs(configfile) {
|
||||
configfile = filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
|
||||
}
|
||||
_, err := os.Stat(configfile)
|
||||
if !os.IsNotExist(err) {
|
||||
// do nothing, file exists
|
||||
return nil
|
||||
}
|
||||
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return nil // do nothing
|
||||
}
|
||||
if !terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||
return nil // do nothing
|
||||
}
|
||||
fmt.Printf("Would you like to set user/password for the web interface authentication (yes/no)?\n")
|
||||
yesno, err := promptAndGet("Please type 'yes' or 'no': ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if yesno[0] != 'y' && yesno[0] != 'Y' {
|
||||
return nil
|
||||
}
|
||||
username, err := promptAndGet("Please enter the username: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password, err := promptAndGetPassword("Please enter the password: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password2, err := promptAndGetPassword("Please enter password again: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if password2 != password {
|
||||
fmt.Printf("Passwords do not match! Aborting\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.AuthName = username
|
||||
config.AuthPass = password
|
||||
return nil
|
||||
}
|
||||
74
build_docker.sh
Executable file
74
build_docker.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eE
|
||||
set -o pipefail
|
||||
set -x
|
||||
|
||||
DOCKERFILE="Dockerfile.travis"
|
||||
IMAGE_NAME="adguard/adguardhome"
|
||||
|
||||
if [[ "${TRAVIS_BRANCH}" == "master" ]]
|
||||
then
|
||||
VERSION="edge"
|
||||
else
|
||||
VERSION=`git describe --abbrev=4 --dirty --always --tags`
|
||||
fi
|
||||
|
||||
build_image() {
|
||||
from="$(awk '$1 == toupper("FROM") { print $2 }' ${DOCKERFILE})"
|
||||
|
||||
# See https://hub.docker.com/r/multiarch/alpine/tags
|
||||
case "${GOARCH}" in
|
||||
arm64)
|
||||
alpineArch='arm64-edge'
|
||||
imageArch='arm64'
|
||||
;;
|
||||
arm)
|
||||
alpineArch='armhf-edge'
|
||||
imageArch='armhf'
|
||||
;;
|
||||
386)
|
||||
alpineArch='i386-edge'
|
||||
imageArch='i386'
|
||||
;;
|
||||
amd64)
|
||||
alpineArch='amd64-edge'
|
||||
;;
|
||||
*)
|
||||
alpineArch='amd64-edge'
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${GOOS}" == "linux" ]] && [[ "${GOARCH}" == "amd64" ]]
|
||||
then
|
||||
image="${IMAGE_NAME}:${VERSION}"
|
||||
else
|
||||
image="${IMAGE_NAME}:${imageArch}-${VERSION}"
|
||||
fi
|
||||
|
||||
make cleanfast; CGO_DISABLED=1 make
|
||||
|
||||
docker pull "multiarch/alpine:${alpineArch}"
|
||||
docker tag "multiarch/alpine:${alpineArch}" "$from"
|
||||
docker build -t "${image}" -f ${DOCKERFILE} .
|
||||
docker push ${image}
|
||||
if [[ "${VERSION}" != "edge" ]]
|
||||
then
|
||||
latest=${image/$VERSION/latest}
|
||||
docker tag "${image}" "${latest}"
|
||||
docker push ${latest}
|
||||
docker rmi ${latest}
|
||||
fi
|
||||
docker rmi "$from"
|
||||
}
|
||||
|
||||
# prepare qemu
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
|
||||
make clean
|
||||
|
||||
# Prepare releases
|
||||
GOOS=linux GOARCH=amd64 build_image
|
||||
GOOS=linux GOARCH=386 build_image
|
||||
GOOS=linux GOARCH=arm GOARM=6 build_image
|
||||
GOOS=linux GOARCH=arm64 GOARM=6 build_image
|
||||
4
client/.eslintrc
vendored
4
client/.eslintrc
vendored
@@ -45,9 +45,7 @@
|
||||
}],
|
||||
"class-methods-use-this": "off",
|
||||
"no-shadow": "off",
|
||||
"camelcase": ["error", {
|
||||
"properties": "never"
|
||||
}],
|
||||
"camelcase": "off",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"import/prefer-default-export": "off"
|
||||
|
||||
10099
client/package-lock.json
generated
vendored
10099
client/package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
14
client/package.json
vendored
14
client/package.json
vendored
@@ -16,7 +16,7 @@
|
||||
"file-saver": "^1.3.8",
|
||||
"i18next": "^12.0.0",
|
||||
"i18next-browser-languagedetector": "^2.2.3",
|
||||
"lodash": "^4.17.10",
|
||||
"lodash": "^4.17.11",
|
||||
"nanoid": "^1.2.3",
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.4.0",
|
||||
@@ -33,14 +33,12 @@
|
||||
"redux-actions": "^2.4.0",
|
||||
"redux-form": "^7.4.2",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
"svg-url-loader": "^2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^8.6.3",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-jest": "20.0.3",
|
||||
"babel-loader": "7.1.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
@@ -49,7 +47,8 @@
|
||||
"babel-runtime": "6.26.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"compression-webpack-plugin": "^1.1.11",
|
||||
"css-loader": "^0.28.11",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-react-app": "^2.1.0",
|
||||
@@ -60,7 +59,6 @@
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "1.1.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "20.0.4",
|
||||
"postcss-flexbugs-fixes": "3.2.0",
|
||||
"postcss-import": "^11.1.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
@@ -68,12 +66,12 @@
|
||||
"postcss-preset-env": "^5.1.0",
|
||||
"postcss-svg": "^2.4.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"stylelint": "9.2.1",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-webpack-plugin": "0.10.4",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "3.8.1",
|
||||
"webpack-dev-server": "2.9.4",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-merge": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
client/public/favicon.png
Normal file
BIN
client/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,16 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="shortcut icon" href="https://adguard.com/img/favicons/favicon.ico">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="icon" type="image/png" href="favicon.png" sizes="48x48">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
17
client/public/install.html
Normal file
17
client/public/install.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="icon" type="image/png" href="favicon.png" sizes="48x48">
|
||||
<title>Setup AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
250
client/src/__locales/bg.json
Normal file
250
client/src/__locales/bg.json
Normal file
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"url_added_successfully": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0435\u043d URL",
|
||||
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"save_config": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435",
|
||||
"enabled_dhcp": "DHCP \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d",
|
||||
"disabled_dhcp": "DHCP \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d",
|
||||
"dhcp_title": "DHCP \u0441\u044a\u0440\u0432\u044a\u0440 (\u0442\u0435\u0441\u0442\u043e\u0432\u0438!)",
|
||||
"dhcp_description": "\u0410\u043a\u043e \u0440\u0443\u0442\u0435\u0440\u0430 \u0432\u0438 \u043d\u0435 \u0440\u0430\u0437\u0434\u0430\u0432\u0430 DHCP \u0430\u0434\u0440\u0435\u0441\u0438, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f \u0432 AdGuard DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
|
||||
"dhcp_enable": "\u0420\u0437\u0440\u0435\u0448\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"dhcp_disable": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"dhcp_not_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043d\u044f\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0435 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
|
||||
"dhcp_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0442\u043e\u0440\u0438!",
|
||||
"dhcp_leases": "DHCP \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_leases_not_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u0438 DHCP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_config_saved": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u043d\u0430 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"form_error_required": "\u0417\u0430\u0434\u044a\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u043f\u043e\u043b\u0435",
|
||||
"form_error_ip_format": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d IPv4 \u0430\u0434\u0440\u0435\u0441",
|
||||
"form_error_positive": "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u0447\u0438\u0441\u043b\u043e",
|
||||
"dhcp_form_gateway_input": "IP \u0448\u043b\u044e\u0437",
|
||||
"dhcp_form_subnet_input": "\u041c\u0440\u0435\u0436\u043e\u0432\u0430 \u043c\u0430\u0441\u043a\u0430",
|
||||
"dhcp_form_range_title": "\u0413\u0440\u0443\u043f\u0430 \u043e\u0442 IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_form_range_start": "\u041f\u044a\u0440\u0432\u0438 \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_form_range_end": "\u041f\u043e\u0441\u043b\u0435\u0434\u0435\u043d \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_form_lease_title": "\u041e\u0442\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (\u0441\u0435\u043a\u0443\u043d\u0434\u0438)",
|
||||
"dhcp_form_lease_input": "\u041e\u0442\u0447\u0435\u0442 \u0437\u0430 \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_interface_select": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0440\u0435\u0436\u043e\u0432 \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0437\u0430 DHCP",
|
||||
"dhcp_hardware_address": "\u0425\u0430\u0440\u0434\u0443\u0435\u0440\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (MAC)",
|
||||
"dhcp_ip_addresses": "IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_table_hostname": "\u0418\u043c\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
|
||||
"dhcp_table_expires": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f",
|
||||
"dhcp_warning": "\u0410\u043a\u043e \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440, \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u044f\u043c\u0430 \u0434\u0440\u0443\u0433 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0412\u0438!",
|
||||
"back": "\u041d\u0430\u0437\u0430\u0434",
|
||||
"dashboard": "\u0422\u0430\u0431\u043b\u043e",
|
||||
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"filters": "\u0424\u0438\u043b\u0442\u0440\u0438",
|
||||
"query_log": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
|
||||
"faq": "\u0427\u0417\u0412",
|
||||
"version": "\u0432\u0435\u0440\u0441\u0438\u044f",
|
||||
"address": "\u0430\u0434\u0440\u0435\u0441",
|
||||
"on": "\u0412\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
|
||||
"off": "\u0418\u0417\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
|
||||
"copyright": "\u0410\u0432\u0442\u043e\u0440\u0441\u043a\u043e \u043f\u0440\u0430\u0432\u043e",
|
||||
"homepage": "\u0414\u043e\u043c\u0430\u0448\u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
|
||||
"report_an_issue": "\u0421\u044a\u043e\u0431\u0449\u0438 \u0437\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c",
|
||||
"enable_protection": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
|
||||
"enabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
|
||||
"disable_protection": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
|
||||
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
|
||||
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430\u0442\u0430",
|
||||
"dns_query": "DNS \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f",
|
||||
"blocked_by": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u043e\u0442",
|
||||
"stats_malware_phishing": "\u0432\u0438\u0440\u0443\u0441\u0438\/\u0430\u0442\u0430\u043a\u0438",
|
||||
"stats_adult": "\u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
|
||||
"stats_query_domain": "\u041d\u0430\u0439-\u043e\u0442\u0432\u0430\u0440\u044f\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
|
||||
"for_last_24_hours": "\u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
|
||||
"no_domains_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0438",
|
||||
"requests_count": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
|
||||
"top_blocked_domains": "\u041d\u0430\u0439-\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
|
||||
"top_clients": "\u041d\u0430\u0439-\u0430\u043a\u0442\u0438\u0432\u043d\u0438 IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"no_clients_found": "\u041d\u044f\u043ca \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"general_statistics": "\u041e\u0431\u0449\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0438\u043a\u0430",
|
||||
"number_of_dns_query_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
|
||||
"number_of_dns_query_blocked_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 \u0444\u0438\u043b\u0442\u0440\u0438\u0442\u0435 \u0437\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0430 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 AdGuard \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441\u044a\u0441 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
|
||||
"enforced_save_search": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"number_of_dns_query_to_safe_search": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043f\u0440\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"average_processing_time": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430",
|
||||
"average_processing_time_hint": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0432 \u043c\u0438\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0438",
|
||||
"block_domain_use_filters_and_hosts": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0434\u043e\u043c\u0435\u0439\u043d\u0438 - \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"filters_block_toggle_hint": "\u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 <a href='#filters'>\u0424\u0438\u043b\u0442\u0440\u0438<\/a>.",
|
||||
"use_adguard_browsing_sec": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 AdGuard \u043c\u043e\u0434\u0443\u043b \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
|
||||
"use_adguard_browsing_sec_hint": "\u041c\u043e\u0434\u0443\u043b \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442 \u0432 AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0432\u0430 \u0432\u0441\u044f\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u044f\u0442\u043e \u043e\u0442\u0432\u0430\u0440\u044f\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432 \u0447\u0435\u0440\u043d\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0437\u0430\u0441\u0442\u0440\u0430\u0448\u0430\u0432\u0430\u0449\u0438 \u0432\u0430\u0448\u0430\u0442\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0435\u043d \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u043e\u0439\u0442\u043e \u0437\u0430\u0449\u0438\u0442\u0430\u0432\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u0438 \u0438\u0437\u043f\u0440\u0430\u0449\u0430 \u0441\u0430\u043c\u043e SHA256 \u0441\u0443\u043c\u0430 \u0431\u0430\u0437\u0438\u0440\u0430\u043d\u0430 \u043d\u0430 \u0447\u0430\u0441\u0442 \u043e\u0442 \u0434\u043e\u043c\u0435\u0439\u043d\u0430 \u043a\u043e\u0439\u0442\u043e \u043f\u043e\u0441\u0435\u0449\u0430\u0432\u0430\u0442\u0435.",
|
||||
"use_adguard_parental": "\u0412\u043a\u043b\u044e\u0447\u0438 AdGuard \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"use_adguard_parental_hint": "\u041c\u043e\u0434\u0443\u043b XXX \u0432 AdGuard Home \u0449\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438 \u0434\u0430\u043b\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0438\u043c\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438 \u0437\u0430 \u0432\u044a\u0437\u0432\u044a\u0441\u0442\u043d\u0438. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u0441\u044a\u0449\u0438\u044f API \u0437\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u043a\u0430\u0442\u043e \u043f\u0440\u0438 \u043c\u043e\u0434\u0443\u043b\u0430 \u0437\u0430 \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442.",
|
||||
"enforce_safe_search": "\u0412\u043a\u043b\u044e\u0447\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enforce_save_search_hint": "AdGuard Home \u043f\u0440\u0438\u043b\u0430\u0433\u0430 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0432 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0442\u044a\u0440\u0441\u0430\u0447\u043a\u0438 \u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435: Google, Youtube, Bing, \u0438 Yandex.",
|
||||
"no_servers_specified": "\u041d\u044f\u043c\u0430 \u0438\u0437\u0431\u0440\u0430\u043d\u0438 \u0443\u0441\u043b\u0443\u0433\u0438",
|
||||
"no_settings": "\u041d\u044f\u043c\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"general_settings": "\u041e\u0431\u0449\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"upstream_dns": "\u0413\u043b\u0430\u0432\u0435\u043d DNS \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"upstream_dns_hint": "\u0410\u043a\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, AdGuard Home \u0449\u0435 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u0437\u0430 \u0433\u043b\u0430\u0432\u0435\u043d. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 tls:\/\/ \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043a\u0430 \u0437\u0430 DNS \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0449\u0438 TLS \u0432\u0440\u044a\u0437\u043a\u0430.",
|
||||
"test_upstream_btn": "\u0422\u0435\u0441\u0442\u0432\u0430\u0439 \u0433\u043b\u0430\u0432\u043d\u0438\u044f DNS",
|
||||
"apply_btn": "\u041f\u0440\u0438\u043b\u043e\u0436\u0438",
|
||||
"disabled_filtering_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
|
||||
"enabled_filtering_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0444\u0438\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
|
||||
"disabled_safe_browsing_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
|
||||
"enabled_safe_browsing_toast": "\u0420\u0437\u0440\u0435\u0448\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
|
||||
"disabled_parental_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"enabled_parental_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"disabled_safe_search_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enabled_save_search_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enabled_table_header": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438",
|
||||
"name_table_header": "\u0418\u043c\u0435",
|
||||
"filter_url_table_header": "URL \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"rules_count_table_header": "\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u043e\u0431\u0449\u043e",
|
||||
"last_time_updated_table_header": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0435\u043d",
|
||||
"actions_table_header": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
|
||||
"delete_table_action": "\u0418\u0437\u0442\u0440\u0438\u0439",
|
||||
"filters_and_hosts": "\u0427\u0435\u0440\u043d\u0438 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0441 \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"filters_and_hosts_hint": "AdGuard Home \u0440\u0430\u0437\u0431\u0438\u0440\u0430 adblock \u0438 host \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
|
||||
"no_filters_added": "\u041d\u044f\u043c\u0430 \u0434\u043e\u0431\u0430\u0432\u0435\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"add_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u0438 \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"cancel_btn": "\u041e\u0442\u043a\u0430\u0436\u0438",
|
||||
"enter_name_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 \u0438\u043c\u0435",
|
||||
"enter_url_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 URL",
|
||||
"check_updates_btn": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438 \u0437\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f",
|
||||
"new_filter_btn": "\u0412\u044a\u0432\u0435\u0434\u0438 \u043d\u043e\u0432 \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"enter_valid_filter_url": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d URL \u0437\u0430 \u0444\u0438\u043b\u0442\u044a\u0440\u0430 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 host \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430.",
|
||||
"custom_filter_rules": "\u041c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"custom_filter_rules_hint": "\u0412\u044a\u0432\u0435\u0436\u0434\u0430\u0439\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u043d\u0430 \u043d\u043e\u0432 \u0440\u0435\u0434. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 adblock \u0438\u043b\u0438 hosts \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
|
||||
"examples_title": "\u041f\u0440\u0438\u043c\u0435\u0440\u0438",
|
||||
"example_meaning_filter_block": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"example_meaning_filter_whitelist": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u043c\u0443 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"example_meaning_host_block": "AdGuard Home \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438 \u0441 127.0.0.1 = \u043f\u0440\u0430\u0437\u0435\u043d \u0430\u0434\u0440\u0435\u0441 \u0437\u0430 \u0434\u043e\u043c\u0435\u0439\u043d example.org (\u043d\u043e \u043d\u0435 \u0438 \u0437\u0430 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438).",
|
||||
"example_comment": "! \u0422\u043e\u0432\u0430 \u0435 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_comment_meaning": "\u043f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_comment_hash": "# \u0422\u043e\u0432\u0430 \u0435 \u0441\u044a\u0449\u043e \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_regex_meaning": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d\u0438 \u043a\u043e\u0439\u0442\u043e \u0441\u044a\u0432\u043f\u0430\u0434\u0430\u0442 \u0441\u044a\u0441 \u0441\u043b\u0435\u0434\u043d\u043e\u0442\u043e",
|
||||
"example_upstream_regular": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (UDP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
|
||||
"example_upstream_dot": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-TLS<\/0>",
|
||||
"example_upstream_doh": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 <0>DNS \u041f\u043e\u0434\u043f\u0438\u0441\u0432\u0430\u043d\u0435<\/0> \u0437\u0430 <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/2> \u0441\u044a\u0440\u0432\u044a\u0440\u0438",
|
||||
"example_upstream_tcp": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (TCP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
|
||||
"all_filters_up_to_date_toast": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0444\u0438\u043b\u0442\u0438 \u0441\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438",
|
||||
"updated_upstream_dns_toast": "\u0413\u043b\u043e\u0431\u0430\u043b\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0441\u0430 \u043e\u0431\u043d\u043e\u0432\u0435\u043d\u0438",
|
||||
"dns_test_ok_toast": "\u0412\u044a\u0432\u0435\u0434\u0435\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u044f\u0442 \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
|
||||
"dns_test_not_ok_toast": "\u0421\u044a\u0440\u0432\u044a\u0440 \"{{key}}\": \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0438, \u043c\u043e\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432\u044a\u0432\u0435\u0434\u0435\u043d \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
|
||||
"unblock_btn": "\u041e\u0442\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
|
||||
"block_btn": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
|
||||
"time_table_header": "\u0412\u0440\u0435\u043c\u0435",
|
||||
"domain_name_table_header": "\u0418\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d",
|
||||
"type_table_header": "\u0422\u0438\u043f",
|
||||
"response_table_header": "\u041e\u0442\u0433\u043e\u0432\u043e\u0440",
|
||||
"client_table_header": "\u041a\u043b\u0438\u0435\u043d\u0442",
|
||||
"empty_response_status": "\u041f\u0440\u0430\u0437\u0435\u043d",
|
||||
"show_all_filter_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0432\u0441\u0438\u0447\u043a\u0438",
|
||||
"show_filtered_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0438",
|
||||
"no_logs_found": "\u041d\u044f\u043c\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u044f",
|
||||
"disabled_log_btn": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"download_log_file_btn": "\u0421\u043c\u044a\u043a\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"refresh_btn": "\u041e\u0431\u043d\u043e\u0432\u0438",
|
||||
"enabled_log_btn": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"last_dns_queries": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 5000 DNS \u0437\u0430\u044f\u0432\u043a\u0438",
|
||||
"previous_btn": "\u041f\u0440\u0435\u0434\u0445\u043e\u0434\u0435\u043d",
|
||||
"next_btn": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
|
||||
"loading_table_status": "\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435...",
|
||||
"page_table_footer_text": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
|
||||
"of_table_footer_text": "\u043e\u0442",
|
||||
"rows_table_footer_text": "\u0440\u0435\u0434\u043e\u0432\u0435",
|
||||
"updated_custom_filtering_toast": "\u041e\u0431\u043d\u043e\u0432\u0435\u043d\u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"rule_removed_from_custom_filtering_toast": "\u041f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u043e \u043e\u0442 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"rule_added_to_custom_filtering_toast": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0434\u043e \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"query_log_disabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
|
||||
"query_log_enabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
|
||||
"source_label": "\u0418\u0437\u0442\u043e\u0447\u043d\u0438\u043a",
|
||||
"found_in_known_domain_db": "\u041d\u0430\u043c\u0435\u0440\u0435\u043d \u0432 \u0441\u043f\u0438\u0441\u044a\u0446\u0438\u0442\u0435 \u0441 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
|
||||
"rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e",
|
||||
"filter_label": "\u0424\u0438\u043b\u0442\u044a\u0440",
|
||||
"unknown_filter": "\u041d\u0435\u043f\u043e\u0437\u043d\u0430\u0442 \u0444\u0438\u043b\u0442\u044a\u0440 {{filterId}}",
|
||||
"install_welcome_title": "\u0414\u043e\u0431\u0440\u0435 \u0434\u043e\u0448\u043b\u0438 \u0432 AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home e \u043c\u0440\u0435\u0436\u043e\u0432\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0438 \u0438 \u0442\u0440\u0430\u043a\u0435\u0440\u0438 \u043d\u0430 DNS \u043d\u0438\u0432\u043e. \u0421\u044a\u0437\u0434\u0430\u0434\u0435\u043d\u043e \u0435 \u0437\u0430 \u0434\u0430 \u0432\u0438 \u0434\u0430\u0434\u0435 \u043f\u044a\u043b\u0435\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b \u043d\u0430\u0434 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u0432\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0431\u0435\u0437 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0434\u0440\u0443\u0433 \u0441\u043e\u0444\u0442\u0443\u0435\u0440.",
|
||||
"install_settings_title": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
|
||||
"install_settings_listen": "\u0410\u043a\u0442\u0438\u0432\u043d\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
|
||||
"install_settings_port": "\u041f\u043e\u0440\u0442",
|
||||
"install_settings_interface_link": "\u0412\u0430\u0448\u0430\u0442\u0430 AdGuard Home \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0449\u0435 \u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 \u0442\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441:",
|
||||
"form_error_port": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u043f\u043e\u0440\u0442",
|
||||
"install_settings_dns": "DNS \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"install_settings_dns_desc": "\u0417\u0430 \u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0438, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u0438\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442 DNS \u0441\u044a\u0440\u0432\u044a\u0440 \u0441 \u0430\u0434\u0440\u0435\u0441:",
|
||||
"install_settings_all_interfaces": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
|
||||
"install_auth_title": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435",
|
||||
"install_auth_desc": "\u041c\u043d\u043e\u0433\u043e \u0435 \u0432\u0430\u0436\u043d\u043e \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0432\u0430\u0448\u0438\u044f \u043f\u0430\u043d\u0435\u043b \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home. \u041f\u0440\u0435\u043f\u043e\u0440\u044a\u0447\u0432\u0430\u043c\u0435 \u0432\u0438 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0447\u0435 \u0433\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0432 \u043a\u044a\u0449\u0438.",
|
||||
"install_auth_username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
|
||||
"install_auth_password": "\u041f\u0430\u0440\u043e\u043b\u0430",
|
||||
"install_auth_confirm": "\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430",
|
||||
"install_auth_username_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
|
||||
"install_auth_password_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430",
|
||||
"install_step": "\u0421\u0442\u044a\u043f\u043a\u0430",
|
||||
"install_devices_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0432\u0430\u0448\u0435\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
|
||||
"install_devices_desc": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home, \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_submit_title": "\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f!",
|
||||
"install_submit_desc": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0442\u0430 \u0435 \u0437\u0430\u0432\u044a\u0440\u0448\u0435\u043d\u0430, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home.",
|
||||
"install_devices_router": "\u0420\u0443\u0442\u0435\u0440",
|
||||
"install_devices_router_desc": "\u0410\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u043d\u044f\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u0440\u044a\u0447\u043d\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u0435\u0434\u043d\u043e \u043e\u0442 \u0443\u0441\u0442\u0440\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.",
|
||||
"install_devices_address": "AdGuard Home DNS \u0441\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0435 \u043d\u0430 \u0441\u043b\u0435\u0434\u043d\u0438\u044f \u0430\u0434\u0440\u0435\u0441",
|
||||
"install_devices_router_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u0440\u0443\u0442\u0435\u0440. \u041e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u043e \u0442\u044f \u0441\u0435 \u043d\u0430\u043c\u0438\u0440\u0430 \u043d\u0430 URL (\u0442\u0443\u043a http:\/\/192.168.0.1\/ \u0438\u043b\u0438 \u0442\u0443\u043a http:\/\/192.168.1.1\/). \u0417\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u0438 \u0442\u0440\u044f\u0431\u0432\u0430 \u043f\u0430\u0440\u043e\u043b\u0430. \u0410\u043a\u043e \u0441\u0442\u0435 \u0437\u0430\u0431\u0440\u0430\u0432\u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u044f \u0440\u0435\u0441\u0435\u0442\u043d\u0435\u0442\u0435 \u043a\u0430\u0442\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0430 \u0441\u043a\u0440\u0438\u0442\u0438\u044f \u0440\u0435\u0441\u0435\u0442 \u0431\u0443\u0442\u043e\u043d - \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0442\u043e\u0432\u0430 \u0449\u0435 \u0440\u0435\u0441\u0435\u0442\u043d\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0440\u0443\u0442\u0435\u0440\u0430 \u0434\u043e \u0444\u0430\u0431\u0440\u0438\u0447\u043d\u0438! \u041d\u044f\u043a\u043e\u0439 \u0440\u0443\u0442\u0435\u0440\u0438 \u043c\u043e\u0433\u0430\u0442 \u0434\u0430 \u0431\u044a\u0434\u0430\u0442\u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d\u0438 \u043e\u0442 \u0441\u043e\u0444\u0442\u0443\u0435\u0440 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0439\u0442\u043e \u0431\u0438 \u0442\u0440\u044f\u0431\u0432\u0430\u043b\u043e \u0434\u0430 \u0435 \u0432\u0435\u0447\u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440\u0430\/\u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432\u0438.",
|
||||
"install_devices_router_list_2": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0430 DHCP\/DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0412 \u043f\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b DHCP \u0440\u0437\u0433\u043b\u0435\u0434\u0430\u0439\u0442\u0435 \u0438 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u043a\u044a\u0434\u0435 \u0435 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432 \u043a\u043e\u0435\u0442\u043e \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438.",
|
||||
"install_devices_router_list_3": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u044a\u0442 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_windows_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043d\u0438\u044f \u041f\u0430\u043d\u0435\u043b \u043f\u0440\u0435\u0437 \u0421\u0442\u0430\u0440\u0442 \u043c\u0435\u043d\u044e \u0438\u043b\u0438 \u0447\u0440\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u044a\u0440\u0441\u0435\u043d\u0435 \u043d\u0430 Windows.",
|
||||
"install_devices_windows_list_2": "\u0412\u044a\u0440\u0432\u0435\u0442\u0435 \u0434\u043e \u041d\u0430\u0441\u0442\u0440\u0439\u043a\u0438 \u043d\u0430 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0438 \u043e\u0442 \u0442\u0430\u043c \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0426\u0435\u043d\u0442\u044a\u0440 \u0437\u0430 \u0421\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435.",
|
||||
"install_devices_windows_list_3": "\u041e\u0442 \u043b\u044f\u0432\u043e \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0421\u043c\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 \u043c\u0440\u0435\u0436\u043e\u0432\u0438\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u043d\u0435\u0433\u043e.",
|
||||
"install_devices_windows_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0442\u043e\u0437\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u0430\u043a\u0442\u0438\u0432\u0435\u043d, \u0434\u044f\u0441\u043d\u043e-\u043a\u043b\u0438\u043a\u0432\u0430\u043d\u0435 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_devices_windows_list_5": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0412\u0435\u0440\u0441\u0438\u044f 4 (TCP\/IP) \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043d\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_devices_windows_list_6": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0441\u044a\u0440\u0438 \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0432\u0438.",
|
||||
"install_devices_macos_list_1": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Apple \u0438\u043a\u043e\u043d\u043a\u0430\u0442\u0430 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 System Preferences...",
|
||||
"install_devices_macos_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Network.",
|
||||
"install_devices_macos_list_3": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0437\u0435\u043b\u0435\u043d\u0430\u0442\u0430-\u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Advanced.",
|
||||
"install_devices_macos_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 DNS \u0442\u0430\u0431 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 + \u0437\u0430 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_android_list_1": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Android \u041c\u0435\u043d\u044e \u043e\u0442 \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u044f \u0435\u043a\u0440\u0430\u043d, \u0438 \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.",
|
||||
"install_devices_android_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Wi-Fi \u043c\u0435\u043d\u044e. \u041d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u0449\u0435 \u0441\u0435 \u043f\u043e\u044f\u0432\u0430\u0442 \u0432\u0441\u0438\u0447\u043a\u0438 \u0431\u0435\u0437\u0436\u0438\u0447\u043d\u0438 \u043f\u0440\u0435\u0436\u0438 (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
|
||||
"install_devices_android_list_3": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u0438 \u0437\u0430\u0434\u0440\u044a\u0436\u0434\u0435 \u0432\u044a\u0440\u0445\u0443 \u0412\u0438\u0435 \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441.., \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0430\u0439 \u043c\u0440\u0435\u0436\u0430.",
|
||||
"install_devices_android_list_4": "\u041d\u0430 \u043d\u044f\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043c\u0430\u0440\u043a\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u043a\u0430\u0436\u0438 \u0420\u0430\u0437\u0448\u0438\u0440\u0435\u043d\u0438, \u0437\u0430 \u0434\u0430 \u0432\u0438\u0434\u0438\u0442\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0417\u0430 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 Android DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043d\u0430\u043b\u043e\u0436\u0438 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 IP \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u043e\u0442 DHCP \u043d\u0430 \u0421\u0442\u0430\u0442\u0438\u0447\u043d\u0438.",
|
||||
"install_devices_android_list_5": "\u041f\u0440\u043e\u043c\u0435\u043d\u0435\u0442\u0435 \u0441\u0442\u043e\u0439\u043d\u043e\u0441\u0442\u0438\u0442\u0435 \u043d\u0430 DNS 1 \u0438 DNS 2 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_ios_list_1": "\u041e\u0442 \u043d\u0430\u0447\u0430\u043b\u0435\u043d \u0435\u043a\u0440\u0430\u043d, \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Settings.",
|
||||
"install_devices_ios_list_2": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Wi-Fi \u043e\u0442 \u043b\u044f\u0432\u043e\u0442\u043e \u043c\u0435\u043d\u044e (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
|
||||
"install_devices_ios_list_3": "\u041a\u043b\u0438\u043d\u0435\u0442\u0435 \u043d\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043a\u044a\u043c \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438.",
|
||||
"install_devices_ios_list_4": "\u0412 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0440\u044a\u0447\u043d\u043e \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"get_started": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u0432\u0430\u043c\u0435",
|
||||
"next": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
|
||||
"open_dashboard": "\u041e\u0442\u0432\u043e\u0440\u0438 \u0442\u0430\u0431\u043b\u043e",
|
||||
"install_saved": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043e",
|
||||
"encryption_title": "\u041a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435",
|
||||
"encryption_desc": "\u041f\u043e\u0434\u044a\u0440\u0436\u0430 \u0441\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 (HTTPS\/TLS) \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u043d\u043e \u0437\u0430 DNS \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
|
||||
"encryption_config_saved": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430",
|
||||
"encryption_server": "\u0418\u043c\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"encryption_server_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d\u0430",
|
||||
"encryption_server_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 HTTPS, \u0442\u0440\u044f\u0431\u0432\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430 \u0441 \u0442\u043e\u0432\u0430 \u043d\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430.",
|
||||
"encryption_redirect": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043a\u044a\u043c HTTPS",
|
||||
"encryption_redirect_desc": "\u0421\u043b\u0443\u0436\u0438 \u0437\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043e\u0442 HTTP \u043a\u044a\u043c HTTPS \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0432 AdGuard Home.",
|
||||
"encryption_https": "HTTPS \u043f\u043e\u0440\u0442",
|
||||
"encryption_https_desc": "\u0410\u043a\u043e \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 HTTPS \u043f\u043e\u0440\u0442, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 HTTPS, \u0438 \u0441\u044a\u0449\u043e \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u043d\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS '\/dns-\u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f'.",
|
||||
"encryption_dot": "DNS-\u0432\u044a\u0440\u0445\u0443-TLS \u043f\u043e\u0440\u0442",
|
||||
"encryption_dot_desc": "\u0410\u043a\u043e \u043f\u043e\u0440\u0442\u0430 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d, AdGuard Home \u0449\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0438 \u0441\u044a\u0440\u0432\u044a\u0440 \u0437\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
|
||||
"encryption_certificates": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438",
|
||||
"encryption_certificates_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043e\u0441\u0438\u0433\u0443\u0440\u0438\u0442\u0435 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0434\u043e\u043c\u0435\u0439\u043d. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u044f\u0432\u0438\u0442\u0435 \u0431\u0435\u0437\u043f\u043b\u0430\u0442\u0435\u043d \u043e\u0442 <0>{{link}}<\/0> \u0438\u043b\u0438 \u0434\u0430 \u0437\u0430\u043a\u0443\u043f\u0438\u0442\u0435 \u043e\u0442 Certificate Authorities.",
|
||||
"encryption_certificates_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
|
||||
"encryption_status": "\u0421\u044a\u0441\u0442\u043e\u044f\u043d\u0438\u0435",
|
||||
"encryption_expire": "\u0413\u043e\u0434\u0435\u043d \u0434\u043e",
|
||||
"encryption_key": "\u0427\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_key_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0447p\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
|
||||
"encryption_enable": "\u0420\u0430\u0437p\u0435\u0448\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435 (HTTPS, DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS, \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS)",
|
||||
"encryption_enable_desc": "\u0410\u043a\u043e \u0441\u0442\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043b\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043f\u0440\u0435\u0437 HTTPS, \u0438 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u0441\u044a\u0449\u043e \u043d\u0430 \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
|
||||
"encryption_chain_valid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u0430",
|
||||
"encryption_chain_invalid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0430",
|
||||
"encryption_key_valid": "\u0422\u043e\u0432\u0430 \u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_key_invalid": "\u0422\u043e\u0432\u0430 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_subject": "\u0422\u0435\u043c\u0430",
|
||||
"encryption_issuer": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b",
|
||||
"encryption_hostnames": "\u0418\u043c\u0435\u043d\u0430 \u043d\u0430 \u0445\u043e\u0441\u0442\u0430",
|
||||
"encryption_reset": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435 \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435?",
|
||||
"topline_expiring_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438\u0437\u0442\u0438\u0447\u0430. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
|
||||
"topline_expired_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0435 \u0438\u0437\u0442\u0435\u043a\u044a\u043b. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
|
||||
"form_error_port_range": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0440\u0442 \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430 80-65535",
|
||||
"form_error_port_unsafe": "\u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0442\u043e\u0437\u0438 \u043f\u043e\u0440\u0442",
|
||||
"form_error_equal": "\u041d\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
|
||||
"form_error_password": "\u041f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043d\u0435 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
|
||||
"reset_settings": "\u0418\u0437\u0442\u0440\u0438\u0439 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"update_announcement": "\u0418\u043c\u0430 \u043d\u043e\u0432\u0430 AdGuard Home {{version}}! <0>\u0426\u044a\u043a\u043d\u0438 \u0442\u0443\u043a<\/0> \u0437\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f."
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
{
|
||||
"client_settings": "Client settings",
|
||||
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)<\/0>",
|
||||
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
|
||||
"bootstrap_dns": "Bootstrap DNS servers",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
|
||||
"url_added_successfully": "URL added successfully",
|
||||
"check_dhcp_servers": "Check for DHCP servers",
|
||||
"save_config": "Save config",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
@@ -7,13 +13,15 @@
|
||||
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
|
||||
"dhcp_enable": "Enable DHCP server",
|
||||
"dhcp_disable": "Disable DHCP server",
|
||||
"dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
|
||||
"dhcp_found": "Found active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
|
||||
"dhcp_not_found": "It is safe to enable the built-in DHCP server - we didn't find any active DHCP servers on the network. However, we encourage you to re-check it manually as our automatic test currently doesn't give 100% guarantee.",
|
||||
"dhcp_found": "An active DHCP server is found on the network. It is not safe to enable the built-in DHCP server.",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "Saved DHCP server config",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip_format": "Invalid IPv4 format",
|
||||
"form_error_mac_format": "Invalid MAC format",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
@@ -25,19 +33,34 @@
|
||||
"dhcp_interface_select": "Select DHCP interface",
|
||||
"dhcp_hardware_address": "Hardware address",
|
||||
"dhcp_ip_addresses": "IP addresses",
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expires",
|
||||
"dhcp_warning": "If you want to enable DHCP server anyway, make sure that there is no other active DHCP server in your network. Otherwise, it can break the Internet for connected devices!",
|
||||
"dhcp_error": "We could not determine whether there is another DHCP server in the network.",
|
||||
"dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.",
|
||||
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}<\/0>. In order to use DHCP server a static IP address must be set. Your current IP address is <0>{{ipAddress}}<\/0>. We will automatically set this IP address as static if you press Enable DHCP button.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Enter hostname",
|
||||
"error_details": "Error details",
|
||||
"back": "Back",
|
||||
"dashboard": "Dashboard",
|
||||
"settings": "Settings",
|
||||
"filters": "Filters",
|
||||
"query_log": "Query Log",
|
||||
"faq": "FAQ",
|
||||
"version": "version",
|
||||
"version": "Version",
|
||||
"address": "address",
|
||||
"on": "ON",
|
||||
"off": "OFF",
|
||||
"copyright": "Copyright",
|
||||
"homepage": "Homepage",
|
||||
"report_an_issue": "Report an issue",
|
||||
"privacy_policy": "Privacy policy",
|
||||
"enable_protection": "Enable protection",
|
||||
"enabled_protection": "Enabled protection",
|
||||
"disable_protection": "Disable protection",
|
||||
@@ -70,12 +93,15 @@
|
||||
"use_adguard_parental": "Use AdGuard parental control web service",
|
||||
"use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.",
|
||||
"enforce_safe_search": "Enforce safe search",
|
||||
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, and Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, DuckDuckGo and Yandex.",
|
||||
"no_servers_specified": "No servers specified",
|
||||
"no_settings": "No settings",
|
||||
"general_settings": "General settings",
|
||||
"dns_settings": "DNS settings",
|
||||
"encryption_settings": "Encryption settings",
|
||||
"dhcp_settings": "DHCP settings",
|
||||
"upstream_dns": "Upstream DNS servers",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream. Use tls:\/\/ prefix for DNS over TLS servers.",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
"apply_btn": "Apply",
|
||||
"disabled_filtering_toast": "Disabled filtering",
|
||||
@@ -92,6 +118,7 @@
|
||||
"rules_count_table_header": "Rules count",
|
||||
"last_time_updated_table_header": "Last time updated",
|
||||
"actions_table_header": "Actions",
|
||||
"edit_table_action": "Edit",
|
||||
"delete_table_action": "Delete",
|
||||
"filters_and_hosts": "Filters and hosts blocklists",
|
||||
"filters_and_hosts_hint": "AdGuard Home understands basic adblock rules and hosts files syntax.",
|
||||
@@ -112,10 +139,11 @@
|
||||
"example_comment": "! Here goes a comment",
|
||||
"example_comment_meaning": "just a comment",
|
||||
"example_comment_hash": "# Also a comment",
|
||||
"example_regex_meaning": "block access to the domains matching the <0>specified regular expression</0>",
|
||||
"example_upstream_regular": "regular DNS (over UDP)",
|
||||
"example_upstream_dot": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "you can use <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> or <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolvers",
|
||||
"example_upstream_dot": "encrypted <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "you can use <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> or <2>DNS-over-HTTPS<\/2> resolvers",
|
||||
"example_upstream_tcp": "regular DNS (over TCP)",
|
||||
"all_filters_up_to_date_toast": "All filters are already up-to-date",
|
||||
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
|
||||
@@ -153,5 +181,155 @@
|
||||
"category_label": "Category",
|
||||
"rule_label": "Rule",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Unknown filter {{filterId}}"
|
||||
"unknown_filter": "Unknown filter {{filterId}}",
|
||||
"install_welcome_title": "Welcome to AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home is a network-wide ad-and-tracker blocking DNS server. Its purpose is to let you control your entire network and all your devices, and it does not require using a client-side program.",
|
||||
"install_settings_title": "Admin Web Interface",
|
||||
"install_settings_listen": "Listen interface",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
|
||||
"form_error_port": "Enter valid port value",
|
||||
"install_settings_dns": "DNS server",
|
||||
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
|
||||
"install_settings_all_interfaces": "All interfaces",
|
||||
"install_auth_title": "Authentication",
|
||||
"install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to have it protected from unrestricted access.",
|
||||
"install_auth_username": "Username",
|
||||
"install_auth_password": "Password",
|
||||
"install_auth_confirm": "Confirm password",
|
||||
"install_auth_username_enter": "Enter username",
|
||||
"install_auth_password_enter": "Enter password",
|
||||
"install_step": "Step",
|
||||
"install_devices_title": "Configure your devices",
|
||||
"install_devices_desc": "To start using AdGuard Home, you need to configure your devices to use it.",
|
||||
"install_submit_title": "Congratulations!",
|
||||
"install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
|
||||
"install_devices_address": "AdGuard Home DNS server is listening on the following addresses",
|
||||
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http:\/\/192.168.0.1\/ or http:\/\/192.168.1.1\/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer\/phone.",
|
||||
"install_devices_router_list_2": "Find the DHCP\/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
|
||||
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
|
||||
"install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.",
|
||||
"install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.",
|
||||
"install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.",
|
||||
"install_devices_windows_list_4": "Select your active connection, right-click on it and choose Properties.",
|
||||
"install_devices_windows_list_5": "Find Internet Protocol Version 4 (TCP\/IP) in the list, select it and then click on Properties again.",
|
||||
"install_devices_windows_list_6": "Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.",
|
||||
"install_devices_macos_list_1": "Click on Apple icon and go to System Preferences.",
|
||||
"install_devices_macos_list_2": "Click on Network.",
|
||||
"install_devices_macos_list_3": "Select the first connection in your list and click Advanced.",
|
||||
"install_devices_macos_list_4": "Select the DNS tab and enter your AdGuard Home server addresses.",
|
||||
"install_devices_android_list_1": "From the Android Menu home screen, tap Settings.",
|
||||
"install_devices_android_list_2": "Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).",
|
||||
"install_devices_android_list_3": "Long press the network you're connected to, and tap Modify Network.",
|
||||
"install_devices_android_list_4": "On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.",
|
||||
"install_devices_android_list_5": "Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.",
|
||||
"install_devices_ios_list_1": "From the home screen, tap Settings.",
|
||||
"install_devices_ios_list_2": "Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).",
|
||||
"install_devices_ios_list_3": "Tap on the name of the currently active network.",
|
||||
"install_devices_ios_list_4": "In the DNS field enter your AdGuard Home server addresses.",
|
||||
"get_started": "Get Started",
|
||||
"next": "Next",
|
||||
"open_dashboard": "Open Dashboard",
|
||||
"install_saved": "Saved successfully",
|
||||
"encryption_title": "Encryption",
|
||||
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
||||
"encryption_certificates": "Certificates",
|
||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||
"encryption_certificates_input": "Copy\/paste your PEM-encoded certificates here.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expires",
|
||||
"encryption_key": "Private key",
|
||||
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate here.",
|
||||
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certificate chain is valid",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||
"encryption_subject": "Subject",
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings<\/0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings<\/0>.",
|
||||
"form_error_port_range": "Enter port value in the range of 80-65535",
|
||||
"form_error_port_unsafe": "This is an unsafe port",
|
||||
"form_error_equal": "Shouldn't be equal",
|
||||
"form_error_password": "Password mismatched",
|
||||
"reset_settings": "Reset settings",
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info.",
|
||||
"setup_guide": "Setup guide",
|
||||
"dns_addresses": "DNS addresses",
|
||||
"down": "Down",
|
||||
"fix": "Fix",
|
||||
"dns_providers": "Here is a <0>list of known DNS providers<\/0> to choose from.",
|
||||
"update_now": "Update now",
|
||||
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
|
||||
"processing_update": "Please wait, AdGuard Home is being updated",
|
||||
"clients_title": "Clients",
|
||||
"clients_desc": "Configure devices connected to AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Custom",
|
||||
"table_client": "Client",
|
||||
"table_name": "Name",
|
||||
"save_btn": "Save",
|
||||
"client_add": "Add Client",
|
||||
"client_new": "New Client",
|
||||
"client_edit": "Edit Client",
|
||||
"client_identifier": "Identifier",
|
||||
"ip_address": "IP address",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
|
||||
"form_enter_ip": "Enter IP",
|
||||
"form_enter_mac": "Enter MAC",
|
||||
"form_client_name": "Enter client name",
|
||||
"client_global_settings": "Use global settings",
|
||||
"client_deleted": "Client \"{{key}}\" successfully deleted",
|
||||
"client_added": "Client \"{{key}}\" successfully added",
|
||||
"client_updated": "Client \"{{key}}\" successfully updated",
|
||||
"table_statistics": "Requests count (last 24 hours)",
|
||||
"clients_not_found": "No clients found",
|
||||
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Are you sure you want to delete filter?",
|
||||
"auto_clients_title": "Clients (runtime)",
|
||||
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
|
||||
"access_title": "Access settings",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
|
||||
"access_allowed_title": "Allowed clients",
|
||||
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
|
||||
"access_disallowed_title": "Disallowed clients",
|
||||
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
|
||||
"access_blocked_title": "Blocked domains",
|
||||
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
|
||||
"access_settings_saved": "Access settings successfully saved",
|
||||
"updates_checked": "Updates successfully checked",
|
||||
"updates_version_equal": "AdGuard Home is up-to-date",
|
||||
"check_updates_now": "Check for updates now",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_3": "<0>Please note that encrypted DNS protocols are supported only on Android 9. So you need to install additional software for other operating systems.</0><0>Here's a list of software you can use.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 supports DNS-over-TLS natively. To configure it, go to Settings → Network & internet → Advanced → Private DNS and enter your domain name there.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> adds <1>DNS-over-HTTPS</1> support to Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> supports <1>DNS-over-HTTPS</1>, but in order to configure it to use your own server, you'll need to generate a <2>DNS Stamp</2> for it.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1> setup.",
|
||||
"setup_dns_privacy_other_title": "Other implementations",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home itself can be a secure DNS client on any platform.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> supports all known secure DNS protocols.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supports <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supports <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "You will find more implementations <0>here</0> and <1>here</1>.",
|
||||
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings."
|
||||
}
|
||||
@@ -1,103 +1,158 @@
|
||||
{
|
||||
"example_upstream_reserved": "puede especificar el DNS de subida <0>para un dominio espec\u00edfico<\/0>",
|
||||
"upstream_parallel": "Usar consultas paralelas para acelerar la resoluci\u00f3n al consultar simult\u00e1neamente a todos los servidores de subida",
|
||||
"bootstrap_dns": "Servidores DNS de arranque",
|
||||
"bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH\/DoT que usted especifique como DNS de subida.",
|
||||
"url_added_successfully": "URL a\u00f1adida correctamente",
|
||||
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
||||
"save_config": "Guardar configuraci\u00f3n",
|
||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||
"disabled_dhcp": "Servidor DHCP deshabilitado",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Si su router no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
|
||||
"dhcp_enable": "Habilitar servidor DHCP",
|
||||
"dhcp_disable": "Deshabilitar servidor DHCP",
|
||||
"dhcp_not_found": "Es seguro habilitar el servidor DHCP incorporado. No se ha encontrado ning\u00fan servidor DHCP activo en la red, sin embargo le recomendamos que lo vuelva a comprobar manualmente, ya que nuestra prueba autom\u00e1tica no ofrece actualmente una garant\u00eda del 100%.",
|
||||
"dhcp_found": "Un servidor DHCP activo se encuentra en la red. No es seguro habilitar el servidor DHCP incorporado.",
|
||||
"dhcp_leases": "Asignaciones DHCP",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "No se han encontrado asignaciones DHCP",
|
||||
"dhcp_config_saved": "Configuraci\u00f3n del servidor DHCP guardada",
|
||||
"form_error_required": "Campo obligatorio",
|
||||
"form_error_ip_format": "Formato IPv4 no v\u00e1lido",
|
||||
"form_error_mac_format": "Formato MAC no v\u00e1lido",
|
||||
"form_error_positive": "Debe ser mayor que 0",
|
||||
"dhcp_form_gateway_input": "IP de puerta de enlace",
|
||||
"dhcp_form_subnet_input": "M\u00e1scara de subred",
|
||||
"dhcp_form_range_title": "Rango de direcciones IP",
|
||||
"dhcp_form_range_start": "Inicio de rango",
|
||||
"dhcp_form_range_end": "Final de rango",
|
||||
"dhcp_form_lease_title": "Tiempo de asignaci\u00f3n DHCP (en segundos)",
|
||||
"dhcp_form_lease_input": "Duraci\u00f3n de asignaci\u00f3n",
|
||||
"dhcp_interface_select": "Seleccione la interfaz DHCP",
|
||||
"dhcp_hardware_address": "Direcci\u00f3n MAC",
|
||||
"dhcp_ip_addresses": "Direcciones IP",
|
||||
"dhcp_table_hostname": "Nombre del host",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"dhcp_warning": "Si de todos modos desea habilitar el servidor DHCP, aseg\u00farese de que no hay otro servidor DHCP activo en su red. \u00a1De lo contrario, puede dejar sin Internet a los dispositivos conectados!",
|
||||
"dhcp_error": "No pudimos determinar si hay otro servidor DHCP en la red.",
|
||||
"dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una direcci\u00f3n IP est\u00e1tica. No hemos podido determinar si esta interfaz de red est\u00e1 configurada utilizando una direcci\u00f3n IP est\u00e1tica. Por favor establezca una direcci\u00f3n IP est\u00e1tica manualmente.",
|
||||
"dhcp_dynamic_ip_found": "Su sistema utiliza la configuraci\u00f3n de direcci\u00f3n IP din\u00e1mica para la interfaz <0>{{interfaceName}}<\/0>. Para poder utilizar el servidor DHCP se debe establecer una direcci\u00f3n IP est\u00e1tica. Su direcci\u00f3n IP actual es <0>{{ipAddress}}<\/0>. Si presiona el bot\u00f3n Habilitar servidor DHCP, estableceremos autom\u00e1ticamente esta direcci\u00f3n IP como est\u00e1tica.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Enter hostname",
|
||||
"error_details": "Detalles del error",
|
||||
"back": "Atr\u00e1s",
|
||||
"dashboard": "Tablero de rendimiento",
|
||||
"settings": "Ajustes",
|
||||
"dashboard": "Panel de control",
|
||||
"settings": "Configuraci\u00f3n",
|
||||
"filters": "Filtros",
|
||||
"query_log": "Log de consulta",
|
||||
"faq": "FAQ",
|
||||
"query_log": "Registro de consultas",
|
||||
"faq": "Preguntas frecuentes",
|
||||
"version": "versi\u00f3n",
|
||||
"address": "direcci\u00f3n",
|
||||
"on": "Activado",
|
||||
"off": "Desactivado",
|
||||
"copyright": "Derechos de autor",
|
||||
"copyright": "Copyright",
|
||||
"homepage": "P\u00e1gina de inicio",
|
||||
"report_an_issue": "Reportar un error",
|
||||
"enable_protection": "Activar la protecci\u00f3n",
|
||||
"enabled_protection": "Protecci\u00f3n activada",
|
||||
"disable_protection": "Desactivar protecci\u00f3n",
|
||||
"disabled_protection": "Protecci\u00f3n desactivada",
|
||||
"privacy_policy": "Pol\u00edtica de privacidad",
|
||||
"enable_protection": "Habilitar protecci\u00f3n",
|
||||
"enabled_protection": "Protecci\u00f3n habilitada",
|
||||
"disable_protection": "Deshabilitar protecci\u00f3n",
|
||||
"disabled_protection": "Protecci\u00f3n deshabilitada",
|
||||
"refresh_statics": "Restablecer estad\u00edsticas",
|
||||
"dns_query": "Consultas DNS",
|
||||
"blocked_by": "Bloqueado por Filtros",
|
||||
"blocked_by": "Bloqueado por filtros",
|
||||
"stats_malware_phishing": "Malware\/phishing bloqueado",
|
||||
"stats_adult": "Contenido para adultos bloqueado",
|
||||
"stats_query_domain": "Dominios m\u00e1s solicitados",
|
||||
"stats_adult": "Sitios web para adultos bloqueado",
|
||||
"stats_query_domain": "Dominios m\u00e1s consultados",
|
||||
"for_last_24_hours": "en las \u00faltimas 24 horas",
|
||||
"no_domains_found": "Dominios no encontrados",
|
||||
"requests_count": "N\u00famero de solicitudes",
|
||||
"no_domains_found": "No se han encontrado dominios",
|
||||
"requests_count": "N\u00famero de peticiones",
|
||||
"top_blocked_domains": "Dominios m\u00e1s bloqueados",
|
||||
"top_clients": "Clientes m\u00e1s populares",
|
||||
"no_clients_found": "No hay clientes",
|
||||
"top_clients": "Clientes m\u00e1s frecuentes",
|
||||
"no_clients_found": "No se han encontrado clientes",
|
||||
"general_statistics": "Estad\u00edsticas generales",
|
||||
"number_of_dns_query_24_hours": "Una serie de consultas DNS procesadas durante las \u00faltimas 24 horas",
|
||||
"number_of_dns_query_blocked_24_hours": "El n\u00famero de solicitudes de DNS bloqueadas por los filtros de publicidad y listas de bloqueo de hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Un n\u00famero de solicitudes de DNS bloqueadas por el m\u00f3dulo de navegaci\u00f3n segura de AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Un n\u00famero de sitios para adultos bloqueados",
|
||||
"enforced_save_search": "B\u00fasqueda segura forzada",
|
||||
"number_of_dns_query_to_safe_search": "Una serie de solicitudes de DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la B\u00fasqueda Segura",
|
||||
"number_of_dns_query_24_hours": "N\u00famero de consultas DNS procesadas durante las \u00faltimas 24 horas",
|
||||
"number_of_dns_query_blocked_24_hours": "N\u00famero de peticiones DNS bloqueadas por los filtros y listas de bloqueo de hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "N\u00famero de peticiones DNS bloqueadas por el m\u00f3dulo de seguridad de navegaci\u00f3n de AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "N\u00famero de sitios web para adultos bloqueado",
|
||||
"enforced_save_search": "B\u00fasquedas seguras forzadas",
|
||||
"number_of_dns_query_to_safe_search": "N\u00famero de peticiones DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la b\u00fasqueda segura forzada",
|
||||
"average_processing_time": "Tiempo promedio de procesamiento",
|
||||
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una solicitud DNS",
|
||||
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petici\u00f3n DNS",
|
||||
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
|
||||
"filters_block_toggle_hint": "Puede configurar las reglas de bloqueo en los ajustes <a href='#filters'>Filtros<\/a>.",
|
||||
"use_adguard_browsing_sec": "Usar el servicio web de Seguridad de navegaci\u00f3n de AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home comprobar\u00e1 si el dominio est\u00e1 en la lista negra del servicio web de seguridad de navegaci\u00f3n. Utilizar\u00e1 una API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: s\u00f3lo se env\u00eda al servidor un prefijo corto del hash del nombre de dominio SHA256.",
|
||||
"use_adguard_parental": "Usar Control Parental de AdGuard ",
|
||||
"use_adguard_parental_hint": "AdGuard Home comprobar\u00e1 si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad que el servicio web de seguridad de navegaci\u00f3n.",
|
||||
"filters_block_toggle_hint": "Puede configurar las reglas de bloqueo en la configuraci\u00f3n de <a href='#filters'>filtros<\/a>.",
|
||||
"use_adguard_browsing_sec": "Usar el servicio web de seguridad de navegaci\u00f3n de AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home comprobar\u00e1 si el dominio est\u00e1 en la lista negra del servicio web de seguridad de navegaci\u00f3n. Utilizar\u00e1 la API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: solo se env\u00eda al servidor un prefijo corto del nombre de dominio con hash SHA256.",
|
||||
"use_adguard_parental": "Usar el control parental de AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home comprobar\u00e1 si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad del servicio web de seguridad de navegaci\u00f3n.",
|
||||
"enforce_safe_search": "Forzar b\u00fasqueda segura",
|
||||
"enforce_save_search_hint": "AdGuard Home puede forzar la b\u00fasqueda segura en los siguientes motores de b\u00fasqueda: Google, Youtube, Bing y Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home puede forzar la b\u00fasqueda segura en los siguientes motores de b\u00fasqueda: Google, YouTube, Bing, DuckDuckGo y Yandex.",
|
||||
"no_servers_specified": "No hay servidores especificados",
|
||||
"no_settings": "No hay ajustes",
|
||||
"general_settings": "Ajustes generales",
|
||||
"upstream_dns": "Servidores DNS upstream",
|
||||
"upstream_dns_hint": "Si mantiene este campo vac\u00edo, AdGuard Home utilizar\u00e1 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> como upstream. Utilice el prefijo tls:\/\/ para DNS sobre servidores TLS.",
|
||||
"test_upstream_btn": "Probar upstream",
|
||||
"no_settings": "Sin configuraci\u00f3n",
|
||||
"general_settings": "Configuraci\u00f3n general",
|
||||
"dns_settings": "DNS settings",
|
||||
"encryption_settings": "Encryption settings",
|
||||
"dhcp_settings": "DHCP settings",
|
||||
"client_settings": "Client settings",
|
||||
"upstream_dns": "Servidores DNS de subida",
|
||||
"upstream_dns_hint": "Si mantiene este campo vac\u00edo, AdGuard Home utilizar\u00e1 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> como DNS de subida. Utilice el prefijo tls:\/\/ para los servidores DNS mediante TLS.",
|
||||
"test_upstream_btn": "Probar DNS de subida",
|
||||
"apply_btn": "Aplicar",
|
||||
"disabled_filtering_toast": "Desactivar filtrado",
|
||||
"enabled_filtering_toast": "Filtrado activado",
|
||||
"disabled_safe_browsing_toast": "Navegaci\u00f3n segura desactivada",
|
||||
"enabled_safe_browsing_toast": "Navegaci\u00f3n segura activada",
|
||||
"disabled_parental_toast": "Control parental desactivado",
|
||||
"enabled_parental_toast": "Control parental activado",
|
||||
"disabled_safe_search_toast": "B\u00fasqueda segura desactivada",
|
||||
"enabled_save_search_toast": "B\u00fasqueda segura activada",
|
||||
"enabled_table_header": "Activado",
|
||||
"disabled_filtering_toast": "Filtrado deshabilitado",
|
||||
"enabled_filtering_toast": "Filtrado habilitado",
|
||||
"disabled_safe_browsing_toast": "B\u00fasqueda segura deshabilitada",
|
||||
"enabled_safe_browsing_toast": "B\u00fasqueda segura habilitada",
|
||||
"disabled_parental_toast": "Control parental deshabilitado",
|
||||
"enabled_parental_toast": "Control parental habilitado",
|
||||
"disabled_safe_search_toast": "B\u00fasqueda segura deshabilitada",
|
||||
"enabled_save_search_toast": "B\u00fasqueda segura habilitada",
|
||||
"enabled_table_header": "Habilitado",
|
||||
"name_table_header": "Nombre",
|
||||
"filter_url_table_header": "Filtro URL",
|
||||
"filter_url_table_header": "URL del filtro",
|
||||
"rules_count_table_header": "N\u00famero de reglas",
|
||||
"last_time_updated_table_header": "\u00daltima actualizaci\u00f3n",
|
||||
"actions_table_header": "Acciones",
|
||||
"edit_table_action": "Editar",
|
||||
"delete_table_action": "Eliminar",
|
||||
"filters_and_hosts": "Filtros y listas de bloqueo de hosts",
|
||||
"filters_and_hosts_hint": "AdGuard Home entiende reglas b\u00e1sicas de bloqueo y la sintaxis de los archivos de hosts.",
|
||||
"no_filters_added": "No hay filtros agregados",
|
||||
"add_filter_btn": "Agregar filtro",
|
||||
"filters_and_hosts_hint": "AdGuard Home entiende las reglas b\u00e1sicas de bloqueo y la sintaxis de los archivos hosts.",
|
||||
"no_filters_added": "No hay filtros a\u00f1adidos",
|
||||
"add_filter_btn": "A\u00f1adir filtro",
|
||||
"cancel_btn": "Cancelar",
|
||||
"enter_name_hint": "Ingresar nombre",
|
||||
"enter_url_hint": "Ingresar URL",
|
||||
"check_updates_btn": "Revisar si hay actualizaciones",
|
||||
"new_filter_btn": "Nueva suscripci\u00f3n de filtro",
|
||||
"enter_valid_filter_url": "Ingrese una URL v\u00e1lida para suscribirse o un archivo de hosts.",
|
||||
"custom_filter_rules": "Personalizar reglas del filtrado",
|
||||
"custom_filter_rules_hint": "Introduzca una regla en una l\u00ednea. Puede utilizar reglas de bloqueo de anuncios o sintaxis de archivos de hosts.",
|
||||
"enter_name_hint": "Ingrese el nombre",
|
||||
"enter_url_hint": "Ingrese la URL",
|
||||
"check_updates_btn": "Buscar actualizaciones",
|
||||
"new_filter_btn": "Nueva suscripci\u00f3n a filtro",
|
||||
"enter_valid_filter_url": "Ingrese una URL v\u00e1lida para suscribirse a un filtro o archivo hosts.",
|
||||
"custom_filter_rules": "Reglas de filtrado personalizado",
|
||||
"custom_filter_rules_hint": "Ingrese una regla por l\u00ednea. Puede utilizar reglas de bloqueo o la sintaxis de los archivos hosts.",
|
||||
"examples_title": "Ejemplos",
|
||||
"example_meaning_filter_block": "bloquear acceso al dominio ejemplo.org\ny a todos sus subdominios",
|
||||
"example_meaning_filter_whitelist": "desbloquear el acceso al dominio ejemplo.org y a sus subdominios",
|
||||
"example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org\ny a todos sus subdominios",
|
||||
"example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos sus subdominios",
|
||||
"example_meaning_host_block": "AdGuard Home regresar\u00e1 la direcci\u00f3n 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).",
|
||||
"example_comment": "! Aqu\u00ed va un comentario",
|
||||
"example_comment_meaning": "solo un comentario",
|
||||
"example_comment_hash": "# Tambi\u00e9n un comentario",
|
||||
"example_upstream_regular": "DNS regular (a trav\u00e9s de UDP)",
|
||||
"example_upstream_dot": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
|
||||
"example_upstream_doh": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
|
||||
"example_upstream_tcp": "DNS regular (a trav\u00e9s de TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos los filtros son actualizados",
|
||||
"updated_upstream_dns_toast": "Servidores DNS upstream actualizados",
|
||||
"dns_test_ok_toast": "Servidores DNS especificados funcionan correctamente",
|
||||
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no puede ser usado, por favor, revise si lo ha escrito correctamente",
|
||||
"example_regex_meaning": "bloquear el acceso a los dominios que coincidan con la expresi\u00f3n regular especificada",
|
||||
"example_upstream_regular": "DNS regular (mediante UDP)",
|
||||
"example_upstream_dot": "cifrado <0>DNS mediante TLS<\/0>",
|
||||
"example_upstream_doh": "cifrado <0>DNS mediante HTTPS<\/0>",
|
||||
"example_upstream_sdns": "puedes usar <0>DNS Stamps<\/0> para <1>DNSCrypt<\/1> o resolutores <2>DNS mediante HTTPS<\/2>",
|
||||
"example_upstream_tcp": "DNS regular (mediante TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos los filtros ya est\u00e1n actualizados",
|
||||
"updated_upstream_dns_toast": "Servidores DNS de subida actualizados",
|
||||
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
|
||||
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revise si lo ha escrito correctamente",
|
||||
"unblock_btn": "Desbloquear",
|
||||
"block_btn": "Bloquear",
|
||||
"time_table_header": "Tiempo",
|
||||
"domain_name_table_header": "Nombre de dominio",
|
||||
"time_table_header": "Hora",
|
||||
"domain_name_table_header": "Nombre del dominio",
|
||||
"type_table_header": "Tipo",
|
||||
"response_table_header": "Respuesta",
|
||||
"client_table_header": "Cliente",
|
||||
@@ -105,25 +160,157 @@
|
||||
"show_all_filter_type": "Mostrar todo",
|
||||
"show_filtered_type": "Mostrar filtrados",
|
||||
"no_logs_found": "No se han encontrado registros",
|
||||
"disabled_log_btn": "Desactivar registro",
|
||||
"download_log_file_btn": "Descargar el archivo de registro",
|
||||
"refresh_btn": "Refrescar",
|
||||
"enabled_log_btn": "Activar registro",
|
||||
"last_dns_queries": "\u00daltimas 500 solicitudes de DNS",
|
||||
"previous_btn": "Anterior",
|
||||
"disabled_log_btn": "Deshabilitar registro",
|
||||
"download_log_file_btn": "Descargar archivo de registro",
|
||||
"refresh_btn": "Actualizar",
|
||||
"enabled_log_btn": "Habilitar registro",
|
||||
"last_dns_queries": "\u00daltimas 5000 consultas DNS",
|
||||
"previous_btn": "Atr\u00e1s",
|
||||
"next_btn": "Siguiente",
|
||||
"loading_table_status": "Cargando...",
|
||||
"page_table_footer_text": "P\u00e1gina",
|
||||
"of_table_footer_text": "de",
|
||||
"rows_table_footer_text": "filas",
|
||||
"updated_custom_filtering_toast": "Actualizadas las reglas de filtrado personalizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizadas",
|
||||
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizadas",
|
||||
"query_log_disabled_toast": "Log de consulta desactivado",
|
||||
"query_log_enabled_toast": "Log de consulta activado",
|
||||
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado",
|
||||
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizado",
|
||||
"query_log_disabled_toast": "Registro de consultas deshabilitado",
|
||||
"query_log_enabled_toast": "Registro de consultas habilitado",
|
||||
"source_label": "Fuente",
|
||||
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
|
||||
"category_label": "Categor\u00eda",
|
||||
"rule_label": "Regla",
|
||||
"filter_label": "Filtro"
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconocido {{filterId}}",
|
||||
"install_welcome_title": "\u00a1Bienvenido a AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home es un servidor DNS para bloqueo de anuncios y rastreadores a nivel de red. Su prop\u00f3sito es permitirle controlar toda su red y todos sus dispositivos, y no requiere el uso de un programa del lado del cliente.",
|
||||
"install_settings_title": "Interfaz web de administraci\u00f3n",
|
||||
"install_settings_listen": "Interfaz de escucha",
|
||||
"install_settings_port": "Puerto",
|
||||
"install_settings_interface_link": "Su interfaz web de administraci\u00f3n de AdGuard Home estar\u00e1 disponible en las siguientes direcciones:",
|
||||
"form_error_port": "Ingrese un valor de puerto v\u00e1lido",
|
||||
"install_settings_dns": "Servidor DNS",
|
||||
"install_settings_dns_desc": "Deber\u00e1 configurar sus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
|
||||
"install_settings_all_interfaces": "Todas las interfaces",
|
||||
"install_auth_title": "Autenticaci\u00f3n",
|
||||
"install_auth_desc": "Se recomienda encarecidamente configurar la autenticaci\u00f3n por contrase\u00f1a para la interfaz web de administraci\u00f3n de AdGuard Home. Incluso si solo es accesible en su red local, es importante que est\u00e9 protegido contra el acceso no autorizado.",
|
||||
"install_auth_username": "Usuario",
|
||||
"install_auth_password": "Contrase\u00f1a",
|
||||
"install_auth_confirm": "Confirmar contrase\u00f1a",
|
||||
"install_auth_username_enter": "Ingrese su nombre de usuario",
|
||||
"install_auth_password_enter": "Ingrese su contrase\u00f1a",
|
||||
"install_step": "Paso",
|
||||
"install_devices_title": "Configure sus dispositivos",
|
||||
"install_devices_desc": "Para que AdGuard Home comience a funcionar, necesita configurar sus dispositivos para usarlo.",
|
||||
"install_submit_title": "\u00a1Felicitaciones!",
|
||||
"install_submit_desc": "El proceso de configuraci\u00f3n ha finalizado y est\u00e1 listo para comenzar a usar AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Esta configuraci\u00f3n cubrir\u00e1 autom\u00e1ticamente todos los dispositivos conectados a su router dom\u00e9stico y no necesitar\u00e1 configurar cada uno de ellos manualmente.",
|
||||
"install_devices_address": "El servidor DNS de AdGuard Home est\u00e1 escuchando en las siguientes direcciones",
|
||||
"install_devices_router_list_1": "Abra las preferencias de su router. Por lo general, puede acceder a \u00e9l desde su navegador a trav\u00e9s de una URL (como http:\/\/192.168.0.1\/ o http:\/\/192.168.1.1\/). Se le puede pedir que ingrese la contrase\u00f1a. Si no lo recuerda, a menudo puede restablecer la contrase\u00f1a presionando un bot\u00f3n en el router. Algunos routers requieren una aplicaci\u00f3n espec\u00edfica, que en ese caso ya deber\u00eda estar instalada en su computadora\/tel\u00e9fono.",
|
||||
"install_devices_router_list_2": "Busque la configuraci\u00f3n de DHCP\/DNS. Busque las letras DNS junto a un campo que permita ingresar dos o tres grupos de n\u00fameros, cada uno dividido en cuatro grupos de uno a tres d\u00edgitos.",
|
||||
"install_devices_router_list_3": "Ingrese las direcciones de su servidor AdGuard Home all\u00ed.",
|
||||
"install_devices_windows_list_1": "Abra el Panel de control a trav\u00e9s del men\u00fa Inicio o en el buscador de Windows.",
|
||||
"install_devices_windows_list_2": "Vaya a la categor\u00eda Red e Internet, luego al Centro de redes y recursos compartidos.",
|
||||
"install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busque Cambiar la configuraci\u00f3n del adaptador y haga clic en \u00e9l.",
|
||||
"install_devices_windows_list_4": "Seleccione su conexi\u00f3n activa, luego haga clic derecho sobre ella y elija Propiedades.",
|
||||
"install_devices_windows_list_5": "Busque el Protocolo de Internet versi\u00f3n 4 (TCP\/IP) en la lista, selecci\u00f3nelo y luego haga clic en Propiedades nuevamente.",
|
||||
"install_devices_windows_list_6": "Elija Usar las siguientes direcciones de servidor DNS e ingrese las direcciones de su servidor AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Haga clic en el icono de Apple y vaya a Preferencias del sistema.",
|
||||
"install_devices_macos_list_2": "Haga clic en Red.",
|
||||
"install_devices_macos_list_3": "Seleccione la primera conexi\u00f3n de la lista y haga clic en Avanzado.",
|
||||
"install_devices_macos_list_4": "Seleccione la pesta\u00f1a DNS e ingrese las direcciones de su servidor AdGuard Home.",
|
||||
"install_devices_android_list_1": "En la pantalla de inicio del men\u00fa Android, pulse en Configuraci\u00f3n.",
|
||||
"install_devices_android_list_2": "Pulse Wi-Fi en el men\u00fa. Aparecer\u00e1 la pantalla que lista todas las redes disponibles (es imposible configurar DNS personalizados para la conexi\u00f3n m\u00f3vil).",
|
||||
"install_devices_android_list_3": "Mantenga presionada la red a la que est\u00e1 conectado y pulse Modificar red.",
|
||||
"install_devices_android_list_4": "En algunos dispositivos, es posible que deba marcar la casilla Avanzado para ver m\u00e1s configuraciones. Para ajustar la configuraci\u00f3n DNS de su Android, deber\u00e1 cambiar la configuraci\u00f3n de IP de DHCP a Est\u00e1tica.",
|
||||
"install_devices_android_list_5": "Cambie los valores de DNS 1 y DNS 2 a las direcciones de su servidor AdGuard Home.",
|
||||
"install_devices_ios_list_1": "En la pantalla de inicio, pulse en Configuraci\u00f3n.",
|
||||
"install_devices_ios_list_2": "Elija Wi-Fi en el men\u00fa de la izquierda (es imposible configurar DNS para redes m\u00f3viles).",
|
||||
"install_devices_ios_list_3": "Pulse sobre el nombre de la red activa en ese momento.",
|
||||
"install_devices_ios_list_4": "En el campo DNS ingrese las direcciones de su servidor AdGuard Home.",
|
||||
"get_started": "Comenzar",
|
||||
"next": "Siguiente",
|
||||
"open_dashboard": "Abrir panel de control",
|
||||
"install_saved": "Guardado correctamente",
|
||||
"encryption_title": "Cifrado",
|
||||
"encryption_desc": "Soporte de cifrado (HTTPS\/TLS) tanto para DNS como para la interfaz web de administraci\u00f3n",
|
||||
"encryption_config_saved": "Configuraci\u00f3n de cifrado guardado",
|
||||
"encryption_server": "Nombre del servidor",
|
||||
"encryption_server_enter": "Ingrese su nombre de dominio",
|
||||
"encryption_server_desc": "Para utilizar HTTPS, debe ingresar el nombre del servidor que coincida con su certificado SSL.",
|
||||
"encryption_redirect": "Redireccionar a HTTPS autom\u00e1ticamente",
|
||||
"encryption_redirect_desc": "Si est\u00e1 marcado, AdGuard Home redireccionar\u00e1 autom\u00e1ticamente de HTTP a las direcciones HTTPS.",
|
||||
"encryption_https": "Puerto HTTPS",
|
||||
"encryption_https_desc": "Si el puerto HTTPS est\u00e1 configurado, la interfaz de administraci\u00f3n de AdGuard Home ser\u00e1 accesible a trav\u00e9s de HTTPS, y tambi\u00e9n proporcionar\u00e1 DNS mediante HTTPS en la ubicaci\u00f3n '\/dns-query'.",
|
||||
"encryption_dot": "Puerto DNS mediante TLS",
|
||||
"encryption_dot_desc": "Si este puerto est\u00e1 configurado, AdGuard Home ejecutar\u00e1 un servidor DNS mediante TLS en este puerto.",
|
||||
"encryption_certificates": "Certificados",
|
||||
"encryption_certificates_desc": "Para utilizar el cifrado, debe proporcionar una cadena de certificados SSL v\u00e1lida para su dominio. Puede obtener un certificado gratuito en <0>{{link}}<\/0> o puede comprarlo en una de las autoridades de certificaci\u00f3n de confianza.",
|
||||
"encryption_certificates_input": "Copie\/pegue aqu\u00ed sus certificados codificados PEM.",
|
||||
"encryption_status": "Estado",
|
||||
"encryption_expire": "Expira",
|
||||
"encryption_key": "Clave privada",
|
||||
"encryption_key_input": "Copie\/pegue aqu\u00ed su clave privada codificada PEM para su certificado.",
|
||||
"encryption_enable": "Habilitar cifrado (HTTPS, DNS mediante HTTPS y DNS mediante TLS)",
|
||||
"encryption_enable_desc": "Si el cifrado est\u00e1 habilitado, la interfaz de administraci\u00f3n de AdGuard Home funcionar\u00e1 a trav\u00e9s de HTTPS, y el servidor DNS escuchar\u00e1 las peticiones DNS mediante HTTPS y DNS mediante TLS.",
|
||||
"encryption_chain_valid": "La cadena de certificado es v\u00e1lida",
|
||||
"encryption_chain_invalid": "La cadena de certificado no es v\u00e1lida",
|
||||
"encryption_key_valid": "Esta es una clave privada {{type}} v\u00e1lida",
|
||||
"encryption_key_invalid": "Esta es una clave privada {{type}} no v\u00e1lida",
|
||||
"encryption_subject": "Asunto",
|
||||
"encryption_issuer": "Emisor",
|
||||
"encryption_hostnames": "Nombres de hosts",
|
||||
"encryption_reset": "\u00bfEst\u00e1 seguro de que desea restablecer la configuraci\u00f3n de cifrado?",
|
||||
"topline_expiring_certificate": "Su certificado SSL est\u00e1 a punto de expirar. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
|
||||
"topline_expired_certificate": "Su certificado SSL ha expirado. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
|
||||
"form_error_port_range": "Ingrese el valor del puerto en el rango de 80 a 65535",
|
||||
"form_error_port_unsafe": "Este es un puerto inseguro",
|
||||
"form_error_equal": "No deber\u00eda ser igual",
|
||||
"form_error_password": "La contrase\u00f1a no coincide",
|
||||
"reset_settings": "Restablecer configuraci\u00f3n",
|
||||
"update_announcement": "\u00a1AdGuard Home {{version}} ya est\u00e1 disponible! <0>Haga clic aqu\u00ed<\/0> para m\u00e1s informaci\u00f3n.",
|
||||
"setup_guide": "Gu\u00eda de configuraci\u00f3n",
|
||||
"dns_addresses": "Direcciones DNS",
|
||||
"down": "Abajo",
|
||||
"fix": "Corregir",
|
||||
"dns_providers": "Aqu\u00ed hay una <0>lista de proveedores DNS<\/0> conocidos para elegir.",
|
||||
"update_now": "Actualizar ahora",
|
||||
"update_failed": "Error en la actualizaci\u00f3n autom\u00e1tica. Por favor <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>siga los pasos<\/a> para actualizar manualmente.",
|
||||
"processing_update": "Por favor espere, AdGuard Home se est\u00e1 actualizando",
|
||||
"clients_title": "Clientes",
|
||||
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Personalizado",
|
||||
"table_client": "Cliente",
|
||||
"table_name": "Nombre",
|
||||
"save_btn": "Guardar",
|
||||
"client_add": "A\u00f1adir cliente",
|
||||
"client_new": "Cliente nuevo",
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Direcci\u00f3n IP",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la direcci\u00f3n IP o MAC. Tenga en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home tambi\u00e9n es un <0>servidor DHCP<\/0>.",
|
||||
"form_enter_ip": "Ingresar IP",
|
||||
"form_enter_mac": "Ingresar MAC",
|
||||
"form_client_name": "Ingrese el nombre del cliente",
|
||||
"client_global_settings": "Usar configuraci\u00f3n global",
|
||||
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
|
||||
"client_added": "Cliente \"{{key}}\" a\u00f1adido correctamente",
|
||||
"client_updated": "Cliente \"{{key}}\" actualizado correctamente",
|
||||
"table_statistics": "N\u00famero de peticiones (\u00faltimas 24 horas)",
|
||||
"clients_not_found": "No se han encontrado clientes",
|
||||
"client_confirm_delete": "\u00bfEst\u00e1 seguro de que desea eliminar el cliente \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Are you sure you want to delete filter?",
|
||||
"auto_clients_title": "Clientes (activos)",
|
||||
"auto_clients_desc": "Datos de los clientes que utilizan AdGuard Home pero que no est\u00e1n almacenados en la configuraci\u00f3n",
|
||||
"access_title": "Access settings",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
|
||||
"access_allowed_title": "Allowed clients",
|
||||
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
|
||||
"access_disallowed_title": "Disallowed clients",
|
||||
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
|
||||
"access_blocked_title": "Blocked domains",
|
||||
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
|
||||
"access_settings_saved": "Access settings successfully saved"
|
||||
}
|
||||
@@ -1,4 +1,33 @@
|
||||
{
|
||||
"url_added_successfully": "Url ajout\u00e9e",
|
||||
"check_dhcp_servers": "Rechercher les serveurs DHCP",
|
||||
"save_config": "Sauvegarder la configuration",
|
||||
"enabled_dhcp": "Serveur DHCP activ\u00e9",
|
||||
"disabled_dhcp": "Serveur DHCP d\u00e9sactiv\u00e9",
|
||||
"dhcp_title": "Serveur DHCP (experimental !)",
|
||||
"dhcp_description": "Si votre routeur ne fonctionne pas avec les r\u00e9glages DHCP, vous pouvez utiliser le serveur DHCP par d\u00e9faut d'AdGuard.",
|
||||
"dhcp_enable": "Activer le serveur DHCP",
|
||||
"dhcp_disable": "D\u00e9sactiver le serveur DHCP",
|
||||
"dhcp_not_found": "Aucun serveur DHCP actif trouv\u00e9 sur le r\u00e9seau. Vous pouvez activer le serveur DHCP int\u00e9gr\u00e9.",
|
||||
"dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le r\u00e9seau. Ce n'est pas prudent d'activer le serveur DHCP int\u00e9gr\u00e9 en ce moment.",
|
||||
"dhcp_leases": "Locations des serveurs DHCP",
|
||||
"dhcp_leases_not_found": "Aucune location des serveurs DHCP trouv\u00e9e",
|
||||
"dhcp_config_saved": "La configuration du serveur DHCP est sauvegard\u00e9e",
|
||||
"form_error_required": "Champ requis",
|
||||
"form_error_ip_format": "Format IPv4 invalide",
|
||||
"form_error_positive": "Doit \u00eatre sup\u00e9rieur \u00e0 0\u001c",
|
||||
"dhcp_form_gateway_input": "IP de la passerelle",
|
||||
"dhcp_form_subnet_input": "Masque de sous-r\u00e9seau",
|
||||
"dhcp_form_range_title": "Rang\u00e9e des adresses IP",
|
||||
"dhcp_form_range_start": "D\u00e9but de la rang\u00e9e",
|
||||
"dhcp_form_range_end": "Fin de la rang\u00e9e",
|
||||
"dhcp_form_lease_title": "P\u00e9riode de location du serveur DHCP (secondes)",
|
||||
"dhcp_form_lease_input": "Dur\u00e9e de la location",
|
||||
"dhcp_interface_select": "S\u00e9lectionner l'interface du serveur DHCP",
|
||||
"dhcp_hardware_address": "Adresse de la machine",
|
||||
"dhcp_ip_addresses": "Adresses IP",
|
||||
"dhcp_table_hostname": "Nom de machine",
|
||||
"dhcp_table_expires": "Expire le",
|
||||
"back": "Retour",
|
||||
"dashboard": "Tableau de bord",
|
||||
"settings": "Param\u00e8tres",
|
||||
@@ -87,8 +116,9 @@
|
||||
"example_comment_meaning": "commentaire",
|
||||
"example_comment_hash": "# Et comme \u00e7a aussi on peut laisser des commentaires",
|
||||
"example_upstream_regular": "DNS classique (au-dessus de UDP)",
|
||||
"example_upstream_dot": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-au-dessus-de-TLS<\/a> chiffr\u00e9",
|
||||
"example_upstream_doh": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS<\/a> chiffr\u00e9",
|
||||
"example_upstream_dot": "<0>DNS-au-dessus-de-TLS<\/0> chiffr\u00e9",
|
||||
"example_upstream_doh": "<0>DNS-au-dessus-de-HTTPS<\/0> chiffr\u00e9",
|
||||
"example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps<\/0> pour <1>DNSCrypt<\/1> ou les resolveurs <2>DNS-au-dessus-de-HTTPS<\/2>",
|
||||
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
||||
"all_filters_up_to_date_toast": "Tous les filtres sont mis \u00e0 jour",
|
||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis \u00e0 jour",
|
||||
@@ -125,5 +155,6 @@
|
||||
"found_in_known_domain_db": "Trouv\u00e9 dans la base de donn\u00e9es des domaines connus",
|
||||
"category_label": "Cat\u00e9gorie",
|
||||
"rule_label": "R\u00e8gle",
|
||||
"filter_label": "Filtre"
|
||||
"filter_label": "Filtre",
|
||||
"unknown_filter": "Filtre inconnu {{filterId}}"
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"refresh_status": "\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u6700\u65b0\u306b\u3059\u308b",
|
||||
"check_dhcp_servers": "DHCP\u30b5\u30fc\u30d0\u3092\u30c1\u30a7\u30c3\u30af\u3059\u308b",
|
||||
"save_config": "\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3059\u308b",
|
||||
"enabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"disabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"dhcp_title": "DHCP\u30b5\u30fc\u30d0",
|
||||
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
|
||||
"dhcp_title": "DHCP\u30b5\u30fc\u30d0\uff08\u5b9f\u9a13\u7684\uff01\uff09",
|
||||
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
|
||||
"dhcp_enable": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_disable": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
|
||||
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
|
||||
"dhcp_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u898b\u3064\u3051\u307e\u3057\u305f\u3002\u5185\u81d3\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u5b89\u5168\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002",
|
||||
"dhcp_leases": "DHCP\u5272\u5f53",
|
||||
"dhcp_leases_not_found": "DHCP\u5272\u5f53\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"dhcp_config_saved": "DHCP\u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f",
|
||||
@@ -21,6 +22,11 @@
|
||||
"dhcp_form_range_end": "\u7bc4\u56f2\u306e\u7d42\u4e86",
|
||||
"dhcp_form_lease_title": "DHCP\u5272\u5f53\u6642\u9593\uff08\u79d2\u5358\u4f4d\uff09",
|
||||
"dhcp_form_lease_input": "\u5272\u5f53\u671f\u9593",
|
||||
"dhcp_interface_select": "DHCP\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u306e\u9078\u629e",
|
||||
"dhcp_hardware_address": "MAC\u30a2\u30c9\u30ec\u30b9",
|
||||
"dhcp_ip_addresses": "IP\u30a2\u30c9\u30ec\u30b9",
|
||||
"dhcp_table_hostname": "\u30db\u30b9\u30c8\u540d",
|
||||
"dhcp_table_expires": "\u6709\u52b9\u671f\u9650",
|
||||
"back": "\u623b\u308b",
|
||||
"dashboard": "\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9",
|
||||
"settings": "\u8a2d\u5b9a",
|
||||
@@ -40,7 +46,7 @@
|
||||
"disabled_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u6700\u65b0\u306b\u3059\u308b",
|
||||
"dns_query": "DNS\u30af\u30a8\u30ea",
|
||||
"blocked_by": "\u30d5\u30a3\u30eb\u30bf\u306b\u3088\u308a\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea",
|
||||
"blocked_by": "\u30d5\u30a3\u30eb\u30bf\u306b\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea",
|
||||
"stats_malware_phishing": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30de\u30eb\u30a6\u30a7\u30a2\uff0f\u30d5\u30a3\u30c3\u30b7\u30f3\u30b0",
|
||||
"stats_adult": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30a2\u30c0\u30eb\u30c8\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8",
|
||||
"stats_query_domain": "\u6700\u3082\u554f\u5408\u305b\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3",
|
||||
@@ -109,16 +115,16 @@
|
||||
"example_comment_meaning": "\u305f\u3060\u306e\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_comment_hash": "# \u3053\u3053\u3082\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_upstream_regular": "\u901a\u5e38\u306eDNS\uff08UDP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u307e\u305f\u306f <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> \u3092\u4f7f\u3048\u307e\u3059",
|
||||
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "<0>DNSCrypt<\/0> \u307e\u305f\u306f <1>DNS-over-HTTPS<\/1> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <2>DNS Stamps<\/2> \u3092\u4f7f\u3048\u307e\u3059",
|
||||
"example_upstream_tcp": "\u901a\u5e38\u306eDNS\uff08TCP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"all_filters_up_to_date_toast": "\u3059\u3079\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u306f\u65e2\u306b\u6700\u65b0\u3067\u3059",
|
||||
"updated_upstream_dns_toast": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
|
||||
"dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059",
|
||||
"dns_test_not_ok_toast": "\u30b5\u30fc\u30d0 \"{{key}}\": \u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u6b63\u3057\u304f\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044",
|
||||
"unblock_btn": "\u30d6\u30ed\u30c3\u30af\u89e3\u9664",
|
||||
"block_btn": "\u30d6\u30ed\u30c3\u30af",
|
||||
"block_btn": "\u30d6\u30ed\u30c3\u30af\u3059\u308b",
|
||||
"time_table_header": "\u6642\u523b",
|
||||
"domain_name_table_header": "\u30c9\u30e1\u30a4\u30f3\u540d",
|
||||
"type_table_header": "\u7a2e\u985e",
|
||||
@@ -149,5 +155,5 @@
|
||||
"category_label": "\u30ab\u30c6\u30b4\u30ea",
|
||||
"rule_label": "\u30eb\u30fc\u30eb",
|
||||
"filter_label": "\u30d5\u30a3\u30eb\u30bf",
|
||||
"unknown_filter": "\u4e0d\u660e\u306a\u30d5\u30a3\u30eb\u30bf {{filterId}}"
|
||||
"unknown_filter": "\u4e0d\u660e\u306a\u30d5\u30a3\u30eb\u30bf {{filterId}}"
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
{
|
||||
"refresh_status": "Atualizar status",
|
||||
"example_upstream_reserved": "Voc\u00ea pode especificar um DNS upstream <0>para um dom\u00ednio(s) especifico<\/0>",
|
||||
"upstream_parallel": "Usar consultas paralelas para acelerar a resolu\u00e7\u00e3o consultando simultaneamente todos os servidores upstream",
|
||||
"bootstrap_dns": "Servidores DNS de inicializa\u00e7\u00e3o",
|
||||
"bootstrap_dns_desc": "Servidores DNS de inicializa\u00e7\u00e3o s\u00e3o usados para resolver endere\u00e7os IP dos resolvedores DoH\/DoT que voc\u00ea especifica como upstreams.",
|
||||
"url_added_successfully": "URL adicionada com sucesso",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Salvar configura\u00e7\u00e3o",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
"disabled_dhcp": "Servidor DHCP desativado",
|
||||
"dhcp_title": "Servidor DHCP",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Se o seu roteador n\u00e3o fornecer configura\u00e7\u00f5es de DHCP, voc\u00ea poder\u00e1 usar o servidor DHCP integrado do AdGuard.",
|
||||
"dhcp_enable": "Ativar servidor DHCP",
|
||||
"dhcp_disable": "Desativar servidor DHCP",
|
||||
"dhcp_not_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. \u00c9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_found": "Foram encontrados servidores DHCP ativos na rede. N\u00e3o \u00e9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_leases": "Concess\u00f5es DHCP",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "Nenhuma concess\u00e3o DHCP encontrada",
|
||||
"dhcp_config_saved": "Salvar configura\u00e7\u00f5es do servidor DHCP",
|
||||
"form_error_required": "Campo obrigat\u00f3rio",
|
||||
"form_error_ip_format": "formato de endere\u00e7o IPv4 inv\u00e1lido",
|
||||
"form_error_mac_format": "Invalid MAC format",
|
||||
"form_error_positive": "Deve ser maior que 0",
|
||||
"dhcp_form_gateway_input": "IP do gateway",
|
||||
"dhcp_form_subnet_input": "M\u00e1scara de sub-rede",
|
||||
@@ -21,6 +29,23 @@
|
||||
"dhcp_form_range_end": "Final da faixa",
|
||||
"dhcp_form_lease_title": "Tempo de concess\u00e3o do DHCP (em segundos)",
|
||||
"dhcp_form_lease_input": "Dura\u00e7\u00e3o da concess\u00e3o",
|
||||
"dhcp_interface_select": "Selecione a interface DHCP",
|
||||
"dhcp_hardware_address": "Endere\u00e7o de hardware",
|
||||
"dhcp_ip_addresses": "Endere\u00e7o de IP",
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"dhcp_warning": "Se voc\u00ea quiser ativar o servidor DHCP, verifique se n\u00e3o h\u00e1 outro servidor DHCP ativo na sua rede. Caso contr\u00e1rio, a internet pode parar de funcionar para outros dispositivos conectados!",
|
||||
"dhcp_error": "N\u00e3o foi poss\u00edvel determinar se existe outro servidor DHCP na rede.",
|
||||
"dhcp_static_ip_error": "Para usar o servidor DHCP, voc\u00ea deve definir um endere\u00e7o IP est\u00e1tico. N\u00e3o conseguimos determinar se essa interface de rede est\u00e1 configurada usando o endere\u00e7o de IP est\u00e1tico. Por favor, defina um endere\u00e7o IP est\u00e1tico manualmente.",
|
||||
"dhcp_dynamic_ip_found": "Seu sistema usa a configura\u00e7\u00e3o de endere\u00e7o IP din\u00e2mico para a interface <0>{{interfaceName}}<\/0>. Para usar o servidor DHCP, voc\u00ea deve definir um endere\u00e7o de IP est\u00e1tico. Seu endere\u00e7o IP atual \u00e9 <0> {{ipAddress}} <\/ 0>. Vamos definir automaticamente este endere\u00e7o IP como est\u00e1tico se voc\u00ea pressionar o bot\u00e3o Ativar DHCP.",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Enter hostname",
|
||||
"error_details": "Detalhes do erro",
|
||||
"back": "Voltar",
|
||||
"dashboard": "Painel",
|
||||
"settings": "Configura\u00e7\u00f5es",
|
||||
@@ -34,6 +59,7 @@
|
||||
"copyright": "Copyright",
|
||||
"homepage": "P\u00e1gina inicial",
|
||||
"report_an_issue": "Reportar um problema",
|
||||
"privacy_policy": "Pol\u00edtica de privacidade",
|
||||
"enable_protection": "Ativar prote\u00e7\u00e3o",
|
||||
"enabled_protection": "Prote\u00e7\u00e3o ativada",
|
||||
"disable_protection": "Desativar prote\u00e7\u00e3o",
|
||||
@@ -70,8 +96,12 @@
|
||||
"no_servers_specified": "Nenhum servidor especificado",
|
||||
"no_settings": "N\u00e3o configurado",
|
||||
"general_settings": "Configura\u00e7\u00f5es gerais",
|
||||
"dns_settings": "DNS settings",
|
||||
"encryption_settings": "Encryption settings",
|
||||
"dhcp_settings": "DHCP settings",
|
||||
"client_settings": "Client settings",
|
||||
"upstream_dns": "Servidores DNS upstream",
|
||||
"upstream_dns_hint": "Se voc\u00ea deixar este campo vazio, o AdGuard Home ir\u00e1 usar o<a href='https:\/\/1.1.1.1\/' target='_blank'>DNS da Cloudflare<\/a> como upstream. Use o prefixo tls:\/\/ para servidores DNS com TLS.",
|
||||
"upstream_dns_hint": "Se voc\u00ea deixar este campo vazio, o AdGuard Home ir\u00e1 usar o<a href='https:\/\/1.1.1.1\/' target='_blank'>DNS da Cloudflare<\/a> como upstream.",
|
||||
"test_upstream_btn": "Testar upstreams",
|
||||
"apply_btn": "Aplicar",
|
||||
"disabled_filtering_toast": "Filtragem desativada",
|
||||
@@ -88,6 +118,7 @@
|
||||
"rules_count_table_header": "Quantidade de regras",
|
||||
"last_time_updated_table_header": "\u00daltima atualiza\u00e7\u00e3o",
|
||||
"actions_table_header": "A\u00e7\u00f5es",
|
||||
"edit_table_action": "Edit",
|
||||
"delete_table_action": "Excluir",
|
||||
"filters_and_hosts": "Filtros e listas de bloqueio de hosts",
|
||||
"filters_and_hosts_hint": "O AdGuard Home entende regras b\u00e1sicas de bloqueio de an\u00fancios e a sintaxe de arquivos de hosts.",
|
||||
@@ -108,10 +139,11 @@
|
||||
"example_comment": "! Aqui vai um coment\u00e1rio",
|
||||
"example_comment_meaning": "apenas um coment\u00e1rio",
|
||||
"example_comment_hash": "# Tamb\u00e9m um coment\u00e1rio",
|
||||
"example_regex_meaning": "bloqueia o acesso aos dom\u00ednios correspondentes \u00e0 express\u00e3o regular especificada",
|
||||
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
|
||||
"example_upstream_dot": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>atrav\u00e9s do TLS<\/a>",
|
||||
"example_upstream_doh": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>atrav\u00e9s do HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Voc\u00ea pode usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a>para o<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a>ou usar resolvedores<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-sobre-HTTPS<\/a>",
|
||||
"example_upstream_dot": "<0>DNS-sobre-TLS<\/0> criptografado",
|
||||
"example_upstream_doh": "<0>DNS-sobre-HTTPS<\/0> criptografado",
|
||||
"example_upstream_sdns": "Voc\u00ea pode usar <0>DNS Stamps<\/0>para o <1>DNSCrypt<\/1>ou usar os resolvedores <2>DNS-sobre-HTTPS<\/2>",
|
||||
"example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados",
|
||||
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
|
||||
@@ -149,5 +181,136 @@
|
||||
"category_label": "Categoria",
|
||||
"rule_label": "Regra",
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}"
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
||||
"install_welcome_title": "Bem-vindo(a) ao AdGuard Home!",
|
||||
"install_welcome_desc": "O AdGuard Home \u00e9 um servidor de DNS para bloqueio de an\u00fancios e rastreamento em toda a rede. Sua finalidade \u00e9 permitir que voc\u00ea controle toda a sua rede e seus dispositivos sem precisar ter um programa instalado.",
|
||||
"install_settings_title": "Interface web de administrador",
|
||||
"install_settings_listen": "Interface de escuta",
|
||||
"install_settings_port": "Porta",
|
||||
"install_settings_interface_link": "A interface web de administrador do AdGuard estar\u00e1 dispon\u00edvel nos seguintes endere\u00e7os:",
|
||||
"form_error_port": "Digite uma porta v\u00e1lida",
|
||||
"install_settings_dns": "Servidor DNS",
|
||||
"install_settings_dns_desc": "Voc\u00ea precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endere\u00e7os:",
|
||||
"install_settings_all_interfaces": "Todas interfaces",
|
||||
"install_auth_title": "Autentica\u00e7\u00e3o",
|
||||
"install_auth_desc": "\u00c9 altamente recomend\u00e1vel configurar uma senha de autentica\u00e7\u00e3o na interface web de administrador do AdGuard Home. Mesmo que seja acess\u00edvel apenas em sua rede local, ainda \u00e9 importante proteg\u00ea-lo contra o acesso irrestrito.",
|
||||
"install_auth_username": "Nome de usu\u00e1rio",
|
||||
"install_auth_password": "Senha",
|
||||
"install_auth_confirm": "Confirmar senha",
|
||||
"install_auth_username_enter": "Digite o nome de usu\u00e1rio",
|
||||
"install_auth_password_enter": "Digite a senha",
|
||||
"install_step": "Passo",
|
||||
"install_devices_title": "Configure seus dispositivos",
|
||||
"install_devices_desc": "Para que o AdGuard Home comece a funcionar, voc\u00ea precisa configurar seus dispositivos para us\u00e1-lo.",
|
||||
"install_submit_title": "Parab\u00e9ns!",
|
||||
"install_submit_desc": "O procedimento de configura\u00e7\u00e3o est\u00e1 conclu\u00eddo e voc\u00ea est\u00e1 pronto para come\u00e7ar a usar o AdGuard Home.",
|
||||
"install_devices_router": "Roteador",
|
||||
"install_devices_router_desc": "Esta configura\u00e7\u00e3o cobrir\u00e1 automaticamente todos os dispositivos conectados ao seu roteador dom\u00e9stico e voc\u00ea n\u00e3o ir\u00e1 precisar configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home est\u00e1 capturando os seguintes endere\u00e7os",
|
||||
"install_devices_router_list_1": "Abra as configura\u00e7\u00f5es do seu roteador\nNo navegador digite o IP do roteador, o padr\u00e3o \u00e9 (http:\/\/192.168.0.1\/ ou http:\/\/192.168.1.1\/), e o login e senha \u00e9 admin\/admin; Se voc\u00ea n\u00e3o se lembra da senha, voc\u00ea pode redefinir a senha rapidamente pressionando um bot\u00e3o no pr\u00f3prio roteador. Alguns roteadores t\u00eam um aplicativo espec\u00edfico que j\u00e1 deve estar instalado em seu computador\/telefone.",
|
||||
"install_devices_router_list_2": "Encontre as Configura\u00e7\u00f5es de DNS. Procure as letras DNS ao lado de um campo que permite dois ou tr\u00eas conjuntos de n\u00fameros, cada um dividido em quatro grupos de um a tr\u00eas n\u00fameros.",
|
||||
"install_devices_router_list_3": "Digite aqui seu servidor do AdGuard Home.",
|
||||
"install_devices_windows_list_1": "Abra o Painel de Controle pelo Menu Iniciar ou pela Pesquisa do Windows.",
|
||||
"install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Compartilhamento.",
|
||||
"install_devices_windows_list_3": "No lado esquerdo da janela clique em Alterar as configura\u00e7\u00f5es do adaptador.",
|
||||
"install_devices_windows_list_4": "Selecione sua atual conex\u00e3o, clique nela com o bot\u00e3o direito do mouse e depois clique em Propriedades.",
|
||||
"install_devices_windows_list_5": "Procure na lista por Internet Protocol Version 4 (TCP\/IP), selecione e clique em Propriedades novamente.",
|
||||
"install_devices_windows_list_6": "Marque usar os seguintes endere\u00e7os de servidor DNS e digite os endere\u00e7os do servidores do AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Clique na \u00edcone da Apple e depois em Prefer\u00eancias do Sistema.",
|
||||
"install_devices_macos_list_2": "Clique em Rede.",
|
||||
"install_devices_macos_list_3": "Selecione a primeira conex\u00e3o da lista e clique em Avan\u00e7ado.",
|
||||
"install_devices_macos_list_4": "Selecione a guia DNS e digite os endere\u00e7os dos servidores do AdGuard Home.",
|
||||
"install_devices_android_list_1": "Na tela inicial do menu Android, toque em Configura\u00e7\u00f5es.",
|
||||
"install_devices_android_list_2": "Toque em Wi-Fi. A tela listando todas as redes ser\u00e1 exibida (n\u00e3o \u00e9 poss\u00edvel configurar DNS personalizado para uma conex\u00e3o de dados m\u00f3veis)",
|
||||
"install_devices_android_list_3": "Pressione prolongadamente a rede para a qual voc\u00ea est\u00e1 conectado e toque em Modificar rede",
|
||||
"install_devices_android_list_4": "Em alguns dispositivos, talvez seja necess\u00e1rio marcar a caixa Avan\u00e7ado para ver as outras configura\u00e7\u00f5es. Para ajustar suas configura\u00e7\u00f5es de DNS do Android, voc\u00ea precisar\u00e1 alternar as configura\u00e7\u00f5es de IP de DHCP para Est\u00e1tico.",
|
||||
"install_devices_android_list_5": "Altere o conjunto dos valores DNS 1 e DNS 2 para os endere\u00e7os de servidores do AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Na tela incial, toque em Ajustes.",
|
||||
"install_devices_ios_list_2": "Selecione Wi-Fi no menu esquerdo (n\u00e3o \u00e9 poss\u00edvel configurar o DNS em conex\u00f5es de dados m\u00f3veis).",
|
||||
"install_devices_ios_list_3": "Toque no nome da rede atualmente ativa.",
|
||||
"install_devices_ios_list_4": "No campo DNS, digite os endere\u00e7os dos servidores do AdGuard Home.",
|
||||
"get_started": "Come\u00e7ar",
|
||||
"next": "Pr\u00f3ximo",
|
||||
"open_dashboard": "Abrir painel",
|
||||
"install_saved": "Salvo com sucesso",
|
||||
"encryption_title": "Criptografia",
|
||||
"encryption_desc": "Suporte a criptografia (HTTPS\/TLS) para DNS e interface de administra\u00e7\u00e3o web",
|
||||
"encryption_config_saved": "Configura\u00e7\u00e3o de criptografia salva",
|
||||
"encryption_server": "Nome do servidor",
|
||||
"encryption_server_enter": "Digite seu nome de dom\u00ednio",
|
||||
"encryption_server_desc": "Para usar o protocolo HTTPS, voc\u00ea precisa digitar o nome do servidor que corresponde ao seu certificado SSL.",
|
||||
"encryption_redirect": "Redirecionar automaticamente para HTTPS",
|
||||
"encryption_redirect_desc": "Se marcado, o AdGuard Home ir\u00e1 redirecionar automaticamente os endere\u00e7os HTTP para HTTPS.",
|
||||
"encryption_https": "Porta HTTPS",
|
||||
"encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home ser\u00e1 acess\u00edvel via HTTPS e tamb\u00e9m fornecer\u00e1 o DNS-sobre-HTTPS no local '\/dns-query'.",
|
||||
"encryption_dot": "Porta DNS-sobre-TLS",
|
||||
"encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home ir\u00e1 executar o servidor DNS-sobre- TSL nesta porta.",
|
||||
"encryption_certificates": "Certificados",
|
||||
"encryption_certificates_desc": "Para usar criptografia, voc\u00ea precisa fornecer uma cadeia de certificados SSL v\u00e1lida para seu dom\u00ednio. Voc\u00ea pode obter um certificado gratuito em <0> {{link}}<\/0> ou pode compr\u00e1-lo de uma das autoridades de certifica\u00e7\u00e3o confi\u00e1veis.",
|
||||
"encryption_certificates_input": "Copie\/cole aqui seu certificado codificado em PEM.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expira",
|
||||
"encryption_key": "Chave privada",
|
||||
"encryption_key_input": "Copie\/cole aqui a chave privada codificada em PEM para seu certificado.",
|
||||
"encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
|
||||
"encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionar\u00e1 em HTTPS, o servidor DNS ir\u00e1 capturar as solicita\u00e7\u00f5es por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
|
||||
"encryption_chain_valid": "Cadeia de chave v\u00e1lida.",
|
||||
"encryption_chain_invalid": "A cadeia de certificado \u00e9 inv\u00e1lida",
|
||||
"encryption_key_valid": "Esta \u00e9 uma chave privada {{type}} v\u00e1lida",
|
||||
"encryption_key_invalid": "Esta \u00e9 uma chave privada {{type}} inv\u00e1lida",
|
||||
"encryption_subject": "Assunto",
|
||||
"encryption_issuer": "Emissor",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Voc\u00ea tem certeza de que deseja redefinir a configura\u00e7\u00e3o de criptografia?",
|
||||
"topline_expiring_certificate": "Seu certificado SSL est\u00e1 prestes a expirar. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/]0>",
|
||||
"topline_expired_certificate": "Seu certificado SSL est\u00e1 expirado. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/0>",
|
||||
"form_error_port_range": "Digite um porta entre 80 e 65535",
|
||||
"form_error_port_unsafe": "Esta porta n\u00e3o \u00e9 segura",
|
||||
"form_error_equal": "N\u00e3o deve ser igual",
|
||||
"form_error_password": "Senhas n\u00e3o coincidem",
|
||||
"reset_settings": "Redefinir configura\u00e7\u00f5es",
|
||||
"update_announcement": "AdGuard Home {{version}} est\u00e1 dispon\u00edvel!<0>Clique aqui<\/0> para mais informa\u00e7\u00f5es.",
|
||||
"setup_guide": "Guia de configura\u00e7\u00e3o",
|
||||
"dns_addresses": "Endere\u00e7os DNS",
|
||||
"down": "Caiu",
|
||||
"fix": "Corrigido",
|
||||
"dns_providers": "Aqui est\u00e1 uma <0>lista de provedores de DNS conhecidos<\/0> para escolher.",
|
||||
"update_now": "Update now",
|
||||
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
|
||||
"processing_update": "Please wait, AdGuard Home is being updated",
|
||||
"clients_title": "Clients",
|
||||
"clients_desc": "Configure devices connected to AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Custom",
|
||||
"table_client": "Client",
|
||||
"table_name": "Name",
|
||||
"save_btn": "Save",
|
||||
"client_add": "Add Client",
|
||||
"client_new": "New Client",
|
||||
"client_edit": "Edit Client",
|
||||
"client_identifier": "Identifier",
|
||||
"ip_address": "IP address",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
|
||||
"form_enter_ip": "Enter IP",
|
||||
"form_enter_mac": "Enter MAC",
|
||||
"form_client_name": "Enter client name",
|
||||
"client_global_settings": "Use global settings",
|
||||
"client_deleted": "Client \"{{key}}\" successfully deleted",
|
||||
"client_added": "Client \"{{key}}\" successfully added",
|
||||
"client_updated": "Client \"{{key}}\" successfully updated",
|
||||
"table_statistics": "Requests count (last 24 hours)",
|
||||
"clients_not_found": "No clients found",
|
||||
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Are you sure you want to delete filter?",
|
||||
"auto_clients_title": "Clients (runtime)",
|
||||
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
|
||||
"access_title": "Access settings",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
|
||||
"access_allowed_title": "Allowed clients",
|
||||
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
|
||||
"access_disallowed_title": "Disallowed clients",
|
||||
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
|
||||
"access_blocked_title": "Blocked domains",
|
||||
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
|
||||
"access_settings_saved": "Access settings successfully saved"
|
||||
}
|
||||
@@ -1,4 +1,32 @@
|
||||
{
|
||||
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b",
|
||||
"save_config": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e",
|
||||
"enabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"disabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"dhcp_title": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439!)",
|
||||
"dhcp_description": "\u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 DHCP, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 AdGuard.",
|
||||
"dhcp_enable": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
|
||||
"dhcp_disable": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
|
||||
"dhcp_not_found": "\u0410\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u0432 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 DHCP.",
|
||||
"dhcp_found": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438. \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e.",
|
||||
"dhcp_leases": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP",
|
||||
"dhcp_leases_not_found": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430",
|
||||
"dhcp_config_saved": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430",
|
||||
"form_error_required": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435",
|
||||
"form_error_ip_format": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 IPv4",
|
||||
"form_error_positive": "\u0414\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 0",
|
||||
"dhcp_form_gateway_input": "IP-\u0430\u0434\u0440\u0435\u0441 \u0448\u043b\u044e\u0437\u0430",
|
||||
"dhcp_form_subnet_input": "\u041c\u0430\u0441\u043a\u0430 \u043f\u043e\u0434\u0441\u0435\u0442\u0438",
|
||||
"dhcp_form_range_title": "\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d IP-\u0430\u0434\u0440\u0435\u0441\u043e\u0432",
|
||||
"dhcp_form_range_start": "\u041d\u0430\u0447\u0430\u043b\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
|
||||
"dhcp_form_range_end": "\u041a\u043e\u043d\u0435\u0446 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
|
||||
"dhcp_form_lease_title": "\u0412\u0440\u0435\u043c\u044f \u0430\u0440\u0435\u043d\u0434\u044b DHCP (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)",
|
||||
"dhcp_form_lease_input": "\u0421\u0440\u043e\u043a \u0430\u0440\u0435\u043d\u0434\u044b",
|
||||
"dhcp_interface_select": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 DHCP",
|
||||
"dhcp_hardware_address": "\u0410\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_ip_addresses": "IP-\u0430\u0434\u0440\u0435\u0441\u0430",
|
||||
"dhcp_table_hostname": "\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430",
|
||||
"dhcp_table_expires": "\u0418\u0441\u0442\u0435\u043a\u0430\u0435\u0442",
|
||||
"back": "\u041d\u0430\u0437\u0430\u0434",
|
||||
"dashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f",
|
||||
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
@@ -18,7 +46,7 @@
|
||||
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u044b\u043a\u043b.",
|
||||
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443",
|
||||
"dns_query": "DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u044b",
|
||||
"blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0424\u0438\u043b\u044c\u0442\u0440\u044b",
|
||||
"blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438",
|
||||
"stats_malware_phishing": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0435 \u0438 \u0444\u0438\u0448\u0438\u043d\u0433\u043e\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b",
|
||||
"stats_adult": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \"\u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0435\" \u0441\u0430\u0439\u0442\u044b",
|
||||
"stats_query_domain": "\u0427\u0430\u0441\u0442\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b",
|
||||
@@ -87,8 +115,9 @@
|
||||
"example_comment_meaning": "\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439",
|
||||
"example_comment_hash": "# \u0418 \u0432\u043e\u0442 \u0442\u0430\u043a \u0442\u043e\u0436\u0435",
|
||||
"example_upstream_regular": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 UDP)",
|
||||
"example_upstream_dot": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/a>",
|
||||
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/a>",
|
||||
"example_upstream_dot": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/0>",
|
||||
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <0>DNS Stamps<\/0> \u0434\u043b\u044f <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-over-HTTPS<\/2> \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u043e\u0432",
|
||||
"example_upstream_tcp": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 TCP)",
|
||||
"all_filters_up_to_date_toast": "\u0412\u0441\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
|
||||
"updated_upstream_dns_toast": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
|
||||
@@ -125,5 +154,6 @@
|
||||
"found_in_known_domain_db": "\u041d\u0430\u0439\u0434\u0435\u043d \u0432 \u0431\u0430\u0437\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432.",
|
||||
"category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
|
||||
"rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e",
|
||||
"filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440"
|
||||
"filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440",
|
||||
"unknown_filter": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 {{filterId}}"
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"refresh_status": "Uppdatera status",
|
||||
"url_added_successfully": "URL tillagd utan fel",
|
||||
"check_dhcp_servers": "Letar efter DHCP-servrar",
|
||||
"save_config": "Spara inst\u00e4llningar",
|
||||
"enabled_dhcp": "DHCP-server aktiverad",
|
||||
"disabled_dhcp": "Dhcp-server avaktiverad",
|
||||
"dhcp_title": "DHCP-server",
|
||||
"dhcp_title": "DHCP-server (experimentell)",
|
||||
"dhcp_description": "Om din router inte har inst\u00e4llningar f\u00f6r DHCP kan du anv\u00e4nda AdGuards inbyggda server.",
|
||||
"dhcp_enable": "Aktivera DHCP.-server",
|
||||
"dhcp_disable": "Avaktivera DHCP-server",
|
||||
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i n\u00e4tverkat.",
|
||||
"dhcp_found": "N\u00e5gra aktiva DHCP-servar uppt\u00e4cktes. Det \u00e4r inte s\u00e4kert att aktivera inbyggda DHCP-servrar.",
|
||||
"dhcp_leases": "DHCP-lease",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||
"dhcp_config_saved": "Sparade inst\u00e4llningar f\u00f6r DHCP-servern",
|
||||
@@ -21,6 +23,12 @@
|
||||
"dhcp_form_range_end": "Gr\u00e4nsslut",
|
||||
"dhcp_form_lease_title": "DHCP-leasetid (i sekunder)",
|
||||
"dhcp_form_lease_input": "Leasetid",
|
||||
"dhcp_interface_select": "V\u00e4lj DHCP-gr\u00e4nssnitt",
|
||||
"dhcp_hardware_address": "H\u00e5rdvaruadress",
|
||||
"dhcp_ip_addresses": "IP-adresser",
|
||||
"dhcp_table_hostname": "V\u00e4rdnamn",
|
||||
"dhcp_table_expires": "Utg\u00e5r",
|
||||
"dhcp_warning": "Om du vill koppla in den inbyggda DHCP-servern m\u00e5ste du f\u00f6rs\u00e4kra dig om att det finns n\u00e5gra andra DHCP-servar som i s\u00e5 fall riskerar att orsaka avbrott p\u00e5 internet!",
|
||||
"back": "Tiilbaka",
|
||||
"dashboard": "Kontrollpanel",
|
||||
"settings": "Inst\u00e4llningar",
|
||||
@@ -108,10 +116,11 @@
|
||||
"example_comment": "! H\u00e4r kommer en kommentar",
|
||||
"example_comment_meaning": "Endast en kommentar",
|
||||
"example_comment_hash": "# Ocks\u00e5 en kommentar",
|
||||
"example_regex_meaning": "blockera \u00e5tkomst till en dom\u00e4n som matchar det angivna uttrycket",
|
||||
"example_upstream_regular": "vanlig DNS (\u00f6ver UDP)",
|
||||
"example_upstream_dot": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Du kan anv\u00e4nda <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS-stamps<\/a> f\u00f6r <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> eller <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u00f6ver-HTTPS<\/a>\n-resolvers",
|
||||
"example_upstream_dot": "krypterat <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "krypterat <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "Du kan anv\u00e4nda <0>DNS-stamps<\/0> f\u00f6r <1>DNSCrypt<\/1> eller <2>DNS-\u00f6ver-HTTPS<\/2>\n-resolvers",
|
||||
"example_upstream_tcp": "vanlig DNS (\u00f6ver UDP)",
|
||||
"all_filters_up_to_date_toast": "Alla filter \u00e4r redan aktuella",
|
||||
"updated_upstream_dns_toast": "Uppdaterade uppstr\u00f6ms-dns-servrar",
|
||||
@@ -149,5 +158,93 @@
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Regel",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Ok\u00e4nt filter {{filterId}}"
|
||||
"unknown_filter": "Ok\u00e4nt filter {{filterId}}",
|
||||
"install_welcome_title": "V\u00e4lkommen till AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home \u00e4r en DNS-server f\u00f6r n\u00e4tverkst\u00e4ckande annons- och sp\u00e5rningsblockering. Dess syfte \u00e4r att de dig kontroll \u00f6ver hela n\u00e4tverket och alla dina enheter, utan behov av att anv\u00e4nda klientbaserade program.",
|
||||
"install_settings_title": "Administrat\u00f6rens webbgr\u00e4nssnitt",
|
||||
"install_settings_listen": "\u00d6vervakningsgr\u00e4nssnitt",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Din administrat\u00f6rssida f\u00f6r AdGuard Home finns p\u00e5 f\u00f6ljande adresser:",
|
||||
"form_error_port": "Skriv in ett giltigt portnummer",
|
||||
"install_settings_dns": "DNS-server",
|
||||
"install_settings_dns_desc": "Du beh\u00f6ver st\u00e4lla in dina enheter eller din router f\u00f6r att anv\u00e4nda DNS-server p\u00e5 f\u00f6ljande adresser.",
|
||||
"install_settings_all_interfaces": "Alla gr\u00e4nssnitt",
|
||||
"install_auth_title": "Autentisering",
|
||||
"install_auth_desc": "Det rekommenderas starkt att st\u00e4lla in l\u00f6senordsskydd till webbgr\u00e4nssnittets administrativa del i ditt AdGuard Home. \u00c4ven om den endast \u00e4r \u00e5tkomlig p\u00e5 ditt lokala n\u00e4tverk rekommenderas det \u00e4nd\u00e5 att skydda det mot o\u00f6nskad \u00e5tkomst.",
|
||||
"install_auth_username": "Anv\u00e4ndarnamn",
|
||||
"install_auth_password": "L\u00f6senord",
|
||||
"install_auth_confirm": "Bekr\u00e4fta l\u00f6senord",
|
||||
"install_auth_username_enter": "Skriv in anv\u00e4ndarnamn",
|
||||
"install_auth_password_enter": "Skriv in l\u00f6senord",
|
||||
"install_step": "Steg",
|
||||
"install_devices_title": "St\u00e4ll in dina enheter",
|
||||
"install_devices_desc": "F\u00f6r att kunna anv\u00e4nda AdGuard Home m\u00e5ste du s\u00e4lla in dina enheter f\u00f6r att utnyttja den.",
|
||||
"install_submit_title": "Grattis!",
|
||||
"install_submit_desc": "Inst\u00e4llningsproceduren \u00e4r klar och du kan b\u00f6rja anv\u00e4nda AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Den h\u00e4r anpassningen kommer att automatiskt t\u00e4cka in alla de enheter som \u00e4r anslutna till din hemmarouter och du beh\u00f6ver d\u00e4rf\u00f6r inte konfigurera var och en individuellt.",
|
||||
"install_devices_address": "AdGuard Home DNS-server t\u00e4cker f\u00f6ljande adresser",
|
||||
"install_devices_router_list_1": "\u00d6ppna routern Inst\u00e4llningar. Vanligtvis f\u00e5r man \u00e5tkomst via en URL (http:\/\/192.168.0.1 eller 192.168.1.1)- Du kommer att bli ombes att ange ett l\u00f6senord. L\u00f6senordet kan st\u00e5 angivet p\u00e5 routerns bak- eller undersida. Om l\u00f6senordet \u00e4ndrats och du inte k\u00e4nner till det kan du \u00e5terst\u00e4lla med Reset-knappen. En del routrar kr\u00e4ver en s\u00e4rskild applikation som skall finnas p\u00e5 antingen din dator eller i din mobil.",
|
||||
"install_devices_router_list_2": "Leta upp DHCP\/DNS-inst\u00e4llningarna. Titta efter DNS-tecken intill ett f\u00e4lt med tv\u00e5 eller tre upps\u00e4ttningar siffror, var och en uppdelade i grupper om fyra med en eller tre siffror.",
|
||||
"install_devices_router_list_3": "Ange serveradressen till ditt AdGuard Home.",
|
||||
"install_devices_windows_list_1": "\u00d6ppna Kontrollpanelen via Start eller Windows S\u00f6k.",
|
||||
"install_devices_windows_list_2": "V\u00e4lj N\u00e4tverks och delningscenter, N\u00e4tverk och Internet.",
|
||||
"install_devices_windows_list_3": "Leta upp \u00c4ndra n\u00e4tverkskortsalternativ",
|
||||
"install_devices_windows_list_4": "Markera din aktiva anslutning. H\u00f6gerklicka p\u00e5 den och v\u00e4lj Egenskaper.",
|
||||
"install_devices_windows_list_5": "Markera Internet Protocol Version 4 (TCP\/IP) och klicka p\u00e5 knappen Egenskaper.",
|
||||
"install_devices_windows_list_6": "Markera Anv\u00e4nd f\u00f6ljande DNS-serveradresser och skriv in adresserna till ditt AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Klicka p\u00e5 Apple-ikonen och v\u00e4lj Systemalternativ.",
|
||||
"install_devices_macos_list_2": "Klicka p\u00e5 N\u00e4tverk.",
|
||||
"install_devices_macos_list_3": "V\u00e4lj den f\u00f6rsta anslutningen i listan och klicka p\u00e5 Avancerat.",
|
||||
"install_devices_macos_list_4": "Klicka p\u00e5 DNS-fliken och skriv in AdGuard Homes serveradresser",
|
||||
"install_devices_android_list_1": "V\u00e4lj Inst\u00e4llningar fr\u00e5n Androids hemknapp",
|
||||
"install_devices_android_list_2": "Tryck p\u00e5 N\u00e4tverk och Internet, Wi-Fi. Alla tillg\u00e4ngliga n\u00e4tverk visas i en lista (det g\u00e5r inte all v\u00e4lja egen DNS p\u00e5 mobiln\u00e4tverk.",
|
||||
"install_devices_android_list_3": "H\u00e5ll ner p\u00e5 n\u00e4tverksnamnet som du \u00e4r ansluten till och v\u00e4lj \u00c4ndra n\u00e4tverk.",
|
||||
"install_devices_android_list_4": "P\u00e5 en del enheter kan du beh\u00f6va v\u00e4lja Avancerat f\u00f6r att komma \u00e5t ytterligare inst\u00e4llningar. F\u00f6r att \u00e4ndra p\u00e5 DNS-inst\u00e4llningar m\u00e5ste du byta IP-inst\u00e4llning fr\u00e5n DHCP till Statisk. P\u00e5 Android Pie v\u00e4ljs Privat DNS p\u00e5 N\u00e4tverk och internet.",
|
||||
"install_devices_android_list_5": "\u00c4ndra DNS 1 och DNS 2 till serveradresserna f\u00f6r AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Tryck Inst\u00e4llningar fr\u00e5n hemsk\u00e4rmen.",
|
||||
"install_devices_ios_list_2": "V\u00e4lj Wi_Fi p\u00e5 den v\u00e4nstra menyn (det g\u00e5r inte att st\u00e4lla in egen DNS f\u00f6r mobila n\u00e4tverk).",
|
||||
"install_devices_ios_list_3": "Tryck p\u00e5 namnet p\u00e5 den aktiva anslutningen.",
|
||||
"install_devices_ios_list_4": "Skriv in AdGuard Homes serveradresser i DNS-f\u00e4lten.",
|
||||
"get_started": "Kom ig\u00e5ng",
|
||||
"next": "N\u00e4sta",
|
||||
"open_dashboard": "\u00d6ppna Kontrollbordet",
|
||||
"install_saved": "Sparat utan fel",
|
||||
"encryption_title": "Encryption",
|
||||
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
||||
"encryption_certificates": "Certificates",
|
||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||
"encryption_certificates_input": "Copy\/paste your PEM-encoded certificates here.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expires",
|
||||
"encryption_key": "Private key",
|
||||
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate here.",
|
||||
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certificate chain is valid",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||
"encryption_subject": "Subject",
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings<\/0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings<\/0>.",
|
||||
"form_error_port_range": "Enter port value in the range of 80-65535",
|
||||
"form_error_port_unsafe": "This is an unsafe port",
|
||||
"form_error_equal": "Shouldn't be equal",
|
||||
"form_error_password": "L\u00f6senorden \u00f6verensst\u00e4mmer inte",
|
||||
"reset_settings": "Reset settings",
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info."
|
||||
}
|
||||
@@ -1,4 +1,30 @@
|
||||
{
|
||||
"check_dhcp_servers": "Ki\u1ec3m tra m\u00e1y ch\u1ee7 DHCP",
|
||||
"save_config": "L\u01b0u thi\u1ebft l\u1eadp",
|
||||
"enabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 k\u00edch ho\u1ea1t",
|
||||
"disabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 t\u1eaft",
|
||||
"dhcp_title": "M\u00e1y ch\u1ee7 DHCP (th\u1eed nghi\u1ec7m!)",
|
||||
"dhcp_description": "N\u1ebfu b\u1ed9 \u0111\u1ecbnh tuy\u1ebfn kh\u00f4ng tr\u1ee3 c\u00e0i \u0111\u1eb7t DHCP, b\u1ea1n c\u00f3 th\u1ec3 d\u00f9ng m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n c\u1ee7a AdGuard",
|
||||
"dhcp_enable": "B\u1eadt m\u00e1y ch\u1ee7 DHCP",
|
||||
"dhcp_disable": "T\u1eaft m\u00e1y ch\u1ee7 DHCP",
|
||||
"dhcp_not_found": "Kh\u00f4ng c\u00f3 m\u00e1y ch\u1ee7 DHCP n\u00e0o \u0111\u01b0\u1ee3c t\u00ecm th\u1ea5y trong m\u1ea1ng. C\u00f3 th\u1ec3 b\u1eadt m\u00e1y ch\u1ee7 DHCP m\u1ed9t c\u00e1ch an to\u00e0n",
|
||||
"dhcp_found": "\u0110\u00e3 t\u00ecm th\u1ea5y m\u00e1y ch\u1ee7 DHCP trong m\u1ea1ng. C\u00f3 th\u1ec3 c\u00f3 r\u1ee7i ro n\u1ebfu k\u00edch ho\u1ea1t m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "Saved DHCP server config",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip_format": "Invalid IPv4 format",
|
||||
"form_error_positive": "Ph\u1ea3i l\u1edbn h\u01a1n 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
"dhcp_form_range_start": "Range start",
|
||||
"dhcp_form_range_end": "IP k\u1ebft th\u00fac",
|
||||
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
|
||||
"dhcp_form_lease_input": "Lease duration",
|
||||
"dhcp_interface_select": "Ch\u1ecdn m\u1ed9t card m\u1ea1ng",
|
||||
"dhcp_hardware_address": "Hardware address",
|
||||
"dhcp_ip_addresses": "IP addresses",
|
||||
"back": "Quay l\u1ea1i",
|
||||
"dashboard": "T\u1ed5ng quan",
|
||||
"settings": "C\u00e0i \u0111\u1eb7t",
|
||||
@@ -18,7 +44,7 @@
|
||||
"disabled_protection": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7",
|
||||
"refresh_statics": "L\u00e0m m\u1edbi th\u1ed1ng k\u00ea",
|
||||
"dns_query": "Truy v\u1ea5n DNS",
|
||||
"blocked_by": "Ch\u1eb7n b\u1edfi B\u1ed9 l\u1ecdc",
|
||||
"blocked_by": "Ch\u1eb7n b\u1edfi b\u1ed9 l\u1ecdc",
|
||||
"stats_malware_phishing": "M\u00e3 \u0111\u1ed9c\/l\u1eeba \u0111\u1ea3o \u0111\u00e3 ch\u1eb7n",
|
||||
"stats_adult": "Website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n",
|
||||
"stats_query_domain": "T\u00ean mi\u1ec1n truy v\u1ea5n nhi\u1ec1u",
|
||||
@@ -87,8 +113,9 @@
|
||||
"example_comment_meaning": "Ch\u1ec9 l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
|
||||
"example_comment_hash": "# C\u0169ng l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
|
||||
"example_upstream_regular": "DNS th\u00f4ng th\u01b0\u1eddng (d\u00f9ng UDP)",
|
||||
"example_upstream_dot": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-d\u1ef1a te-TLS<\/a>",
|
||||
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_dot": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "b\u1ea1n c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> ho\u1eb7c <2>DNS-over-HTTPS<\/2> ",
|
||||
"example_upstream_tcp": "DNS th\u00f4ng th\u01b0\u1eddng(d\u00f9ng TCP)",
|
||||
"all_filters_up_to_date_toast": "T\u1ea5t c\u1ea3 b\u1ed9 l\u1ecdc \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1eadp nh\u1eadt",
|
||||
"updated_upstream_dns_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt m\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm",
|
||||
@@ -103,7 +130,7 @@
|
||||
"client_table_header": "Ng\u01b0\u1eddi d\u00f9ng cu\u1ed1i",
|
||||
"empty_response_status": "R\u1ed7ng",
|
||||
"show_all_filter_type": "Hi\u1ec7n t\u1ea5t c\u1ea3",
|
||||
"show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 ch\u1eb7n",
|
||||
"show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 l\u1ecdc",
|
||||
"no_logs_found": "Kh\u00f4ng c\u00f3 l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
"disabled_log_btn": "T\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
"download_log_file_btn": "T\u1ea3i t\u1eadp tin l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
@@ -125,5 +152,7 @@
|
||||
"found_in_known_domain_db": "T\u00ecm th\u1ea5y trong c\u01a1 s\u1edf d\u1eef li\u1ec7u t\u00ean mi\u1ec1n",
|
||||
"category_label": "Th\u1ec3 lo\u1ea1i",
|
||||
"rule_label": "Quy t\u1eafc",
|
||||
"filter_label": "B\u1ed9 l\u1ecdc"
|
||||
"filter_label": "B\u1ed9 l\u1ecdc",
|
||||
"url_added_successfully": "Th\u00eam b\u1ed9 l\u1ecdc th\u00e0nh c\u00f4ng",
|
||||
"unknown_filter": "B\u1ed9 l\u1ecdc kh\u00f4ng r\u00f5 {{filterId}}"
|
||||
}
|
||||
253
client/src/__locales/zh-cn.json
Normal file
253
client/src/__locales/zh-cn.json
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"upstream_parallel": "\u901a\u8fc7\u540c\u65f6\u67e5\u8be2\u6240\u6709\u4e0a\u6d41\u670d\u52a1\u5668\u4ee5\u4f7f\u7528\u5e76\u884c\u67e5\u8be2\u52a0\u901f\u89e3\u6790",
|
||||
"bootstrap_dns": "Bootstrap DNS \u670d\u52a1\u5668",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
|
||||
"url_added_successfully": "\u7f51\u5740\u6dfb\u52a0\u6210\u529f",
|
||||
"check_dhcp_servers": "\u68c0\u67e5 DHCP \u670d\u52a1\u5668",
|
||||
"save_config": "\u4fdd\u5b58\u914d\u7f6e",
|
||||
"enabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u542f\u7528",
|
||||
"disabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u7981\u7528",
|
||||
"dhcp_title": "DHCP \u670d\u52a1\u5668\uff08\u5b9e\u9a8c\u6027\uff09",
|
||||
"dhcp_description": "\u5982\u679c\u4f60\u7684\u8def\u7531\u5668\u6ca1\u6709\u63d0\u4f9b\u52a8\u6001\u4e3b\u673a\u8bbe\u7f6e\u534f\u8bae\uff08DHCP\uff09\u8bbe\u7f6e\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 AdGuard \u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u3002",
|
||||
"dhcp_enable": "\u542f\u7528 DHCP \u670d\u52a1\u5668",
|
||||
"dhcp_disable": "\u7981\u7528 DHCP \u670d\u52a1\u5668",
|
||||
"dhcp_not_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u672a\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u60a8\u53ef\u4ee5\u5b89\u5168\u5730\u542f\u7528\u5185\u7f6e DHCP \u670d\u52a1\u5668\u3002",
|
||||
"dhcp_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u5982\u679c\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u53ef\u80fd\u4e0d\u5b89\u5168\u3002",
|
||||
"dhcp_leases": "DHCP \u79df\u7ea6",
|
||||
"dhcp_leases_not_found": "\u672a\u68c0\u6d4b\u5230 DHCP \u79df\u7ea6",
|
||||
"dhcp_config_saved": "\u4fdd\u5b58 DHCP \u670d\u52a1\u5668\u914d\u7f6e",
|
||||
"form_error_required": "\u5fc5\u586b\u5b57\u6bb5",
|
||||
"form_error_ip_format": "\u65e0\u6548\u7684 IPv4 \u683c\u5f0f",
|
||||
"form_error_positive": "\u5fc5\u987b\u5927\u4e8e 0",
|
||||
"dhcp_form_gateway_input": "\u7f51\u5173 IP",
|
||||
"dhcp_form_subnet_input": "\u5b50\u7f51\u63a9\u7801",
|
||||
"dhcp_form_range_title": "IP \u5730\u5740\u8303\u56f4",
|
||||
"dhcp_form_range_start": "\u8d77\u59cb IP \u5730\u5740",
|
||||
"dhcp_form_range_end": "\u672b\u5c3e IP \u5730\u5740",
|
||||
"dhcp_form_lease_title": "DHCP \u79df\u7ea6\u65f6\u95f4\uff08\u79d2\uff09",
|
||||
"dhcp_form_lease_input": "\u79df\u671f",
|
||||
"dhcp_interface_select": "\u9009\u62e9 DHCP \u63a5\u53e3",
|
||||
"dhcp_hardware_address": "\u786c\u4ef6\u5730\u5740",
|
||||
"dhcp_ip_addresses": "IP \u5730\u5740",
|
||||
"dhcp_table_hostname": "\u4e3b\u673a\u540d",
|
||||
"dhcp_table_expires": "\u5230\u671f",
|
||||
"dhcp_warning": "\u5982\u679c\u4f60\u60f3\u8981\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\uff0c\u8bf7\u786e\u4fdd\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u6ca1\u6709\u5176\u5b83\u6d3b\u52a8\u7684 DHCP \u670d\u52a1\u5668\u3002\u5426\u5219\uff0c\u6b64\u64cd\u4f5c\u53ef\u80fd\u4f1a\u7834\u574f\u5df2\u8fde\u63a5\u8bbe\u5907\u7684\u7f51\u7edc\u8fde\u63a5\uff01",
|
||||
"back": "\u8fd4\u56de",
|
||||
"dashboard": "\u4eea\u8868\u76d8",
|
||||
"settings": "\u8bbe\u7f6e",
|
||||
"filters": "\u8fc7\u6ee4\u5668",
|
||||
"query_log": "\u67e5\u8be2\u65e5\u5fd7",
|
||||
"faq": "\u5e38\u89c1\u95ee\u9898",
|
||||
"version": "\u7248\u672c",
|
||||
"address": "\u5730\u5740",
|
||||
"on": "\u542f\u7528\u4e2d",
|
||||
"off": "\u7981\u7528\u4e2d",
|
||||
"copyright": "\u7248\u6743",
|
||||
"homepage": "\u4e3b\u9875",
|
||||
"report_an_issue": "\u95ee\u9898\u53cd\u9988",
|
||||
"enable_protection": "\u542f\u7528\u4fdd\u62a4",
|
||||
"enabled_protection": "\u4fdd\u62a4\u5df2\u542f\u7528",
|
||||
"disable_protection": "\u7981\u7528\u4fdd\u62a4",
|
||||
"disabled_protection": "\u4fdd\u62a4\u5df2\u7981\u7528",
|
||||
"refresh_statics": "\u5237\u65b0\u72b6\u6001",
|
||||
"dns_query": "DNS\u67e5\u8be2",
|
||||
"blocked_by": "\u5df2\u88ab\u8fc7\u6ee4\u5668\u62e6\u622a",
|
||||
"stats_malware_phishing": "\u88ab\u62e6\u622a\u7684\u6076\u610f\/\u9493\u9c7c\u7f51\u7ad9",
|
||||
"stats_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9",
|
||||
"stats_query_domain": "\u8bf7\u6c42\u57df\u540d\u6392\u884c",
|
||||
"for_last_24_hours": "\u5728\u8fc7\u53bb 24 \u5c0f\u65f6",
|
||||
"no_domains_found": "\u672a\u627e\u5230\u57df\u540d",
|
||||
"requests_count": "\u8bf7\u6c42\u6570",
|
||||
"top_blocked_domains": "\u88ab\u62e6\u622a\u57df\u540d\u6392\u884c",
|
||||
"top_clients": "\u5ba2\u6237\u7aef\u6392\u884c",
|
||||
"no_clients_found": "\u672a\u627e\u5230\u5ba2\u6237\u7aef",
|
||||
"general_statistics": "\u6982\u51b5\u7edf\u8ba1",
|
||||
"number_of_dns_query_24_hours": "\u8fc7\u53bb 24 \u5c0f\u65f6\u5185\u5904\u7406\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours": "\u88ab\u5e7f\u544a\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u88ab AdGuard \u5b89\u5168\u6d4f\u89c8\u6a21\u5757\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9\u603b\u6570",
|
||||
"enforced_save_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
|
||||
"number_of_dns_query_to_safe_search": "\u542f\u7528\u5f3a\u5236\u5b89\u5168\u641c\u7d22\u540e\u5bf9\u641c\u7d22\u5f15\u64ce\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"average_processing_time": "\u5e73\u5747\u5904\u7406\u65f6\u95f4",
|
||||
"average_processing_time_hint": "\u5904\u7406 DNS \u8bf7\u6c42\u7684\u5e73\u5747\u65f6\u95f4\uff08\u6beb\u79d2\uff09",
|
||||
"block_domain_use_filters_and_hosts": "\u4f7f\u7528\u8fc7\u6ee4\u5668\u548c Hosts \u6587\u4ef6\u4ee5\u62e6\u622a\u6307\u5b9a\u57df\u540d",
|
||||
"filters_block_toggle_hint": "\u4f60\u53ef\u4ee5\u5728 <a href='#filters'>\u8fc7\u6ee4\u5668<\/a> \u8bbe\u7f6e\u4e2d\u6dfb\u52a0\u8fc7\u6ee4\u89c4\u5219\u3002",
|
||||
"use_adguard_browsing_sec": "\u4f7f\u7528 AdGuard\u3010\u6d4f\u89c8\u5b89\u5168\u3011\u7f51\u9875\u670d\u52a1",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home \u5c06\u68c0\u67e5\u57df\u540d\u662f\u5426\u88ab\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u5217\u5165\u9ed1\u540d\u5355\u3002\u5b83\u5c06\u4f7f\u7528\u9690\u79c1\u6027\u5f3a\u7684\u68c0\u7d22 API \u6765\u6267\u884c\u68c0\u67e5\uff0c\u53ea\u6709\u57df\u540d\u7684 SHA256 \u7684\u77ed\u524d\u7f00\u4f1a\u88ab\u53d1\u9001\u5230\u670d\u52a1\u5668\u3002",
|
||||
"use_adguard_parental": "\u4f7f\u7528 AdGuard \u3010\u5bb6\u957f\u63a7\u5236\u3011\u670d\u52a1",
|
||||
"use_adguard_parental_hint": "AdGuard Home \u5c06\u4f7f\u7528\u4e0e\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u76f8\u540c\u7684\u9690\u79c1\u6027\u5f3a\u7684 API \u6765\u68c0\u67e5\u57df\u540d\u6307\u5411\u7684\u7f51\u7ad9\u662f\u5426\u5305\u542b\u6210\u4eba\u5185\u5bb9\u3002",
|
||||
"enforce_safe_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
|
||||
"enforce_save_search_hint": "AdGuard Home \u5c06\u5bf9\u4ee5\u4e0b\u641c\u7d22\u5f15\u64ce\u5f3a\u5236\u542f\u7528\u5b89\u5168\u641c\u7d22\uff1aGoogle\u3001YouTube\u3001Bing \u548c Yandex\u3002",
|
||||
"no_servers_specified": "\u672a\u627e\u5230\u6307\u5b9a\u7684\u670d\u52a1\u5668",
|
||||
"no_settings": "\u672a\u627e\u5230\u8bbe\u7f6e",
|
||||
"general_settings": "\u5e38\u89c4\u8bbe\u7f6e",
|
||||
"upstream_dns": "\u4e0a\u6e38 DNS \u670d\u52a1\u5668",
|
||||
"upstream_dns_hint": "\u5982\u679c\u6b64\u5904\u7559\u7a7a\uff0cAdGuard Home \u5c06\u4f1a\u4f7f\u7528 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u4f5c\u4e3a\u4e0a\u6e38 DNS\u3002\u5982\u679c\u60f3\u8981\u4f7f\u7528\u4f7f\u7528 DNS over TLS\uff0c\u8bf7\u4ee5 tls:\/\/ \u4e3a\u5f00\u5934\u3002",
|
||||
"test_upstream_btn": "\u6d4b\u8bd5\u4e0a\u6e38 DNS",
|
||||
"apply_btn": "\u5e94\u7528",
|
||||
"disabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u7981\u7528",
|
||||
"enabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u542f\u7528",
|
||||
"disabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u7981\u7528",
|
||||
"enabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u542f\u7528",
|
||||
"disabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u7981\u7528",
|
||||
"enabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u542f\u7528",
|
||||
"disabled_safe_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u7981\u7528",
|
||||
"enabled_save_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u542f\u7528",
|
||||
"enabled_table_header": "\u5df2\u542f\u7528",
|
||||
"name_table_header": "\u540d\u79f0",
|
||||
"filter_url_table_header": "\u8fc7\u6ee4\u5668\u5730\u5740",
|
||||
"rules_count_table_header": "\u89c4\u5219\u6570",
|
||||
"last_time_updated_table_header": "\u4e0a\u6b21\u66f4\u65b0\u65f6\u95f4",
|
||||
"actions_table_header": "\u6d3b\u8dc3\u72b6\u6001",
|
||||
"delete_table_action": "\u5220\u9664",
|
||||
"filters_and_hosts": "\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355",
|
||||
"filters_and_hosts_hint": "AdGuard Home \u53ef\u4ee5\u89e3\u6790\u57fa\u7840\u7684 adblock \u89c4\u5219\u548c Hosts \u8bed\u6cd5\u3002",
|
||||
"no_filters_added": "\u672a\u6dfb\u52a0\u4efb\u4f55\u8fc7\u6ee4\u5668",
|
||||
"add_filter_btn": "\u6dfb\u52a0\u8fc7\u6ee4\u5668",
|
||||
"cancel_btn": "\u53d6\u6d88",
|
||||
"enter_name_hint": "\u8f93\u5165\u540d\u79f0",
|
||||
"enter_url_hint": "\u8f93\u5165 URL",
|
||||
"check_updates_btn": "\u68c0\u67e5\u66f4\u65b0",
|
||||
"new_filter_btn": "\u8ba2\u9605\u65b0\u7684\u8fc7\u6ee4\u5668",
|
||||
"enter_valid_filter_url": "\u8f93\u5165\u4e00\u4e2a\u8fc7\u6ee4\u5668\u8ba2\u9605\u6216 Hosts \u6587\u4ef6\u7684\u6709\u6548 URL",
|
||||
"custom_filter_rules": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668\u89c4\u5219",
|
||||
"custom_filter_rules_hint": "\u8bf7\u786e\u4fdd\u6bcf\u884c\u53ea\u8f93\u5165\u4e00\u6761\u89c4\u5219\u3002\u4f60\u53ef\u4ee5\u8f93\u5165\u7b26\u5408 adblock \u8bed\u6cd5\u6216 Hosts \u8bed\u6cd5\u7684\u89c4\u5219\u3002",
|
||||
"examples_title": "\u8303\u4f8b",
|
||||
"example_meaning_filter_block": "\u62e6\u622a example.org \u57df\u540d\u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
|
||||
"example_meaning_filter_whitelist": "\u653e\u884c example.org \u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
|
||||
"example_meaning_host_block": "AdGuard Home \u73b0\u5728\u5c06\u4f1a\u628a example.org\uff08\u4f46\u4e0d\u5305\u62ec\u5b83\u7684\u5b50\u57df\u540d\uff09\u89e3\u6790\u5230 127.0.0.1\u3002",
|
||||
"example_comment": "! \u8fd9\u662f\u4e00\u884c\u6ce8\u91ca",
|
||||
"example_comment_meaning": "\u53ea\u662f\u4e00\u6761\u6ce8\u91ca",
|
||||
"example_comment_hash": "# \u8fd9\u4e5f\u662f\u4e00\u884c\u6ce8\u91ca",
|
||||
"example_regex_meaning": "\u963b\u6b62\u8bbf\u95ee\u4e0e\u6307\u5b9a\u7684\u6b63\u5219\u8868\u8fbe\u5f0f\u5339\u914d\u7684\u57df\u540d",
|
||||
"example_upstream_regular": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e UDP\uff09",
|
||||
"example_upstream_dot": "\u52a0\u5bc6 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u52a0\u5bc6 <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u4f60\u53ef\u4ee5\u4f7f\u7528 <0>DNSCrypt<\/0> \u7684 <1>DNS Stamps<\/1> \u6216\u8005 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668",
|
||||
"example_upstream_tcp": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e TCP \uff09",
|
||||
"all_filters_up_to_date_toast": "\u6240\u6709\u8fc7\u6ee4\u5668\u5df2\u66f4\u65b0\u81f3\u6700\u65b0",
|
||||
"updated_upstream_dns_toast": "\u4e0a\u6e38 DNS \u5df2\u66f4\u65b0",
|
||||
"dns_test_ok_toast": "\u6307\u5b9a\u7684 DNS \u670d\u52a1\u5668\u73b0\u5df2\u6b63\u5e38\u8fd0\u884c",
|
||||
"dns_test_not_ok_toast": "\u670d\u52a1\u5668 \"{{key}}\"\uff1a\u65e0\u6cd5\u4f7f\u7528\uff0c\u8bf7\u68c0\u67e5\u4f60\u8f93\u5165\u7684\u662f\u5426\u6b63\u786e",
|
||||
"unblock_btn": "\u653e\u884c",
|
||||
"block_btn": "\u62e6\u622a",
|
||||
"time_table_header": "\u65f6\u95f4",
|
||||
"domain_name_table_header": "\u57df\u540d",
|
||||
"type_table_header": "\u7c7b\u578b",
|
||||
"response_table_header": "\u54cd\u5e94",
|
||||
"client_table_header": "\u5ba2\u6237\u7aef",
|
||||
"empty_response_status": "\u7a7a",
|
||||
"show_all_filter_type": "\u663e\u793a\u6240\u6709",
|
||||
"show_filtered_type": "\u663e\u793a\u88ab\u62e6\u622a\u7684",
|
||||
"no_logs_found": "\u672a\u627e\u5230\u65e5\u5fd7",
|
||||
"disabled_log_btn": "\u7981\u7528\u65e5\u5fd7",
|
||||
"download_log_file_btn": "\u4e0b\u8f7d\u65e5\u5fd7\u6587\u4ef6",
|
||||
"refresh_btn": "\u5237\u65b0",
|
||||
"enabled_log_btn": "\u542f\u7528\u65e5\u5fd7",
|
||||
"last_dns_queries": "\u6700\u8fd1\u7684 5000 \u4e2a DNS \u8bf7\u6c42",
|
||||
"previous_btn": "\u4e0a\u4e00\u9875",
|
||||
"next_btn": "\u4e0b\u4e00\u9875",
|
||||
"loading_table_status": "\u52a0\u8f7d\u4e2d\u2026\u2026",
|
||||
"page_table_footer_text": "\u9875",
|
||||
"of_table_footer_text": "\u5728",
|
||||
"rows_table_footer_text": "\u884c",
|
||||
"updated_custom_filtering_toast": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5df2\u66f4\u65b0",
|
||||
"rule_removed_from_custom_filtering_toast": "\u89c4\u5219\u5df2\u4ece\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d\u79fb\u9664",
|
||||
"rule_added_to_custom_filtering_toast": "\u89c4\u5219\u5df2\u6dfb\u52a0\u5230\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d",
|
||||
"query_log_disabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u7981\u7528",
|
||||
"query_log_enabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u542f\u7528",
|
||||
"source_label": "\u6e90",
|
||||
"found_in_known_domain_db": "\u6210\u529f\u5728\u5df2\u77e5\u57df\u540d\u6570\u636e\u5e93\u4e2d\u67e5\u8be2\u5230",
|
||||
"category_label": "\u7c7b\u522b",
|
||||
"rule_label": "\u89c4\u5219",
|
||||
"filter_label": "\u8fc7\u6ee4\u5668",
|
||||
"unknown_filter": "\u672a\u77e5\u8fc7\u6ee4\u5668 {{filterId}}",
|
||||
"install_welcome_title": "\u6b22\u8fce\u4f7f\u7528 AdGuard Home\uff01",
|
||||
"install_welcome_desc": "AdGuard Home \u662f\u4e00\u4e2a\u53ef\u5728\u7279\u5b9a\u7f51\u7edc\u8303\u56f4\u5185\u62e6\u622a\u6240\u6709\u5e7f\u544a\u548c\u8ddf\u8e2a\u5668\u7684 DNS \u670d\u52a1\u5668\u3002\u5b83\u7684\u76ee\u7684\u662f\u8ba9\u60a8\u63a7\u5236\u6574\u4e2a\u7f51\u7edc\u548c\u60a8\u7684\u6240\u6709\u8bbe\u5907\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u4efb\u4f55\u5ba2\u6237\u7aef\u7a0b\u5e8f\u3002",
|
||||
"install_settings_title": "\u7f51\u9875\u7ba1\u7406\u754c\u9762",
|
||||
"install_settings_listen": "\u76d1\u542c\u63a5\u53e3",
|
||||
"install_settings_port": "\u7aef\u53e3",
|
||||
"install_settings_interface_link": "\u60a8\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u5730\u5740\u8bbf\u95ee\u60a8\u7684 AdGuard Home \u7f51\u9875\u7ba1\u7406\u754c\u9762\uff1a",
|
||||
"form_error_port": "\u8f93\u5165\u6709\u6548\u7684\u7aef\u53e3\u503c",
|
||||
"install_settings_dns": "DNS \u670d\u52a1\u5668",
|
||||
"install_settings_dns_desc": "\u60a8\u5c06\u9700\u8981\u4f7f\u7528\u4ee5\u4e0b\u5730\u5740\u6765\u8bbe\u7f6e\u60a8\u7684\u8bbe\u5907\u6216\u8def\u7531\u5668\u7684 DNS \u670d\u52a1\u5668\uff1a",
|
||||
"install_settings_all_interfaces": "\u6240\u6709\u63a5\u53e3",
|
||||
"install_auth_title": "\u8eab\u4efd\u8ba4\u8bc1",
|
||||
"install_auth_desc": "\u6211\u4eec\u5f3a\u70c8\u5efa\u8bae\u60a8\u4e3a AdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u914d\u7f6e\u5bc6\u7801\u3002\u5c3d\u7ba1\u8be5\u9875\u9762\u53ea\u80fd\u901a\u8fc7\u60a8\u7684\u672c\u5730\u7f51\u7edc\u8bbf\u95ee\uff0c\u4f46\u907f\u514d\u5b83\u88ab\u4e0d\u52a0\u9650\u5236\u5730\u8bbf\u95ee\u4ecd\u5341\u5206\u91cd\u8981\u3002",
|
||||
"install_auth_username": "\u7528\u6237\u540d",
|
||||
"install_auth_password": "\u5bc6\u7801",
|
||||
"install_auth_confirm": "\u786e\u8ba4\u5bc6\u7801",
|
||||
"install_auth_username_enter": "\u8f93\u5165\u7528\u6237\u540d",
|
||||
"install_auth_password_enter": "\u8f93\u5165\u5bc6\u7801",
|
||||
"install_step": "\u6b65\u9aa4",
|
||||
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u8bbe\u5907",
|
||||
"install_devices_desc": "\u4e3a\u4fdd\u8bc1 AdGuard Home \u53ef\u4ee5\u5f00\u59cb\u6b63\u5e38\u5de5\u4f5c\uff0c\u60a8\u9700\u8981\u5728\u8bbe\u5907\u4e0a\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002",
|
||||
"install_submit_title": "\u606d\u559c\u60a8\uff01",
|
||||
"install_submit_desc": "\u5b89\u88c5\u8fc7\u7a0b\u5df2\u7ecf\u5b8c\u6210\uff0c\u60a8\u53ef\u4ee5\u5f00\u59cb\u4f7f\u7528 AdGuard Home \u4e86\u3002",
|
||||
"install_devices_router": "\u8def\u7531\u5668",
|
||||
"install_devices_router_desc": "\u6b64\u8bbe\u7f6e\u5c06\u81ea\u52a8\u8986\u76d6\u8fde\u63a5\u5230\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u7684\u6240\u6709\u8bbe\u5907\uff0c\u60a8\u4e0d\u9700\u8981\u624b\u52a8\u914d\u7f6e\u5b83\u4eec\u3002",
|
||||
"install_devices_address": "AdGuard Home DNS \u670d\u52a1\u5668\u6b63\u5728\u76d1\u542c\u4ee5\u4e0b\u5730\u5740",
|
||||
"install_devices_router_list_1": "\u6253\u5f00\u60a8\u7684\u8def\u7531\u5668\u914d\u7f6e\u754c\u9762\u3002\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u6d4f\u89c8\u5668\u8bbf\u95ee\u5730\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1 \uff09\u3002\u6253\u5f00\u540e\u60a8\u53ef\u80fd\u9700\u8981\u8f93\u5165\u5bc6\u7801\u4ee5\u8fdb\u5165\u914d\u7f6e\u754c\u9762\u3002\u5982\u679c\u60a8\u4e0d\u8bb0\u5f97\u5bc6\u7801\uff0c\u901a\u5e38\u53ef\u4ee5\u901a\u8fc7\u6309\u4e0b\u8def\u7531\u5668\u4e0a\u7684\u91cd\u7f6e\u6309\u94ae\u6765\u91cd\u8bbe\u5bc6\u7801\u3002\u4e00\u4e9b\u8def\u7531\u5668\u53ef\u80fd\u9700\u8981\u901a\u8fc7\u7279\u5b9a\u7684\u5e94\u7528\u6765\u8fdb\u884c\u8fd9\u4e00\u64cd\u4f5c\uff0c\u8bf7\u786e\u4fdd\u60a8\u5df2\u7ecf\u5728\u8ba1\u7b97\u673a\u6216\u624b\u673a\u4e0a\u5b89\u88c5\u4e86\u76f8\u5173\u5e94\u7528\u3002",
|
||||
"install_devices_router_list_2": "\u627e\u5230\u8def\u7531\u5668\u7684 DHCP\/DNS \u8bbe\u7f6e\u9875\u9762\u3002\u60a8\u4f1a\u5728 DNS \u8fd9\u4e00\u5355\u8bcd\u65c1\u8fb9\u627e\u5230\u4e24\u5230\u4e09\u884c\u5141\u8bb8\u8f93\u5165\u7684\u8f93\u5165\u6846\uff0c\u6bcf\u4e00\u884c\u8f93\u5165\u6846\u5206\u4e3a\u56db\u7ec4\uff0c\u6bcf\u7ec4\u5141\u8bb8\u8f93\u5165\u4e00\u5230\u4e09\u4e2a\u6570\u5b57\u3002",
|
||||
"install_devices_router_list_3": "\u8bf7\u5728\u6b64\u5904\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_windows_list_1": "\u901a\u8fc7\u5f00\u59cb\u83dc\u5355\u6216 Windows \u641c\u7d22\u529f\u80fd\u6253\u5f00\u63a7\u5236\u9762\u677f\u3002",
|
||||
"install_devices_windows_list_2": "\u70b9\u51fb\u8fdb\u5165 \u201d\u7f51\u7edc\u548c Internet\u201c \u540e\uff0c\u518d\u6b21\u70b9\u51fb\u8fdb\u5165 \u201c\u7f51\u7edc\u548c\u5171\u4eab\u4e2d\u5fc3\u201d",
|
||||
"install_devices_windows_list_3": "\u5728\u7a97\u53e3\u7684\u5de6\u4fa7\u627e\u5230 \u201d\u66f4\u6539\u9002\u914d\u5668\u8bbe\u7f6e\u201c \u5e76\u70b9\u51fb\u8fdb\u5165\u3002",
|
||||
"install_devices_windows_list_4": "\u9009\u62e9\u60a8\u6b63\u5728\u8fde\u63a5\u7684\u7f51\u7edc\u8bbe\u5907\uff0c\u53f3\u51fb\u5b83\u5e76\u9009\u62e9 \u201d\u5c5e\u6027\u201c \u3002",
|
||||
"install_devices_windows_list_5": "\u5728\u5217\u8868\u4e2d\u627e\u5230 \u201dInternet \u534f\u8bae\u7248\u672c 4 (TCP\/IPv4)\u201c \uff0c\u9009\u62e9\u5e76\u518d\u6b21\u70b9\u51fb \u201d\u5c5e\u6027\u201c \u3002",
|
||||
"install_devices_windows_list_6": "\u9009\u62e9 \u201d\u4f7f\u7528\u4e0b\u9762\u7684 DNS \u670d\u52a1\u5668\u5730\u5740\u201c \uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_macos_list_1": "\u70b9\u51fb\u82f9\u679c\u56fe\u6807\uff0c\u8fdb\u5165 \u201d\u7cfb\u7edf\u9996\u9009\u9879\u201c\u3002",
|
||||
"install_devices_macos_list_2": "\u70b9\u51fb \u201d\u7f51\u7edc\u201c \u3002",
|
||||
"install_devices_macos_list_3": "\u9009\u62e9\u5728\u5217\u8868\u4e2d\u7684\u7b2c\u4e00\u4e2a\u8fde\u63a5\uff0c\u5e76\u70b9\u51fb \u201d\u9ad8\u7ea7\u201c \u3002",
|
||||
"install_devices_macos_list_4": "\u9009\u62e9 \u201dDNS\u201c \u9009\u9879\u5361\uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_android_list_1": "\u5728\u5b89\u5353\u4e3b\u5c4f\u5e55\u83dc\u5355\u4e2d\u70b9\u51fb\u8bbe\u7f6e\u3002",
|
||||
"install_devices_android_list_2": "\u70b9\u51fb\u83dc\u5355\u4e0a\u7684 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c \u9009\u9879\u3002\u5728\u5c4f\u5e55\u4e0a\u5c06\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7f51\u7edc\uff08\u8702\u7a9d\u79fb\u52a8\u7f51\u7edc\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
|
||||
"install_devices_android_list_3": "\u957f\u6309\u5f53\u524d\u5df2\u8fde\u63a5\u7684\u7f51\u7edc\uff0c\u7136\u540e\u70b9\u51fb \u201d\u4fee\u6539\u7f51\u7edc\u8bbe\u7f6e\u201c \u3002",
|
||||
"install_devices_android_list_4": "\u5728\u67d0\u4e9b\u8bbe\u5907\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u9009\u4e2d \u201d\u9ad8\u7ea7\u201c \u590d\u9009\u6846\u4ee5\u67e5\u770b\u8fdb\u4e00\u6b65\u7684\u8bbe\u7f6e\u3002\u60a8\u53ef\u80fd\u9700\u8981\u8c03\u6574\u60a8\u5b89\u5353\u8bbe\u5907\u7684 DNS \u8bbe\u7f6e\uff0c\u6216\u662f\u9700\u8981\u5c06 IP \u8bbe\u7f6e\u4ece DHCP \u5207\u6362\u5230\u9759\u6001\u3002",
|
||||
"install_devices_android_list_5": "\u5c06 \"DNS 1 \/ \u4e3b DNS\" \u548c \u201dDNS 2 \/ \u526f DNS\u201c \u7684\u503c\u6539\u4e3a\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_ios_list_1": "\u4ece\u4e3b\u5c4f\u5e55\u4e2d\u70b9\u51fb \u201d\u8bbe\u7f6e\u201c \u3002",
|
||||
"install_devices_ios_list_2": "\u4ece\u5de6\u4fa7\u76ee\u5f55\u4e2d\u9009\u62e9 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c\uff08\u79fb\u52a8\u6570\u636e\u7f51\u7edc\u73af\u5883\u4e0b\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
|
||||
"install_devices_ios_list_3": "\u70b9\u51fb\u5f53\u524d\u5df2\u8fde\u63a5\u7f51\u7edc\u7684\u540d\u79f0\u3002",
|
||||
"install_devices_ios_list_4": "\u5728 DNS \u5b57\u6bb5\u4e2d\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"get_started": "\u5f00\u59cb\u914d\u7f6e",
|
||||
"next": "\u4e0b\u4e00\u6b65",
|
||||
"open_dashboard": "\u6253\u5f00\u4eea\u8868\u76d8",
|
||||
"install_saved": "\u4fdd\u5b58\u6210\u529f",
|
||||
"encryption_title": "\u52a0\u5bc6",
|
||||
"encryption_desc": "\u4e3a DNS \u4e0e\u7f51\u9875\u7ba1\u7406\u754c\u9762\u542f\u7528\u52a0\u5bc6\uff08HTTPS\/TLS\uff09",
|
||||
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u4fdd\u5b58",
|
||||
"encryption_server": "\u670d\u52a1\u5668\u540d\u79f0",
|
||||
"encryption_server_enter": "\u8f93\u5165\u60a8\u7684\u57df\u540d",
|
||||
"encryption_server_desc": "\u82e5\u8981\u4f7f\u7528 HTTPS \uff0c\u60a8\u9700\u8981\u8f93\u5165\u4e0e SSL \u8bc1\u4e66\u76f8\u5339\u914d\u7684\u670d\u52a1\u5668\u540d\u79f0\u3002",
|
||||
"encryption_redirect": "HTTPS \u81ea\u52a8\u91cd\u5b9a\u5411",
|
||||
"encryption_redirect_desc": "\u5982\u679c\u52fe\u9009\u6b64\u9009\u9879\uff0cAdGuard Home \u5c06\u81ea\u52a8\u5c06\u60a8\u4ece HTTP \u91cd\u5b9a\u5411\u5230 HTTPS \u5730\u5740\u3002",
|
||||
"encryption_https": "HTTPS \u7aef\u53e3",
|
||||
"encryption_https_desc": "\u5982\u679c\u914d\u7f6e\u4e86 HTTPS \u7aef\u53e3\uff0cAdGuard Home \u7ba1\u7406\u754c\u9762\u5c06\u53ef\u4ee5\u901a\u8fc7 HTTPS \u8bbf\u95ee\uff0c\u5b83\u8fd8\u5c06\u5728\u5728 '\/dns-query' \u4f4d\u7f6e\u63d0\u4f9b DNS-over-HTTPS \u3002",
|
||||
"encryption_dot": "DNS-over-TLS \u7aef\u53e3",
|
||||
"encryption_dot_desc": "\u5982\u679c\u914d\u7f6e\u4e86\u6b64\u7aef\u53e3\uff0cAdGuard Home \u5c06\u5728\u6b64\u7aef\u53e3\u4e0a\u8fd0\u884c\u4e00\u4e2a DNS-over-TLS \u670d\u52a1\u5668\u3002",
|
||||
"encryption_certificates": "\u8bc1\u4e66",
|
||||
"encryption_certificates_desc": "\u4e3a\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u4e3a\u57df\u63d0\u4f9b\u6709\u6548\u7684 SSL \u8bc1\u4e66\u94fe\u3002\u60a8\u53ef\u4ee5\u5728 <0>{{link}}<\/0> \u4e0a\u83b7\u5f97\u514d\u8d39\u8bc1\u4e66\uff0c\u4e5f\u53ef\u4ee5\u4ece\u53d7\u4fe1\u4efb\u7684\u8bc1\u4e66\u9881\u53d1\u673a\u6784\u8d2d\u4e70\u8bc1\u4e66\u3002",
|
||||
"encryption_certificates_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
|
||||
"encryption_status": "\u72b6\u6001",
|
||||
"encryption_expire": "\u6709\u6548\u671f",
|
||||
"encryption_key": "\u79c1\u94a5",
|
||||
"encryption_key_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u79c1\u94a5\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
|
||||
"encryption_enable": "\u542f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS\u3001DNS-over-TLS\uff09",
|
||||
"encryption_enable_desc": "\u5982\u679c\u542f\u7528\u52a0\u5bc6\u9009\u9879\uff0cAdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u5c06\u901a\u8fc7 HTTPS \u8fde\u63a5\u8bbf\u95ee\uff0c\u540c\u65f6 DNS \u670d\u52a1\u5668\u5c06\u76d1\u542c\u901a\u8fc7 DNS-over-HTTPS \u4e0e DNS-over-TLS \u53d1\u9001\u7684\u8bf7\u6c42\u3002",
|
||||
"encryption_chain_valid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u6709\u6548",
|
||||
"encryption_chain_invalid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u65e0\u6548",
|
||||
"encryption_key_valid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u6709\u6548",
|
||||
"encryption_key_invalid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u65e0\u6548",
|
||||
"encryption_subject": "\u4f7f\u7528\u8005",
|
||||
"encryption_issuer": "\u9881\u53d1\u8005",
|
||||
"encryption_hostnames": "\u4e3b\u673a\u540d",
|
||||
"encryption_reset": "\u60a8\u786e\u5b9a\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8bbe\u7f6e\uff1f",
|
||||
"topline_expiring_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5373\u5c06\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
|
||||
"topline_expired_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5df2\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
|
||||
"form_error_port_range": "\u8f93\u5165 80 - 65535 \u8303\u56f4\u5185\u7684\u7aef\u53e3\u503c",
|
||||
"form_error_port_unsafe": "\u8fd9\u662f\u4e00\u4e2a\u4e0d\u5b89\u5168\u7684\u7aef\u53e3",
|
||||
"form_error_equal": "\u4e0d\u5e94\u8be5\u76f8\u540c",
|
||||
"form_error_password": "\u5bc6\u7801\u4e0d\u5339\u914d",
|
||||
"reset_settings": "\u91cd\u7f6e\u8bbe\u7f6e",
|
||||
"update_announcement": "AdGuard Home {{version}} \u73b0\u5df2\u53d1\u5e03\uff01 <0>\u70b9\u51fb\u6b64\u5904<\/0> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u4fe1\u606f\u3002"
|
||||
}
|
||||
316
client/src/__locales/zh-tw.json
Normal file
316
client/src/__locales/zh-tw.json
Normal file
@@ -0,0 +1,316 @@
|
||||
{
|
||||
"example_upstream_reserved": "\u60a8\u53ef\u660e\u78ba\u6307\u5b9a<0>\u7528\u65bc\u7279\u5b9a\u7684\u7db2\u57df<\/0>\u4e4b DNS \u4e0a\u6e38",
|
||||
"upstream_parallel": "\u900f\u904e\u540c\u6642\u5730\u67e5\u8a62\u6240\u6709\u4e0a\u6e38\u7684\u4f3a\u670d\u5668\uff0c\u4f7f\u7528\u4e26\u884c\u7684\u67e5\u8a62\u4ee5\u52a0\u901f\u89e3\u6790",
|
||||
"bootstrap_dns": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668",
|
||||
"bootstrap_dns_desc": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668\u88ab\u7528\u65bc\u89e3\u6790\u60a8\u660e\u78ba\u6307\u5b9a\u4f5c\u70ba\u4e0a\u6e38\u7684 DoH\/DoT \u89e3\u6790\u5668\u4e4b IP \u4f4d\u5740\u3002",
|
||||
"url_added_successfully": "\u7db2\u5740\u88ab\u6210\u529f\u5730\u52a0\u5165",
|
||||
"check_dhcp_servers": "\u6aa2\u67e5\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"save_config": "\u5132\u5b58\u914d\u7f6e",
|
||||
"enabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u555f\u7528",
|
||||
"disabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u7981\u7528",
|
||||
"dhcp_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff08\u5be6\u9a57\u6027\u7684\uff01\uff09",
|
||||
"dhcp_description": "\u5982\u679c\u60a8\u7684\u8def\u7531\u5668\u672a\u63d0\u4f9b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u8a2d\u5b9a\uff0c\u60a8\u53ef\u4f7f\u7528 AdGuard \u81ea\u8eab\u5167\u5efa\u7684 DHCP \u4f3a\u670d\u5668\u3002",
|
||||
"dhcp_enable": "\u555f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"dhcp_disable": "\u7981\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"dhcp_not_found": "\u555f\u7528\u5167\u5efa\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u70ba\u5b89\u5168\u7684 - \u65bc\u8a72\u7db2\u8def\u4e0a\uff0c\u6211\u5011\u672a\u767c\u73fe\u4efb\u4f55\u73fe\u884c\u7684 DHCP \u4f3a\u670d\u5668\u3002\u7136\u800c\uff0c\u6211\u5011\u9f13\u52f5\u60a8\u624b\u52d5\u5730\u91cd\u65b0\u6aa2\u67e5\u5b83\uff0c\u56e0\u70ba\u6211\u5011\u7684\u81ea\u52d5\u4e4b\u6e2c\u8a66\u76ee\u524d\u4e0d\u4e88 100\uff05 \u4fdd\u8b49\u3002",
|
||||
"dhcp_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\uff0c\u4e00\u500b\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684 DHCP \u4f3a\u670d\u5668\u70ba\u4e0d\u5b89\u5168\u7684\u3002",
|
||||
"dhcp_leases": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
|
||||
"dhcp_config_saved": "\u5df2\u5132\u5b58\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u914d\u7f6e",
|
||||
"form_error_required": "\u5fc5\u586b\u7684\u6b04\u4f4d",
|
||||
"form_error_ip_format": "\u7121\u6548\u7684 IPv4 \u683c\u5f0f",
|
||||
"form_error_mac_format": "\u7121\u6548\u7684\u5a92\u9ad4\u5b58\u53d6\u63a7\u5236\uff08MAC\uff09\u683c\u5f0f",
|
||||
"form_error_positive": "\u5fc5\u9808\u5927\u65bc 0",
|
||||
"dhcp_form_gateway_input": "\u9598\u9053 IP",
|
||||
"dhcp_form_subnet_input": "\u5b50\u7db2\u8def\u906e\u7f69",
|
||||
"dhcp_form_range_title": "IP \u4f4d\u5740\u7bc4\u570d",
|
||||
"dhcp_form_range_start": "\u7bc4\u570d\u958b\u59cb",
|
||||
"dhcp_form_range_end": "\u7bc4\u570d\u7d50\u675f",
|
||||
"dhcp_form_lease_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3\u6642\u9593\uff08\u4ee5\u79d2\u6578\uff09",
|
||||
"dhcp_form_lease_input": "\u79df\u8cc3\u6301\u7e8c\u6642\u9593",
|
||||
"dhcp_interface_select": "\u9078\u64c7\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4ecb\u9762",
|
||||
"dhcp_hardware_address": "\u786c\u9ad4\u4f4d\u5740",
|
||||
"dhcp_ip_addresses": "IP \u4f4d\u5740",
|
||||
"dhcp_table_hostname": "\u4e3b\u6a5f\u540d\u7a31",
|
||||
"dhcp_table_expires": "\u5230\u671f",
|
||||
"dhcp_warning": "\u5982\u679c\u60a8\u7121\u8ad6\u5982\u4f55\u60f3\u8981\u555f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u78ba\u4fdd\u5728\u60a8\u7684\u7db2\u8def\u7121\u5176\u5b83\u73fe\u884c\u7684 DHCP \u4f3a\u670d\u5668\u3002\u5426\u5247\uff0c\u5b83\u53ef\u80fd\u6703\u7834\u58de\u4f9b\u5df2\u9023\u7dda\u7684\u88dd\u7f6e\u4e4b\u7db2\u969b\u7db2\u8def\uff01",
|
||||
"dhcp_error": "\u6211\u5011\u7121\u6cd5\u78ba\u5b9a\u5728\u8a72\u7db2\u8def\u662f\u5426\u6709\u53e6\u5916\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u3002",
|
||||
"dhcp_static_ip_error": "\u70ba\u4e86\u4f7f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u975c\u614b IP \u4f4d\u5740\u5fc5\u9808\u88ab\u8a2d\u5b9a\u3002\u6211\u5011\u672a\u80fd\u78ba\u5b9a\u8a72\u7db2\u8def\u4ecb\u9762\u662f\u5426\u88ab\u914d\u7f6e\u4f7f\u7528\u975c\u614b IP \u4f4d\u5740\u3002\u8acb\u624b\u52d5\u5730\u8a2d\u5b9a\u975c\u614b IP \u4f4d\u5740\u3002",
|
||||
"dhcp_dynamic_ip_found": "\u60a8\u7684\u7cfb\u7d71\u4f7f\u7528\u52d5\u614b IP \u4f4d\u5740\u914d\u7f6e\u4f9b\u4ecb\u9762 <0>{{interfaceName}}<\/0>\u3002\u70ba\u4e86\u4f7f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u975c\u614bIP\u4f4d\u5740\u5fc5\u9808\u88ab\u8a2d\u5b9a\u3002\u60a8\u73fe\u884c\u7684 IP \u4f4d\u5740\u70ba <0>{{ipAddress}}<\/0>\u3002\u5982\u679c\u60a8\u6309\u555f\u7528 DHCP \u6309\u9215\uff0c\u6211\u5011\u5c07\u81ea\u52d5\u5730\u8a2d\u5b9a\u6b64 IP \u4f4d\u5740\u4f5c\u70ba\u975c\u614b\u3002",
|
||||
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
|
||||
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Enter hostname",
|
||||
"error_details": "\u932f\u8aa4\u7d30\u7bc0",
|
||||
"back": "\u8fd4\u56de",
|
||||
"dashboard": "\u5100\u8868\u677f",
|
||||
"settings": "\u8a2d\u5b9a",
|
||||
"filters": "\u904e\u6ffe\u5668",
|
||||
"query_log": "\u67e5\u8a62\u8a18\u9304",
|
||||
"faq": "\u5e38\u898b\u554f\u7b54\u96c6",
|
||||
"version": "\u7248\u672c",
|
||||
"address": "\u4f4d\u5740",
|
||||
"on": "\u958b\u8457",
|
||||
"off": "\u95dc\u8457",
|
||||
"copyright": "\u7248\u6b0a",
|
||||
"homepage": "\u9996\u9801",
|
||||
"report_an_issue": "\u5831\u544a\u554f\u984c",
|
||||
"privacy_policy": "\u96b1\u79c1\u653f\u7b56",
|
||||
"enable_protection": "\u555f\u7528\u9632\u8b77",
|
||||
"enabled_protection": "\u5df2\u555f\u7528\u9632\u8b77",
|
||||
"disable_protection": "\u7981\u7528\u9632\u8b77",
|
||||
"disabled_protection": "\u5df2\u7981\u7528\u9632\u8b77",
|
||||
"refresh_statics": "\u91cd\u65b0\u6574\u7406\u7d71\u8a08\u8cc7\u6599",
|
||||
"dns_query": "DNS \u67e5\u8a62",
|
||||
"blocked_by": "\u5df2\u88ab\u904e\u6ffe\u5668\u5c01\u9396",
|
||||
"stats_malware_phishing": "\u5df2\u5c01\u9396\u7684\u60e1\u610f\u8edf\u9ad4\/\u7db2\u8def\u91e3\u9b5a",
|
||||
"stats_adult": "\u5df2\u5c01\u9396\u7684\u6210\u4eba\u7db2\u7ad9",
|
||||
"stats_query_domain": "\u71b1\u9580\u5df2\u67e5\u8a62\u7684\u7db2\u57df",
|
||||
"for_last_24_hours": "\u5728\u6700\u8fd1\u7684 24 \u5c0f\u6642\u5167",
|
||||
"no_domains_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u7db2\u57df",
|
||||
"requests_count": "\u8acb\u6c42\u7e3d\u6578",
|
||||
"top_blocked_domains": "\u71b1\u9580\u5df2\u5c01\u9396\u7684\u7db2\u57df",
|
||||
"top_clients": "\u71b1\u9580\u7528\u6236\u7aef",
|
||||
"no_clients_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u7528\u6236\u7aef",
|
||||
"general_statistics": "\u4e00\u822c\u7684\u7d71\u8a08\u8cc7\u6599",
|
||||
"number_of_dns_query_24_hours": "\u5728\u6700\u8fd1\u7684 24 \u5c0f\u6642\u5167\u5df2\u8655\u7406\u7684 DNS \u67e5\u8a62\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours": "\u5df2\u88ab\u5ee3\u544a\u5c01\u9396\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u5c01\u9396\u6e05\u55ae\u5c01\u9396\u7684 DNS \u8acb\u6c42\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u5df2\u88ab AdGuard \u700f\u89bd\u5b89\u5168\u6a21\u7d44\u5c01\u9396\u7684 DNS \u8acb\u6c42\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u5df2\u5c01\u9396\u7684\u6210\u4eba\u7db2\u7ad9\u4e4b\u6578\u91cf",
|
||||
"enforced_save_search": "\u5df2\u5f37\u5236\u57f7\u884c\u7684\u5b89\u5168\u641c\u5c0b",
|
||||
"number_of_dns_query_to_safe_search": "\u5c0d\u65bc\u90a3\u4e9b\u5b89\u5168\u641c\u5c0b\u5df2\u88ab\u5f37\u5236\u57f7\u884c\u4e4b\u5c6c\u65bc\u641c\u5c0b\u5f15\u64ce\u7684 DNS \u8acb\u6c42\u4e4b\u6578\u91cf",
|
||||
"average_processing_time": "\u5e73\u5747\u7684\u8655\u7406\u6642\u9593",
|
||||
"average_processing_time_hint": "\u65bc\u8655\u7406\u4e00\u9805 DNS \u8acb\u6c42\u4e0a\u4ee5\u6beb\u79d2\uff08ms\uff09\u8a08\u4e4b\u5e73\u5747\u7684\u6642\u9593",
|
||||
"block_domain_use_filters_and_hosts": "\u900f\u904e\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u6a94\u6848\u5c01\u9396\u7db2\u57df",
|
||||
"filters_block_toggle_hint": "\u60a8\u53ef\u5728<a href='#filters'>\u904e\u6ffe\u5668<\/a>\u8a2d\u5b9a\u4e2d\u8a2d\u7f6e\u5c01\u9396\u898f\u5247\u3002",
|
||||
"use_adguard_browsing_sec": "\u4f7f\u7528 AdGuard \u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home \u5c07\u6aa2\u67e5\u7db2\u57df\u662f\u5426\u88ab\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9\u5217\u5165\u9ed1\u540d\u55ae\u3002\u5b83\u5c07\u4f7f\u7528\u53cb\u597d\u7684\u96b1\u79c1\u67e5\u627e\u61c9\u7528\u7a0b\u5f0f\u4ecb\u9762\uff08API\uff09\u4ee5\u57f7\u884c\u6aa2\u67e5\uff1a\u50c5\u57df\u540d SHA256 \u96dc\u6e4a\u7684\u77ed\u524d\u7db4\u88ab\u50b3\u9001\u5230\u4f3a\u670d\u5668\u3002",
|
||||
"use_adguard_parental": "\u4f7f\u7528 AdGuard \u5bb6\u9577\u76e3\u63a7\u4e4b\u7db2\u8def\u670d\u52d9",
|
||||
"use_adguard_parental_hint": "AdGuard Home \u5c07\u6aa2\u67e5\u7db2\u57df\u662f\u5426\u5305\u542b\u6210\u4eba\u8cc7\u6599\u3002\u5b83\u4f7f\u7528\u5982\u540c\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9\u4e00\u6a23\u4e4b\u53cb\u597d\u7684\u96b1\u79c1\u61c9\u7528\u7a0b\u5f0f\u4ecb\u9762\uff08API\uff09\u3002",
|
||||
"enforce_safe_search": "\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b",
|
||||
"enforce_save_search_hint": "AdGuard Home \u53ef\u5728\u4e0b\u5217\u7684\u641c\u5c0b\u5f15\u64ce\uff1aGoogle\u3001YouTube\u3001Bing\u3001DuckDuckGo \u548c Yandex \u4e2d\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b\u3002",
|
||||
"no_servers_specified": "\u7121\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u4f3a\u670d\u5668",
|
||||
"no_settings": "\u7121\u8a2d\u5b9a",
|
||||
"general_settings": "\u4e00\u822c\u7684\u8a2d\u5b9a",
|
||||
"dns_settings": "DNS settings",
|
||||
"encryption_settings": "Encryption settings",
|
||||
"dhcp_settings": "DHCP settings",
|
||||
"client_settings": "Clients settings",
|
||||
"upstream_dns": "\u4e0a\u6e38\u7684 DNS \u4f3a\u670d\u5668",
|
||||
"upstream_dns_hint": "\u5982\u679c\u60a8\u5c07\u8a72\u6b04\u4f4d\u7559\u7a7a\uff0cAdGuard Home \u5c07\u4f7f\u7528 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u4f5c\u70ba\u4e0a\u6e38\u3002",
|
||||
"test_upstream_btn": "\u6e2c\u8a66\u4e0a\u884c\u8cc7\u6599\u6d41",
|
||||
"apply_btn": "\u5957\u7528",
|
||||
"disabled_filtering_toast": "\u5df2\u7981\u7528\u904e\u6ffe",
|
||||
"enabled_filtering_toast": "\u5df2\u555f\u7528\u904e\u6ffe",
|
||||
"disabled_safe_browsing_toast": "\u5df2\u7981\u7528\u5b89\u5168\u700f\u89bd",
|
||||
"enabled_safe_browsing_toast": "\u5df2\u555f\u7528\u5b89\u5168\u700f\u89bd",
|
||||
"disabled_parental_toast": "\u5df2\u7981\u7528\u5bb6\u9577\u76e3\u63a7",
|
||||
"enabled_parental_toast": "\u5df2\u555f\u7528\u5bb6\u9577\u76e3\u63a7",
|
||||
"disabled_safe_search_toast": "\u5df2\u7981\u7528\u5b89\u5168\u641c\u5c0b",
|
||||
"enabled_save_search_toast": "\u5df2\u555f\u7528\u5b89\u5168\u641c\u5c0b",
|
||||
"enabled_table_header": "\u5df2\u555f\u7528\u7684",
|
||||
"name_table_header": "\u540d\u7a31",
|
||||
"filter_url_table_header": "\u904e\u6ffe\u5668\u7db2\u5740",
|
||||
"rules_count_table_header": "\u898f\u5247\u7e3d\u6578",
|
||||
"last_time_updated_table_header": "\u6700\u8fd1\u7684\u66f4\u65b0\u6642\u9593",
|
||||
"actions_table_header": "\u884c\u52d5",
|
||||
"edit_table_action": "\u7de8\u8f2f",
|
||||
"delete_table_action": "\u522a\u9664",
|
||||
"filters_and_hosts": "\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u5c01\u9396\u6e05\u55ae",
|
||||
"filters_and_hosts_hint": "AdGuard Home \u61c2\u5f97\u57fa\u672c\u7684\u5ee3\u544a\u5c01\u9396\u898f\u5247\u548c\u4e3b\u6a5f\u6a94\u6848\u8a9e\u6cd5\u3002",
|
||||
"no_filters_added": "\u7121\u5df2\u52a0\u5165\u7684\u904e\u6ffe\u5668",
|
||||
"add_filter_btn": "\u589e\u52a0\u904e\u6ffe\u5668",
|
||||
"cancel_btn": "\u53d6\u6d88",
|
||||
"enter_name_hint": "\u8f38\u5165\u540d\u7a31",
|
||||
"enter_url_hint": "\u8f38\u5165\u7db2\u5740",
|
||||
"check_updates_btn": "\u6aa2\u67e5\u66f4\u65b0",
|
||||
"new_filter_btn": "\u65b0\u7684\u904e\u6ffe\u5668\u8a02\u95b1",
|
||||
"enter_valid_filter_url": "\u8f38\u5165\u95dc\u65bc\u904e\u6ffe\u5668\u8a02\u95b1\u6216\u4e3b\u6a5f\u6a94\u6848\u4e4b\u6709\u6548\u7684\u7db2\u5740\u3002",
|
||||
"custom_filter_rules": "\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247",
|
||||
"custom_filter_rules_hint": "\u65bc\u4e00\u884c\u4e0a\u8f38\u5165\u4e00\u500b\u898f\u5247\u3002\u60a8\u53ef\u4f7f\u7528\u5ee3\u544a\u5c01\u9396\u898f\u5247\u6216\u4e3b\u6a5f\u6a94\u6848\u8a9e\u6cd5\u3002",
|
||||
"examples_title": "\u7bc4\u4f8b",
|
||||
"example_meaning_filter_block": "\u5c01\u9396\u81f3 example.org \u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
|
||||
"example_meaning_filter_whitelist": "\u89e3\u9664\u5c01\u9396\u81f3 example.org \u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
|
||||
"example_meaning_host_block": "AdGuard Home \u73fe\u5728\u5c07\u5c0d example.org \u7db2\u57df\u8fd4\u56de 127.0.0.1 \u4f4d\u5740\uff08\u4f46\u975e\u5176\u5b50\u7db2\u57df\uff09\u3002",
|
||||
"example_comment": "! \u770b\uff0c\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_comment_meaning": "\u53ea\u662f\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_comment_hash": "# \u4e5f\u662f\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_regex_meaning": "\u5c01\u9396\u81f3\u8207\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u898f\u5247\u904b\u7b97\u5f0f\uff08Regular Expression\uff09\u76f8\u7b26\u7684\u7db2\u57df\u4e4b\u5b58\u53d6",
|
||||
"example_upstream_regular": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904e UDP\uff09",
|
||||
"example_upstream_dot": "\u52a0\u5bc6\u7684 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u52a0\u5bc6\u7684 <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <1>DNSCrypt<\/1> \u6216 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668\u4e4b <0>DNS \u6233\u8a18<\/0>",
|
||||
"example_upstream_tcp": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904e TCP\uff09",
|
||||
"all_filters_up_to_date_toast": "\u6240\u6709\u7684\u904e\u6ffe\u5668\u5df2\u662f\u6700\u65b0\u7684",
|
||||
"updated_upstream_dns_toast": "\u5df2\u66f4\u65b0\u4e0a\u6e38\u7684 DNS \u4f3a\u670d\u5668",
|
||||
"dns_test_ok_toast": "\u5df2\u660e\u78ba\u6307\u5b9a\u7684 DNS \u4f3a\u670d\u5668\u6b63\u5728\u6b63\u78ba\u5730\u904b\u4f5c",
|
||||
"dns_test_not_ok_toast": "\u4f3a\u670d\u5668 \"{{key}}\"\uff1a\u7121\u6cd5\u88ab\u4f7f\u7528\uff0c\u8acb\u6aa2\u67e5\u60a8\u5df2\u6b63\u78ba\u5730\u586b\u5beb\u5b83",
|
||||
"unblock_btn": "\u89e3\u9664\u5c01\u9396",
|
||||
"block_btn": "\u5c01\u9396",
|
||||
"time_table_header": "\u6642\u9593",
|
||||
"domain_name_table_header": "\u57df\u540d",
|
||||
"type_table_header": "\u985e\u578b",
|
||||
"response_table_header": "\u56de\u61c9",
|
||||
"client_table_header": "\u7528\u6236\u7aef",
|
||||
"empty_response_status": "\u7a7a\u767d\u7684",
|
||||
"show_all_filter_type": "\u986f\u793a\u5168\u90e8",
|
||||
"show_filtered_type": "\u986f\u793a\u5df2\u904e\u6ffe\u7684",
|
||||
"no_logs_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u8a18\u9304",
|
||||
"disabled_log_btn": "\u7981\u7528\u8a18\u9304",
|
||||
"download_log_file_btn": "\u4e0b\u8f09\u8a18\u9304\u6a94\u6848",
|
||||
"refresh_btn": "\u91cd\u65b0\u6574\u7406",
|
||||
"enabled_log_btn": "\u555f\u7528\u8a18\u9304",
|
||||
"last_dns_queries": "\u6700\u8fd1\u7684 5000 \u7b46 DNS \u67e5\u8a62",
|
||||
"previous_btn": "\u4e0a\u4e00\u9801",
|
||||
"next_btn": "\u4e0b\u4e00\u9801",
|
||||
"loading_table_status": "\u6b63\u5728\u8f09\u5165...",
|
||||
"page_table_footer_text": "\u9801\u9762",
|
||||
"of_table_footer_text": "\u4e4b",
|
||||
"rows_table_footer_text": "\u5217",
|
||||
"updated_custom_filtering_toast": "\u5df2\u66f4\u65b0\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247",
|
||||
"rule_removed_from_custom_filtering_toast": "\u898f\u5247\u5f9e\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d\u88ab\u79fb\u9664",
|
||||
"rule_added_to_custom_filtering_toast": "\u898f\u5247\u88ab\u52a0\u81f3\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d",
|
||||
"query_log_disabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u7981\u7528",
|
||||
"query_log_enabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u555f\u7528",
|
||||
"source_label": "\u4f86\u6e90",
|
||||
"found_in_known_domain_db": "\u5728\u5df2\u77e5\u7684\u57df\u540d\u8cc7\u6599\u5eab\u4e2d\u88ab\u767c\u73fe\u3002",
|
||||
"category_label": "\u985e\u5225",
|
||||
"rule_label": "\u898f\u5247",
|
||||
"filter_label": "\u904e\u6ffe\u5668",
|
||||
"unknown_filter": "\u672a\u77e5\u7684\u904e\u6ffe\u5668 {{filterId}}",
|
||||
"install_welcome_title": "\u6b61\u8fce\u81f3 AdGuard Home\uff01",
|
||||
"install_welcome_desc": "AdGuard Home \u662f\u5168\u7db2\u8def\u7bc4\u570d\u5ee3\u544a\u548c\u8ffd\u8e64\u5668\u5c01\u9396\u7684 DNS \u4f3a\u670d\u5668\u3002\u5b83\u7684\u76ee\u7684\u70ba\u8b93\u60a8\u63a7\u5236\u60a8\u6574\u500b\u7684\u7db2\u8def\u548c\u6240\u6709\u60a8\u7684\u88dd\u7f6e\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u7528\u6236\u7aef\u7a0b\u5f0f\u3002",
|
||||
"install_settings_title": "\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762",
|
||||
"install_settings_listen": "\u76e3\u807d\u4ecb\u9762",
|
||||
"install_settings_port": "\u9023\u63a5\u57e0",
|
||||
"install_settings_interface_link": "\u60a8\u7684 AdGuard Home \u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5c07\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u70ba\u53ef\u7528\u7684\uff1a",
|
||||
"form_error_port": "\u8f38\u5165\u6709\u6548\u7684\u9023\u63a5\u57e0\u503c",
|
||||
"install_settings_dns": "DNS \u4f3a\u670d\u5668",
|
||||
"install_settings_dns_desc": "\u60a8\u5c07\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u6216\u8def\u7531\u5668\u4ee5\u4f7f\u7528\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u4e4b DNS \u4f3a\u670d\u5668\uff1a",
|
||||
"install_settings_all_interfaces": "\u6240\u6709\u7684\u4ecb\u9762",
|
||||
"install_auth_title": "\u9a57\u8b49",
|
||||
"install_auth_desc": "\u88ab\u975e\u5e38\u5efa\u8b70\u914d\u7f6e\u5c6c\u65bc\u60a8\u7684 AdGuard Home \u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u4e4b\u5bc6\u78bc\u9a57\u8b49\u3002\u5373\u4f7f\u5b83\u50c5\u5728\u60a8\u7684\u5340\u57df\u7db2\u8def\u4e2d\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u8b93\u5b83\u53d7\u4fdd\u8b77\u514d\u65bc\u4e0d\u53d7\u9650\u5236\u7684\u5b58\u53d6\u70ba\u4ecd\u7136\u91cd\u8981\u7684\u3002",
|
||||
"install_auth_username": "\u7528\u6236\u540d",
|
||||
"install_auth_password": "\u5bc6\u78bc",
|
||||
"install_auth_confirm": "\u78ba\u8a8d\u5bc6\u78bc",
|
||||
"install_auth_username_enter": "\u8f38\u5165\u7528\u6236\u540d",
|
||||
"install_auth_password_enter": "\u8f38\u5165\u5bc6\u78bc",
|
||||
"install_step": "\u6b65\u9a5f",
|
||||
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e",
|
||||
"install_devices_desc": "\u70ba\u4f7f AdGuard Home \u958b\u59cb\u904b\u4f5c\uff0c\u60a8\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u4ee5\u4f7f\u7528\u5b83\u3002",
|
||||
"install_submit_title": "\u606d\u559c\uff01",
|
||||
"install_submit_desc": "\u8a72\u8a2d\u7f6e\u7a0b\u5e8f\u88ab\u5b8c\u6210\uff0c\u4e14\u60a8\u6e96\u5099\u597d\u958b\u59cb\u4f7f\u7528 AdGuard Home\u3002",
|
||||
"install_devices_router": "\u8def\u7531\u5668",
|
||||
"install_devices_router_desc": "\u8a72\u8a2d\u7f6e\u5c07\u81ea\u52d5\u5730\u6db5\u84cb\u88ab\u9023\u7dda\u81f3\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u4e4b\u6240\u6709\u7684\u88dd\u7f6e\uff0c\u4e14\u60a8\u5c07\u7121\u9700\u624b\u52d5\u5730\u914d\u7f6e\u5b83\u5011\u6bcf\u500b\u3002",
|
||||
"install_devices_address": "AdGuard Home DNS \u4f3a\u670d\u5668\u6b63\u5728\u76e3\u807d\u4e0b\u5217\u7684\u4f4d\u5740",
|
||||
"install_devices_router_list_1": "\u958b\u555f\u95dc\u65bc\u60a8\u7684\u8def\u7531\u5668\u4e4b\u504f\u597d\u8a2d\u5b9a\u3002\u901a\u5e38\u5730\uff0c\u60a8\u53ef\u900f\u904e\u7db2\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1\/\uff09\u5f9e\u60a8\u7684\u700f\u89bd\u5668\u4e2d\u5b58\u53d6\u5b83\u3002\u60a8\u53ef\u80fd\u88ab\u8981\u6c42\u8f38\u5165\u8a72\u5bc6\u78bc\u3002\u5982\u679c\u60a8\u4e0d\u8a18\u5f97\u5b83\uff0c\u60a8\u7d93\u5e38\u53ef\u900f\u904e\u6309\u58d3\u65bc\u8a72\u8def\u7531\u5668\u672c\u8eab\u4e0a\u7684\u6309\u9215\u4f86\u91cd\u7f6e\u5bc6\u78bc\u3002\u67d0\u4e9b\u8def\u7531\u5668\u9700\u8981\u7279\u5b9a\u7684\u61c9\u7528\u7a0b\u5f0f\uff0c\u65e2\u7136\u5982\u6b64\u5176\u61c9\u5df2\u88ab\u5b89\u88dd\u65bc\u60a8\u7684\u96fb\u8166\/\u624b\u6a5f\u4e0a\u3002",
|
||||
"install_devices_router_list_2": "\u627e\u5230 DHCP\/DNS \u8a2d\u5b9a\u3002\u5c0b\u627e\u7dca\u9130\u8457\u5141\u8a31\u5169\u7d44\u6216\u4e09\u7d44\u6578\u5b57\u96c6\u7684\u6b04\u4f4d\u4e4b DNS \u5b57\u6bcd\uff0c\u6bcf\u7d44\u88ab\u62c6\u6210\u56db\u500b\u542b\u6709\u4e00\u81f3\u4e09\u500b\u6578\u5b57\u7684\u7fa4\u96c6\u3002",
|
||||
"install_devices_router_list_3": "\u5728\u90a3\u88e1\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_windows_list_1": "\u901a\u904e\u958b\u59cb\u529f\u80fd\u8868\u6216 Windows \u641c\u5c0b\uff0c\u958b\u555f\u63a7\u5236\u53f0\u3002",
|
||||
"install_devices_windows_list_2": "\u53bb\u7db2\u8def\u548c\u7db2\u969b\u7db2\u8def\u985e\u5225\uff0c\u7136\u5f8c\u53bb\u7db2\u8def\u548c\u5171\u7528\u4e2d\u5fc3\u3002",
|
||||
"install_devices_windows_list_3": "\u65bc\u756b\u9762\u4e4b\u5de6\u5074\u4e0a\u627e\u5230\u8b8a\u66f4\u4ecb\u9762\u5361\u8a2d\u5b9a\u4e26\u65bc\u5b83\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_windows_list_4": "\u9078\u64c7\u60a8\u73fe\u884c\u7684\u9023\u7dda\uff0c\u65bc\u5b83\u4e0a\u9ede\u64ca\u6ed1\u9f20\u53f3\u9375\uff0c\u7136\u5f8c\u9078\u64c7\u5167\u5bb9\u3002",
|
||||
"install_devices_windows_list_5": "\u5728\u6e05\u55ae\u4e2d\u627e\u5230\u7db2\u969b\u7db2\u8def\u901a\u8a0a\u5354\u5b9a\u7b2c 4 \u7248\uff08TCP\/IPv4\uff09\uff0c\u9078\u64c7\u5b83\uff0c\u7136\u5f8c\u518d\u6b21\u65bc\u5167\u5bb9\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_windows_list_6": "\u9078\u64c7\u4f7f\u7528\u4e0b\u5217\u7684 DNS \u4f3a\u670d\u5668\u4f4d\u5740\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_macos_list_1": "\u65bc Apple \u5716\u50cf\u4e0a\u9ede\u64ca\uff0c\u7136\u5f8c\u53bb\u7cfb\u7d71\u504f\u597d\u8a2d\u5b9a\u3002",
|
||||
"install_devices_macos_list_2": "\u65bc\u7db2\u8def\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_macos_list_3": "\u9078\u64c7\u5728\u60a8\u7684\u6e05\u55ae\u4e2d\u4e4b\u9996\u8981\u7684\u9023\u7dda\uff0c\u7136\u5f8c\u9ede\u64ca\u9032\u968e\u7684\u3002",
|
||||
"install_devices_macos_list_4": "\u9078\u64c7\u8a72 DNS \u5206\u9801\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_android_list_1": "\u5f9e Android \u9078\u55ae\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
|
||||
"install_devices_android_list_2": "\u65bc\u8a72\u9078\u55ae\u4e0a\u8f15\u89f8 Wi-Fi\u3002\u6b63\u5728\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7db2\u8def\u4e4b\u756b\u9762\u5c07\u88ab\u986f\u793a\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u9023\u7dda\u8a2d\u5b9a\u81ea\u8a02\u7684 DNS\uff09\u3002",
|
||||
"install_devices_android_list_3": "\u9577\u6309\u60a8\u6240\u9023\u7dda\u81f3\u7684\u7db2\u8def\uff0c\u7136\u5f8c\u8f15\u89f8\u4fee\u6539\u7db2\u8def\u3002",
|
||||
"install_devices_android_list_4": "\u65bc\u67d0\u4e9b\u88dd\u7f6e\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u6aa2\u67e5\u95dc\u65bc\u9032\u968e\u7684\u65b9\u6846\u4ee5\u67e5\u770b\u9032\u4e00\u6b65\u7684\u8a2d\u5b9a\u3002\u70ba\u4e86\u8abf\u6574\u60a8\u7684 Android DNS \u8a2d\u5b9a\uff0c\u60a8\u5c07\u9700\u8981\u628a IP \u8a2d\u5b9a\u5f9e DHCP \u8f49\u63db\u6210\u975c\u614b\u3002",
|
||||
"install_devices_android_list_5": "\u4f7f\u8a2d\u5b9a DNS 1 \u548c DNS 2 \u503c\u66f4\u6539\u6210\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_ios_list_1": "\u5f9e\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
|
||||
"install_devices_ios_list_2": "\u5728\u5de6\u5074\u7684\u9078\u55ae\u4e2d\u9078\u64c7 Wi-Fi\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u7db2\u8def\u914d\u7f6e DNS\uff09\u3002",
|
||||
"install_devices_ios_list_3": "\u65bc\u76ee\u524d\u73fe\u884c\u7684\u7db2\u8def\u4e4b\u540d\u7a31\u4e0a\u8f15\u89f8\u3002",
|
||||
"install_devices_ios_list_4": "\u5728\u8a72 DNS \u6b04\u4f4d\u4e2d\uff0c\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"get_started": "\u958b\u59cb\u5427",
|
||||
"next": "\u4e0b\u4e00\u6b65",
|
||||
"open_dashboard": "\u958b\u555f\u5100\u8868\u677f",
|
||||
"install_saved": "\u5df2\u6210\u529f\u5730\u5132\u5b58",
|
||||
"encryption_title": "\u52a0\u5bc6",
|
||||
"encryption_desc": "\u52a0\u5bc6\uff08HTTPS\/TLS\uff09\u652f\u63f4\u4f9b DNS \u548c\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5169\u8005",
|
||||
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u88ab\u5132\u5b58",
|
||||
"encryption_server": "\u4f3a\u670d\u5668\u540d\u7a31",
|
||||
"encryption_server_enter": "\u8f38\u5165\u60a8\u7684\u57df\u540d",
|
||||
"encryption_server_desc": "\u70ba\u4e86\u4f7f\u7528 HTTPS\uff0c\u60a8\u9700\u8981\u8f38\u5165\u8207\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u76f8\u7b26\u7684\u4f3a\u670d\u5668\u540d\u7a31\u3002",
|
||||
"encryption_redirect": "\u81ea\u52d5\u5730\u91cd\u65b0\u5c0e\u5411\u5230 HTTPS",
|
||||
"encryption_redirect_desc": "\u5982\u679c\u88ab\u52fe\u9078\uff0cAdGuard Home \u5c07\u81ea\u52d5\u5730\u91cd\u65b0\u5c0e\u5411\u60a8\u5f9e HTTP \u5230 HTTPS \u4f4d\u5740\u3002",
|
||||
"encryption_https": "HTTPS \u9023\u63a5\u57e0",
|
||||
"encryption_https_desc": "\u5982\u679c HTTPS \u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home \u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904e HTTPS \u5c07\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u4e14\u5b83\u4e5f\u5c07\u65bc '\/dns-query' \u4f4d\u7f6e\u4e0a\u63d0\u4f9b DNS-over-HTTPS\u3002",
|
||||
"encryption_dot": "DNS-over-TLS \u9023\u63a5\u57e0",
|
||||
"encryption_dot_desc": "\u5982\u679c\u8a72\u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home \u5c07\u65bc\u6b64\u9023\u63a5\u57e0\u4e0a\u904b\u884c DNS-over-TLS \u4f3a\u670d\u5668\u3002",
|
||||
"encryption_certificates": "\u6191\u8b49",
|
||||
"encryption_certificates_desc": "\u70ba\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u63d0\u4f9b\u6709\u6548\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u93c8\u7d50\u4f9b\u60a8\u7684\u7db2\u57df\u3002\u65bc <0>{{link}}<\/0> \u4e0a\u60a8\u53ef\u53d6\u5f97\u514d\u8cbb\u7684\u6191\u8b49\u6216\u60a8\u53ef\u5f9e\u53d7\u4fe1\u4efb\u7684\u6191\u8b49\u6388\u6b0a\u55ae\u4f4d\u4e4b\u4e00\u8cfc\u8cb7\u5b83\u3002",
|
||||
"encryption_certificates_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u6191\u8b49\u3002",
|
||||
"encryption_status": "\u72c0\u614b",
|
||||
"encryption_expire": "\u5230\u671f",
|
||||
"encryption_key": "\u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_key_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u79c1\u5bc6\u91d1\u9470\u4f9b\u60a8\u7684\u6191\u8b49\u3002",
|
||||
"encryption_enable": "\u555f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS \u548c DNS-over-TLS\uff09",
|
||||
"encryption_enable_desc": "\u5982\u679c\u52a0\u5bc6\u88ab\u555f\u7528\uff0cAdGuard Home \u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904e HTTPS \u5c07\u904b\u4f5c\uff0c\u4e14\u8a72 DNS \u4f3a\u670d\u5668\u5c07\u7559\u5fc3\u76e3\u807d\u900f\u904e DNS-over-HTTPS \u548c DNS-over-TLS \u4e4b\u8acb\u6c42\u3002",
|
||||
"encryption_chain_valid": "\u6191\u8b49\u93c8\u7d50\u70ba\u6709\u6548\u7684",
|
||||
"encryption_chain_invalid": "\u6191\u8b49\u93c8\u7d50\u70ba\u7121\u6548\u7684",
|
||||
"encryption_key_valid": "\u6b64\u70ba\u6709\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_key_invalid": "\u6b64\u70ba\u7121\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_subject": "\u7269\u4ef6",
|
||||
"encryption_issuer": "\u7c3d\u767c\u8005",
|
||||
"encryption_hostnames": "\u4e3b\u6a5f\u540d\u7a31",
|
||||
"encryption_reset": "\u60a8\u78ba\u5b9a\u60a8\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8a2d\u5b9a\u55ce\uff1f",
|
||||
"topline_expiring_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u5373\u5c07\u5230\u671f\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
|
||||
"topline_expired_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u70ba\u5df2\u5230\u671f\u7684\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
|
||||
"form_error_port_range": "\u5728 80-65535 \u4e4b\u7bc4\u570d\u5167\u8f38\u5165\u9023\u63a5\u57e0\u503c",
|
||||
"form_error_port_unsafe": "\u6b64\u70ba\u4e0d\u5b89\u5168\u7684\u9023\u63a5\u57e0",
|
||||
"form_error_equal": "\u4e0d\u61c9\u70ba\u76f8\u7b49\u7684",
|
||||
"form_error_password": "\u4e0d\u76f8\u7b26\u7684\u5bc6\u78bc",
|
||||
"reset_settings": "\u91cd\u7f6e\u8a2d\u5b9a",
|
||||
"update_announcement": "AdGuard Home {{version}} \u73fe\u70ba\u53ef\u7528\u7684\uff01\u95dc\u65bc\u66f4\u591a\u7684\u8cc7\u8a0a\uff0c<0>\u9ede\u64ca\u9019\u88e1<\/0>\u3002",
|
||||
"setup_guide": "\u5b89\u88dd\u6307\u5357",
|
||||
"dns_addresses": "DNS \u4f4d\u5740",
|
||||
"down": "\u505c\u6b62\u904b\u4f5c\u7684",
|
||||
"fix": "\u4fee\u5fa9",
|
||||
"dns_providers": "\u9019\u88e1\u662f\u4e00\u500b\u5f9e\u4e2d\u9078\u64c7\u4e4b<0>\u5df2\u77e5\u7684 DNS \u4f9b\u61c9\u5546\u4e4b\u6e05\u55ae<\/0>\u3002",
|
||||
"update_now": "\u7acb\u5373\u66f4\u65b0",
|
||||
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
|
||||
"processing_update": "Please wait, AdGuard Home is being updated",
|
||||
"clients_title": "\u7528\u6236\u7aef",
|
||||
"clients_desc": "Configure devices connected to AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Custom",
|
||||
"table_client": "\u7528\u6236\u7aef",
|
||||
"table_name": "\u540d\u7a31",
|
||||
"save_btn": "\u5132\u5b58",
|
||||
"client_add": "\u589e\u52a0\u7528\u6236\u7aef",
|
||||
"client_new": "\u65b0\u7684\u7528\u6236\u7aef",
|
||||
"client_edit": "\u7de8\u8f2f\u7528\u6236\u7aef",
|
||||
"client_identifier": "Identifier",
|
||||
"ip_address": "IP \u4f4d\u5740",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
|
||||
"form_enter_ip": "\u8f38\u5165 IP",
|
||||
"form_enter_mac": "\u8f38\u5165\u5a92\u9ad4\u5b58\u53d6\u63a7\u5236\uff08MAC\uff09",
|
||||
"form_client_name": "\u8f38\u5165\u7528\u6236\u7aef\u540d\u7a31",
|
||||
"client_global_settings": "Use global settings",
|
||||
"client_deleted": "Client \"{{key}}\" successfully deleted",
|
||||
"client_added": "Client \"{{key}}\" successfully added",
|
||||
"client_updated": "Client \"{{key}}\" successfully updated",
|
||||
"table_statistics": "Requests count (last 24 hours)",
|
||||
"clients_not_found": "No clients found",
|
||||
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Are you sure you want to delete filter?",
|
||||
"auto_clients_title": "Clients (runtime)",
|
||||
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
|
||||
"access_title": "Access settings",
|
||||
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
|
||||
"access_allowed_title": "Allowed clients",
|
||||
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
|
||||
"access_disallowed_title": "Disallowed clients",
|
||||
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
|
||||
"access_blocked_title": "Blocked domains",
|
||||
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
|
||||
"access_settings_saved": "Access settings successfully saved"
|
||||
}
|
||||
45
client/src/actions/access.js
Normal file
45
client/src/actions/access.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
import { normalizeTextarea } from '../helpers/helpers';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
|
||||
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
|
||||
export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS');
|
||||
|
||||
export const getAccessList = () => async (dispatch) => {
|
||||
dispatch(getAccessListRequest());
|
||||
try {
|
||||
const data = await apiClient.getAccessList();
|
||||
dispatch(getAccessListSuccess(data));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getAccessListFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST');
|
||||
export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE');
|
||||
export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS');
|
||||
|
||||
export const setAccessList = config => async (dispatch) => {
|
||||
dispatch(setAccessListRequest());
|
||||
try {
|
||||
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
|
||||
|
||||
const values = {
|
||||
allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [],
|
||||
disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [],
|
||||
blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [],
|
||||
};
|
||||
|
||||
await apiClient.setAccessList(values);
|
||||
dispatch(setAccessListSuccess());
|
||||
dispatch(addSuccessToast('access_settings_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setAccessListFailure());
|
||||
}
|
||||
};
|
||||
84
client/src/actions/clients.js
Normal file
84
client/src/actions/clients.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { t } from 'i18next';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast, getClients } from './index';
|
||||
import { CLIENT_ID } from '../helpers/constants';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const toggleClientModal = createAction('TOGGLE_CLIENT_MODAL');
|
||||
|
||||
export const addClientRequest = createAction('ADD_CLIENT_REQUEST');
|
||||
export const addClientFailure = createAction('ADD_CLIENT_FAILURE');
|
||||
export const addClientSuccess = createAction('ADD_CLIENT_SUCCESS');
|
||||
|
||||
export const addClient = config => async (dispatch) => {
|
||||
dispatch(addClientRequest());
|
||||
try {
|
||||
let data;
|
||||
if (config.identifier === CLIENT_ID.MAC) {
|
||||
const { ip, identifier, ...values } = config;
|
||||
|
||||
data = { ...values };
|
||||
} else {
|
||||
const { mac, identifier, ...values } = config;
|
||||
|
||||
data = { ...values };
|
||||
}
|
||||
|
||||
await apiClient.addClient(data);
|
||||
dispatch(addClientSuccess());
|
||||
dispatch(toggleClientModal());
|
||||
dispatch(addSuccessToast(t('client_added', { key: config.name })));
|
||||
dispatch(getClients());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(addClientFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteClientRequest = createAction('DELETE_CLIENT_REQUEST');
|
||||
export const deleteClientFailure = createAction('DELETE_CLIENT_FAILURE');
|
||||
export const deleteClientSuccess = createAction('DELETE_CLIENT_SUCCESS');
|
||||
|
||||
export const deleteClient = config => async (dispatch) => {
|
||||
dispatch(deleteClientRequest());
|
||||
try {
|
||||
await apiClient.deleteClient(config);
|
||||
dispatch(deleteClientSuccess());
|
||||
dispatch(addSuccessToast(t('client_deleted', { key: config.name })));
|
||||
dispatch(getClients());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(deleteClientFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const updateClientRequest = createAction('UPDATE_CLIENT_REQUEST');
|
||||
export const updateClientFailure = createAction('UPDATE_CLIENT_FAILURE');
|
||||
export const updateClientSuccess = createAction('UPDATE_CLIENT_SUCCESS');
|
||||
|
||||
export const updateClient = (config, name) => async (dispatch) => {
|
||||
dispatch(updateClientRequest());
|
||||
try {
|
||||
let data;
|
||||
if (config.identifier === CLIENT_ID.MAC) {
|
||||
const { ip, identifier, ...values } = config;
|
||||
|
||||
data = { name, data: { ...values } };
|
||||
} else {
|
||||
const { mac, identifier, ...values } = config;
|
||||
|
||||
data = { name, data: { ...values } };
|
||||
}
|
||||
|
||||
await apiClient.updateClient(data);
|
||||
dispatch(updateClientSuccess());
|
||||
dispatch(toggleClientModal());
|
||||
dispatch(addSuccessToast(t('client_updated', { key: name })));
|
||||
dispatch(getClients());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(updateClientFailure());
|
||||
}
|
||||
};
|
||||
73
client/src/actions/encryption.js
Normal file
73
client/src/actions/encryption.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
import { redirectToCurrentProtocol } from '../helpers/helpers';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
|
||||
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
|
||||
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
|
||||
|
||||
export const getTlsStatus = () => async (dispatch) => {
|
||||
dispatch(getTlsStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getTlsStatus();
|
||||
status.certificate_chain = atob(status.certificate_chain);
|
||||
status.private_key = atob(status.private_key);
|
||||
|
||||
dispatch(getTlsStatusSuccess(status));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getTlsStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
|
||||
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
|
||||
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
|
||||
|
||||
export const setTlsConfig = config => async (dispatch, getState) => {
|
||||
dispatch(setTlsConfigRequest());
|
||||
try {
|
||||
const { httpPort } = getState().dashboard;
|
||||
const values = { ...config };
|
||||
values.certificate_chain = btoa(values.certificate_chain);
|
||||
values.private_key = btoa(values.private_key);
|
||||
values.port_https = values.port_https || 0;
|
||||
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||
|
||||
const response = await apiClient.setTlsConfig(values);
|
||||
response.certificate_chain = atob(response.certificate_chain);
|
||||
response.private_key = atob(response.private_key);
|
||||
dispatch(setTlsConfigSuccess(response));
|
||||
dispatch(addSuccessToast('encryption_config_saved'));
|
||||
redirectToCurrentProtocol(response, httpPort);
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setTlsConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUEST');
|
||||
export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE');
|
||||
export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS');
|
||||
|
||||
export const validateTlsConfig = config => async (dispatch) => {
|
||||
dispatch(validateTlsConfigRequest());
|
||||
try {
|
||||
const values = { ...config };
|
||||
values.certificate_chain = btoa(values.certificate_chain);
|
||||
values.private_key = btoa(values.private_key);
|
||||
values.port_https = values.port_https || 0;
|
||||
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||
|
||||
const response = await apiClient.validateTlsConfig(values);
|
||||
response.certificate_chain = atob(response.certificate_chain);
|
||||
response.private_key = atob(response.private_key);
|
||||
dispatch(validateTlsConfigSuccess(response));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(validateTlsConfigFailure());
|
||||
}
|
||||
};
|
||||
@@ -2,14 +2,19 @@ import { createAction } from 'redux-actions';
|
||||
import round from 'lodash/round';
|
||||
import { t } from 'i18next';
|
||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||
import axios from 'axios';
|
||||
|
||||
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs } from '../helpers/helpers';
|
||||
import versionCompare from '../helpers/versionCompare';
|
||||
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers';
|
||||
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
|
||||
import { getTlsStatus } from './encryption';
|
||||
import Api from '../api/Api';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const addErrorToast = createAction('ADD_ERROR_TOAST');
|
||||
export const addSuccessToast = createAction('ADD_SUCCESS_TOAST');
|
||||
export const addNoticeToast = createAction('ADD_NOTICE_TOAST');
|
||||
export const removeToast = createAction('REMOVE_TOAST');
|
||||
|
||||
export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
|
||||
@@ -18,9 +23,8 @@ export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||
export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
let successMessage = '';
|
||||
try {
|
||||
// TODO move setting keys to constants
|
||||
switch (settingKey) {
|
||||
case 'filtering':
|
||||
case SETTINGS_NAMES.filtering:
|
||||
if (status) {
|
||||
successMessage = 'disabled_filtering_toast';
|
||||
await apiClient.disableFiltering();
|
||||
@@ -30,7 +34,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safebrowsing':
|
||||
case SETTINGS_NAMES.safebrowsing:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_browsing_toast';
|
||||
await apiClient.disableSafebrowsing();
|
||||
@@ -40,7 +44,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'parental':
|
||||
case SETTINGS_NAMES.parental:
|
||||
if (status) {
|
||||
successMessage = 'disabled_parental_toast';
|
||||
await apiClient.disableParentalControl();
|
||||
@@ -50,7 +54,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safesearch':
|
||||
case SETTINGS_NAMES.safesearch:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_search_toast';
|
||||
await apiClient.disableSafesearch();
|
||||
@@ -139,6 +143,130 @@ export const toggleProtection = status => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getVersionRequest = createAction('GET_VERSION_REQUEST');
|
||||
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
|
||||
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
|
||||
|
||||
export const getVersion = (recheck = false) => async (dispatch, getState) => {
|
||||
dispatch(getVersionRequest());
|
||||
try {
|
||||
const data = await apiClient.getGlobalVersion({ recheck_now: recheck });
|
||||
dispatch(getVersionSuccess(data));
|
||||
|
||||
if (recheck) {
|
||||
const { dnsVersion } = getState().dashboard;
|
||||
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
|
||||
|
||||
if (data && versionCompare(currentVersion, data.new_version) === -1) {
|
||||
dispatch(addSuccessToast('updates_checked'));
|
||||
} else {
|
||||
dispatch(addSuccessToast('updates_version_equal'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getVersionFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpdateRequest = createAction('GET_UPDATE_REQUEST');
|
||||
export const getUpdateFailure = createAction('GET_UPDATE_FAILURE');
|
||||
export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS');
|
||||
|
||||
export const getUpdate = () => async (dispatch, getState) => {
|
||||
const { dnsVersion } = getState().dashboard;
|
||||
|
||||
dispatch(getUpdateRequest());
|
||||
try {
|
||||
await apiClient.getUpdate();
|
||||
|
||||
const checkUpdate = async (attempts) => {
|
||||
let count = attempts || 1;
|
||||
let timeout;
|
||||
|
||||
if (count > 60) {
|
||||
dispatch(addNoticeToast({ error: 'update_failed' }));
|
||||
dispatch(getUpdateFailure());
|
||||
return false;
|
||||
}
|
||||
|
||||
const rmTimeout = t => t && clearTimeout(t);
|
||||
const setRecursiveTimeout = (time, ...args) => setTimeout(
|
||||
checkUpdate,
|
||||
time,
|
||||
...args,
|
||||
);
|
||||
|
||||
axios.get('control/status')
|
||||
.then((response) => {
|
||||
rmTimeout(timeout);
|
||||
if (response && response.status === 200) {
|
||||
const responseVersion = response.data && response.data.version;
|
||||
|
||||
if (dnsVersion !== responseVersion) {
|
||||
dispatch(getUpdateSuccess());
|
||||
window.location.reload(true);
|
||||
}
|
||||
}
|
||||
timeout = setRecursiveTimeout(CHECK_TIMEOUT, count += 1);
|
||||
})
|
||||
.catch(() => {
|
||||
rmTimeout(timeout);
|
||||
timeout = setRecursiveTimeout(CHECK_TIMEOUT, count += 1);
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
checkUpdate();
|
||||
} catch (error) {
|
||||
dispatch(addNoticeToast({ error: 'update_failed' }));
|
||||
dispatch(getUpdateFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
|
||||
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
|
||||
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
|
||||
|
||||
export const getClients = () => async (dispatch) => {
|
||||
dispatch(getClientsRequest());
|
||||
try {
|
||||
const data = await apiClient.getClients();
|
||||
const sortedClients = data.clients && sortClients(data.clients);
|
||||
const sortedAutoClients = data.auto_clients && sortClients(data.auto_clients);
|
||||
|
||||
dispatch(getClientsSuccess({
|
||||
clients: sortedClients || [],
|
||||
autoClients: sortedAutoClients || [],
|
||||
}));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getClientsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
|
||||
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
|
||||
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
|
||||
|
||||
export const getTopStats = () => async (dispatch, getState) => {
|
||||
dispatch(getTopStatsRequest());
|
||||
const timer = setInterval(async () => {
|
||||
const state = getState();
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
clearInterval(timer);
|
||||
try {
|
||||
const stats = await apiClient.getGlobalStatsTop();
|
||||
dispatch(getTopStatsSuccess(stats));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getTopStatsFailure(error));
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
|
||||
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
|
||||
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
||||
@@ -148,6 +276,8 @@ export const getDnsStatus = () => async (dispatch) => {
|
||||
try {
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
dispatch(getVersion());
|
||||
dispatch(getTlsStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(initSettingsFailure());
|
||||
@@ -205,42 +335,6 @@ export const getStats = () => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getVersionRequest = createAction('GET_VERSION_REQUEST');
|
||||
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
|
||||
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
|
||||
|
||||
export const getVersion = () => async (dispatch) => {
|
||||
dispatch(getVersionRequest());
|
||||
try {
|
||||
const newVersion = await apiClient.getGlobalVersion();
|
||||
dispatch(getVersionSuccess(newVersion));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getVersionFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
|
||||
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
|
||||
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
|
||||
|
||||
export const getTopStats = () => async (dispatch, getState) => {
|
||||
dispatch(getTopStatsRequest());
|
||||
const timer = setInterval(async () => {
|
||||
const state = getState();
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
clearInterval(timer);
|
||||
try {
|
||||
const stats = await apiClient.getGlobalStatsTop();
|
||||
dispatch(getTopStatsSuccess(stats));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getTopStatsFailure(error));
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||
@@ -352,11 +446,11 @@ export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
||||
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
||||
|
||||
export const refreshFilters = () => async (dispatch) => {
|
||||
dispatch(refreshFiltersRequest);
|
||||
dispatch(refreshFiltersRequest());
|
||||
dispatch(showLoading());
|
||||
try {
|
||||
const refreshText = await apiClient.refreshFilters();
|
||||
dispatch(refreshFiltersSuccess);
|
||||
dispatch(refreshFiltersSuccess());
|
||||
|
||||
if (refreshText.includes('OK')) {
|
||||
if (refreshText.includes('OK 0')) {
|
||||
@@ -434,7 +528,6 @@ export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST'
|
||||
export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
|
||||
export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
|
||||
|
||||
// TODO create some common flasher with all server errors
|
||||
export const downloadQueryLog = () => async (dispatch) => {
|
||||
let data;
|
||||
dispatch(downloadQueryLogRequest());
|
||||
@@ -453,10 +546,18 @@ export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||
export const setUpstreamSuccess = createAction('SET_UPSTREAM_SUCCESS');
|
||||
|
||||
export const setUpstream = url => async (dispatch) => {
|
||||
export const setUpstream = config => async (dispatch) => {
|
||||
dispatch(setUpstreamRequest());
|
||||
try {
|
||||
await apiClient.setUpstream(url);
|
||||
const values = { ...config };
|
||||
values.bootstrap_dns = (
|
||||
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
|
||||
) || [];
|
||||
values.upstream_dns = (
|
||||
values.upstream_dns && normalizeTextarea(values.upstream_dns)
|
||||
) || [];
|
||||
|
||||
await apiClient.setUpstream(values);
|
||||
dispatch(addSuccessToast('updated_upstream_dns_toast'));
|
||||
dispatch(setUpstreamSuccess());
|
||||
} catch (error) {
|
||||
@@ -469,11 +570,18 @@ export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
|
||||
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
|
||||
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
||||
|
||||
export const testUpstream = servers => async (dispatch) => {
|
||||
export const testUpstream = config => async (dispatch) => {
|
||||
dispatch(testUpstreamRequest());
|
||||
try {
|
||||
const upstreamResponse = await apiClient.testUpstream(servers);
|
||||
const values = { ...config };
|
||||
values.bootstrap_dns = (
|
||||
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
|
||||
) || [];
|
||||
values.upstream_dns = (
|
||||
values.upstream_dns && normalizeTextarea(values.upstream_dns)
|
||||
) || [];
|
||||
|
||||
const upstreamResponse = await apiClient.testUpstream(values);
|
||||
const testMessages = Object.keys(upstreamResponse).map((key) => {
|
||||
const message = upstreamResponse[key];
|
||||
if (message !== 'OK') {
|
||||
@@ -572,34 +680,15 @@ export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
|
||||
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
|
||||
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
|
||||
|
||||
// TODO rewrite findActiveDhcp part
|
||||
export const setDhcpConfig = config => async (dispatch) => {
|
||||
export const setDhcpConfig = values => async (dispatch, getState) => {
|
||||
const { config } = getState().dhcp;
|
||||
const updatedConfig = { ...config, ...values };
|
||||
dispatch(setDhcpConfigRequest());
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
try {
|
||||
if (config.interface_name) {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
|
||||
if (!activeDhcp.found) {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
} else {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
}
|
||||
await apiClient.setDhcpConfig(updatedConfig);
|
||||
dispatch(setDhcpConfigSuccess(updatedConfig));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
@@ -610,42 +699,60 @@ export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
|
||||
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
|
||||
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
|
||||
|
||||
// TODO rewrite findActiveDhcp part
|
||||
export const toggleDhcp = config => async (dispatch) => {
|
||||
export const toggleDhcp = values => async (dispatch) => {
|
||||
dispatch(toggleDhcpRequest());
|
||||
let config = { ...values, enabled: false };
|
||||
let successMessage = 'disabled_dhcp';
|
||||
|
||||
if (config.enabled) {
|
||||
dispatch(addSuccessToast('disabled_dhcp'));
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: false });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
} else {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
if (!values.enabled) {
|
||||
config = { ...values, enabled: true };
|
||||
successMessage = 'enabled_dhcp';
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
}
|
||||
|
||||
if (!activeDhcp.found) {
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: true });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
dispatch(addSuccessToast('enabled_dhcp'));
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
try {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(addSuccessToast(successMessage));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL');
|
||||
|
||||
export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
|
||||
export const addStaticLeaseFailure = createAction('ADD_STATIC_LEASE_FAILURE');
|
||||
export const addStaticLeaseSuccess = createAction('ADD_STATIC_LEASE_SUCCESS');
|
||||
|
||||
export const addStaticLease = config => async (dispatch) => {
|
||||
dispatch(addStaticLeaseRequest());
|
||||
try {
|
||||
const name = config.hostname || config.ip;
|
||||
await apiClient.addStaticLease(config);
|
||||
dispatch(addStaticLeaseSuccess(config));
|
||||
dispatch(addSuccessToast(t('dhcp_lease_added', { key: name })));
|
||||
dispatch(toggleLeaseModal());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(addStaticLeaseFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const removeStaticLeaseRequest = createAction('REMOVE_STATIC_LEASE_REQUEST');
|
||||
export const removeStaticLeaseFailure = createAction('REMOVE_STATIC_LEASE_FAILURE');
|
||||
export const removeStaticLeaseSuccess = createAction('REMOVE_STATIC_LEASE_SUCCESS');
|
||||
|
||||
export const removeStaticLease = config => async (dispatch) => {
|
||||
dispatch(removeStaticLeaseRequest());
|
||||
try {
|
||||
const name = config.hostname || config.ip;
|
||||
await apiClient.removeStaticLease(config);
|
||||
dispatch(removeStaticLeaseSuccess(config));
|
||||
dispatch(addSuccessToast(t('dhcp_lease_deleted', { key: name })));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(removeStaticLeaseFailure());
|
||||
}
|
||||
};
|
||||
|
||||
61
client/src/actions/install.js
Normal file
61
client/src/actions/install.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const nextStep = createAction('NEXT_STEP');
|
||||
export const prevStep = createAction('PREV_STEP');
|
||||
|
||||
export const getDefaultAddressesRequest = createAction('GET_DEFAULT_ADDRESSES_REQUEST');
|
||||
export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE');
|
||||
export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS');
|
||||
|
||||
export const getDefaultAddresses = () => async (dispatch) => {
|
||||
dispatch(getDefaultAddressesRequest());
|
||||
try {
|
||||
const addresses = await apiClient.getDefaultAddresses();
|
||||
dispatch(getDefaultAddressesSuccess(addresses));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDefaultAddressesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setAllSettingsRequest = createAction('SET_ALL_SETTINGS_REQUEST');
|
||||
export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE');
|
||||
export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
|
||||
|
||||
export const setAllSettings = values => async (dispatch) => {
|
||||
dispatch(setAllSettingsRequest());
|
||||
try {
|
||||
const {
|
||||
confirm_password,
|
||||
...config
|
||||
} = values;
|
||||
|
||||
await apiClient.setAllSettings(config);
|
||||
dispatch(setAllSettingsSuccess());
|
||||
dispatch(addSuccessToast('install_saved'));
|
||||
dispatch(nextStep());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setAllSettingsFailure());
|
||||
dispatch(prevStep());
|
||||
}
|
||||
};
|
||||
|
||||
export const checkConfigRequest = createAction('CHECK_CONFIG_REQUEST');
|
||||
export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE');
|
||||
export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS');
|
||||
|
||||
export const checkConfig = values => async (dispatch) => {
|
||||
dispatch(checkConfigRequest());
|
||||
try {
|
||||
const check = await apiClient.checkConfig(values);
|
||||
dispatch(checkConfigSuccess(check));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(checkConfigFailure());
|
||||
}
|
||||
};
|
||||
@@ -15,7 +15,11 @@ export default class Api {
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`${this.baseUrl}/${path} | ${error.response.data} | ${error.response.status}`);
|
||||
const errorPath = `${this.baseUrl}/${path}`;
|
||||
if (error.response) {
|
||||
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
|
||||
}
|
||||
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +34,12 @@ export default class Api {
|
||||
GLOBAL_QUERY_LOG = { path: 'querylog', method: 'GET' };
|
||||
GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' };
|
||||
GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstream_dns', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
|
||||
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
|
||||
GLOBAL_VERSION = { path: 'version.json', method: 'GET' };
|
||||
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
|
||||
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
|
||||
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
|
||||
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
|
||||
|
||||
restartGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_RESTART;
|
||||
@@ -106,7 +111,7 @@ export default class Api {
|
||||
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: url,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -115,14 +120,18 @@ export default class Api {
|
||||
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: servers,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
getGlobalVersion() {
|
||||
getGlobalVersion(data) {
|
||||
const { path, method } = this.GLOBAL_VERSION;
|
||||
return this.makeRequest(path, method);
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
enableGlobalProtection() {
|
||||
@@ -135,13 +144,18 @@ export default class Api {
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getUpdate() {
|
||||
const { path, method } = this.GLOBAL_UPDATE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Filtering
|
||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
||||
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
|
||||
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
|
||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'PUT' };
|
||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'DELETE' };
|
||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'PUT' };
|
||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
|
||||
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
|
||||
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
|
||||
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
||||
@@ -178,15 +192,14 @@ export default class Api {
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
removeFilter(url) {
|
||||
removeFilter(config) {
|
||||
const { path, method } = this.FILTERING_REMOVE_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
setRules(rules) {
|
||||
@@ -308,6 +321,8 @@ export default class Api {
|
||||
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
|
||||
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
|
||||
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
|
||||
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
|
||||
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
|
||||
|
||||
getDhcpStatus() {
|
||||
const { path, method } = this.DHCP_STATUS;
|
||||
@@ -336,4 +351,134 @@ export default class Api {
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
addStaticLease(config) {
|
||||
const { path, method } = this.DHCP_ADD_STATIC_LEASE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
removeStaticLease(config) {
|
||||
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// Installation
|
||||
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
|
||||
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
|
||||
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
|
||||
|
||||
getDefaultAddresses() {
|
||||
const { path, method } = this.INSTALL_GET_ADDRESSES;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setAllSettings(config) {
|
||||
const { path, method } = this.INSTALL_CONFIGURE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
checkConfig(config) {
|
||||
const { path, method } = this.INSTALL_CHECK_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DNS-over-HTTPS and DNS-over-TLS
|
||||
TLS_STATUS = { path: 'tls/status', method: 'GET' };
|
||||
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
|
||||
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
|
||||
|
||||
getTlsStatus() {
|
||||
const { path, method } = this.TLS_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setTlsConfig(config) {
|
||||
const { path, method } = this.TLS_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
validateTlsConfig(config) {
|
||||
const { path, method } = this.TLS_VALIDATE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// Per-client settings
|
||||
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
||||
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
||||
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
|
||||
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
|
||||
|
||||
getClients() {
|
||||
const { path, method } = this.GET_CLIENTS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
addClient(config) {
|
||||
const { path, method } = this.ADD_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
deleteClient(config) {
|
||||
const { path, method } = this.DELETE_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
updateClient(config) {
|
||||
const { path, method } = this.UPDATE_CLIENT;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DNS access settings
|
||||
ACCESS_LIST = { path: 'access/list', method: 'GET' };
|
||||
ACCESS_SET = { path: 'access/set', method: 'POST' };
|
||||
|
||||
getAccessList() {
|
||||
const { path, method } = this.ACCESS_LIST;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setAccessList(config) {
|
||||
const { path, method } = this.ACCESS_SET;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.status {
|
||||
@@ -19,8 +19,14 @@ body {
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 103;
|
||||
height: 3px;
|
||||
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { HashRouter, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import LoadingBar from 'react-redux-loading-bar';
|
||||
|
||||
import 'react-table/react-table.css';
|
||||
@@ -12,17 +13,26 @@ import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Filters from '../../containers/Filters';
|
||||
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
import Dhcp from '../../containers/Dhcp';
|
||||
import Clients from '../../containers/Clients';
|
||||
|
||||
import Logs from '../../containers/Logs';
|
||||
import Footer from '../ui/Footer';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import Toasts from '../Toasts';
|
||||
import Footer from '../ui/Footer';
|
||||
import Status from '../ui/Status';
|
||||
import Update from '../ui/Update';
|
||||
import UpdateTopline from '../ui/UpdateTopline';
|
||||
import UpdateOverlay from '../ui/UpdateOverlay';
|
||||
import EncryptionTopline from '../ui/EncryptionTopline';
|
||||
import Icons from '../ui/Icons';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsStatus();
|
||||
this.props.getVersion();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -35,6 +45,10 @@ class App extends Component {
|
||||
this.props.enableDns();
|
||||
};
|
||||
|
||||
handleUpdate = () => {
|
||||
this.props.getUpdate();
|
||||
};
|
||||
|
||||
setLanguage = () => {
|
||||
const { processing, language } = this.props.dashboard;
|
||||
|
||||
@@ -47,45 +61,57 @@ class App extends Component {
|
||||
i18n.on('languageChanged', (lang) => {
|
||||
this.props.changeLanguage(lang);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const updateAvailable =
|
||||
!dashboard.processingVersions &&
|
||||
dashboard.isCoreRunning &&
|
||||
dashboard.isUpdateAvailable;
|
||||
const { dashboard, encryption } = this.props;
|
||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
|
||||
return (
|
||||
<HashRouter hashType='noslash'>
|
||||
<HashRouter hashType="noslash">
|
||||
<Fragment>
|
||||
{updateAvailable &&
|
||||
<Update
|
||||
announcement={dashboard.announcement}
|
||||
announcementUrl={dashboard.announcementUrl}
|
||||
/>
|
||||
}
|
||||
{updateAvailable && (
|
||||
<Fragment>
|
||||
<UpdateTopline
|
||||
url={dashboard.announcementUrl}
|
||||
version={dashboard.newVersion}
|
||||
canAutoUpdate={dashboard.canAutoUpdate}
|
||||
getUpdate={this.handleUpdate}
|
||||
processingUpdate={dashboard.processingUpdate}
|
||||
/>
|
||||
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
||||
</Fragment>
|
||||
)}
|
||||
{!encryption.processing && (
|
||||
<EncryptionTopline notAfter={encryption.not_after} />
|
||||
)}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap">
|
||||
{!dashboard.processing && !dashboard.isCoreRunning &&
|
||||
{!dashboard.processing && !dashboard.isCoreRunning && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status handleStatusChange={this.handleStatusChange} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!dashboard.processing && dashboard.isCoreRunning &&
|
||||
)}
|
||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||
<Fragment>
|
||||
<Route path="/" exact component={Dashboard} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/dns" component={Dns} />
|
||||
<Route path="/encryption" component={Encryption} />
|
||||
<Route path="/dhcp" component={Dhcp} />
|
||||
<Route path="/clients" component={Clients} />
|
||||
<Route path="/filters" component={Filters} />
|
||||
<Route path="/logs" component={Logs} />
|
||||
<Route path="/guide" component={SetupGuide} />
|
||||
</Fragment>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
<Toasts />
|
||||
<Icons />
|
||||
</Fragment>
|
||||
</HashRouter>
|
||||
);
|
||||
@@ -94,12 +120,13 @@ class App extends Component {
|
||||
|
||||
App.propTypes = {
|
||||
getDnsStatus: PropTypes.func,
|
||||
getUpdate: PropTypes.func,
|
||||
enableDns: PropTypes.func,
|
||||
dashboard: PropTypes.object,
|
||||
isCoreRunning: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
getVersion: PropTypes.func,
|
||||
changeLanguage: PropTypes.func,
|
||||
encryption: PropTypes.object,
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default withNamespaces()(App);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
|
||||
import { getPercent } from '../../helpers/helpers';
|
||||
import { getPercent, getClientName } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
class Clients extends Component {
|
||||
@@ -23,7 +23,25 @@ class Clients extends Component {
|
||||
columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
|
||||
Cell: ({ value }) => {
|
||||
const clientName = getClientName(this.props.clients, value)
|
||||
|| getClientName(this.props.autoClients, value);
|
||||
let client;
|
||||
|
||||
if (clientName) {
|
||||
client = <span>{clientName} <small>({value})</small></span>;
|
||||
} else {
|
||||
client = value;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{client}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
}, {
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
@@ -61,6 +79,8 @@ Clients.propTypes = {
|
||||
topClients: PropTypes.object.isRequired,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
clients: PropTypes.array.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'whatwg-fetch';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Statistics from './Statistics';
|
||||
@@ -22,15 +21,21 @@ class Dashboard extends Component {
|
||||
this.props.getStats();
|
||||
this.props.getStatsHistory();
|
||||
this.props.getTopStats();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
getToggleFilteringButton = () => {
|
||||
const { protectionEnabled } = this.props.dashboard;
|
||||
const { protectionEnabled, processingProtection } = this.props.dashboard;
|
||||
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
|
||||
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
|
||||
|
||||
return (
|
||||
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm mr-2 ${buttonClass}`}
|
||||
onClick={() => this.props.toggleProtection(protectionEnabled)}
|
||||
disabled={processingProtection}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
);
|
||||
@@ -42,10 +47,29 @@ class Dashboard extends Component {
|
||||
dashboard.processing ||
|
||||
dashboard.processingStats ||
|
||||
dashboard.processingStatsHistory ||
|
||||
dashboard.processingClients ||
|
||||
dashboard.processingTopStats;
|
||||
|
||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>refresh_statics</Trans></button>;
|
||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />;
|
||||
const refreshFullButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<Trans>refresh_statics</Trans>
|
||||
</button>
|
||||
);
|
||||
const refreshButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -90,6 +114,8 @@ class Dashboard extends Component {
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
refreshButton={refreshButton}
|
||||
topClients={dashboard.topStats.top_clients}
|
||||
clients={dashboard.clients}
|
||||
autoClients={dashboard.autoClients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
@@ -125,6 +151,8 @@ Dashboard.propTypes = {
|
||||
isCoreRunning: PropTypes.bool,
|
||||
getFiltering: PropTypes.func,
|
||||
toggleProtection: PropTypes.func,
|
||||
getClients: PropTypes.func,
|
||||
processingProtection: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
.remove-icon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.remove-icon:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import ReactModal from 'react-modal';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
|
||||
import './Modal.css';
|
||||
import '../ui/Modal.css';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
|
||||
@@ -17,10 +17,7 @@ const initialState = {
|
||||
class Modal extends Component {
|
||||
state = initialState;
|
||||
|
||||
// eslint-disable-next-line
|
||||
isUrlValid = url => {
|
||||
return R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
};
|
||||
isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
|
||||
handleUrlChange = async (e) => {
|
||||
const { value: url } = e.currentTarget;
|
||||
@@ -55,6 +52,7 @@ class Modal extends Component {
|
||||
isOpen,
|
||||
title,
|
||||
inputDescription,
|
||||
processingAddFilter,
|
||||
} = this.props;
|
||||
const { isUrlValid, url, name } = this.state;
|
||||
const inputUrlClass = classnames({
|
||||
@@ -71,8 +69,8 @@ class Modal extends Component {
|
||||
if (!this.props.isFilterAdded) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input type="text" className={inputNameClass} placeholder={ this.props.t('enter_name_hint') } onChange={this.handleNameChange} />
|
||||
<input type="text" className={inputUrlClass} placeholder={ this.props.t('enter_url_hint') } onChange={this.handleUrlChange} />
|
||||
<input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
|
||||
<input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
|
||||
{inputDescription &&
|
||||
<div className="description">
|
||||
{inputDescription}
|
||||
@@ -82,7 +80,7 @@ class Modal extends Component {
|
||||
}
|
||||
return (
|
||||
<div className="description">
|
||||
<Trans>Url added successfully</Trans>
|
||||
<Trans>url_added_successfully</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -93,7 +91,7 @@ class Modal extends Component {
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={ isOpen }
|
||||
isOpen={isOpen}
|
||||
onRequestClose={this.closeModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
@@ -106,14 +104,26 @@ class Modal extends Component {
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{ renderBody()}
|
||||
{renderBody()}
|
||||
</div>
|
||||
{
|
||||
!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={this.closeModal}><Trans>cancel_btn</Trans></button>
|
||||
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}><Trans>add_filter_btn</Trans></button>
|
||||
</div>
|
||||
{!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={this.closeModal}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success"
|
||||
onClick={this.handleNext}
|
||||
disabled={isValidForSubmit || processingAddFilter}
|
||||
>
|
||||
<Trans>add_filter_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ReactModal>
|
||||
@@ -128,6 +138,7 @@ Modal.propTypes = {
|
||||
inputDescription: PropTypes.string,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
processingAddFilter: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
@@ -17,15 +17,16 @@ class UserRules extends Component {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card
|
||||
title={ t('custom_filter_rules') }
|
||||
subtitle={ t('custom_filter_rules_hint') }
|
||||
>
|
||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} />
|
||||
<textarea
|
||||
className="form-control form-control--textarea-large"
|
||||
value={this.props.userRules}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
className="btn btn-success btn-standard"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
@@ -33,24 +34,42 @@ class UserRules extends Component {
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<hr />
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> - { t('example_meaning_filter_block') }
|
||||
<code>||example.org^</code> – {t('example_meaning_filter_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> - { t('example_meaning_filter_whitelist') }
|
||||
<code> @@||example.org^</code> – {t('example_meaning_filter_whitelist')}
|
||||
</li>
|
||||
<li>
|
||||
<code>127.0.0.1 example.org</code> - { t('example_meaning_host_block') }
|
||||
<code>127.0.0.1 example.org</code> – {t('example_meaning_host_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code>{ t('example_comment') }</code> - { t('example_comment_meaning') }
|
||||
<code>{t('example_comment')}</code> – {t('example_comment_meaning')}
|
||||
</li>
|
||||
<li>
|
||||
<code>{ t('example_comment_hash') }</code> - { t('example_comment_meaning') }
|
||||
<code>{t('example_comment_hash')}</code> –
|
||||
{t('example_comment_meaning')}
|
||||
</li>
|
||||
<li>
|
||||
<code>/REGEX/</code> –
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://kb.adguard.com/general/dns-filtering-syntax"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_regex_meaning
|
||||
</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,10 @@ import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Modal from '../ui/Modal';
|
||||
import Modal from './Modal';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import UserRules from './UserRules';
|
||||
import './Filters.css';
|
||||
|
||||
class Filters extends Component {
|
||||
componentDidMount() {
|
||||
@@ -33,6 +32,13 @@ class Filters extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
handleDelete = (url) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('filter_confirm_delete'))) {
|
||||
this.props.removeFilter({ url });
|
||||
}
|
||||
}
|
||||
|
||||
columns = [{
|
||||
Header: <Trans>enabled_table_header</Trans>,
|
||||
accessor: 'enabled',
|
||||
@@ -59,7 +65,18 @@ class Filters extends Component {
|
||||
}, {
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
Cell: ({ value }) => (<span title={ this.props.t('delete_table_action') } className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>),
|
||||
Cell: ({ value }) => (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => this.handleDelete(value)}
|
||||
title={this.props.t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
),
|
||||
className: 'text-center',
|
||||
width: 80,
|
||||
sortable: false,
|
||||
@@ -68,7 +85,7 @@ class Filters extends Component {
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { filters, userRules } = this.props.filtering;
|
||||
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title={ t('filters') } />
|
||||
@@ -84,12 +101,32 @@ class Filters extends Component {
|
||||
columns={this.columns}
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
minRows={4}
|
||||
// Text
|
||||
previousText={ t('previous_btn') }
|
||||
nextText={ t('next_btn') }
|
||||
loadingText={ t('loading_table_status') }
|
||||
pageText={ t('page_table_footer_text') }
|
||||
ofText={ t('of_table_footer_text') }
|
||||
rowsText={ t('rows_table_footer_text') }
|
||||
noDataText={ t('no_filters_added') }
|
||||
minRows={4} // TODO find out what to show if rules.length is 0
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}><Trans>add_filter_btn</Trans></button>
|
||||
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}><Trans>check_updates_btn</Trans></button>
|
||||
<button
|
||||
className="btn btn-success btn-standard mr-2"
|
||||
type="submit"
|
||||
onClick={this.props.toggleFilteringModal}
|
||||
>
|
||||
<Trans>add_filter_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-standard"
|
||||
type="submit"
|
||||
onClick={this.props.refreshFilters}
|
||||
disabled={processingRefreshFilters}
|
||||
>
|
||||
<Trans>check_updates_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -107,6 +144,7 @@ class Filters extends Component {
|
||||
toggleModal={this.props.toggleFilteringModal}
|
||||
addFilter={this.props.addFilter}
|
||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
||||
processingAddFilter={this.props.filtering.processingAddFilter}
|
||||
title={ t('new_filter_btn') }
|
||||
inputDescription={ t('enter_valid_filter_url') }
|
||||
/>
|
||||
@@ -123,6 +161,8 @@ Filters.propTypes = {
|
||||
filters: PropTypes.array,
|
||||
isFilteringModalOpen: PropTypes.bool.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
processingAddFilter: PropTypes.bool,
|
||||
processingRefreshFilters: PropTypes.bool,
|
||||
}),
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
stroke: #9aa0ac;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active .nav-icon {
|
||||
.nav-tabs .nav-link.active .nav-icon,
|
||||
.nav-tabs .nav-item.show .nav-icon {
|
||||
stroke: #66b574;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover .nav-icon {
|
||||
.nav-tabs .nav-link.active:hover .nav-icon,
|
||||
.nav-tabs .nav-item.show:hover .nav-icon {
|
||||
stroke: #58a273;
|
||||
}
|
||||
|
||||
@@ -73,13 +75,43 @@
|
||||
}
|
||||
|
||||
.nav-version__value {
|
||||
max-width: 110px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.nav-version__value {
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-version__link {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-bottom: 1px dashed #495057;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-version__text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-brand-img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-item.show .nav-link {
|
||||
color: #66b574;
|
||||
background-color: #fff;
|
||||
border-bottom-color: #66b574;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.header {
|
||||
padding: 0;
|
||||
@@ -110,6 +142,10 @@
|
||||
.nav-version {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
@@ -120,6 +156,10 @@
|
||||
.nav-version {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dns-status {
|
||||
|
||||
@@ -4,7 +4,9 @@ import PropTypes from 'prop-types';
|
||||
import enhanceWithClickOutside from 'react-click-outside';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { REPOSITORY } from '../../helpers/constants';
|
||||
|
||||
import { SETTINGS_URLS } from '../../helpers/constants';
|
||||
import Dropdown from '../ui/Dropdown';
|
||||
|
||||
class Menu extends Component {
|
||||
handleClickOutside = () => {
|
||||
@@ -15,51 +17,88 @@ class Menu extends Component {
|
||||
this.props.toggleMenuOpen();
|
||||
};
|
||||
|
||||
getActiveClassForSettings = () => {
|
||||
const { pathname } = this.props.location;
|
||||
const isSettingsPage = SETTINGS_URLS.some(item => item === pathname);
|
||||
|
||||
return isSettingsPage ? 'active' : '';
|
||||
};
|
||||
|
||||
render() {
|
||||
const menuClass = classnames({
|
||||
'col-lg-6 mobile-menu': true,
|
||||
'mobile-menu--active': this.props.isMenuOpen,
|
||||
});
|
||||
|
||||
const dropdownControlClass = `nav-link ${this.getActiveClassForSettings()}`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={menuClass}>
|
||||
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
|
||||
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
|
||||
<div className="nav-link nav-link--back">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg>
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#back" />
|
||||
</svg>
|
||||
<Trans>back</Trans>
|
||||
</div>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/" exact={true} className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg>
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#dashboard" />
|
||||
</svg>
|
||||
<Trans>dashboard</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/settings" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg>
|
||||
<Trans>settings</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
<Dropdown
|
||||
label={this.props.t('settings')}
|
||||
baseClassName="dropdown nav-item"
|
||||
controlClassName={dropdownControlClass}
|
||||
icon="settings"
|
||||
>
|
||||
<Fragment>
|
||||
<NavLink to="/settings" className="dropdown-item">
|
||||
<Trans>general_settings</Trans>
|
||||
</NavLink>
|
||||
<NavLink to="/dns" className="dropdown-item">
|
||||
<Trans>dns_settings</Trans>
|
||||
</NavLink>
|
||||
<NavLink to="/encryption" className="dropdown-item">
|
||||
<Trans>encryption_settings</Trans>
|
||||
</NavLink>
|
||||
<NavLink to="/clients" className="dropdown-item">
|
||||
<Trans>client_settings</Trans>
|
||||
</NavLink>
|
||||
<NavLink to="/dhcp" className="dropdown-item">
|
||||
<Trans>dhcp_settings</Trans>
|
||||
</NavLink>
|
||||
</Fragment>
|
||||
</Dropdown>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/filters" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg>
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#filters" />
|
||||
</svg>
|
||||
<Trans>filters</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/logs" className="nav-link">
|
||||
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg>
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#log" />
|
||||
</svg>
|
||||
<Trans>query_log</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
|
||||
<svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
||||
<Trans>faq</Trans>
|
||||
</a>
|
||||
<NavLink to="/guide" className="nav-link">
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#setup" />
|
||||
</svg>
|
||||
<Trans>setup_guide</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -72,6 +111,8 @@ Menu.propTypes = {
|
||||
isMenuOpen: PropTypes.bool,
|
||||
closeMenu: PropTypes.func,
|
||||
toggleMenuOpen: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(enhanceWithClickOutside(Menu));
|
||||
|
||||
@@ -2,24 +2,48 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
function Version(props) {
|
||||
const { dnsVersion, dnsAddress, dnsPort } = props;
|
||||
const Version = (props) => {
|
||||
const {
|
||||
dnsVersion, dnsAddresses, processingVersion, t,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="nav-version">
|
||||
<div className="nav-version__text">
|
||||
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
|
||||
<Trans>version</Trans>: <span className="nav-version__value" title={dnsVersion}>{dnsVersion}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
|
||||
onClick={() => props.getVersion(true)}
|
||||
disabled={processingVersion}
|
||||
title={t('check_updates_now')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="nav-version__text">
|
||||
<Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
|
||||
<div className="nav-version__link">
|
||||
<div className="popover__trigger popover__trigger--address">
|
||||
<Trans>dns_addresses</Trans>
|
||||
</div>
|
||||
<div className="popover__body popover__body--address">
|
||||
<div className="popover__list">
|
||||
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Version.propTypes = {
|
||||
dnsVersion: PropTypes.string,
|
||||
dnsAddress: PropTypes.string,
|
||||
dnsPort: PropTypes.number,
|
||||
dnsVersion: PropTypes.string.isRequired,
|
||||
dnsAddresses: PropTypes.array.isRequired,
|
||||
dnsPort: PropTypes.number.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
processingVersion: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Version);
|
||||
|
||||
@@ -6,13 +6,12 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Menu from './Menu';
|
||||
import Version from './Version';
|
||||
import logo from './logo.svg';
|
||||
import logo from '../ui/svg/logo.svg';
|
||||
import './Header.css';
|
||||
|
||||
class Header extends Component {
|
||||
state = {
|
||||
isMenuOpen: false,
|
||||
isDropdownOpen: false,
|
||||
};
|
||||
|
||||
toggleMenuOpen = () => {
|
||||
@@ -24,7 +23,8 @@ class Header extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, getVersion, location } = this.props;
|
||||
const { isMenuOpen } = this.state;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
'badge-success': dashboard.protectionEnabled,
|
||||
@@ -51,16 +51,19 @@ class Header extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
location={this.props.location}
|
||||
isMenuOpen={this.state.isMenuOpen}
|
||||
location={location}
|
||||
isMenuOpen={isMenuOpen}
|
||||
toggleMenuOpen={this.toggleMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...this.props.dashboard }
|
||||
/>
|
||||
</div>
|
||||
{!dashboard.processing &&
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...dashboard }
|
||||
getVersion={getVersion}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,8 +72,9 @@ class Header extends Component {
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
dashboard: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Header);
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.logs__row--center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logs__row--overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { formatTime } from '../../helpers/helpers';
|
||||
import { formatTime, getClientName } from '../../helpers/helpers';
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
@@ -21,6 +21,7 @@ class Logs extends Component {
|
||||
componentDidMount() {
|
||||
this.getLogs();
|
||||
this.props.getFilteringStatus();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -77,6 +78,7 @@ class Logs extends Component {
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonClass}`}
|
||||
onClick={() => this.toggleBlocking(buttonType, domain)}
|
||||
disabled={this.props.filtering.processingRules}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
@@ -85,7 +87,7 @@ class Logs extends Component {
|
||||
}
|
||||
|
||||
renderLogs(logs) {
|
||||
const { t } = this.props;
|
||||
const { t, dashboard } = this.props;
|
||||
const columns = [{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
@@ -195,11 +197,20 @@ class Logs extends Component {
|
||||
Cell: (row) => {
|
||||
const { reason } = row.original;
|
||||
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
|
||||
const clientName = getClientName(dashboard.clients, row.value)
|
||||
|| getClientName(dashboard.autoClients, row.value);
|
||||
let client;
|
||||
|
||||
if (clientName) {
|
||||
client = <span>{clientName} <small>({row.value})</small></span>;
|
||||
} else {
|
||||
client = row.value;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{row.value}
|
||||
{client}
|
||||
</div>
|
||||
{this.renderBlockingButton(isFiltered, row.original.domain)}
|
||||
</Fragment>
|
||||
@@ -269,7 +280,7 @@ class Logs extends Component {
|
||||
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
|
||||
};
|
||||
|
||||
renderButtons(queryLogEnabled) {
|
||||
renderButtons(queryLogEnabled, logStatusProcessing) {
|
||||
if (queryLogEnabled) {
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -277,6 +288,7 @@ class Logs extends Component {
|
||||
className="btn btn-gray btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>disabled_log_btn</Trans></button>
|
||||
<button
|
||||
className="btn btn-primary btn-sm mr-2"
|
||||
@@ -297,6 +309,7 @@ class Logs extends Component {
|
||||
className="btn btn-success btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>enabled_log_btn</Trans></button>
|
||||
);
|
||||
}
|
||||
@@ -308,13 +321,22 @@ class Logs extends Component {
|
||||
<Fragment>
|
||||
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
|
||||
<div className="page-title__actions">
|
||||
{this.renderButtons(queryLogEnabled)}
|
||||
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
|
||||
</div>
|
||||
</PageTitle>
|
||||
<Card>
|
||||
{queryLogEnabled && queryLogs.getLogsProcessing && <Loading />}
|
||||
{queryLogEnabled && !queryLogs.getLogsProcessing &&
|
||||
this.renderLogs(queryLogs.logs)}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& queryLogs.getLogsProcessing
|
||||
&& dashboard.processingClients
|
||||
&& <Loading />
|
||||
}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& !queryLogs.getLogsProcessing
|
||||
&& !dashboard.processingClients
|
||||
&& this.renderLogs(queryLogs.logs)
|
||||
}
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -332,7 +354,10 @@ Logs.propTypes = {
|
||||
userRules: PropTypes.string,
|
||||
setRules: PropTypes.func,
|
||||
addSuccessToast: PropTypes.func,
|
||||
processingRules: PropTypes.bool,
|
||||
logStatusProcessing: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Logs);
|
||||
|
||||
118
client/src/components/Settings/Clients/AutoClients.js
Normal file
118
client/src/components/Settings/Clients/AutoClients.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { CLIENT_ID } from '../../../helpers/constants';
|
||||
import Card from '../../ui/Card';
|
||||
|
||||
class AutoClients extends Component {
|
||||
getClient = (name, clients) => {
|
||||
const client = clients.find(item => name === item.name);
|
||||
|
||||
if (client) {
|
||||
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
|
||||
|
||||
return {
|
||||
identifier,
|
||||
use_global_settings: true,
|
||||
...client,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: 'ip',
|
||||
use_global_settings: true,
|
||||
};
|
||||
};
|
||||
|
||||
getStats = (ip, stats) => {
|
||||
if (stats && stats.top_clients) {
|
||||
return stats.top_clients[ip];
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
columns = [
|
||||
{
|
||||
Header: this.props.t('table_client'),
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_name'),
|
||||
accessor: 'name',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('source_label'),
|
||||
accessor: 'source',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_statistics'),
|
||||
accessor: 'statistics',
|
||||
Cell: (row) => {
|
||||
const clientIP = row.original.ip;
|
||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
|
||||
|
||||
if (clientStats) {
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<div className="logs__text" title={clientStats}>
|
||||
{clientStats}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return '–';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const { t, autoClients } = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('auto_clients_title')}
|
||||
subtitle={t('auto_clients_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<ReactTable
|
||||
data={autoClients || []}
|
||||
columns={this.columns}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
minRows={5}
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('clients_not_found')}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AutoClients.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
topStats: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(AutoClients);
|
||||
260
client/src/components/Settings/Clients/ClientsTable.js
Normal file
260
client/src/components/Settings/Clients/ClientsTable.js
Normal file
@@ -0,0 +1,260 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import ReactTable from 'react-table';
|
||||
|
||||
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
|
||||
import Card from '../../ui/Card';
|
||||
import Modal from './Modal';
|
||||
|
||||
class ClientsTable extends Component {
|
||||
handleFormAdd = (values) => {
|
||||
this.props.addClient(values);
|
||||
};
|
||||
|
||||
handleFormUpdate = (values, name) => {
|
||||
this.props.updateClient(values, name);
|
||||
};
|
||||
|
||||
handleSubmit = (values) => {
|
||||
if (this.props.modalType === MODAL_TYPE.EDIT) {
|
||||
this.handleFormUpdate(values, this.props.modalClientName);
|
||||
} else {
|
||||
this.handleFormAdd(values);
|
||||
}
|
||||
};
|
||||
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
getClient = (name, clients) => {
|
||||
const client = clients.find(item => name === item.name);
|
||||
|
||||
if (client) {
|
||||
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
|
||||
|
||||
return {
|
||||
identifier,
|
||||
use_global_settings: true,
|
||||
...client,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: CLIENT_ID.IP,
|
||||
use_global_settings: true,
|
||||
};
|
||||
};
|
||||
|
||||
getStats = (ip, stats) => {
|
||||
if (stats && stats.top_clients) {
|
||||
return stats.top_clients[ip];
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
handleDelete = (data) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
|
||||
this.props.deleteClient(data);
|
||||
}
|
||||
};
|
||||
|
||||
columns = [
|
||||
{
|
||||
Header: this.props.t('table_client'),
|
||||
accessor: 'ip',
|
||||
Cell: (row) => {
|
||||
if (row.original && row.original.mac) {
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={row.original.mac}>
|
||||
{row.original.mac} <em>(MAC)</em>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else if (row.value) {
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={row.value}>
|
||||
{row.value} <em>(IP)</em>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_name'),
|
||||
accessor: 'name',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('settings'),
|
||||
accessor: 'use_global_settings',
|
||||
Cell: ({ value }) => {
|
||||
const title = value ? (
|
||||
<Trans>settings_global</Trans>
|
||||
) : (
|
||||
<Trans>settings_custom</Trans>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<div className="logs__text" title={title}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_statistics'),
|
||||
accessor: 'statistics',
|
||||
Cell: (row) => {
|
||||
const clientIP = row.original.ip;
|
||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
|
||||
|
||||
if (clientStats) {
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<div className="logs__text" title={clientStats}>
|
||||
{clientStats}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return '–';
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: this.props.t('actions_table_header'),
|
||||
accessor: 'actions',
|
||||
maxWidth: 150,
|
||||
Cell: (row) => {
|
||||
const clientName = row.original.name;
|
||||
const {
|
||||
toggleClientModal, processingDeleting, processingUpdating, t,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm mr-2"
|
||||
onClick={() =>
|
||||
toggleClientModal({
|
||||
type: MODAL_TYPE.EDIT,
|
||||
name: clientName,
|
||||
})
|
||||
}
|
||||
disabled={processingUpdating}
|
||||
title={t('edit_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#edit" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() => this.handleDelete({ name: clientName })}
|
||||
disabled={processingDeleting}
|
||||
title={t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
clients,
|
||||
isModalOpen,
|
||||
modalType,
|
||||
modalClientName,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
} = this.props;
|
||||
|
||||
const currentClientData = this.getClient(modalClientName, clients);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('clients_title')}
|
||||
subtitle={t('clients_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Fragment>
|
||||
<ReactTable
|
||||
data={clients || []}
|
||||
columns={this.columns}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
minRows={5}
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('clients_not_found')}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleClientModal(MODAL_TYPE.ADD)}
|
||||
disabled={processingAdding}
|
||||
>
|
||||
<Trans>client_add</Trans>
|
||||
</button>
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
modalType={modalType}
|
||||
toggleClientModal={toggleClientModal}
|
||||
currentClientData={currentClientData}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
processingUpdating={processingUpdating}
|
||||
/>
|
||||
</Fragment>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ClientsTable.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
clients: PropTypes.array.isRequired,
|
||||
topStats: PropTypes.object.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
deleteClient: PropTypes.func.isRequired,
|
||||
addClient: PropTypes.func.isRequired,
|
||||
updateClient: PropTypes.func.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
modalClientName: PropTypes.string.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingDeleting: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(ClientsTable);
|
||||
217
client/src/components/Settings/Clients/Form.js
Normal file
217
client/src/components/Settings/Clients/Form.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { renderField, renderSelectField, ipv4, mac, required } from '../../../helpers/form';
|
||||
import { CLIENT_ID } from '../../../helpers/constants';
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
reset,
|
||||
pristine,
|
||||
submitting,
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form__group">
|
||||
<div className="form-inline mb-3">
|
||||
<strong className="mr-3">
|
||||
<Trans>client_identifier</Trans>
|
||||
</strong>
|
||||
<label className="mr-3">
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="ip"
|
||||
/>{' '}
|
||||
<Trans>ip_address</Trans>
|
||||
</label>
|
||||
<label>
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="mac"
|
||||
/>{' '}
|
||||
MAC
|
||||
</label>
|
||||
</div>
|
||||
{clientIdentifier === CLIENT_ID.IP && (
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="ip"
|
||||
name="ip"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{clientIdentifier === CLIENT_ID.MAC && (
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="mac"
|
||||
name="mac"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_mac')}
|
||||
validate={[mac, required]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="form__desc">
|
||||
<Trans
|
||||
components={[
|
||||
<a href="#dhcp" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
client_identifier_desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="name"
|
||||
name="name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_client_name')}
|
||||
validate={[required]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<strong>
|
||||
<Trans>settings</Trans>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="use_global_settings"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('client_global_settings')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="filtering_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('block_domain_use_filters_and_hosts')}
|
||||
disabled={useGlobalSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="safebrowsing_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('use_adguard_browsing_sec')}
|
||||
disabled={useGlobalSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="parental_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('use_adguard_parental')}
|
||||
disabled={useGlobalSettings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="safesearch_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('enforce_safe_search')}
|
||||
disabled={useGlobalSettings}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standard"
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
reset();
|
||||
toggleClientModal();
|
||||
}}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || pristine || processingAdding || processingUpdating}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
clientIdentifier: PropTypes.string,
|
||||
useGlobalSettings: PropTypes.bool,
|
||||
t: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('clientForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const clientIdentifier = selector(state, 'identifier');
|
||||
const useGlobalSettings = selector(state, 'use_global_settings');
|
||||
return {
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'clientForm',
|
||||
enableReinitialize: true,
|
||||
}),
|
||||
])(Form);
|
||||
64
client/src/components/Settings/Clients/Modal.js
Normal file
64
client/src/components/Settings/Clients/Modal.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
|
||||
const Modal = (props) => {
|
||||
const {
|
||||
isModalOpen,
|
||||
modalType,
|
||||
currentClientData,
|
||||
handleSubmit,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => toggleClientModal()}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{modalType === MODAL_TYPE.EDIT ? (
|
||||
<Trans>client_edit</Trans>
|
||||
) : (
|
||||
<Trans>client_new</Trans>
|
||||
)}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleClientModal()}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
initialValues={{
|
||||
...currentClientData,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
toggleClientModal={toggleClientModal}
|
||||
processingAdding={processingAdding}
|
||||
processingUpdating={processingUpdating}
|
||||
/>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
modalType: PropTypes.string.isRequired,
|
||||
currentClientData: PropTypes.object.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Modal);
|
||||
71
client/src/components/Settings/Clients/index.js
Normal file
71
client/src/components/Settings/Clients/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ClientsTable from './ClientsTable';
|
||||
import AutoClients from './AutoClients';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
class Clients extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getClients();
|
||||
this.props.getTopStats();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
dashboard,
|
||||
clients,
|
||||
addClient,
|
||||
updateClient,
|
||||
deleteClient,
|
||||
toggleClientModal,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('client_settings')} />
|
||||
{(dashboard.processingTopStats || dashboard.processingClients) && <Loading />}
|
||||
{!dashboard.processingTopStats && !dashboard.processingClients && (
|
||||
<Fragment>
|
||||
<ClientsTable
|
||||
clients={dashboard.clients}
|
||||
topStats={dashboard.topStats}
|
||||
isModalOpen={clients.isModalOpen}
|
||||
modalClientName={clients.modalClientName}
|
||||
modalType={clients.modalType}
|
||||
addClient={addClient}
|
||||
updateClient={updateClient}
|
||||
deleteClient={deleteClient}
|
||||
toggleClientModal={toggleClientModal}
|
||||
processingAdding={clients.processingAdding}
|
||||
processingDeleting={clients.processingDeleting}
|
||||
processingUpdating={clients.processingUpdating}
|
||||
/>
|
||||
<AutoClients
|
||||
autoClients={dashboard.autoClients}
|
||||
topStats={dashboard.topStats}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Clients.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
clients: PropTypes.object.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
deleteClient: PropTypes.func.isRequired,
|
||||
addClient: PropTypes.func.isRequired,
|
||||
updateClient: PropTypes.func.isRequired,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
getTopStats: PropTypes.func.isRequired,
|
||||
topStats: PropTypes.object,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Clients);
|
||||
@@ -1,62 +1,100 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { R_IPV4 } from '../../../helpers/constants';
|
||||
import { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
const renderInterfaces = (interfaces => (
|
||||
Object.keys(interfaces).map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
|
||||
let interfaceIP = option.ip_addresses[0];
|
||||
|
||||
const ipv4 = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value)) {
|
||||
return <Trans>form_error_ip_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!onlyIPv6) {
|
||||
option.ip_addresses.forEach((ip) => {
|
||||
if (!ip.includes(':')) {
|
||||
interfaceIP = ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const isPositive = (value) => {
|
||||
if ((value || value === 0) && (value <= 0)) {
|
||||
return <Trans>form_error_positive</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return (
|
||||
<option value={name} key={name} disabled={onlyIPv6}>
|
||||
{name} - {interfaceIP}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
));
|
||||
|
||||
const toNumber = value => value && parseInt(value, 10);
|
||||
const renderInterfaceValues = (interfaceValues => (
|
||||
<ul className="list-unstyled mt-1 mb-0">
|
||||
<li>
|
||||
<span className="interface__title">MTU: </span>
|
||||
{interfaceValues.mtu}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
|
||||
{interfaceValues.hardware_address}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
|
||||
{
|
||||
interfaceValues.ip_addresses
|
||||
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
));
|
||||
|
||||
const renderField = ({
|
||||
input, className, placeholder, type, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<input
|
||||
{...input}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const Form = (props) => {
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
invalid,
|
||||
enabled,
|
||||
interfaces,
|
||||
interfaceValue,
|
||||
processingConfig,
|
||||
processingInterfaces,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{!processingInterfaces && interfaces &&
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_interface_select')}</label>
|
||||
<Field
|
||||
name="interface_name"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
validate={[required]}
|
||||
>
|
||||
<option value="" disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
{interfaceValue &&
|
||||
<div className="col-sm-12 col-md-6">
|
||||
{interfaces[interfaceValue] &&
|
||||
renderInterfaceValues(interfaces[interfaceValue])}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
name="gateway_ip"
|
||||
@@ -67,7 +105,7 @@ const Form = (props) => {
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
name="subnet_mask"
|
||||
@@ -80,7 +118,7 @@ const Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
@@ -107,7 +145,7 @@ const Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="lease_duration"
|
||||
@@ -124,8 +162,8 @@ const Form = (props) => {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standart"
|
||||
disabled={pristine || submitting}
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
@@ -135,14 +173,27 @@ const Form = (props) => {
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
pristine: PropTypes.bool,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
interfaces: PropTypes.object,
|
||||
processing: PropTypes.bool,
|
||||
interfaceValue: PropTypes.string,
|
||||
initialValues: PropTypes.object,
|
||||
processingConfig: PropTypes.bool,
|
||||
processingInterfaces: PropTypes.bool,
|
||||
enabled: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
const selector = formValueSelector('dhcpForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const interfaceValue = selector(state, 'interface_name');
|
||||
return {
|
||||
interfaceValue,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'dhcpForm' }),
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
const renderInterfaces = (interfaces => (
|
||||
Object.keys(interfaces).map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
|
||||
let interfaceIP = option.ip_addresses[0];
|
||||
|
||||
if (!onlyIPv6) {
|
||||
option.ip_addresses.forEach((ip) => {
|
||||
if (!ip.includes(':')) {
|
||||
interfaceIP = ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<option value={name} key={name} disabled={onlyIPv6}>
|
||||
{name} - {interfaceIP}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
));
|
||||
|
||||
const renderInterfaceValues = (interfaceValues => (
|
||||
<ul className="list-unstyled mt-1 mb-0">
|
||||
<li>
|
||||
<span className="interface__title">MTU: </span>
|
||||
{interfaceValues.mtu}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
|
||||
{interfaceValues.hardware_address}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
|
||||
{
|
||||
interfaceValues.ip_addresses
|
||||
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
));
|
||||
|
||||
let Interface = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleChange,
|
||||
interfaces,
|
||||
processing,
|
||||
interfaceValue,
|
||||
enabled,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form>
|
||||
{!processing && interfaces &&
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<label>{t('dhcp_interface_select')}</label>
|
||||
<Field
|
||||
name="interface_name"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="" disabled={enabled}>{t('dhcp_interface_select')}</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
{interfaceValue &&
|
||||
<div className="col-sm-12 col-md-6">
|
||||
{renderInterfaceValues(interfaces[interfaceValue])}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Interface.propTypes = {
|
||||
handleChange: PropTypes.func,
|
||||
interfaces: PropTypes.object,
|
||||
processing: PropTypes.bool,
|
||||
interfaceValue: PropTypes.string,
|
||||
initialValues: PropTypes.object,
|
||||
enabled: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('dhcpInterface');
|
||||
|
||||
Interface = connect((state) => {
|
||||
const interfaceValue = selector(state, 'interface_name');
|
||||
return {
|
||||
interfaceValue,
|
||||
};
|
||||
})(Interface);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'dhcpInterface' }),
|
||||
])(Interface);
|
||||
@@ -1,32 +1,49 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
const columns = [{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
}, {
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Hostname',
|
||||
accessor: 'hostname',
|
||||
}, {
|
||||
Header: 'Expires',
|
||||
accessor: 'expires',
|
||||
}];
|
||||
class Leases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Leases = props => (
|
||||
<ReactTable
|
||||
data={props.leases || []}
|
||||
columns={columns}
|
||||
showPagination={false}
|
||||
noDataText={ props.t('dhcp_leases_not_found') }
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
);
|
||||
render() {
|
||||
const { leases, t } = this.props;
|
||||
return (
|
||||
<ReactTable
|
||||
data={leases || []}
|
||||
columns={[
|
||||
{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
Cell: this.cellWrap,
|
||||
}, {
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
}, {
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
Cell: this.cellWrap,
|
||||
}, {
|
||||
Header: <Trans>dhcp_table_expires</Trans>,
|
||||
accessor: 'expires',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
noDataText={t('dhcp_leases_not_found')}
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Leases.propTypes = {
|
||||
leases: PropTypes.array,
|
||||
|
||||
96
client/src/components/Settings/Dhcp/StaticLeases/Form.js
Normal file
96
client/src/components/Settings/Dhcp/StaticLeases/Form.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { renderField, ipv4, mac, required } from '../../../../helpers/form';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
reset,
|
||||
pristine,
|
||||
submitting,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="mac"
|
||||
name="mac"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_mac')}
|
||||
validate={[required, mac]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="ip"
|
||||
name="ip"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[required, ipv4]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="hostname"
|
||||
name="hostname"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_hostname')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standard"
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
reset();
|
||||
toggleLeaseModal();
|
||||
}}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || pristine || processingAdding}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'leaseForm' }),
|
||||
])(Form);
|
||||
49
client/src/components/Settings/Dhcp/StaticLeases/Modal.js
Normal file
49
client/src/components/Settings/Dhcp/StaticLeases/Modal.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import Form from './Form';
|
||||
|
||||
const Modal = (props) => {
|
||||
const {
|
||||
isModalOpen,
|
||||
handleSubmit,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => toggleLeaseModal()}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
<Trans>dhcp_new_static_lease</Trans>
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleLeaseModal()}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={processingAdding}
|
||||
/>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Modal);
|
||||
112
client/src/components/Settings/Dhcp/StaticLeases/index.js
Normal file
112
client/src/components/Settings/Dhcp/StaticLeases/index.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Modal from './Modal';
|
||||
|
||||
class StaticLeases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
handleSubmit = (data) => {
|
||||
this.props.addStaticLease(data);
|
||||
}
|
||||
|
||||
handleDelete = (ip, mac, hostname = '') => {
|
||||
const name = hostname || ip;
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('delete_confirm', { key: name }))) {
|
||||
this.props.removeStaticLease({ ip, mac, hostname });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isModalOpen,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
staticLeases,
|
||||
t,
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<ReactTable
|
||||
data={staticLeases || []}
|
||||
columns={[
|
||||
{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'actions',
|
||||
maxWidth: 150,
|
||||
Cell: (row) => {
|
||||
const { ip, mac, hostname } = row.original;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
title={t('delete_table_action')}
|
||||
disabled={processingDeleting}
|
||||
onClick={() =>
|
||||
this.handleDelete(ip, mac, hostname)
|
||||
}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
noDataText={t('dhcp_static_leases_not_found')}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
minRows={6}
|
||||
/>
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StaticLeases.propTypes = {
|
||||
staticLeases: PropTypes.array.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
removeStaticLease: PropTypes.func.isRequired,
|
||||
addStaticLease: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingDeleting: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(StaticLeases);
|
||||
@@ -3,30 +3,39 @@ import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
import Leases from './Leases';
|
||||
import Interface from './Interface';
|
||||
import StaticLeases from './StaticLeases/index';
|
||||
import Card from '../../ui/Card';
|
||||
import Accordion from '../../ui/Accordion';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
class Dhcp extends Component {
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setDhcpConfig(values);
|
||||
};
|
||||
|
||||
handleFormChange = (value) => {
|
||||
this.props.setDhcpConfig(value);
|
||||
componentDidMount() {
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
if (values.interface_name) {
|
||||
this.props.setDhcpConfig(values);
|
||||
}
|
||||
};
|
||||
|
||||
handleToggle = (config) => {
|
||||
this.props.toggleDhcp(config);
|
||||
this.props.findActiveDhcp(config.interface_name);
|
||||
}
|
||||
};
|
||||
|
||||
getToggleDhcpButton = () => {
|
||||
const { config, active } = this.props.dhcp;
|
||||
const activeDhcpFound = active && active.found;
|
||||
const {
|
||||
config, check, processingDhcp, processingConfig,
|
||||
} = this.props.dhcp;
|
||||
const otherDhcpFound =
|
||||
check && check.otherServer && check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
|
||||
const filledConfig = Object.keys(config).every((key) => {
|
||||
if (key === 'enabled') {
|
||||
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,8 +46,9 @@ class Dhcp extends Component {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-gray"
|
||||
className="btn btn-standard mr-2 btn-gray"
|
||||
onClick={() => this.props.toggleDhcp(config)}
|
||||
disabled={processingDhcp || processingConfig}
|
||||
>
|
||||
<Trans>dhcp_disable</Trans>
|
||||
</button>
|
||||
@@ -48,99 +58,202 @@ class Dhcp extends Component {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-success"
|
||||
className="btn btn-standard mr-2 btn-success"
|
||||
onClick={() => this.handleToggle(config)}
|
||||
disabled={!filledConfig || activeDhcpFound}
|
||||
disabled={
|
||||
!filledConfig || !check || otherDhcpFound || processingDhcp || processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>dhcp_enable</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getActiveDhcpMessage = () => {
|
||||
const { active } = this.props.dhcp;
|
||||
getActiveDhcpMessage = (t, check) => {
|
||||
const { found } = check.otherServer;
|
||||
|
||||
if (active) {
|
||||
if (active.error) {
|
||||
return (
|
||||
<div className="text-danger">
|
||||
{active.error}
|
||||
if (found === DHCP_STATUS_RESPONSE.ERROR) {
|
||||
return (
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.otherServer.error}</span>
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
{found === DHCP_STATUS_RESPONSE.YES ? (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_found</Trans>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-secondary">
|
||||
<Trans>dhcp_not_found</Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getDhcpWarning = (check) => {
|
||||
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_warning</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getStaticIpWarning = (t, check, interfaceName) => {
|
||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
||||
return (
|
||||
<Fragment>
|
||||
{active.found ? (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_found</Trans>
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_static_ip_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.staticIP.error}</span>
|
||||
</Accordion>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-secondary">
|
||||
<Trans>dhcp_not_found</Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
} else if (
|
||||
check.staticIP.static === DHCP_STATUS_RESPONSE.NO &&
|
||||
check.staticIP.ip &&
|
||||
interfaceName
|
||||
) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-secondary mb-2">
|
||||
<Trans
|
||||
components={[<strong key="0">example</strong>]}
|
||||
values={{
|
||||
interfaceName,
|
||||
ipAddress: check.staticIP.ip,
|
||||
}}
|
||||
>
|
||||
dhcp_dynamic_ip_found
|
||||
</Trans>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, dhcp } = this.props;
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standart': true,
|
||||
'btn btn-primary btn-standart btn-loading': dhcp.processingStatus,
|
||||
'btn btn-primary btn-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
const { enabled, interface_name, ...values } = dhcp.config;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings">
|
||||
<div className="dhcp">
|
||||
{!dhcp.processing &&
|
||||
<Fragment>
|
||||
<Interface
|
||||
onChange={this.handleFormChange}
|
||||
initialValues={dhcp.config}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
enabled={dhcp.config.enabled}
|
||||
/>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={dhcp.config}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
/>
|
||||
<hr/>
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() =>
|
||||
this.props.findActiveDhcp(dhcp.config.interface_name)
|
||||
}
|
||||
disabled={!dhcp.config.interface_name}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{this.getActiveDhcpMessage()}
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Card>
|
||||
{!dhcp.processing && dhcp.config.enabled &&
|
||||
<Card title={ t('dhcp_leases') } bodyType="card-body box-body--settings">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
<PageTitle title={t('dhcp_settings')} />
|
||||
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
||||
{!dhcp.processing && !dhcp.processingInterfaces && (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={t('dhcp_title')}
|
||||
subtitle={t('dhcp_description')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="dhcp">
|
||||
<Fragment>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={{
|
||||
interface_name,
|
||||
...values,
|
||||
}}
|
||||
interfaces={dhcp.interfaces}
|
||||
processingConfig={dhcp.processingConfig}
|
||||
processingInterfaces={dhcp.processingInterfaces}
|
||||
enabled={enabled}
|
||||
/>
|
||||
<hr />
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() =>
|
||||
this.props.findActiveDhcp(interface_name)
|
||||
}
|
||||
disabled={
|
||||
enabled || !interface_name || dhcp.processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{!enabled && dhcp.check && (
|
||||
<Fragment>
|
||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||
{this.getDhcpWarning(dhcp.check)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
}
|
||||
</Card>
|
||||
{dhcp.config.enabled && (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={t('dhcp_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('dhcp_static_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<StaticLeases
|
||||
staticLeases={dhcp.staticLeases}
|
||||
isModalOpen={dhcp.isModalOpen}
|
||||
addStaticLease={this.props.addStaticLease}
|
||||
removeStaticLease={this.props.removeStaticLease}
|
||||
toggleLeaseModal={this.props.toggleLeaseModal}
|
||||
processingAdding={dhcp.processingAdding}
|
||||
processingDeleting={dhcp.processingDeleting}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => this.props.toggleLeaseModal()}
|
||||
>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -152,7 +265,10 @@ Dhcp.propTypes = {
|
||||
getDhcpStatus: PropTypes.func,
|
||||
setDhcpConfig: PropTypes.func,
|
||||
findActiveDhcp: PropTypes.func,
|
||||
handleSubmit: PropTypes.func,
|
||||
addStaticLease: PropTypes.func,
|
||||
removeStaticLease: PropTypes.func,
|
||||
toggleLeaseModal: PropTypes.func,
|
||||
getDhcpInterfaces: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
80
client/src/components/Settings/Dns/Access/Form.js
Normal file
80
client/src/components/Settings/Dns/Access/Form.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
const Form = (props) => {
|
||||
const { handleSubmit, submitting, invalid } = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form__group mb-5">
|
||||
<label className="form__label form__label--with-desc" htmlFor="allowed_clients">
|
||||
<Trans>access_allowed_title</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>access_allowed_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="allowed_clients"
|
||||
name="allowed_clients"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group mb-5">
|
||||
<label className="form__label form__label--with-desc" htmlFor="disallowed_clients">
|
||||
<Trans>access_disallowed_title</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>access_disallowed_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="disallowed_clients"
|
||||
name="disallowed_clients"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group mb-5">
|
||||
<label className="form__label form__label--with-desc" htmlFor="blocked_hosts">
|
||||
<Trans>access_blocked_title</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>access_blocked_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="blocked_hosts"
|
||||
name="blocked_hosts"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
/>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid}
|
||||
>
|
||||
<Trans>save_config</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
initialValues: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default flow([withNamespaces(), reduxForm({ form: 'accessForm' })])(Form);
|
||||
43
client/src/components/Settings/Dns/Access/index.js
Normal file
43
client/src/components/Settings/Dns/Access/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
|
||||
class Access extends Component {
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setAccessList(values);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, access } = this.props;
|
||||
|
||||
const {
|
||||
processing,
|
||||
processingSet,
|
||||
...values
|
||||
} = access;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('access_title')}
|
||||
subtitle={t('access_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={values}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Access.propTypes = {
|
||||
access: PropTypes.object.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Access);
|
||||
131
client/src/components/Settings/Dns/Upstream/Examples.js
Normal file
131
client/src/components/Settings/Dns/Upstream/Examples.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
const Examples = props => (
|
||||
<div className="list leading-loose">
|
||||
<p>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://kb.adguard.com/general/dns-providers"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
DNS providers
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
dns_providers
|
||||
</Trans>
|
||||
</p>
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>1.1.1.1</code> - {props.t('example_upstream_regular')}
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/DNS_over_TLS"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
DNS-over-TLS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_dot
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>https://cloudflare-dns.com/dns-query</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
DNS-over-HTTPS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_doh
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://1.1.1.1</code> – <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://dnscrypt.info/stamps/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
DNS Stamps
|
||||
</a>,
|
||||
<a
|
||||
href="https://dnscrypt.info/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="1"
|
||||
>
|
||||
DNSCrypt
|
||||
</a>,
|
||||
<a
|
||||
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="2"
|
||||
>
|
||||
DNS-over-HTTPS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_sdns
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>[/example.local/]1.1.1.1</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
Link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_reserved
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
|
||||
Examples.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Examples);
|
||||
145
client/src/components/Settings/Dns/Upstream/Form.js
Normal file
145
client/src/components/Settings/Dns/Upstream/Form.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { renderSelectField } from '../../../../helpers/form';
|
||||
import Examples from './Examples';
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
testUpstream,
|
||||
upstreamDns,
|
||||
bootstrapDns,
|
||||
allServers,
|
||||
submitting,
|
||||
invalid,
|
||||
processingSetUpstream,
|
||||
processingTestUpstream,
|
||||
} = props;
|
||||
|
||||
const testButtonClass = classnames({
|
||||
'btn btn-primary btn-standard mr-2': true,
|
||||
'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="upstream_dns">
|
||||
<Trans>upstream_dns</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="upstream_dns"
|
||||
name="upstream_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('upstream_dns')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="all_servers"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('upstream_parallel')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Examples />
|
||||
<hr />
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group">
|
||||
<label className="form__label form__label--with-desc" htmlFor="bootstrap_dns">
|
||||
<Trans>bootstrap_dns</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>bootstrap_dns_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="bootstrap_dns"
|
||||
name="bootstrap_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('bootstrap_dns')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className={testButtonClass}
|
||||
onClick={() =>
|
||||
testUpstream({
|
||||
upstream_dns: upstreamDns,
|
||||
bootstrap_dns: bootstrapDns,
|
||||
all_servers: allServers,
|
||||
})
|
||||
}
|
||||
disabled={!upstreamDns || processingTestUpstream}
|
||||
>
|
||||
<Trans>test_upstream_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={
|
||||
submitting || invalid || processingSetUpstream || processingTestUpstream
|
||||
}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
testUpstream: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
initialValues: PropTypes.object,
|
||||
upstreamDns: PropTypes.string,
|
||||
bootstrapDns: PropTypes.string,
|
||||
allServers: PropTypes.bool,
|
||||
processingTestUpstream: PropTypes.bool,
|
||||
processingSetUpstream: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('upstreamForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const upstreamDns = selector(state, 'upstream_dns');
|
||||
const bootstrapDns = selector(state, 'bootstrap_dns');
|
||||
const allServers = selector(state, 'all_servers');
|
||||
return {
|
||||
upstreamDns,
|
||||
bootstrapDns,
|
||||
allServers,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'upstreamForm',
|
||||
}),
|
||||
])(Form);
|
||||
64
client/src/components/Settings/Dns/Upstream/index.js
Normal file
64
client/src/components/Settings/Dns/Upstream/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
|
||||
class Upstream extends Component {
|
||||
handleSubmit = (values) => {
|
||||
this.props.setUpstream(values);
|
||||
};
|
||||
|
||||
handleTest = (values) => {
|
||||
this.props.testUpstream(values);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
upstreamDns: upstream_dns,
|
||||
bootstrapDns: bootstrap_dns,
|
||||
allServers: all_servers,
|
||||
processingSetUpstream,
|
||||
processingTestUpstream,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('upstream_dns')}
|
||||
subtitle={t('upstream_dns_hint')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Form
|
||||
initialValues={{
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
all_servers,
|
||||
}}
|
||||
testUpstream={this.handleTest}
|
||||
onSubmit={this.handleSubmit}
|
||||
processingTestUpstream={processingTestUpstream}
|
||||
processingSetUpstream={processingSetUpstream}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstreamDns: PropTypes.string,
|
||||
bootstrapDns: PropTypes.string,
|
||||
allServers: PropTypes.bool,
|
||||
setUpstream: PropTypes.func.isRequired,
|
||||
testUpstream: PropTypes.func.isRequired,
|
||||
processingSetUpstream: PropTypes.bool.isRequired,
|
||||
processingTestUpstream: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Upstream);
|
||||
60
client/src/components/Settings/Dns/index.js
Normal file
60
client/src/components/Settings/Dns/index.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Upstream from './Upstream';
|
||||
import Access from './Access';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
class Dns extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getAccessList();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
dashboard,
|
||||
settings,
|
||||
access,
|
||||
setAccessList,
|
||||
testUpstream,
|
||||
setUpstream,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{(dashboard.processing || access.processing) && <Loading />}
|
||||
{!dashboard.processing && !access.processing && (
|
||||
<Fragment>
|
||||
<Upstream
|
||||
upstreamDns={dashboard.upstreamDns}
|
||||
bootstrapDns={dashboard.bootstrapDns}
|
||||
allServers={dashboard.allServers}
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
processingSetUpstream={settings.processingSetUpstream}
|
||||
setUpstream={setUpstream}
|
||||
testUpstream={testUpstream}
|
||||
/>
|
||||
<Access access={access} setAccessList={setAccessList} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dns.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
settings: PropTypes.object.isRequired,
|
||||
setUpstream: PropTypes.func.isRequired,
|
||||
testUpstream: PropTypes.func.isRequired,
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
access: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Dns);
|
||||
377
client/src/components/Settings/Encryption/Form.js
Normal file
377
client/src/components/Settings/Encryption/Form.js
Normal file
@@ -0,0 +1,377 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import format from 'date-fns/format';
|
||||
|
||||
import { renderField, renderSelectField, toNumber, port, portTLS, isSafePort } from '../../../helpers/form';
|
||||
import { EMPTY_DATE } from '../../../helpers/constants';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
|
||||
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');
|
||||
errors.port_https = i18n.t('form_error_equal');
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
const clearFields = (change, setTlsConfig, t) => {
|
||||
const fields = {
|
||||
private_key: '',
|
||||
certificate_chain: '',
|
||||
port_https: 443,
|
||||
port_dns_over_tls: 853,
|
||||
server_name: '',
|
||||
force_https: false,
|
||||
enabled: false,
|
||||
};
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t('encryption_reset'))) {
|
||||
Object.keys(fields).forEach(field => change(field, fields[field]));
|
||||
setTlsConfig(fields);
|
||||
}
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
isEnabled,
|
||||
certificateChain,
|
||||
privateKey,
|
||||
change,
|
||||
invalid,
|
||||
submitting,
|
||||
processingConfig,
|
||||
processingValidate,
|
||||
not_after,
|
||||
valid_chain,
|
||||
valid_key,
|
||||
valid_cert,
|
||||
valid_pair,
|
||||
dns_names,
|
||||
key_type,
|
||||
issuer,
|
||||
subject,
|
||||
warning_validation,
|
||||
setTlsConfig,
|
||||
} = props;
|
||||
|
||||
const isSavingDisabled =
|
||||
invalid ||
|
||||
submitting ||
|
||||
processingConfig ||
|
||||
processingValidate ||
|
||||
(isEnabled && (!privateKey || !certificateChain)) ||
|
||||
(privateKey && !valid_key) ||
|
||||
(certificateChain && !valid_cert) ||
|
||||
(privateKey && certificateChain && !valid_pair);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('encryption_enable')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_enable_desc</Trans>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form__label" htmlFor="server_name">
|
||||
<Trans>encryption_server</Trans>
|
||||
</label>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
id="server_name"
|
||||
name="server_name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_server_enter')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_server_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="force_https"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('encryption_redirect')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_redirect_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="port_https">
|
||||
<Trans>encryption_https</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="port_https"
|
||||
name="port_https"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_https')}
|
||||
validate={[port, isSafePort]}
|
||||
normalize={toNumber}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_https_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="port_dns_over_tls">
|
||||
<Trans>encryption_dot</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="port_dns_over_tls"
|
||||
name="port_dns_over_tls"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_dot')}
|
||||
validate={[portTLS]}
|
||||
normalize={toNumber}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_dot_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label
|
||||
className="form__label form__label--bold"
|
||||
htmlFor="certificate_chain"
|
||||
>
|
||||
<Trans>encryption_certificates</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans
|
||||
values={{ link: 'letsencrypt.org' }}
|
||||
components={[
|
||||
<a href="https://letsencrypt.org/" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
encryption_certificates_desc
|
||||
</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="certificate_chain"
|
||||
name="certificate_chain"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('encryption_certificates_input')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__status">
|
||||
{certificateChain && (
|
||||
<Fragment>
|
||||
<div className="form__label form__label--bold">
|
||||
<Trans>encryption_status</Trans>:
|
||||
</div>
|
||||
<ul className="encryption__list">
|
||||
<li
|
||||
className={valid_chain ? 'text-success' : 'text-danger'}
|
||||
>
|
||||
{valid_chain ? (
|
||||
<Trans>encryption_chain_valid</Trans>
|
||||
) : (
|
||||
<Trans>encryption_chain_invalid</Trans>
|
||||
)}
|
||||
</li>
|
||||
{valid_cert && (
|
||||
<Fragment>
|
||||
{subject && (
|
||||
<li>
|
||||
<Trans>encryption_subject</Trans>:
|
||||
{subject}
|
||||
</li>
|
||||
)}
|
||||
{issuer && (
|
||||
<li>
|
||||
<Trans>encryption_issuer</Trans>:
|
||||
{issuer}
|
||||
</li>
|
||||
)}
|
||||
{not_after && not_after !== EMPTY_DATE && (
|
||||
<li>
|
||||
<Trans>encryption_expire</Trans>:
|
||||
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
|
||||
</li>
|
||||
)}
|
||||
{dns_names && (
|
||||
<li>
|
||||
<Trans>encryption_hostnames</Trans>:
|
||||
{dns_names}
|
||||
</li>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--bold" htmlFor="private_key">
|
||||
<Trans>encryption_key</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="private_key"
|
||||
name="private_key"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('encryption_key_input')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__status">
|
||||
{privateKey && (
|
||||
<Fragment>
|
||||
<div className="form__label form__label--bold">
|
||||
<Trans>encryption_status</Trans>:
|
||||
</div>
|
||||
<ul className="encryption__list">
|
||||
<li className={valid_key ? 'text-success' : 'text-danger'}>
|
||||
{valid_key ? (
|
||||
<Trans values={{ type: key_type }}>
|
||||
encryption_key_valid
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans values={{ type: key_type }}>
|
||||
encryption_key_invalid
|
||||
</Trans>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{warning_validation && (
|
||||
<div className="col-12">
|
||||
<p className="text-danger">{warning_validation}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="btn-list mt-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standart"
|
||||
disabled={isSavingDisabled}
|
||||
>
|
||||
<Trans>save_config</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standart"
|
||||
disabled={submitting || processingConfig}
|
||||
onClick={() => clearFields(change, setTlsConfig, t)}
|
||||
>
|
||||
<Trans>reset_settings</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
handleChange: PropTypes.func,
|
||||
isEnabled: PropTypes.bool.isRequired,
|
||||
certificateChain: PropTypes.string.isRequired,
|
||||
privateKey: PropTypes.string.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
processingValidate: PropTypes.bool.isRequired,
|
||||
status_key: PropTypes.string,
|
||||
not_after: PropTypes.string,
|
||||
warning_validation: PropTypes.string,
|
||||
valid_chain: PropTypes.bool,
|
||||
valid_key: PropTypes.bool,
|
||||
valid_cert: PropTypes.bool,
|
||||
valid_pair: PropTypes.bool,
|
||||
dns_names: PropTypes.string,
|
||||
key_type: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
subject: PropTypes.string,
|
||||
t: PropTypes.func.isRequired,
|
||||
setTlsConfig: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('encryptionForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const isEnabled = selector(state, 'enabled');
|
||||
const certificateChain = selector(state, 'certificate_chain');
|
||||
const privateKey = selector(state, 'private_key');
|
||||
return {
|
||||
isEnabled,
|
||||
certificateChain,
|
||||
privateKey,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'encryptionForm',
|
||||
validate,
|
||||
}),
|
||||
])(Form);
|
||||
80
client/src/components/Settings/Encryption/index.js
Normal file
80
client/src/components/Settings/Encryption/index.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
import Card from '../../ui/Card';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
class Encryption extends Component {
|
||||
componentDidMount() {
|
||||
const { validateTlsConfig, encryption } = this.props;
|
||||
|
||||
if (encryption.enabled) {
|
||||
validateTlsConfig(encryption);
|
||||
}
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setTlsConfig(values);
|
||||
};
|
||||
|
||||
handleFormChange = debounce((values) => {
|
||||
this.props.validateTlsConfig(values);
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
render() {
|
||||
const { encryption, t } = this.props;
|
||||
const {
|
||||
enabled,
|
||||
server_name,
|
||||
force_https,
|
||||
port_https,
|
||||
port_dns_over_tls,
|
||||
certificate_chain,
|
||||
private_key,
|
||||
} = encryption;
|
||||
|
||||
return (
|
||||
<div className="encryption">
|
||||
<PageTitle title={t('encryption_settings')} />
|
||||
{encryption.processing && <Loading />}
|
||||
{!encryption.processing && (
|
||||
<Card
|
||||
title={t('encryption_title')}
|
||||
subtitle={t('encryption_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={{
|
||||
enabled,
|
||||
server_name,
|
||||
force_https,
|
||||
port_https,
|
||||
port_dns_over_tls,
|
||||
certificate_chain,
|
||||
private_key,
|
||||
}}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
onChange={this.handleFormChange}
|
||||
setTlsConfig={this.props.setTlsConfig}
|
||||
{...this.props.encryption}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Encryption.propTypes = {
|
||||
setTlsConfig: PropTypes.func.isRequired,
|
||||
validateTlsConfig: PropTypes.func.isRequired,
|
||||
encryption: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Encryption);
|
||||
@@ -7,11 +7,11 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form__group--dhcp:last-child {
|
||||
margin-bottom: 15px;
|
||||
.form__group--settings:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-standart {
|
||||
.btn-standard {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
@@ -48,3 +48,50 @@
|
||||
.dhcp {
|
||||
min-height: 450px;
|
||||
}
|
||||
|
||||
.form__desc {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: rgba(74, 74, 74, 0.7);
|
||||
}
|
||||
|
||||
.form__desc--top {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.form__label--bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form__label--with-desc {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form__status {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.encryption__list {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.encryption__list li {
|
||||
list-style: inside;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.btn-icon-sm {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
min-width: 23px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
class Upstream extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleUpstreamChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleUpstreamSubmit();
|
||||
};
|
||||
|
||||
handleTest = () => {
|
||||
this.props.handleUpstreamTest();
|
||||
}
|
||||
|
||||
render() {
|
||||
const testButtonClass = classnames({
|
||||
'btn btn-primary btn-standart mr-2': true,
|
||||
'btn btn-primary btn-standart mr-2 btn-loading': this.props.processingTestUpstream,
|
||||
});
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={ t('upstream_dns') }
|
||||
subtitle={ t('upstream_dns_hint') }
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<form>
|
||||
<textarea
|
||||
className="form-control form-control--textarea"
|
||||
value={this.props.upstreamDns}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className={testButtonClass}
|
||||
type="button"
|
||||
onClick={this.handleTest}
|
||||
>
|
||||
<Trans>test_upstream_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>1.1.1.1</code> - { t('example_upstream_regular') }
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_dot') }} />
|
||||
</li>
|
||||
<li>
|
||||
<code>https://cloudflare-dns.com/dns-query</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_doh') }} />
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://1.1.1.1</code> - { t('example_upstream_tcp') }
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_sdns') }} />
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstreamDns: PropTypes.string,
|
||||
processingTestUpstream: PropTypes.bool,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
handleUpstreamSubmit: PropTypes.func,
|
||||
handleUpstreamTest: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Upstream);
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import Upstream from './Upstream';
|
||||
import Dhcp from './Dhcp';
|
||||
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
import './Settings.css';
|
||||
|
||||
class Settings extends Component {
|
||||
@@ -35,77 +35,48 @@ class Settings extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.initSettings(this.settings);
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
}
|
||||
|
||||
handleUpstreamChange = (value) => {
|
||||
this.props.handleUpstreamChange({ upstreamDns: value });
|
||||
};
|
||||
|
||||
handleUpstreamSubmit = () => {
|
||||
this.props.setUpstream(this.props.dashboard.upstreamDns);
|
||||
};
|
||||
|
||||
handleUpstreamTest = () => {
|
||||
if (this.props.dashboard.upstreamDns.length > 0) {
|
||||
this.props.testUpstream(this.props.dashboard.upstreamDns);
|
||||
} else {
|
||||
this.props.addErrorToast({ error: this.props.t('no_servers_specified') });
|
||||
}
|
||||
};
|
||||
|
||||
renderSettings = (settings) => {
|
||||
if (Object.keys(settings).length > 0) {
|
||||
return Object.keys(settings).map((key) => {
|
||||
const setting = settings[key];
|
||||
const { enabled } = setting;
|
||||
return (<Checkbox
|
||||
key={key}
|
||||
{...settings[key]}
|
||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||
/>);
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
{...settings[key]}
|
||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div><Trans>no_settings</Trans></div>
|
||||
<div>
|
||||
<Trans>no_settings</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { settings, t } = this.props;
|
||||
const { upstreamDns } = this.props.dashboard;
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={ t('settings') } />
|
||||
<PageTitle title={t('general_settings')} />
|
||||
{settings.processing && <Loading />}
|
||||
{!settings.processing &&
|
||||
{!settings.processing && (
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card title={ t('general_settings') } bodyType="card-body box-body--settings">
|
||||
<Card bodyType="card-body box-body--settings">
|
||||
<div className="form">
|
||||
{this.renderSettings(settings.settingsList)}
|
||||
</div>
|
||||
</Card>
|
||||
<Upstream
|
||||
upstreamDns={upstreamDns}
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
handleUpstreamChange={this.handleUpstreamChange}
|
||||
handleUpstreamSubmit={this.handleUpstreamSubmit}
|
||||
handleUpstreamTest={this.handleUpstreamTest}
|
||||
/>
|
||||
<Dhcp
|
||||
dhcp={this.props.dhcp}
|
||||
toggleDhcp={this.props.toggleDhcp}
|
||||
getDhcpStatus={this.props.getDhcpStatus}
|
||||
findActiveDhcp={this.props.findActiveDhcp}
|
||||
setDhcpConfig={this.props.setDhcpConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -116,9 +87,6 @@ Settings.propTypes = {
|
||||
settings: PropTypes.object,
|
||||
settingsList: PropTypes.object,
|
||||
toggleSetting: PropTypes.func,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
setUpstream: PropTypes.func,
|
||||
upstream: PropTypes.string,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
15
client/src/components/SetupGuide/Guide.css
Normal file
15
client/src/components/SetupGuide/Guide.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.guide {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.guide__title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.guide__desc {
|
||||
margin-bottom: 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
41
client/src/components/SetupGuide/index.js
Normal file
41
client/src/components/SetupGuide/index.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Guide from '../ui/Guide';
|
||||
import Card from '../ui/Card';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import './Guide.css';
|
||||
|
||||
const SetupGuide = ({
|
||||
t,
|
||||
dashboard: {
|
||||
dnsAddresses,
|
||||
},
|
||||
}) => (
|
||||
<div className="guide">
|
||||
<PageTitle title={t('setup_guide')} />
|
||||
<Card>
|
||||
<div className="guide__title">
|
||||
<Trans>install_devices_title</Trans>
|
||||
</div>
|
||||
<div className="guide__desc">
|
||||
<Trans>install_devices_desc</Trans>
|
||||
<div className="mt-1">
|
||||
<Trans>install_devices_address</Trans>:
|
||||
</div>
|
||||
<div className="mt-2 font-weight-bold">
|
||||
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
|
||||
</div>
|
||||
</div>
|
||||
<Guide dnsAddresses={dnsAddresses} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
SetupGuide.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(SetupGuide);
|
||||
@@ -2,7 +2,7 @@
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 24px;
|
||||
z-index: 103;
|
||||
z-index: 105;
|
||||
width: 345px;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast__content a {
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toast__dismiss {
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
class Toast extends Component {
|
||||
componentDidMount() {
|
||||
const timeout = this.props.type === 'error' ? 30000 : 5000;
|
||||
const timeout = this.props.type === 'success' ? 5000 : 30000;
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.removeToast(this.props.id);
|
||||
@@ -15,13 +15,25 @@ class Toast extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
showMessage(t, type, message) {
|
||||
if (type === 'notice') {
|
||||
return <span dangerouslySetInnerHTML={{ __html: t(message) }} />;
|
||||
}
|
||||
|
||||
return <Trans>{message}</Trans>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
type, id, t, message,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={`toast toast--${this.props.type}`}>
|
||||
<div className={`toast toast--${type}`}>
|
||||
<p className="toast__content">
|
||||
<Trans>{this.props.message}</Trans>
|
||||
{this.showMessage(t, type, message)}
|
||||
</p>
|
||||
<button className="toast__dismiss" onClick={() => this.props.removeToast(this.props.id)}>
|
||||
<button className="toast__dismiss" onClick={() => this.props.removeToast(id)}>
|
||||
<svg stroke="#fff" fill="none" width="20" height="20" strokeWidth="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -30,6 +42,7 @@ class Toast extends Component {
|
||||
}
|
||||
|
||||
Toast.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
||||
32
client/src/components/ui/Accordion.css
Normal file
32
client/src/components/ui/Accordion.css
Normal file
@@ -0,0 +1,32 @@
|
||||
.accordion {
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.accordion__label {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-left: 25px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.accordion__label:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
width: 17px;
|
||||
height: 10px;
|
||||
background-image: url("./svg/chevron-down.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.accordion__label--open:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.accordion__content {
|
||||
padding-top: 5px;
|
||||
}
|
||||
43
client/src/components/ui/Accordion.js
Normal file
43
client/src/components/ui/Accordion.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Accordion.css';
|
||||
|
||||
class Accordion extends Component {
|
||||
state = {
|
||||
isOpen: false,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
|
||||
};
|
||||
|
||||
render() {
|
||||
const accordionClass = this.state.isOpen
|
||||
? 'accordion__label accordion__label--open'
|
||||
: 'accordion__label';
|
||||
|
||||
return (
|
||||
<div className="accordion">
|
||||
<div
|
||||
className={accordionClass}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{this.props.label}
|
||||
</div>
|
||||
{this.state.isOpen && (
|
||||
<div className="accordion__content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Accordion.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Accordion;
|
||||
@@ -33,21 +33,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-refresh {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiM0NjdmY2YiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==");
|
||||
}
|
||||
|
||||
.card-refresh:hover,
|
||||
.card-refresh:not(:disabled):not(.disabled):active,
|
||||
.card-refresh:focus:active {
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==");
|
||||
}
|
||||
|
||||
.card-title-stats {
|
||||
font-size: 13px;
|
||||
color: #9aa0ac;
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkbox--form .checkbox__label:before {
|
||||
top: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.checkbox__label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -68,19 +73,28 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.checkbox__input:checked+.checkbox__label:before {
|
||||
.checkbox__input:checked + .checkbox__label:before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
|
||||
}
|
||||
|
||||
.checkbox__input:focus+.checkbox__label:before {
|
||||
.checkbox__input:focus + .checkbox__label:before {
|
||||
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
|
||||
}
|
||||
|
||||
.checkbox__input:disabled + .checkbox__label {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.checkbox__label-text {
|
||||
max-width: 515px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-text--long {
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
.checkbox__label-title {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
|
||||
8
client/src/components/ui/Dropdown.css
Normal file
8
client/src/components/ui/Dropdown.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.dropdown-item.active,
|
||||
.dropdown-item:active {
|
||||
background-color: #66b574;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
cursor: default;
|
||||
}
|
||||
89
client/src/components/ui/Dropdown.js
Normal file
89
client/src/components/ui/Dropdown.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import enhanceWithClickOutside from 'react-click-outside';
|
||||
|
||||
import './Dropdown.css';
|
||||
|
||||
class Dropdown extends Component {
|
||||
state = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
toggleDropdown = () => {
|
||||
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
|
||||
};
|
||||
|
||||
hideDropdown = () => {
|
||||
this.setState({ isOpen: false });
|
||||
};
|
||||
|
||||
handleClickOutside = () => {
|
||||
if (this.state.isOpen) {
|
||||
this.hideDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
controlClassName,
|
||||
menuClassName,
|
||||
baseClassName,
|
||||
icon,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
const { isOpen } = this.state;
|
||||
|
||||
const dropdownClass = classnames({
|
||||
[baseClassName]: true,
|
||||
show: isOpen,
|
||||
});
|
||||
|
||||
const dropdownMenuClass = classnames({
|
||||
[menuClassName]: true,
|
||||
show: isOpen,
|
||||
});
|
||||
|
||||
const ariaSettings = isOpen ? 'true' : 'false';
|
||||
|
||||
return (
|
||||
<div className={dropdownClass}>
|
||||
<a
|
||||
className={controlClassName}
|
||||
aria-expanded={ariaSettings}
|
||||
onClick={this.toggleDropdown}
|
||||
>
|
||||
{icon && (
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref={`#${icon}`} />
|
||||
</svg>
|
||||
)}
|
||||
{label}
|
||||
</a>
|
||||
<div className={dropdownMenuClass} onClick={this.hideDropdown}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dropdown.defaultProps = {
|
||||
baseClassName: 'dropdown',
|
||||
menuClassName: 'dropdown-menu dropdown-menu-arrow',
|
||||
controlClassName: '',
|
||||
};
|
||||
|
||||
Dropdown.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
controlClassName: PropTypes.node.isRequired,
|
||||
menuClassName: PropTypes.string.isRequired,
|
||||
baseClassName: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withNamespaces()(enhanceWithClickOutside(Dropdown));
|
||||
43
client/src/components/ui/EncryptionTopline.js
Normal file
43
client/src/components/ui/EncryptionTopline.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import isAfter from 'date-fns/is_after';
|
||||
import addDays from 'date-fns/add_days';
|
||||
|
||||
import Topline from './Topline';
|
||||
import { EMPTY_DATE } from '../../helpers/constants';
|
||||
|
||||
const EncryptionTopline = (props) => {
|
||||
if (props.notAfter === EMPTY_DATE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
|
||||
const isExpired = isAfter(Date.now(), props.notAfter);
|
||||
|
||||
if (isExpired) {
|
||||
return (
|
||||
<Topline type="danger">
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expired_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
} else if (isAboutExpire) {
|
||||
return (
|
||||
<Topline type="warning">
|
||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||
topline_expiring_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
EncryptionTopline.propTypes = {
|
||||
notAfter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(EncryptionTopline);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { REPOSITORY, LANGUAGES } from '../../helpers/constants';
|
||||
import { REPOSITORY, LANGUAGES, PRIVACY_POLICY_LINK } from '../../helpers/constants';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
import './Footer.css';
|
||||
@@ -23,13 +23,16 @@ class Footer extends Component {
|
||||
<div className="footer__row">
|
||||
<div className="footer__column">
|
||||
<div className="footer__copyright">
|
||||
<Trans>copyright</Trans> © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
|
||||
<Trans>copyright</Trans> © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer__column">
|
||||
<a href={REPOSITORY.URL} className="footer__link" target="_blank" rel="noopener noreferrer">
|
||||
<Trans>homepage</Trans>
|
||||
</a>
|
||||
<a href={PRIVACY_POLICY_LINK} className="footer__link" target="_blank" rel="noopener noreferrer">
|
||||
<Trans>privacy_policy</Trans>
|
||||
</a>
|
||||
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm footer__link footer__link--report" target="_blank" rel="noopener noreferrer">
|
||||
<Trans>report_an_issue</Trans>
|
||||
</a>
|
||||
|
||||
373
client/src/components/ui/Guide.js
Normal file
373
client/src/components/ui/Guide.js
Normal file
@@ -0,0 +1,373 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Tabs from '../ui/Tabs';
|
||||
import Icons from '../ui/Icons';
|
||||
|
||||
const Guide = (props) => {
|
||||
const { dnsAddresses } = props;
|
||||
const tlsAddress = (dnsAddresses && dnsAddresses.filter(item => item.includes('tls://'))) || '';
|
||||
const httpsAddress =
|
||||
(dnsAddresses && dnsAddresses.filter(item => item.includes('https://'))) || '';
|
||||
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Icons />
|
||||
<Tabs>
|
||||
<div label="Router">
|
||||
<div className="tab__title">
|
||||
<Trans>install_devices_router</Trans>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<p>
|
||||
<Trans>install_devices_router_desc</Trans>
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_router_list_3</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Windows">
|
||||
<div className="tab__title">Windows</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_4</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_5</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_windows_list_6</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="macOS">
|
||||
<div className="tab__title">macOS</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_macos_list_4</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Android">
|
||||
<div className="tab__title">Android</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_4</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_android_list_5</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="iOS">
|
||||
<div className="tab__title">iOS</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_2</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_3</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>install_devices_ios_list_4</Trans>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="dns_privacy" title={props.t('dns_privacy')}>
|
||||
<div className="tab__title">
|
||||
<Trans>dns_privacy</Trans>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
{tlsAddress && tlsAddress.length > 0 && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
values={{ address: tlsAddress[0] }}
|
||||
components={[
|
||||
<strong key="0">text</strong>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_1
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{httpsAddress && httpsAddress.length > 0 && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
values={{ address: httpsAddress[0] }}
|
||||
components={[
|
||||
<strong key="0">text</strong>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_2
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{showDnsPrivacyNotice && (
|
||||
<div className="tab__paragraph">
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/AdguardHome/wiki/Encryption"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_notice
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{!showDnsPrivacyNotice && (
|
||||
<Fragment>
|
||||
<div className="tab__paragraph">
|
||||
<Trans components={[<p key="0">text</p>]}>
|
||||
setup_dns_privacy_3
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>Android</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans>setup_dns_privacy_android_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://adguard.com/adguard-android/overview.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_android_2
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://getintra.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_android_3
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>iOS</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://itunes.apple.com/app/id1452162351"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
<a
|
||||
href="https://dnscrypt.info/stamps"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="2"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_ios_1
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://adguard.com/adguard-ios/overview.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_ios_2
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="tab__paragraph">
|
||||
<strong>
|
||||
<Trans>setup_dns_privacy_other_title</Trans>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<Trans>setup_dns_privacy_other_1</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/AdguardTeam/dnsproxy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_2
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://github.com/jedisct1/dnscrypt-proxy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_3
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://www.mozilla.org/firefox/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<code key="1">text</code>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_4
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans
|
||||
components={[
|
||||
<a
|
||||
href="https://dnscrypt.info/implementations"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="0"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
<a
|
||||
href="https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="1"
|
||||
>
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
setup_dns_privacy_other_5
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Guide.defaultProps = {
|
||||
dnsAddresses: [],
|
||||
};
|
||||
|
||||
Guide.propTypes = {
|
||||
dnsAddresses: PropTypes.array,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Guide);
|
||||
5
client/src/components/ui/Icons.css
Normal file
5
client/src/components/ui/Icons.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.icons {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
69
client/src/components/ui/Icons.js
Normal file
69
client/src/components/ui/Icons.js
Normal file
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
z-index: 104;
|
||||
}
|
||||
|
||||
.ReactModal__Overlay--after-open {
|
||||
@@ -38,3 +38,9 @@
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.modal-dialog--clients {
|
||||
max-width: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
40
client/src/components/ui/Overlay.css
Normal file
40
client/src/components/ui/Overlay.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 110;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.overlay--visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.overlay__loading {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 20px;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2047.6%2047.6%22%20height%3D%22100%25%22%20width%3D%22100%25%22%3E%3Cpath%20opacity%3D%22.235%22%20fill%3D%22%23979797%22%20d%3D%22M44.4%2011.9l-5.2%203c1.5%202.6%202.4%205.6%202.4%208.9%200%209.8-8%2017.8-17.8%2017.8-6.6%200-12.3-3.6-15.4-8.9l-5.2%203C7.3%2042.8%2015%2047.6%2023.8%2047.6c13.1%200%2023.8-10.7%2023.8-23.8%200-4.3-1.2-8.4-3.2-11.9z%22%2F%3E%3Cpath%20fill%3D%22%2366b574%22%20d%3D%22M3.2%2035.7C0%2030.2-.8%2023.8.8%2017.6%202.5%2011.5%206.4%206.4%2011.9%203.2%2017.4%200%2023.8-.8%2030%20.8c6.1%201.6%2011.3%205.6%2014.4%2011.1l-5.2%203c-2.4-4.1-6.2-7.1-10.8-8.3C23.8%205.4%2019%206%2014.9%208.4s-7.1%206.2-8.3%2010.8c-1.2%204.6-.6%209.4%201.8%2013.5l-5.2%203z%22%2F%3E%3C%2Fsvg%3E");
|
||||
will-change: transform;
|
||||
animation: clockwise 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes clockwise {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,16 @@
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.popover__trigger--address {
|
||||
top: 0;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.popover__trigger--address:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.popover__body {
|
||||
content: "";
|
||||
display: flex;
|
||||
@@ -57,6 +67,38 @@
|
||||
border-top: 6px solid #585965;
|
||||
}
|
||||
|
||||
.popover__body--address {
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
left: initial;
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
min-width: 100px;
|
||||
padding: 12px 18px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
transform: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.popover__body--address:after {
|
||||
top: -11px;
|
||||
left: initial;
|
||||
right: 40px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.popover__body--address:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.popover__trigger:hover + .popover__body,
|
||||
.popover__body:hover {
|
||||
visibility: visible;
|
||||
@@ -73,6 +115,10 @@
|
||||
stroke: #66b574;
|
||||
}
|
||||
|
||||
.popover__list--bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.popover__list-title {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
43
client/src/components/ui/Tab.js
Normal file
43
client/src/components/ui/Tab.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class Tab extends Component {
|
||||
handleClick = () => {
|
||||
this.props.onClick(this.props.label);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activeTab,
|
||||
label,
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
const tabClass = classnames({
|
||||
tab__control: true,
|
||||
'tab__control--active': activeTab === label,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tabClass}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<svg className="tab__icon">
|
||||
<use xlinkHref={`#${label.toLowerCase()}`} />
|
||||
</svg>
|
||||
{title || label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tab.propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Tab;
|
||||
@@ -3783,7 +3783,7 @@ tbody.collapse.show {
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
vertical-align: middle;
|
||||
background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'%3E%3Cpath fill='#999' d='M0 0L10 0L5 5L0 0'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
|
||||
background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
|
||||
background-size: 8px 10px;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
border-radius: 3px;
|
||||
|
||||
60
client/src/components/ui/Tabs.css
Normal file
60
client/src/components/ui/Tabs.css
Normal file
@@ -0,0 +1,60 @@
|
||||
.tabs__controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.tab__control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 70px;
|
||||
font-size: 13px;
|
||||
color: #555555;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tab__control:hover,
|
||||
.tab__control:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab__control--active {
|
||||
font-weight: 700;
|
||||
color: #4a4a4a;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab__title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tab__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-bottom: 6px;
|
||||
fill: #4a4a4a;
|
||||
}
|
||||
|
||||
.tab__text {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.tab__text li,
|
||||
.tab__text p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tab__text ul,
|
||||
.tab__text ol {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.tab__paragraph {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
60
client/src/components/ui/Tabs.js
Normal file
60
client/src/components/ui/Tabs.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Tab from './Tab';
|
||||
import './Tabs.css';
|
||||
|
||||
class Tabs extends Component {
|
||||
state = {
|
||||
activeTab: this.props.children[0].props.label,
|
||||
};
|
||||
|
||||
onClickTabControl = (tab) => {
|
||||
this.setState({ activeTab: tab });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
props: {
|
||||
children,
|
||||
},
|
||||
state: {
|
||||
activeTab,
|
||||
},
|
||||
} = this;
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
<div className="tabs__controls">
|
||||
{children.map((child) => {
|
||||
const { label, title } = child.props;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
key={label}
|
||||
label={label}
|
||||
title={title}
|
||||
activeTab={activeTab}
|
||||
onClick={this.onClickTabControl}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="tabs__content">
|
||||
{children.map((child) => {
|
||||
if (child.props.label !== activeTab) {
|
||||
return false;
|
||||
}
|
||||
return child.props.children;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tabs.propTypes = {
|
||||
children: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
@@ -1,4 +1,4 @@
|
||||
.update {
|
||||
.topline {
|
||||
position: relative;
|
||||
z-index: 102;
|
||||
margin-bottom: 0;
|
||||
19
client/src/components/ui/Topline.js
Normal file
19
client/src/components/ui/Topline.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Topline.css';
|
||||
|
||||
const Topline = props => (
|
||||
<div className={`alert alert-${props.type} topline`}>
|
||||
<div className="container">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Topline.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Topline;
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Update.css';
|
||||
|
||||
const Update = props => (
|
||||
<div className="alert alert-info update">
|
||||
<div className="container">
|
||||
{props.announcement} <a href={props.announcementUrl} target="_blank" rel="noopener noreferrer">Click here</a> for more info.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Update.propTypes = {
|
||||
announcement: PropTypes.string.isRequired,
|
||||
announcementUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Update;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user