Compare commits
1338 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6bd2a101d | ||
|
|
d2e0940053 | ||
|
|
31af6ae914 | ||
|
|
568a7a7334 | ||
|
|
09a52436fb | ||
|
|
94552a30d7 | ||
|
|
87ca9ed9c2 | ||
|
|
c82e93cfc7 | ||
|
|
91a1eb9e06 | ||
|
|
c54e2585ac | ||
|
|
5b555f95ff | ||
|
|
8e08cddf64 | ||
|
|
eba7931c2d | ||
|
|
5448000f7b | ||
|
|
b37208564b | ||
|
|
d46ebe1c8b | ||
|
|
68d5d595b6 | ||
|
|
56c69cdb79 | ||
|
|
28cc9dc973 | ||
|
|
8aef6d8f91 | ||
|
|
1f0c8c6a3b | ||
|
|
0faf144853 | ||
|
|
57cfd97a6a | ||
|
|
256030f0c1 | ||
|
|
ea2a523f8c | ||
|
|
b302ccb491 | ||
|
|
4407ad3585 | ||
|
|
9d7285e42c | ||
|
|
d7ff61ca59 | ||
|
|
8b63811fa9 | ||
|
|
5c3dfd114c | ||
|
|
61897db0c5 | ||
|
|
ccf72b6008 | ||
|
|
d659e7ee08 | ||
|
|
4f08f96607 | ||
|
|
77592c6382 | ||
|
|
272115724d | ||
|
|
a59b6b3054 | ||
|
|
e7001c3bc4 | ||
|
|
1b95a85651 | ||
|
|
0c46a70d9a | ||
|
|
92cebd5b31 | ||
|
|
3c684d1f85 | ||
|
|
87b379b140 | ||
|
|
15d07a40eb | ||
|
|
dc2d8cf075 | ||
|
|
e81a9c7d56 | ||
|
|
04a477c14a | ||
|
|
8307a5a494 | ||
|
|
01f5a13dd2 | ||
|
|
fdfc8d7683 | ||
|
|
b17cbcb38f | ||
|
|
9389b087be | ||
|
|
0550a9a460 | ||
|
|
4ae8c799ee | ||
|
|
e95aae5744 | ||
|
|
3cca61d599 | ||
|
|
3b0f2e5563 | ||
|
|
70b8cf6ec8 | ||
|
|
1bb6638db7 | ||
|
|
9857024c5d | ||
|
|
0c2459b51b | ||
|
|
b66e370ffc | ||
|
|
a9fbb93f0f | ||
|
|
a742181956 | ||
|
|
873108bf08 | ||
|
|
e9647e2962 | ||
|
|
e09ec5d38e | ||
|
|
479675b6cc | ||
|
|
d9265aa9a8 | ||
|
|
6f51df7d2e | ||
|
|
81303b5db7 | ||
|
|
fdf7ee2c08 | ||
|
|
5a3de2a276 | ||
|
|
4a05ab0057 | ||
|
|
4134a8c30e | ||
|
|
c2dacd32d1 | ||
|
|
bb00a63238 | ||
|
|
bd5162ada3 | ||
|
|
b8444ff46a | ||
|
|
2bbd262968 | ||
|
|
6701e9ce06 | ||
|
|
af21a5f17b | ||
|
|
001b4b981f | ||
|
|
5e309a7b3a | ||
|
|
2780ace63e | ||
|
|
f79008d9d0 | ||
|
|
d3ddfc81a6 | ||
|
|
c426ee0108 | ||
|
|
2682adca39 | ||
|
|
d51f43e27a | ||
|
|
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 | ||
|
|
fd8860a389 | ||
|
|
cbe83e2053 | ||
|
|
b0c4d88d54 | ||
|
|
ec0b8c687a | ||
|
|
4d3f1b83a6 | ||
|
|
368e2d1ebd | ||
|
|
568784b992 | ||
|
|
243603e04c | ||
|
|
d8802a9709 | ||
|
|
7463e54258 | ||
|
|
7acb107cbf | ||
|
|
86d79ae232 | ||
|
|
fedfc3a1fd | ||
|
|
bf15a40248 | ||
|
|
4efa30edc4 | ||
|
|
7ab03e9335 | ||
|
|
55a7ff7447 | ||
|
|
a7e0f66492 | ||
|
|
f312575da4 | ||
|
|
8fc5aebf12 | ||
|
|
03effab345 | ||
|
|
f868fdbf7a | ||
|
|
1b7db49062 | ||
|
|
f5e7eed447 | ||
|
|
6fd9af3c60 | ||
|
|
4aea91a70c | ||
|
|
8b4a1ca713 | ||
|
|
73f71364b3 | ||
|
|
712493aafd | ||
|
|
1270bbad1a | ||
|
|
c073f9db7b | ||
|
|
87b3c92f71 | ||
|
|
9fa85a5c48 | ||
|
|
52b81a27fb | ||
|
|
39bc55e430 | ||
|
|
59adad4d53 | ||
|
|
a74c2248fb | ||
|
|
d46b65f982 | ||
|
|
96fbf7f134 | ||
|
|
9294c9ecb2 | ||
|
|
dd21f497e3 | ||
|
|
390883126c | ||
|
|
fb24447915 | ||
|
|
fcf7b2185e | ||
|
|
b91c829f4c | ||
|
|
7106a8eb35 | ||
|
|
09702c724e | ||
|
|
4623817894 | ||
|
|
413bc75320 | ||
|
|
1b84a9233d | ||
|
|
aed87ce741 | ||
|
|
2652ed34b1 | ||
|
|
cc96593ebf | ||
|
|
3ade62301b | ||
|
|
62606db1af | ||
|
|
8227970d39 | ||
|
|
374a0dc2e5 | ||
|
|
2bc1d737cc | ||
|
|
bac2c39107 | ||
|
|
0a977fee87 | ||
|
|
e711f6e5fe | ||
|
|
9fe9baf7f4 | ||
|
|
b195080012 | ||
|
|
3d17907966 | ||
|
|
45626b139d | ||
|
|
b30b6b1d66 | ||
|
|
6e6c321871 | ||
|
|
6addc04b97 | ||
|
|
717a58a872 | ||
|
|
1c89e1df32 | ||
|
|
5c4ec62d96 | ||
|
|
69a387547d | ||
|
|
8411de8887 | ||
|
|
b5121c5754 | ||
|
|
253d8a4016 | ||
|
|
2ba5cb48b2 | ||
|
|
e056fb2eb9 | ||
|
|
8fb6f92753 | ||
|
|
e5c1211e17 | ||
|
|
217124cb3b | ||
|
|
15f3c82238 | ||
|
|
c82a5ac0cb | ||
|
|
250cc0ec0f | ||
|
|
3ad4b2864d | ||
|
|
0f5dd661f5 | ||
|
|
ff1c19cac5 | ||
|
|
2a1059107a | ||
|
|
609523a59c | ||
|
|
e31905864b | ||
|
|
bb6c596b22 | ||
|
|
2745223dbf | ||
|
|
b847866310 | ||
|
|
f6942213c8 | ||
|
|
478ce03386 | ||
|
|
15f0dee719 | ||
|
|
7ddc71006b | ||
|
|
b0149972cc | ||
|
|
9b43e07d7f | ||
|
|
e357620740 | ||
|
|
052f975762 | ||
|
|
e5d2f883ac | ||
|
|
8396dc2fdb | ||
|
|
09fb539875 | ||
|
|
be4b65fdca | ||
|
|
0a4627f4f0 | ||
|
|
0502ef6cc7 | ||
|
|
2281b60ebb | ||
|
|
7d2e39ed52 | ||
|
|
e26837d9e8 | ||
|
|
3ecc0ee24b | ||
|
|
057db71f3b | ||
|
|
ce615e1855 | ||
|
|
87c54ebd4c | ||
|
|
a6e0a17454 | ||
|
|
9089122b56 | ||
|
|
e0286ee85d | ||
|
|
31f77af534 | ||
|
|
0d1478b635 | ||
|
|
d27fd0488d | ||
|
|
9c4b791621 | ||
|
|
9d87ae95e6 | ||
|
|
8316d39b42 | ||
|
|
7120f551c8 | ||
|
|
e4a3564706 | ||
|
|
4eb122e973 | ||
|
|
feabc21864 | ||
|
|
a904f85e61 | ||
|
|
584f441141 | ||
|
|
7944f23d95 | ||
|
|
639b34c7d1 | ||
|
|
ea1353422f | ||
|
|
5a548be16c | ||
|
|
39eccc62b1 | ||
|
|
ea25510a08 | ||
|
|
45ae984f3b | ||
|
|
2012e707d0 | ||
|
|
942cde79bd | ||
|
|
c37c3e0459 | ||
|
|
cab73c0d68 | ||
|
|
58129543de | ||
|
|
504aaddc32 | ||
|
|
6257ff123f | ||
|
|
aa3f3e2c43 | ||
|
|
70c5afd6a5 | ||
|
|
701fd10c1c | ||
|
|
6cb991fe7f | ||
|
|
ec7efcc9d6 | ||
|
|
489c29b472 | ||
|
|
5609e47c28 | ||
|
|
8796a52c09 | ||
|
|
12a8011fb3 | ||
|
|
47e2a1004d | ||
|
|
89753c4efb | ||
|
|
8e57243275 | ||
|
|
e08c5efd99 | ||
|
|
c17c282901 | ||
|
|
8966383ca3 | ||
|
|
82da886df5 | ||
|
|
afe234759f | ||
|
|
d1f5f781c9 | ||
|
|
f95bea325b | ||
|
|
d8c97cbabe | ||
|
|
c995726f78 | ||
|
|
d2a0d03332 | ||
|
|
69cc597b87 | ||
|
|
15f8cfce64 | ||
|
|
939c902fb0 | ||
|
|
d9a65631b9 | ||
|
|
093bd164d6 | ||
|
|
c500345d16 | ||
|
|
a0482fc201 | ||
|
|
a6c9210461 | ||
|
|
4ae91f0c1b | ||
|
|
903c1da993 | ||
|
|
dcbf083d5b | ||
|
|
1fa250bb35 | ||
|
|
18f210eef5 | ||
|
|
f94c63ed5b | ||
|
|
e4998651fe | ||
|
|
668dcebf13 | ||
|
|
cdd2e8ecb4 | ||
|
|
63f20bc397 | ||
|
|
83544ab0f6 | ||
|
|
2139bb9c79 | ||
|
|
0530f5dff2 | ||
|
|
4e27ad0c8e | ||
|
|
166bc72ff3 | ||
|
|
25f20bd5a7 | ||
|
|
345e4dc89a | ||
|
|
1ae6af44d1 | ||
|
|
3779407291 | ||
|
|
ced5499083 | ||
|
|
5bf38041c5 | ||
|
|
25f469efd7 | ||
|
|
3d3e8e7dbc | ||
|
|
346fa6e921 | ||
|
|
54ee16634c | ||
|
|
3c427ba295 | ||
|
|
a6e4c48567 | ||
|
|
628323761a | ||
|
|
e1276d089b | ||
|
|
d47a23269d | ||
|
|
beab9a1be0 | ||
|
|
82bc5965f4 | ||
|
|
8d209773b3 | ||
|
|
67c8abcb8e | ||
|
|
bd39509458 | ||
|
|
fc7d93b920 | ||
|
|
4a357f1345 | ||
|
|
3693047270 | ||
|
|
92fbbc8cc5 | ||
|
|
914eb612cd | ||
|
|
cc40826299 | ||
|
|
2e879896ff | ||
|
|
451922b858 | ||
|
|
7f018234f6 | ||
|
|
efdd1c1ff2 | ||
|
|
9bc4bf66ed | ||
|
|
a6022fc198 | ||
|
|
d6f560ecaf | ||
|
|
839c2ebdd4 | ||
|
|
9cd7a37646 | ||
|
|
cd75c406c1 | ||
|
|
2449075bca | ||
|
|
4c9a84dda0 | ||
|
|
262e9acc03 | ||
|
|
484c0ceaff | ||
|
|
e399a5fe37 | ||
|
|
19e30dbccc | ||
|
|
49ff0d2b9a | ||
|
|
800002f83d | ||
|
|
73e20d1dd0 | ||
|
|
9bb788ecb5 | ||
|
|
f3fa497af3 | ||
|
|
54bdacdde2 | ||
|
|
0e065a2e61 | ||
|
|
591065aa3a | ||
|
|
760e3596b6 | ||
|
|
21b8b233f8 | ||
|
|
32d4e80c93 | ||
|
|
30f3eb446c | ||
|
|
f711d6558f | ||
|
|
abd1d306dc | ||
|
|
1e1ce606c5 | ||
|
|
abb51ddb8a | ||
|
|
2b2a797cf7 | ||
|
|
c39831abbc | ||
|
|
9173b0ee7a | ||
|
|
c427034e27 | ||
|
|
3cd3b93511 | ||
|
|
41c9a89516 | ||
|
|
9863c1f1ac | ||
|
|
79468ab1bc | ||
|
|
4b821f0bd7 | ||
|
|
54b0f073e8 | ||
|
|
90ed48e9fb | ||
|
|
7a68c3dfc6 | ||
|
|
234ab23557 | ||
|
|
234e29697f | ||
|
|
4590564fea | ||
|
|
bfb7a252ad | ||
|
|
1d12e35dac | ||
|
|
3854a7acf9 | ||
|
|
3be7366ae1 | ||
|
|
e1069f6bd1 | ||
|
|
f8ee8a7907 | ||
|
|
b6bc613c87 | ||
|
|
e466a09e20 | ||
|
|
98bf5322a3 | ||
|
|
b3ae247520 | ||
|
|
b3840b5790 | ||
|
|
0c4646201f | ||
|
|
66b83a5fb5 | ||
|
|
12706d4a97 | ||
|
|
50d2c0a8d3 | ||
|
|
4ad29ee65d | ||
|
|
b2998d77f0 | ||
|
|
a528ed9f94 | ||
|
|
a1bc008190 | ||
|
|
d3a6a86254 | ||
|
|
5437a9d3a6 | ||
|
|
bdfb141d36 | ||
|
|
550dc3b129 | ||
|
|
bacc465ebd | ||
|
|
e606d63525 | ||
|
|
dbde07eea2 | ||
|
|
fc2f01f933 | ||
|
|
a267dbf625 | ||
|
|
c46fcce87d | ||
|
|
af9c47c40a | ||
|
|
59323b2008 | ||
|
|
f0823f1195 | ||
|
|
dca9aebccb | ||
|
|
1ed9faa0c2 | ||
|
|
0d44e3ccdc | ||
|
|
cf9414c107 | ||
|
|
47ce0f3e98 | ||
|
|
2e1acc2bac | ||
|
|
96142b4164 | ||
|
|
1af11c4e45 | ||
|
|
3e2a3afc52 | ||
|
|
40d1b18b28 | ||
|
|
a126a3868c | ||
|
|
c2ba8de206 | ||
|
|
82269bcf33 | ||
|
|
aa691a068a | ||
|
|
bdaea88bf0 | ||
|
|
8c11449d23 | ||
|
|
0e04954673 | ||
|
|
1059669b57 | ||
|
|
c9736ec0fa | ||
|
|
dcbee729fb | ||
|
|
c35f260e53 | ||
|
|
2f61b42e90 | ||
|
|
e67695df8b | ||
|
|
e356540872 | ||
|
|
3d01c3512e | ||
|
|
49567219dc | ||
|
|
3f85625dc6 | ||
|
|
d0d98ba762 | ||
|
|
d2e5692694 | ||
|
|
9d030f38b7 | ||
|
|
5fb603f6c9 | ||
|
|
11e8853a34 | ||
|
|
880ad362a8 | ||
|
|
5192e95a0d | ||
|
|
ac6e0add31 | ||
|
|
7ff89baf45 | ||
|
|
bad88961e9 | ||
|
|
838406353b | ||
|
|
1cdbe3f879 | ||
|
|
47a9c6555e | ||
|
|
35368619b0 | ||
|
|
6ca881ee86 | ||
|
|
1233901822 | ||
|
|
bc11f872fa | ||
|
|
599426a1f9 | ||
|
|
fb2d90832c | ||
|
|
8d13770b0d | ||
|
|
751be05a31 | ||
|
|
33958c5a25 | ||
|
|
1e18235c1d | ||
|
|
4b8ee9ce83 | ||
|
|
5be66e7dc7 | ||
|
|
8cf898e8d9 | ||
|
|
4995c1a1a8 | ||
|
|
557c2268dc | ||
|
|
aa7b99d78c | ||
|
|
e7b6ab4750 | ||
|
|
383f1c2fb3 | ||
|
|
3a74dfdfa4 | ||
|
|
413228e6ec | ||
|
|
c3df81bb8d | ||
|
|
e689c7d940 | ||
|
|
5ae2a32d6e | ||
|
|
a5d1053520 | ||
|
|
e2295c1a11 | ||
|
|
d591ea6264 | ||
|
|
ee8759f063 | ||
|
|
151944bc27 | ||
|
|
a6172d1966 | ||
|
|
90bef94500 | ||
|
|
f5deff63ba | ||
|
|
903b20dcab | ||
|
|
945bd24f67 | ||
|
|
ae9964c445 | ||
|
|
3a5ecb9fc1 | ||
|
|
c499c435c3 | ||
|
|
2113bb5436 | ||
|
|
8503f76747 | ||
|
|
c2be5917ef | ||
|
|
a54984f688 | ||
|
|
5533b434da | ||
|
|
4984c55bce | ||
|
|
9b489c8ddb | ||
|
|
eb5f66ad9e | ||
|
|
75d74a017b | ||
|
|
93c451cb0c | ||
|
|
0545aeff3f | ||
|
|
814005021c | ||
|
|
ca794aed63 | ||
|
|
37f6d38c49 | ||
|
|
165722585f | ||
|
|
7dea729656 | ||
|
|
16b1a343a0 | ||
|
|
a15f21ca1c | ||
|
|
a15c59e24e | ||
|
|
5718f55b9a | ||
|
|
6de0871f2c | ||
|
|
6a90efe957 | ||
|
|
763dcc46e9 | ||
|
|
3109529dbb | ||
|
|
2c84cd6448 | ||
|
|
0440ef016a | ||
|
|
182fa37e5f | ||
|
|
ea1125f57d | ||
|
|
4ecb84f9ad | ||
|
|
a2434d4574 | ||
|
|
3b1faa1365 | ||
|
|
dc1042c3e9 | ||
|
|
a63fe958ae | ||
|
|
0ee112e8a0 | ||
|
|
656d092ad6 | ||
|
|
2244c21b76 | ||
|
|
2c33905a79 | ||
|
|
16fd1359cd | ||
|
|
3a7a80f15f | ||
|
|
5b9a5fff97 | ||
|
|
3f8450337f | ||
|
|
19e76b6938 | ||
|
|
856e26edcf | ||
|
|
51ec58b0ce | ||
|
|
c6eabb5b67 | ||
|
|
1cc1e3749d | ||
|
|
cb97a254a5 | ||
|
|
9e939e5754 | ||
|
|
b72d6f68e6 | ||
|
|
3aac7e7bc9 | ||
|
|
57ade2c3c3 | ||
|
|
7d7360c700 | ||
|
|
8c76e17b1b | ||
|
|
991574f236 | ||
|
|
d7596fe860 | ||
|
|
0c3c8dba9b | ||
|
|
04e9f74435 | ||
|
|
7b7f713880 | ||
|
|
e20bfe9d08 | ||
|
|
c40f7b4d5c | ||
|
|
d7039d9222 | ||
|
|
3282a45978 | ||
|
|
98994916b5 | ||
|
|
f1ae5d78d2 | ||
|
|
2c72035000 | ||
|
|
c7790a8d9f | ||
|
|
c9e10c9de7 | ||
|
|
de7b2d5e6b | ||
|
|
ff86d6b7dc | ||
|
|
3afd8fccc7 | ||
|
|
2cf22898dd | ||
|
|
381b96a4b1 | ||
|
|
a65a40c6be | ||
|
|
da62fac76e | ||
|
|
6a53dd0f00 | ||
|
|
09a39cce03 | ||
|
|
50b188a086 | ||
|
|
dd8396cec1 | ||
|
|
ea320f5ee3 | ||
|
|
afd1fe21f6 | ||
|
|
119d38fa8e | ||
|
|
620212ad37 | ||
|
|
bd0fa4cc4f | ||
|
|
b0549a8e5b | ||
|
|
92399b8ebf | ||
|
|
d8fbb2cd3b | ||
|
|
469b93eaa4 | ||
|
|
92b681cb41 | ||
|
|
1c1b952d48 | ||
|
|
c2a2b3ea6a | ||
|
|
f727f999f9 | ||
|
|
02b28f4511 | ||
|
|
43fcf4117d | ||
|
|
68422b8399 | ||
|
|
c3f6a96f2f | ||
|
|
2c2b951fd6 | ||
|
|
fba70b8b73 | ||
|
|
38cfe95280 | ||
|
|
a76fd7618a | ||
|
|
8d23e29190 | ||
|
|
a185161ad4 | ||
|
|
81c7dbbc16 | ||
|
|
e733c19504 | ||
|
|
0e173d2f70 | ||
|
|
0292d2b32b | ||
|
|
ba56d6c01d | ||
|
|
b8213bf88a | ||
|
|
4548eb8d11 | ||
|
|
a2f06aadc0 | ||
|
|
df12038f33 | ||
|
|
c2aa39efe5 | ||
|
|
5d046c5c16 | ||
|
|
ae50a2f827 | ||
|
|
dcbe3dd405 | ||
|
|
ded02d112c | ||
|
|
076c9de68e | ||
|
|
d237df6389 | ||
|
|
22a5abb7b8 | ||
|
|
828bb40084 | ||
|
|
548010e002 | ||
|
|
5c6aa910ef | ||
|
|
b9999f155e | ||
|
|
3b44efc8e3 | ||
|
|
9258fada47 | ||
|
|
6c70d8ca37 | ||
|
|
5554643cd0 | ||
|
|
7c71d4b445 | ||
|
|
3a92520764 | ||
|
|
aa2e5500e7 | ||
|
|
e2cf9ffd84 | ||
|
|
d8a3ee3676 | ||
|
|
46e447589c | ||
|
|
97161ab4f0 | ||
|
|
3901dda39c | ||
|
|
d49e3769a1 | ||
|
|
c1e16cc584 | ||
|
|
9c1dc6d373 | ||
|
|
fced9178b8 | ||
|
|
d34049b513 | ||
|
|
43dbef8935 | ||
|
|
f6d7d6a37a | ||
|
|
b54f9a7a36 | ||
|
|
9e6ed7f273 | ||
|
|
04faff3e2c | ||
|
|
5fdaf7cb66 | ||
|
|
76f98e2950 | ||
|
|
ba836220b8 | ||
|
|
3189341089 | ||
|
|
4ba8293c06 | ||
|
|
7094ed4f28 | ||
|
|
f623c3d909 | ||
|
|
8198b65f29 | ||
|
|
38b3fe6718 | ||
|
|
9682dc6bc1 | ||
|
|
659b530381 | ||
|
|
1b5748e328 | ||
|
|
ebf2380af4 | ||
|
|
6fc50cd743 | ||
|
|
3b9aaff861 | ||
|
|
c572c7b0e9 | ||
|
|
74275bebdc | ||
|
|
1f0fdef8d6 | ||
|
|
04562dece3 | ||
|
|
c7a5275d42 | ||
|
|
fe397943d6 | ||
|
|
876854d403 | ||
|
|
c143e3d57f | ||
|
|
1102963fa0 | ||
|
|
f2621c4a9a | ||
|
|
859f1590dd | ||
|
|
0ce40fd46e | ||
|
|
33fbccf0ba | ||
|
|
e122d9138b | ||
|
|
606bed9d20 | ||
|
|
3b11648e14 | ||
|
|
e62050fb7e | ||
|
|
fa8bc57082 | ||
|
|
0e99a65687 | ||
|
|
bed92f89f0 | ||
|
|
f12ef5d504 | ||
|
|
379e14c28b | ||
|
|
2ca1a0e586 | ||
|
|
30553c6a9a | ||
|
|
7bf513b638 | ||
|
|
c4fefa10b0 | ||
|
|
3af62e463a | ||
|
|
ad91ba8f43 | ||
|
|
f054dcede4 | ||
|
|
d53f9bafe9 | ||
|
|
0421e1f4f8 |
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
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
client/* linguist-vendored
|
||||
47
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
47
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve AdGuard Home
|
||||
|
||||
---
|
||||
|
||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
|
||||
-->
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
|
||||
|
||||
- [ ] I am running the latest version
|
||||
- [ ] I checked the documentation and found no answer
|
||||
- [ ] I checked to make sure that this issue has not already been filed
|
||||
|
||||
### Issue Details
|
||||
|
||||
<!--- Please include all relevant details about the environment you experienced the bug in -->
|
||||
|
||||
* **Version of AdGuard Home server:**
|
||||
* <!-- (e.g. v1.0) -->
|
||||
* **How did you setup DNS configuration:**
|
||||
* <!-- (System/Router/IoT) -->
|
||||
* **If it's a router or IoT, please write device model:**
|
||||
* <!-- (e.g. Raspberry Pi 3 Model B) -->
|
||||
* **Operating system and version:**
|
||||
* <!-- (e.g. Ubuntu 18.04.1) -->
|
||||
|
||||
### Expected Behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
### Actual Behavior
|
||||
<!-- A clear and concise description of what actually happened. -->
|
||||
|
||||
### Screenshots
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
<details><summary>Screenshot:</summary>
|
||||
|
||||
<!--- drag and drop, upload or paste your screenshot to this area-->
|
||||
|
||||
</details>
|
||||
|
||||
### Additional Information
|
||||
<!-- Add any other context about the problem here. -->
|
||||
28
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for AdGuard Home
|
||||
|
||||
---
|
||||
|
||||
<!-- As an open-source project with a dedicated but small maintainer team, it can sometimes take a long time for issues to be addressed so please be patient and we will get back to you as soon as we can.
|
||||
-->
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
|
||||
|
||||
- [ ] I am running the latest version
|
||||
- [ ] I checked the documentation and found no answer
|
||||
- [ ] I checked to make sure that this issue has not already been filed
|
||||
|
||||
### Problem Description
|
||||
<!-- Is your feature request related to a problem? Please add a clear and concise description of what the problem is. -->
|
||||
|
||||
### Proposed Solution
|
||||
<!-- Describe the solution you'd like in a clear and concise manner -->
|
||||
|
||||
### Alternatives Considered
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
### Additional Information
|
||||
<!-- Add any other context about the problem here. -->
|
||||
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- 'bug'
|
||||
- 'enhancement'
|
||||
- 'feature request'
|
||||
- 'localization'
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: 'wontfix'
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,7 +1,18 @@
|
||||
/AdguardDNS
|
||||
/AdguardDNS.yaml
|
||||
.DS_Store
|
||||
/.vscode
|
||||
/.idea
|
||||
/AdGuardHome
|
||||
/AdGuardHome.exe
|
||||
/AdGuardHome.yaml
|
||||
/AdGuardHome.log
|
||||
/data/
|
||||
/build/
|
||||
/dist/
|
||||
/client/node_modules/
|
||||
/coredns
|
||||
/Corefile
|
||||
/dnsfilter.txt
|
||||
/querylog.json
|
||||
/querylog.json.1
|
||||
/a_main-packr.go
|
||||
|
||||
# Test output
|
||||
dnsfilter/tests/top-1m.csv
|
||||
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||
57
.golangci.yml
Normal file
57
.golangci.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
# 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
|
||||
- ".*_test.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
|
||||
}
|
||||
84
.travis.yml
Normal file
84
.travis.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
- 1.x
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_install:
|
||||
- nvm install node
|
||||
- npm install -g npm
|
||||
|
||||
install:
|
||||
- npm --prefix client install
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/Library/Caches/go-build
|
||||
|
||||
script:
|
||||
- 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)
|
||||
|
||||
notifications:
|
||||
slack: performix:yXTihlSzsLFSZiqbXMNzvTSX
|
||||
|
||||
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
|
||||
20
.twosky.json
Normal file
20
.twosky.json
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"project_id": "home",
|
||||
"base_locale": "en",
|
||||
"localizable_files": "client/src/__locales/en.json",
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
"pt-br": "Portuguese (BR)",
|
||||
"sv": "Svenska",
|
||||
"vi": "Tiếng Việt",
|
||||
"bg": "Български",
|
||||
"ru": "Русский",
|
||||
"ja": "日本語",
|
||||
"zh-tw": "正體中文",
|
||||
"zh-cn": "简体中文"
|
||||
}
|
||||
}
|
||||
]
|
||||
802
AGHTechDoc.md
Normal file
802
AGHTechDoc.md
Normal file
@@ -0,0 +1,802 @@
|
||||
# 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
|
||||
* Rewrites
|
||||
* API: List rewrite entries
|
||||
* API: Add a rewrite entry
|
||||
* API: Remove a rewrite entry
|
||||
* Services Filter
|
||||
* API: Get blocked services list
|
||||
* API: Set blocked services list
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
* If `use_global_blocked_services` is false, then the client-specific settings are used to override (enable or disable) global Blocked Services 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
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
}
|
||||
]
|
||||
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
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
}
|
||||
|
||||
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
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
## Rewrites
|
||||
|
||||
This section allows the administrator to easily configure custom DNS response for a specific domain name.
|
||||
A, AAAA and CNAME records are supported.
|
||||
|
||||
|
||||
### API: List rewrite entries
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/rewrite/list
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
[
|
||||
{
|
||||
domain: "..."
|
||||
answer: "..."
|
||||
}
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
### API: Add a rewrite entry
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/rewrite/add
|
||||
|
||||
{
|
||||
domain: "..."
|
||||
answer: "..." // "1.2.3.4" (A) || "::1" (AAAA) || "hostname" (CNAME)
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
|
||||
### API: Remove a rewrite entry
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/rewrite/delete
|
||||
|
||||
{
|
||||
domain: "..."
|
||||
answer: "..."
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
|
||||
## Services Filter
|
||||
|
||||
Allows to quickly block popular sites globally or for specific client only.
|
||||
UI manages these settings via global or per-client API.
|
||||
UI and server have the same list of the services supported and this list must always be in synchronization.
|
||||
UI code also contains icons for each service: `client/src/components/ui/Icons.js`.
|
||||
|
||||
How it works:
|
||||
* UI presents the list of services which user may want to block
|
||||
* Admin clicks on the checkboxes in front of the services to block and presses Save
|
||||
* UI sends `Set blocked services list` or `Update client` message
|
||||
* Server updates the internal configuration
|
||||
* When a user sends a DNS request for a host which is blocked by these settings, he won't receive its IP address
|
||||
* Query log will show that this request was blocked by "Blocked services"
|
||||
|
||||
Internally, all supported services are stored as a map:
|
||||
|
||||
service name -> list of rules
|
||||
|
||||
|
||||
### API: Get blocked services list
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/blocked_services/list
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
[ "name1", ... ]
|
||||
|
||||
|
||||
### API: Set blocked services list
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/blocked_services/set
|
||||
|
||||
[ "name1", ... ]
|
||||
|
||||
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"]
|
||||
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"]
|
||||
44
Makefile
44
Makefile
@@ -1,33 +1,35 @@
|
||||
GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
NATIVE_GOOS = $(shell unset GOOS; go env GOOS)
|
||||
NATIVE_GOARCH = $(shell unset GOARCH; go env GOARCH)
|
||||
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||
mkfile_dir := $(patsubst %/,%,$(dir $(mkfile_path)))
|
||||
STATIC := build/static/bundle.css build/static/bundle.js build/static/index.html
|
||||
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
|
||||
|
||||
.PHONY: all build clean
|
||||
all: build
|
||||
|
||||
build: AdguardDNS coredns
|
||||
build: $(TARGET)
|
||||
|
||||
$(STATIC):
|
||||
yarn --cwd client install
|
||||
yarn --cwd client run build-prod
|
||||
client/node_modules: client/package.json client/package-lock.json
|
||||
npm --prefix client install
|
||||
touch client/node_modules
|
||||
|
||||
AdguardDNS: $(STATIC) *.go
|
||||
echo mkfile_dir = $(mkfile_dir)
|
||||
go get -v -d .
|
||||
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go get -v github.com/gobuffalo/packr/...
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr build -ldflags="-X main.VersionString=$(GIT_VERSION)" -o AdguardDNS
|
||||
$(STATIC): $(JSFILES) client/node_modules
|
||||
npm --prefix client run build-prod
|
||||
|
||||
coredns: coredns_plugin/*.go dnsfilter/*.go
|
||||
echo mkfile_dir = $(mkfile_dir)
|
||||
go get -v -d github.com/coredns/coredns
|
||||
cd $(GOPATH)/src/github.com/coredns/coredns && grep -q 'dnsfilter:' plugin.cfg || sed -E -i.bak $$'s|^log:log|log:log\\\ndnsfilter:github.com/AdguardTeam/AdguardDNS/coredns_plugin|g' plugin.cfg
|
||||
cd $(GOPATH)/src/github.com/coredns/coredns && GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go generate
|
||||
cd $(GOPATH)/src/github.com/coredns/coredns && go get -v -d .
|
||||
cd $(GOPATH)/src/github.com/coredns/coredns && go build -o $(mkfile_dir)/coredns
|
||||
$(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.version=$(GIT_VERSION) -X main.channel=$(CHANNEL)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
|
||||
clean:
|
||||
rm -vf coredns AdguardDNS
|
||||
$(MAKE) cleanfast
|
||||
rm -rf build
|
||||
rm -rf client/node_modules
|
||||
|
||||
cleanfast:
|
||||
rm -f $(TARGET)
|
||||
|
||||
223
README.md
223
README.md
@@ -1,66 +1,217 @@
|
||||
# Self-hosted AdGuard DNS
|
||||
|
||||
<p align="center">
|
||||
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
|
||||
</p>
|
||||
<h3 align="center">Privacy protection center for you and your devices</h3>
|
||||
<p align="center">
|
||||
Free and open source, powerful network-wide ads & trackers blocking DNS server.
|
||||
</p>
|
||||
|
||||
AdGuard DNS is an ad-filtering DNS server with built-in phishing protection and optional family-friendly protection.
|
||||
<p align="center">
|
||||
<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://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>
|
||||
</p>
|
||||
|
||||
This repository describes how to set up and run your self-hosted instance of AdGuard DNS -- it comes with a web dashboard that can be accessed from browser to control the DNS server and change its settings, it also allows you to add your filters in both AdGuard and hosts format.
|
||||
<br />
|
||||
|
||||
If this seems too complicated, you can always use AdGuard DNS servers that provide same functionality — https://adguard.com/en/adguard-dns/overview.html
|
||||
<p align="center">
|
||||
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.gif" width="800" />
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
<hr />
|
||||
|
||||
Go to https://github.com/AdguardTeam/AdguardDNS/releases and download the binaries for your platform:
|
||||
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.
|
||||
|
||||
### Mac
|
||||
Download file `AdguardDNS_*_darwin_amd64.tar.gz`, then unpack it and follow [how to run](#How-to-run) instructions below.
|
||||
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.
|
||||
|
||||
### Linux
|
||||
Download file `AdguardDNS_*_linux_amd64.tar.gz`, then unpack it and follow [how to run](#How-to-run) instructions below.
|
||||
* [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 to build your own
|
||||
<a id="getting-started"></a>
|
||||
## Getting Started
|
||||
|
||||
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.
|
||||
|
||||
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
|
||||
|
||||
### Guides
|
||||
|
||||
* [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)
|
||||
|
||||
### API
|
||||
|
||||
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).
|
||||
|
||||
<a id="comparison"></a>
|
||||
## Comparing AdGuard Home to other solutions
|
||||
|
||||
<a id="comparison-adguard-dns"></a>
|
||||
### How is this different from public AdGuard DNS servers?
|
||||
|
||||
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:
|
||||
|
||||
* 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.**
|
||||
|
||||
<a id="comparison-pi-hole"></a>
|
||||
### How does AdGuard Home compare to Pi-Hole
|
||||
|
||||
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.
|
||||
|
||||
> We're not going to stop here. DNS sinkholing is not a bad starting point, but this is just the beginning.
|
||||
|
||||
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.
|
||||
|
||||
> 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.
|
||||
|
||||
| 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 lighthttpd |
|
||||
| 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) | ✅ | ❌ |
|
||||
|
||||
<a id="comparison-adblock"></a>
|
||||
### How does AdGuard Home compare to traditional ad blockers
|
||||
|
||||
It depends.
|
||||
|
||||
"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/)
|
||||
* [node.js](https://nodejs.org/en/download/)
|
||||
* [yarn](https://yarnpkg.com/en/docs/install)
|
||||
|
||||
You can either install it from these websites or use [brew.sh](https://brew.sh/) if you're on Mac:
|
||||
* [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:
|
||||
|
||||
```bash
|
||||
brew install go node yarn
|
||||
brew install go node
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
Open Terminal and execute these commands:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/AdguardTeam/AdguardDNS
|
||||
cd AdguardDNS
|
||||
git clone https://github.com/AdguardTeam/AdGuardHome
|
||||
cd AdGuardHome
|
||||
make
|
||||
```
|
||||
|
||||
## How to run
|
||||
|
||||
DNS works on port 53, which requires superuser privileges. Therefore, you need to run it with sudo:
|
||||
```bash
|
||||
sudo ./AdguardDNS
|
||||
#### (For devs) Upload translations
|
||||
```
|
||||
node upload.js
|
||||
```
|
||||
|
||||
Now open the browser and point it to http://localhost:3000/ to control AdGuard DNS server.
|
||||
|
||||
## Running without superuser
|
||||
|
||||
You can run it without superuser privileges, but you need to instruct it to use other port rather than 53. You can do that by opening `AdguardDNS.yaml` and adding this line:
|
||||
```yaml
|
||||
coredns:
|
||||
port: 53535
|
||||
#### (For devs) Download translations
|
||||
```
|
||||
node download.js
|
||||
```
|
||||
|
||||
If the file does not exist, create it and put these two lines down.
|
||||
|
||||
<a id="contributing"></a>
|
||||
## Contributing
|
||||
|
||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdguardDNS/pulls
|
||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||
|
||||
## Reporting issues
|
||||
<a id="test-unstable-versions"></a>
|
||||
### Test unstable versions
|
||||
|
||||
If you come across any problem, or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdguardDNS/issues) and click on the New issue button.
|
||||
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
|
||||
|
||||
Here is a link to AdGuard Home project: https://crowdin.com/project/adguard-applications
|
||||
|
||||
<a id="acknowledgments"></a>
|
||||
## Acknowledgments
|
||||
|
||||
This software wouldn't have been possible without:
|
||||
|
||||
* [Go](https://golang.org/dl/) and it's libraries:
|
||||
* [packr](https://github.com/gobuffalo/packr)
|
||||
* [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)
|
||||
* And many more node.js packages.
|
||||
* [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
|
||||
|
||||
You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuardHome. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded with Home features that we plan to implement.
|
||||
|
||||
For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file.
|
||||
|
||||
128
app.go
128
app.go
@@ -1,128 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
)
|
||||
|
||||
// VersionString will be set through ldflags, contains current version
|
||||
var VersionString = "undefined"
|
||||
|
||||
func main() {
|
||||
log.Printf("AdGuard DNS web interface backend, version %s\n", VersionString)
|
||||
box := packr.NewBox("build/static")
|
||||
{
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
config.ourBinaryDir = filepath.Dir(executable)
|
||||
}
|
||||
|
||||
// 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 configFilename *string
|
||||
var bindHost *string
|
||||
var bindPort *int
|
||||
var opts = []struct {
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callback func(value string)
|
||||
}{
|
||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }},
|
||||
{"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
|
||||
}},
|
||||
{"help", "h", "print this help", nil},
|
||||
}
|
||||
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]
|
||||
// short-circuit for help
|
||||
if v == "--help" || v == "-h" {
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
knownParam := false
|
||||
for _, opt := range opts {
|
||||
if v == "--"+opt.longName {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callback(os.Args[i])
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
if v == "-"+opt.shortName {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callback(os.Args[i])
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !knownParam {
|
||||
log.Printf("ERROR: unknown option %v\n", v)
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
}
|
||||
if configFilename != nil {
|
||||
config.ourConfigFilename = *configFilename
|
||||
}
|
||||
// parse from config file
|
||||
err := parseConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if bindHost != nil {
|
||||
config.BindHost = *bindHost
|
||||
}
|
||||
if bindPort != nil {
|
||||
config.BindPort = *bindPort
|
||||
}
|
||||
}
|
||||
|
||||
err := writeConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
|
||||
runStatsCollectors()
|
||||
runFilterRefreshers()
|
||||
|
||||
http.Handle("/", http.FileServer(box))
|
||||
registerControlHandlers()
|
||||
|
||||
URL := fmt.Sprintf("http://%s", address)
|
||||
log.Println("Go to " + URL)
|
||||
log.Fatal(http.ListenAndServe(address, 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
|
||||
43
changelog.config.js
Normal file
43
changelog.config.js
Normal file
@@ -0,0 +1,43 @@
|
||||
module.exports = {
|
||||
"disableEmoji": true,
|
||||
"list": [
|
||||
"+",
|
||||
"*",
|
||||
"-",
|
||||
],
|
||||
"maxMessageLength": 64,
|
||||
"minMessageLength": 3,
|
||||
"questions": [
|
||||
"type",
|
||||
"scope",
|
||||
"subject",
|
||||
"body",
|
||||
"issues"
|
||||
],
|
||||
"scopes": [
|
||||
"",
|
||||
"global",
|
||||
"dnsfilter",
|
||||
"home",
|
||||
"dnsforward",
|
||||
"dhcpd",
|
||||
"documentation"
|
||||
],
|
||||
"types": {
|
||||
"+": {
|
||||
"description": "A new feature",
|
||||
"emoji": "",
|
||||
"value": "+"
|
||||
},
|
||||
"*": {
|
||||
"description": "A code change that neither fixes a bug or adds a feature",
|
||||
"emoji": "",
|
||||
"value": "*"
|
||||
},
|
||||
"-": {
|
||||
"description": "A bug fix",
|
||||
"emoji": "",
|
||||
"value": "-"
|
||||
}
|
||||
}
|
||||
};
|
||||
13
client/.eslintrc
vendored
13
client/.eslintrc
vendored
@@ -13,6 +13,13 @@
|
||||
"commonjs": true
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "16.4"
|
||||
}
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"indent": ["error", 4, {
|
||||
"SwitchCase": 1,
|
||||
@@ -38,11 +45,9 @@
|
||||
}],
|
||||
"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",
|
||||
"import/prefer-default-export": "off"
|
||||
}
|
||||
}
|
||||
|
||||
13649
client/package-lock.json
generated
vendored
Normal file
13649
client/package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
34
client/package.json
vendored
34
client/package.json
vendored
@@ -3,46 +3,53 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-dev": "NODE_ENV=development webpack --config webpack.dev.js",
|
||||
"watch": "NODE_ENV=development webpack --config webpack.dev.js --watch",
|
||||
"build-prod": "NODE_ENV=production webpack --config webpack.prod.js",
|
||||
"build-dev": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js",
|
||||
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js --watch",
|
||||
"build-prod": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.js",
|
||||
"lint": "eslint frontend/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nivo/line": "^0.42.1",
|
||||
"axios": "^0.18.0",
|
||||
"@nivo/line": "^0.49.1",
|
||||
"axios": "^0.19.0",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^1.29.0",
|
||||
"file-saver": "^1.3.8",
|
||||
"lodash": "^4.17.10",
|
||||
"i18next": "^12.0.0",
|
||||
"i18next-browser-languagedetector": "^2.2.3",
|
||||
"lodash": "^4.17.15",
|
||||
"nanoid": "^1.2.3",
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.4.0",
|
||||
"react-click-outside": "^3.0.1",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-i18next": "^8.2.0",
|
||||
"react-modal": "^3.4.5",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-redux-loading-bar": "^4.0.7",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-router-hash-link": "^1.2.2",
|
||||
"react-table": "^6.8.6",
|
||||
"react-transition-group": "^2.4.0",
|
||||
"redux": "^4.0.0",
|
||||
"redux-actions": "^2.4.0",
|
||||
"redux-form": "^7.4.2",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"tabler-react": "^1.10.0",
|
||||
"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",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"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",
|
||||
@@ -53,7 +60,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",
|
||||
@@ -61,12 +67,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 DNS</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>
|
||||
249
client/src/__locales/bg.json
Normal file
249
client/src/__locales/bg.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"url_added_successfully": "Успешно добавен URL",
|
||||
"check_dhcp_servers": "Проверка за активен DHCP сървър",
|
||||
"save_config": "Запиши настройките",
|
||||
"enabled_dhcp": "DHCP е разрешен",
|
||||
"disabled_dhcp": "DHCP е забранен",
|
||||
"dhcp_title": "DHCP сървър (тестови!)",
|
||||
"dhcp_description": "Ако рутера ви не раздава DHCP адреси, може да използвате вградения в AdGuard DHCP сървър.",
|
||||
"dhcp_enable": "Рзреши DHCP сървъра",
|
||||
"dhcp_disable": "Забрани DHCP сървъра",
|
||||
"dhcp_not_found": "Вашата мрежа няма активен DHCP сървър. Безопасно е ползването на вградения DHCP сървър.",
|
||||
"dhcp_found": "Вашата мрежа вече има активен DHCP сървър. Не е безопасно ползването на втори!",
|
||||
"dhcp_leases": "DHCP раздадени адреси",
|
||||
"dhcp_leases_not_found": "Няма намерени активни DHCP адреси",
|
||||
"dhcp_config_saved": "Запиши конфигурацията на DHCP сървъра",
|
||||
"form_error_required": "Задължително поле",
|
||||
"form_error_ip_format": "Невалиден IPv4 адрес",
|
||||
"form_error_positive": "Проверете дали е положително число",
|
||||
"dhcp_form_gateway_input": "IP шлюз",
|
||||
"dhcp_form_subnet_input": "Мрежова маска",
|
||||
"dhcp_form_range_title": "Група от IP адреси",
|
||||
"dhcp_form_range_start": "Първи адрес",
|
||||
"dhcp_form_range_end": "Последен адрес",
|
||||
"dhcp_form_lease_title": "Отдадени адреси (секунди)",
|
||||
"dhcp_form_lease_input": "Отчет за раздадени адреси",
|
||||
"dhcp_interface_select": "Изберете мрежов адаптер за DHCP",
|
||||
"dhcp_hardware_address": "Хардуерни адреси (MAC)",
|
||||
"dhcp_ip_addresses": "IP адреси",
|
||||
"dhcp_table_hostname": "Име на устройство",
|
||||
"dhcp_table_expires": "История",
|
||||
"dhcp_warning": "Ако искате да използвате вградения DHCP сървър, трябва да няма друг активен DHCP в мрежата Ви!",
|
||||
"back": "Назад",
|
||||
"dashboard": "Табло",
|
||||
"settings": "Настройки",
|
||||
"filters": "Филтри",
|
||||
"query_log": "История на заявките",
|
||||
"faq": "ЧЗВ",
|
||||
"version": "версия",
|
||||
"address": "адрес",
|
||||
"on": "ВКЛЮЧЕНО",
|
||||
"off": "ИЗКЛЮЧЕНО",
|
||||
"copyright": "Авторско право",
|
||||
"homepage": "Домашна страница",
|
||||
"report_an_issue": "Съобщи за проблем",
|
||||
"enable_protection": "Разреши защита",
|
||||
"enabled_protection": "Защитата е разрешена",
|
||||
"disable_protection": "Забрани защита",
|
||||
"disabled_protection": "Защитата е забранена",
|
||||
"refresh_statics": "Обнови статистиката",
|
||||
"dns_query": "DNS запитвания",
|
||||
"blocked_by": "Блокирани от",
|
||||
"stats_malware_phishing": "вируси/атаки",
|
||||
"stats_adult": "сайтове за възрастни",
|
||||
"stats_query_domain": "Най-отваряни страници",
|
||||
"for_last_24_hours": "за последните 24 часа",
|
||||
"no_domains_found": "Няма намерени резултати",
|
||||
"requests_count": "Сума на заявките",
|
||||
"top_blocked_domains": "Най-блокирани страници",
|
||||
"top_clients": "Най-активни IP адреси",
|
||||
"no_clients_found": "Нямa намерени адреси",
|
||||
"general_statistics": "Обща статисика",
|
||||
"number_of_dns_query_24_hours": "Сума на DNS заявки за последните 24 часа",
|
||||
"number_of_dns_query_blocked_24_hours": "Сума на блокирани DNS заявки от филтрите за реклама и местни",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Сума на блокирани DNS заявки от AdGuard свързани със сигурността",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Сума на блокирани сайтове за възрастни",
|
||||
"enforced_save_search": "Активирано Безопасно Търсене",
|
||||
"number_of_dns_query_to_safe_search": "Сума на DNS заявки при който е приложено Безопасно Търсене",
|
||||
"average_processing_time": "Средно време за обработка",
|
||||
"average_processing_time_hint": "Средно време за обработка на DNS заявки в милисекунди",
|
||||
"block_domain_use_filters_and_hosts": "Блокирани домейни - общи и местни филтри",
|
||||
"filters_block_toggle_hint": "Може да зададете собствени настройки в <a href='#filters'>Филтри</a>.",
|
||||
"use_adguard_browsing_sec": "Използвайте AdGuard модул за сигурността",
|
||||
"use_adguard_browsing_sec_hint": "Модул Сигурност в AdGuard Home проверява всяка страница която отваряте дали е в черните списъци застрашаващи вашата сигурност. Използва се програмен интерфейс който защитава вашата анонимност и изпраща само SHA256 сума базирана на част от домейна който посещавате.",
|
||||
"use_adguard_parental": "Включи AdGuard Родителски Надзор",
|
||||
"use_adguard_parental_hint": "Модул XXX в AdGuard Home ще провери дали страницата има материали за възвъстни. Използва се същия API за анонимност като при модула за Сигурност.",
|
||||
"enforce_safe_search": "Включи Безопасно Търсене",
|
||||
"enforce_save_search_hint": "AdGuard Home прилага Безопасно Търсене в следните търсачки и сайтове: Google, Youtube, Bing, и Yandex.",
|
||||
"no_servers_specified": "Няма избрани услуги",
|
||||
"no_settings": "Няма настройки",
|
||||
"general_settings": "Общи настройки",
|
||||
"upstream_dns": "Главен DNS сървър",
|
||||
"upstream_dns_hint": "Ако оставите празно, AdGuard Home ще използва <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> за главен. Използвай tls:// представка за DNS използващи TLS връзка.",
|
||||
"test_upstream_btn": "Тествай главния DNS",
|
||||
"apply_btn": "Приложи",
|
||||
"disabled_filtering_toast": "Забрани филтрирането",
|
||||
"enabled_filtering_toast": "Разреши фитрирането",
|
||||
"disabled_safe_browsing_toast": "Забрани безопасно-сърфиране",
|
||||
"enabled_safe_browsing_toast": "Рзреши безопасно-сърфиране",
|
||||
"disabled_parental_toast": "Забрани Родителски Надзор",
|
||||
"enabled_parental_toast": "Разреши Родителски Надзор",
|
||||
"disabled_safe_search_toast": "Забрани Безопасно Търсене",
|
||||
"enabled_save_search_toast": "Разреши Безопасно Търсене",
|
||||
"enabled_table_header": "Разреши",
|
||||
"name_table_header": "Име",
|
||||
"filter_url_table_header": "URL филтър",
|
||||
"rules_count_table_header": "Правила общо",
|
||||
"last_time_updated_table_header": "Последно обновен",
|
||||
"actions_table_header": "Действия",
|
||||
"delete_table_action": "Изтрий",
|
||||
"filters_and_hosts": "Черни списъци с общи и местни филтри",
|
||||
"filters_and_hosts_hint": "AdGuard Home разбира adblock и host синтаксис.",
|
||||
"no_filters_added": "Няма добавени филтри",
|
||||
"add_filter_btn": "Добави филтър",
|
||||
"cancel_btn": "Откажи",
|
||||
"enter_name_hint": "Въведи име",
|
||||
"enter_url_hint": "Въведи URL",
|
||||
"check_updates_btn": "Провери за актуализация",
|
||||
"new_filter_btn": "Въведи нов филтър",
|
||||
"enter_valid_filter_url": "Моля въведете валиден URL за филтъра или проверете host правописа.",
|
||||
"custom_filter_rules": "Местни правила за филтриране",
|
||||
"custom_filter_rules_hint": "Въвеждайте всяко правило на нов ред. Може да използвате adblock или hosts файлов синтаксис.",
|
||||
"examples_title": "Примери",
|
||||
"example_meaning_filter_block": "Блокирай достъп до домейн example.org и всички под домейни.",
|
||||
"example_meaning_filter_whitelist": "Разреши достъп до домейн example.org и всичките му под домейни.",
|
||||
"example_meaning_host_block": "AdGuard Home ще отговори с 127.0.0.1 = празен адрес за домейн example.org (но не и за под домейни).",
|
||||
"example_comment": "! Това е коментар",
|
||||
"example_comment_meaning": "пример за коментар",
|
||||
"example_comment_hash": "# Това е също коментар",
|
||||
"example_upstream_regular": "класически DNS (UDP протокол)",
|
||||
"example_upstream_dot": "криптиран <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-върху-TLS</a>",
|
||||
"example_upstream_doh": "криптиран <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-върху-HTTPS</a>",
|
||||
"example_upstream_sdns": "може да ползвате <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Подписване</a> за <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> или <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-върху-HTTPS</a> сървъри",
|
||||
"example_upstream_tcp": "класически DNS (TCP протокол)",
|
||||
"all_filters_up_to_date_toast": "Всички филти са актуализирани",
|
||||
"updated_upstream_dns_toast": "Глобалните DNS сървъри са обновени",
|
||||
"dns_test_ok_toast": "Въведените DNS сървъри работят коректно",
|
||||
"dns_test_not_ok_toast": "Сървър \"{{key}}\": не работи, моля проверете дали е въведен коректно",
|
||||
"unblock_btn": "Отблокирай",
|
||||
"block_btn": "Блокирай",
|
||||
"time_table_header": "Време",
|
||||
"domain_name_table_header": "Име на домейн",
|
||||
"type_table_header": "Тип",
|
||||
"response_table_header": "Отговор",
|
||||
"client_table_header": "Клиент",
|
||||
"empty_response_status": "Празен",
|
||||
"show_all_filter_type": "Покажи всички",
|
||||
"show_filtered_type": "Покажи филтрирани",
|
||||
"no_logs_found": "Няма история",
|
||||
"disabled_log_btn": "Забрани историята",
|
||||
"download_log_file_btn": "Смъкни историята",
|
||||
"refresh_btn": "Обнови",
|
||||
"enabled_log_btn": "Разреши историята",
|
||||
"last_dns_queries": "Последните 5000 DNS заявки",
|
||||
"previous_btn": "Предходен",
|
||||
"next_btn": "Следващ",
|
||||
"loading_table_status": "Зареждане...",
|
||||
"page_table_footer_text": "Страница",
|
||||
"of_table_footer_text": "от",
|
||||
"rows_table_footer_text": "редове",
|
||||
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
||||
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране",
|
||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране",
|
||||
"query_log_disabled_toast": "Историята е забранена",
|
||||
"query_log_enabled_toast": "Историята е разрешена",
|
||||
"source_label": "Източник",
|
||||
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
||||
"category_label": "Категория",
|
||||
"rule_label": "Правило",
|
||||
"filter_label": "Филтър",
|
||||
"unknown_filter": "Непознат филтър {{filterId}}",
|
||||
"install_welcome_title": "Добре дошли в AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home e мрежово решение за блокиране на реклами и тракери на DNS ниво. Създадено е за да ви даде пълен контрол над мрежата и всичките ви устройства, без да е необходимо допълнително инсталиране на друг софтуер.",
|
||||
"install_settings_title": "Администрация",
|
||||
"install_settings_listen": "Активни интерфейси",
|
||||
"install_settings_port": "Порт",
|
||||
"install_settings_interface_link": "Вашата AdGuard Home страница за администрация ще е достъпна на този адрес:",
|
||||
"form_error_port": "Моля въведете валиден порт",
|
||||
"install_settings_dns": "DNS сървър",
|
||||
"install_settings_dns_desc": "За да работи, ще трябва да настроите вашият рутер или устройства да ползват DNS сървър с адрес:",
|
||||
"install_settings_all_interfaces": "Всички интерфейси",
|
||||
"install_auth_title": "Удостоверяване",
|
||||
"install_auth_desc": "Много е важно да зададете име и парола за достъп до вашия панел за администрация на AdGuard Home. Препоръчваме ви да зададете име и парола независимо че го ползвате само в къщи.",
|
||||
"install_auth_username": "Потребител",
|
||||
"install_auth_password": "Парола",
|
||||
"install_auth_confirm": "Потвърдете паролата",
|
||||
"install_auth_username_enter": "Въведете потребител",
|
||||
"install_auth_password_enter": "Въведете парола",
|
||||
"install_step": "Стъпка",
|
||||
"install_devices_title": "Настройте вашето устройство",
|
||||
"install_devices_desc": "Да започнете да използвате AdGuard Home, е необходимо да настроите вашите устройства.",
|
||||
"install_submit_title": "Поздравления!",
|
||||
"install_submit_desc": "Настройката е завършена, може да започнете да ползвате AdGuard Home.",
|
||||
"install_devices_router": "Рутер",
|
||||
"install_devices_router_desc": "Ако настроите вашият рутер няма нужда ръчно да настройвате всяко едно от устрйствата в мрежата.",
|
||||
"install_devices_address": "AdGuard Home DNS сървърът е на следния адрес",
|
||||
"install_devices_router_list_1": "Отворете страницата за настройки на вашия рутер. Обикновено тя се намира на URL (тук http://192.168.0.1/ или тук http://192.168.1.1/). За достъп може да ви трябва парола. Ако сте забравили паролата може да я ресетнете като натиснета скрития ресет бутон - внимание това ще ресетне всички настройки на рутера до фабрични! Някой рутери могат да бъдате администрирани от софтуер или приложение, който би трябвало да е вече инсталиран на компютъра/телефона ви.",
|
||||
"install_devices_router_list_2": "Намерета DHCP/DNS настройки. В под раздел DHCP рзгледайте и намерете къде е полето за DNS настройка в което може да въведете персонализирани настройки за DNS сървъри.",
|
||||
"install_devices_router_list_3": "Въведете адресът на AdGuard Home сървъра.",
|
||||
"install_devices_windows_list_1": "Отворете Контролния Панел през Старт меню или чрез функция търсене на Windows.",
|
||||
"install_devices_windows_list_2": "Вървете до Настрйки на Мрежи и Интернет и от там изберете Мрежи и Център за Споделяне.",
|
||||
"install_devices_windows_list_3": "От ляво на екрана намерете Смени настроки на мрежовия адаптер и кликнете на него.",
|
||||
"install_devices_windows_list_4": "Изберете този който е активен, дясно-кликване и изберета Свойства.",
|
||||
"install_devices_windows_list_5": "Намерете Интернет Протокол Версия 4 (TCP/IP) в списъка, изберете и кликнете отново на Свойства.",
|
||||
"install_devices_windows_list_6": "Изберете Използвай следните адреси за DNS сърсъри и въведете адреса на AdGuard Home сървъра ви.",
|
||||
"install_devices_macos_list_1": "Цъкнете на Apple иконката и изберете System Preferences...",
|
||||
"install_devices_macos_list_2": "Цъкнете на Network.",
|
||||
"install_devices_macos_list_3": "Изберете зелената-активна връзка в списъка и кликнете на Advanced.",
|
||||
"install_devices_macos_list_4": "Изберете DNS таб и кликнете на + за да въведете адреса на AdGuard Home сървъра.",
|
||||
"install_devices_android_list_1": "Изберете Android Меню от домашния екран, и цъкнете на Настройки.",
|
||||
"install_devices_android_list_2": "Цъкнете на Wi-Fi меню. На екрана ще се появат всички безжични прежи (там няма възможност за въвеждане на DNS настройки).",
|
||||
"install_devices_android_list_3": "Цъкнете и задръжде върху Вие сте свързани с.., и кликнете на Модифицирай мрежа.",
|
||||
"install_devices_android_list_4": "На някой устройства може да е неоходимо да маркирате покажи Разширени, за да видите всички настройки. За да промените Android DNS настройките, може да се наложи да промените IP настройките от DHCP на Статични.",
|
||||
"install_devices_android_list_5": "Променете стойностите на DNS 1 и DNS 2 да използват AdGuard Home сървъра.",
|
||||
"install_devices_ios_list_1": "От начален екран, цъкнете на Settings.",
|
||||
"install_devices_ios_list_2": "Изберете Wi-Fi от лявото меню (там няма възможност за въвеждане на DNS настройки).",
|
||||
"install_devices_ios_list_3": "Клинете на името на активната мрежа към която сте свързани.",
|
||||
"install_devices_ios_list_4": "В полето за DNS изберете ръчно и въведете адреса на AdGuard Home сървъра.",
|
||||
"get_started": "Да започваме",
|
||||
"next": "Следващ",
|
||||
"open_dashboard": "Отвори табло",
|
||||
"install_saved": "Успешно записано",
|
||||
"encryption_title": "Криптиране",
|
||||
"encryption_desc": "Подържа се сигурна връзка (HTTPS/TLS) включително за DNS и страницата за администрация",
|
||||
"encryption_config_saved": "Конфигурацията е успешно записана",
|
||||
"encryption_server": "Име на сървъра",
|
||||
"encryption_server_enter": "Въведете име на домейна",
|
||||
"encryption_server_desc": "За да използвате HTTPS, трябва името на сървъра да съвпада с това на SSL сертификата.",
|
||||
"encryption_redirect": "Автоматично пренасочване към HTTPS",
|
||||
"encryption_redirect_desc": "Служи за автоматично пренасочване от HTTP към HTTPS на страницата за Администрация в AdGuard Home.",
|
||||
"encryption_https": "HTTPS порт",
|
||||
"encryption_https_desc": "Ако зададете HTTPS порт, страницата за Администрация на AdGuard Home ще бъде достъпна на HTTPS, и също ще отговаря на DNS-върху-HTTPS '/dns-запитвания'.",
|
||||
"encryption_dot": "DNS-върху-TLS порт",
|
||||
"encryption_dot_desc": "Ако порта е конфигуриран, AdGuard Home ще стартира и сървър за DNS-върху-TLS.",
|
||||
"encryption_certificates": "Сертификати",
|
||||
"encryption_certificates_desc": "За да използвате сигурна връзка, ще трябва да осигурите SSL сертификати за вашия домейн. Може да заявите безплатен от <0>{{link}}</0> или да закупите от Certificate Authorities.",
|
||||
"encryption_certificates_input": "Копирай/постави вашия PEM-кодиран сертификат тук.",
|
||||
"encryption_status": "Състояние",
|
||||
"encryption_expire": "Годен до",
|
||||
"encryption_key": "Частен ключ",
|
||||
"encryption_key_input": "Копирай/постави вашия PEM-кодиран чpастен ключ за вашия сертификат тук.",
|
||||
"encryption_enable": "Разpеши криптиране (HTTPS, DNS-върху-HTTPS, и DNS-върху-TLS)",
|
||||
"encryption_enable_desc": "Ако сте разрешили криптиране, страницата за Администрация на AdGuard Home ще бъде достъпна през HTTPS, и DNS сървъра ще отговаря също на запитвания DNS-върху-HTTPS и DNS-върху-TLS.",
|
||||
"encryption_chain_valid": "Йерархията от сертификати е валидна",
|
||||
"encryption_chain_invalid": "Йерархията от сертификати е невалидна",
|
||||
"encryption_key_valid": "Това е валиден {{type}} частен ключ",
|
||||
"encryption_key_invalid": "Това е невалиден {{type}} частен ключ",
|
||||
"encryption_subject": "Тема",
|
||||
"encryption_issuer": "Изпълнител",
|
||||
"encryption_hostnames": "Имена на хоста",
|
||||
"encryption_reset": "Сигурни ли сте че искате да изтриете настройките за криптиране?",
|
||||
"topline_expiring_certificate": "Вашият SSL сертификат изтича. Обнови <0>Настройки за криптиране</0>.",
|
||||
"topline_expired_certificate": "Вашият SSL сертификат е изтекъл. Обнови <0>Настройки за криптиране</0>.",
|
||||
"form_error_port_range": "Въведете порт в диапазона 80-65535",
|
||||
"form_error_port_unsafe": "Не е безопасно да използвате този порт",
|
||||
"form_error_equal": "Не трябва да съвпада",
|
||||
"form_error_password": "Паролата не съвпада",
|
||||
"reset_settings": "Изтрий всички настройки",
|
||||
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация."
|
||||
}
|
||||
357
client/src/__locales/en.json
Normal file
357
client/src/__locales/en.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"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",
|
||||
"disabled_dhcp": "DHCP server disabled",
|
||||
"dhcp_title": "DHCP server (experimental!)",
|
||||
"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": "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",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
"dhcp_form_range_start": "Range start",
|
||||
"dhcp_form_range_end": "Range end",
|
||||
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
|
||||
"dhcp_form_lease_input": "Lease duration",
|
||||
"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",
|
||||
"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",
|
||||
"disabled_protection": "Disabled protection",
|
||||
"refresh_statics": "Refresh statistics",
|
||||
"dns_query": "DNS Queries",
|
||||
"blocked_by": "Blocked by Filters",
|
||||
"stats_malware_phishing": "Blocked malware/phishing",
|
||||
"stats_adult": "Blocked adult websites",
|
||||
"stats_query_domain": "Top queried domains",
|
||||
"for_last_24_hours": "for the last 24 hours",
|
||||
"no_domains_found": "No domains found",
|
||||
"requests_count": "Requests count",
|
||||
"top_blocked_domains": "Top blocked domains",
|
||||
"top_clients": "Top clients",
|
||||
"no_clients_found": "No clients found",
|
||||
"general_statistics": "General statistics",
|
||||
"number_of_dns_query_24_hours": "A number of DNS quieries processed for the last 24 hours",
|
||||
"number_of_dns_query_blocked_24_hours": "A number of DNS requests blocked by adblock filters and hosts blocklists",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "A number of DNS requests blocked by the AdGuard browsing security module",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "A number of adult websites blocked",
|
||||
"enforced_save_search": "Enforced safe search",
|
||||
"number_of_dns_query_to_safe_search": "A number of DNS requests to search engines for which Safe Search was enforced",
|
||||
"average_processing_time": "Average processing time",
|
||||
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
|
||||
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
|
||||
"filters_block_toggle_hint": "You can setup blocking rules in the <a href='#filters'>Filters</a> settings.",
|
||||
"use_adguard_browsing_sec": "Use AdGuard browsing security web service",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home will check if domain is blacklisted by the browsing security web service. It will use privacy-friendly lookup API to perform the check: only a short prefix of the domain name SHA256 hash is sent to the server.",
|
||||
"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, 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.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
"apply_btn": "Apply",
|
||||
"disabled_filtering_toast": "Disabled filtering",
|
||||
"enabled_filtering_toast": "Enabled filtering",
|
||||
"disabled_safe_browsing_toast": "Disabled safebrowsing",
|
||||
"enabled_safe_browsing_toast": "Enabled safebrowsing",
|
||||
"disabled_parental_toast": "Disabled parental control",
|
||||
"enabled_parental_toast": "Enabled parental control",
|
||||
"disabled_safe_search_toast": "Disabled safe search",
|
||||
"enabled_save_search_toast": "Enabled safe search",
|
||||
"enabled_table_header": "Enabled",
|
||||
"name_table_header": "Name",
|
||||
"filter_url_table_header": "Filter URL",
|
||||
"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.",
|
||||
"no_filters_added": "No filters added",
|
||||
"add_filter_btn": "Add filter",
|
||||
"cancel_btn": "Cancel",
|
||||
"enter_name_hint": "Enter name",
|
||||
"enter_url_hint": "Enter URL",
|
||||
"check_updates_btn": "Check updates",
|
||||
"new_filter_btn": "New filter subscription",
|
||||
"enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.",
|
||||
"custom_filter_rules": "Custom filtering rules",
|
||||
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
|
||||
"examples_title": "Examples",
|
||||
"example_meaning_filter_block": "block access to the example.org domain and all its subdomains",
|
||||
"example_meaning_filter_whitelist": "unblock access to the example.org domain and all its subdomains",
|
||||
"example_meaning_host_block": "AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).",
|
||||
"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 <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",
|
||||
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
|
||||
"unblock_btn": "Unblock",
|
||||
"block_btn": "Block",
|
||||
"time_table_header": "Time",
|
||||
"domain_name_table_header": "Domain name",
|
||||
"type_table_header": "Type",
|
||||
"response_table_header": "Response",
|
||||
"client_table_header": "Client",
|
||||
"empty_response_status": "Empty",
|
||||
"show_all_filter_type": "Show all",
|
||||
"show_filtered_type": "Show filtered",
|
||||
"no_logs_found": "No logs found",
|
||||
"disabled_log_btn": "Disable log",
|
||||
"download_log_file_btn": "Download log file",
|
||||
"refresh_btn": "Refresh",
|
||||
"enabled_log_btn": "Enable log",
|
||||
"last_dns_queries": "Last 5000 DNS queries",
|
||||
"previous_btn": "Previous",
|
||||
"next_btn": "Next",
|
||||
"loading_table_status": "Loading...",
|
||||
"page_table_footer_text": "Page",
|
||||
"of_table_footer_text": "of",
|
||||
"rows_table_footer_text": "rows",
|
||||
"updated_custom_filtering_toast": "Updated the custom filtering rules",
|
||||
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules",
|
||||
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules",
|
||||
"query_log_disabled_toast": "Query log disabled",
|
||||
"query_log_enabled_toast": "Query log enabled",
|
||||
"source_label": "Source",
|
||||
"found_in_known_domain_db": "Found in the known domains database.",
|
||||
"category_label": "Category",
|
||||
"rule_label": "Rule",
|
||||
"filter_label": "Filter",
|
||||
"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.",
|
||||
"rewrite_added": "DNS rewrite for \"{{key}}\" successfully added",
|
||||
"rewrite_deleted": "DNS rewrite for \"{{key}}\" successfully deleted",
|
||||
"rewrite_add": "Add DNS rewrite",
|
||||
"rewrite_not_found": "No DNS rewrites found",
|
||||
"rewrite_confirm_delete": "Are you sure you want to delete DNS rewrite for \"{{key}}\"?",
|
||||
"rewrite_desc": "Allows to easily configure custom DNS response for a specific domain name.",
|
||||
"rewrite_applied": "Applied Rewrite rule",
|
||||
"dns_rewrites": "DNS rewrites",
|
||||
"form_domain": "Enter domain",
|
||||
"form_answer": "Enter IP address or domain name",
|
||||
"form_error_domain_format": "Invalid domain format",
|
||||
"form_error_answer_format": "Invalid answer format",
|
||||
"configure": "Configure",
|
||||
"main_settings": "Main settings",
|
||||
"block_services": "Block specific services",
|
||||
"blocked_services": "Blocked services",
|
||||
"blocked_services_desc": "Allows to quickly block popular sites and services.",
|
||||
"blocked_services_saved": "Blocked services successfully saved",
|
||||
"blocked_services_global": "Use global blocked services",
|
||||
"blocked_service": "Blocked service",
|
||||
"block_all": "Block all",
|
||||
"unblock_all": "Unblock all"
|
||||
}
|
||||
357
client/src/__locales/es.json
Normal file
357
client/src/__locales/es.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"client_settings": "Configuración de clientes",
|
||||
"example_upstream_reserved": "puede especificar el DNS de subida <0>para un dominio específico</0>",
|
||||
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente 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ñadida correctamente",
|
||||
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
|
||||
"save_config": "Guardar configuración",
|
||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||
"disabled_dhcp": "Servidor DHCP deshabilitado",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Si su router no proporciona la configuración 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ún servidor DHCP activo en la red, sin embargo le recomendamos que lo vuelva a comprobar manualmente, ya que nuestra prueba automática no ofrece actualmente una garantía 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": "Asignaciones DHCP estáticas",
|
||||
"dhcp_leases_not_found": "No se han encontrado asignaciones DHCP",
|
||||
"dhcp_config_saved": "Configuración del servidor DHCP guardada",
|
||||
"form_error_required": "Campo obligatorio",
|
||||
"form_error_ip_format": "Formato IPv4 no válido",
|
||||
"form_error_mac_format": "Formato MAC no válido",
|
||||
"form_error_positive": "Debe ser mayor que 0",
|
||||
"dhcp_form_gateway_input": "IP de puerta de enlace",
|
||||
"dhcp_form_subnet_input": "Máscara 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ón DHCP (en segundos)",
|
||||
"dhcp_form_lease_input": "Duración de asignación",
|
||||
"dhcp_interface_select": "Seleccione la interfaz DHCP",
|
||||
"dhcp_hardware_address": "Dirección 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úrese de que no hay otro servidor DHCP activo en su red. ¡De 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ón IP estática. No hemos podido determinar si esta interfaz de red está configurada utilizando una dirección IP estática. Por favor establezca una dirección IP estática manualmente.",
|
||||
"dhcp_dynamic_ip_found": "Su sistema utiliza la configuración de dirección IP dinámica para la interfaz <0>{{interfaceName}}</0>. Para poder utilizar el servidor DHCP se debe establecer una dirección IP estática. Su dirección IP actual es <0>{{ipAddress}}</0>. Si presiona el botón Habilitar servidor DHCP, estableceremos automáticamente esta dirección IP como estática.",
|
||||
"dhcp_lease_added": "Asignación estática \"{{key}}\" añadido correctamente",
|
||||
"dhcp_lease_deleted": "Asignación estática \"{{key}}\" eliminado correctamente",
|
||||
"dhcp_new_static_lease": "Nueva asignación estática",
|
||||
"dhcp_static_leases_not_found": "No se han encontrado asignaciones DHCP estáticas",
|
||||
"dhcp_add_static_lease": "Añadir asignación estática",
|
||||
"delete_confirm": "¿Está seguro de que desea eliminar \"{{key}}\"?",
|
||||
"form_enter_hostname": "Ingrese el nombre del host",
|
||||
"error_details": "Detalles del error",
|
||||
"back": "Atrás",
|
||||
"dashboard": "Panel de control",
|
||||
"settings": "Configuración",
|
||||
"filters": "Filtros",
|
||||
"query_log": "Registro de consultas",
|
||||
"faq": "Preguntas frecuentes",
|
||||
"version": "Versión",
|
||||
"address": "dirección",
|
||||
"on": "Activado",
|
||||
"off": "Desactivado",
|
||||
"copyright": "Copyright",
|
||||
"homepage": "Página de inicio",
|
||||
"report_an_issue": "Reportar un error",
|
||||
"privacy_policy": "Política de privacidad",
|
||||
"enable_protection": "Habilitar protección",
|
||||
"enabled_protection": "Protección habilitada",
|
||||
"disable_protection": "Deshabilitar protección",
|
||||
"disabled_protection": "Protección deshabilitada",
|
||||
"refresh_statics": "Restablecer estadísticas",
|
||||
"dns_query": "Consultas DNS",
|
||||
"blocked_by": "Bloqueado por filtros",
|
||||
"stats_malware_phishing": "Malware/phishing bloqueado",
|
||||
"stats_adult": "Sitios web para adultos bloqueado",
|
||||
"stats_query_domain": "Dominios más consultados",
|
||||
"for_last_24_hours": "en las últimas 24 horas",
|
||||
"no_domains_found": "No se han encontrado dominios",
|
||||
"requests_count": "Número de peticiones",
|
||||
"top_blocked_domains": "Dominios más bloqueados",
|
||||
"top_clients": "Clientes más frecuentes",
|
||||
"no_clients_found": "No se han encontrado clientes",
|
||||
"general_statistics": "Estadísticas generales",
|
||||
"number_of_dns_query_24_hours": "Número de consultas DNS procesadas durante las últimas 24 horas",
|
||||
"number_of_dns_query_blocked_24_hours": "Número de peticiones DNS bloqueadas por los filtros y listas de bloqueo de hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Número de peticiones DNS bloqueadas por el módulo de seguridad de navegación de AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Número de sitios web para adultos bloqueado",
|
||||
"enforced_save_search": "Búsquedas seguras forzadas",
|
||||
"number_of_dns_query_to_safe_search": "Número de peticiones DNS a los motores de búsqueda para los que se aplicó la búsqueda segura forzada",
|
||||
"average_processing_time": "Tiempo promedio de procesamiento",
|
||||
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petición 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 la configuración de <a href='#filters'>filtros</a>.",
|
||||
"use_adguard_browsing_sec": "Usar el servicio web de seguridad de navegación de AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home comprobará si el dominio está en la lista negra del servicio web de seguridad de navegación. Utilizará la API de búsqueda amigable con la privacidad para realizar la comprobación: solo se envía 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á si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad del servicio web de seguridad de navegación.",
|
||||
"enforce_safe_search": "Forzar búsqueda segura",
|
||||
"enforce_save_search_hint": "AdGuard Home puede forzar la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo y Yandex.",
|
||||
"no_servers_specified": "No hay servidores especificados",
|
||||
"no_settings": "Sin configuración",
|
||||
"general_settings": "Configuración general",
|
||||
"dns_settings": "Configuración del DNS",
|
||||
"encryption_settings": "Configuración de cifrado",
|
||||
"dhcp_settings": "Configuración DHCP",
|
||||
"upstream_dns": "Servidores DNS de subida",
|
||||
"upstream_dns_hint": "Si mantiene este campo vacío, AdGuard Home utilizará <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": "Filtrado deshabilitado",
|
||||
"enabled_filtering_toast": "Filtrado habilitado",
|
||||
"disabled_safe_browsing_toast": "Búsqueda segura deshabilitada",
|
||||
"enabled_safe_browsing_toast": "Búsqueda segura habilitada",
|
||||
"disabled_parental_toast": "Control parental deshabilitado",
|
||||
"enabled_parental_toast": "Control parental habilitado",
|
||||
"disabled_safe_search_toast": "Búsqueda segura deshabilitada",
|
||||
"enabled_save_search_toast": "Búsqueda segura habilitada",
|
||||
"enabled_table_header": "Habilitado",
|
||||
"name_table_header": "Nombre",
|
||||
"filter_url_table_header": "URL del filtro",
|
||||
"rules_count_table_header": "Número de reglas",
|
||||
"last_time_updated_table_header": "Última actualización",
|
||||
"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 las reglas básicas de bloqueo y la sintaxis de los archivos hosts.",
|
||||
"no_filters_added": "No hay filtros añadidos",
|
||||
"add_filter_btn": "Añadir filtro",
|
||||
"cancel_btn": "Cancelar",
|
||||
"enter_name_hint": "Ingrese el nombre",
|
||||
"enter_url_hint": "Ingrese la URL",
|
||||
"check_updates_btn": "Buscar actualizaciones",
|
||||
"new_filter_btn": "Nueva suscripción a filtro",
|
||||
"enter_valid_filter_url": "Ingrese una URL válida para suscribirse a un filtro o archivo hosts.",
|
||||
"custom_filter_rules": "Reglas de filtrado personalizado",
|
||||
"custom_filter_rules_hint": "Ingrese una regla por línea. Puede utilizar reglas de bloqueo o la sintaxis de los archivos hosts.",
|
||||
"examples_title": "Ejemplos",
|
||||
"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á la dirección 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).",
|
||||
"example_comment": "! Aquí va un comentario",
|
||||
"example_comment_meaning": "solo un comentario",
|
||||
"example_comment_hash": "# También un comentario",
|
||||
"example_regex_meaning": "bloquear el acceso a los dominios que coincidan con la <0>expresión regular especificada</0>",
|
||||
"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án 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": "Hora",
|
||||
"domain_name_table_header": "Nombre del dominio",
|
||||
"type_table_header": "Tipo",
|
||||
"response_table_header": "Respuesta",
|
||||
"client_table_header": "Cliente",
|
||||
"empty_response_status": "Vacío",
|
||||
"show_all_filter_type": "Mostrar todo",
|
||||
"show_filtered_type": "Mostrar filtrados",
|
||||
"no_logs_found": "No se han encontrado registros",
|
||||
"disabled_log_btn": "Deshabilitar registro",
|
||||
"download_log_file_btn": "Descargar archivo de registro",
|
||||
"refresh_btn": "Actualizar",
|
||||
"enabled_log_btn": "Habilitar registro",
|
||||
"last_dns_queries": "Últimas 5000 consultas DNS",
|
||||
"previous_btn": "Atrás",
|
||||
"next_btn": "Siguiente",
|
||||
"loading_table_status": "Cargando...",
|
||||
"page_table_footer_text": "Página",
|
||||
"of_table_footer_text": "de",
|
||||
"rows_table_footer_text": "filas",
|
||||
"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ñadida 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ía",
|
||||
"rule_label": "Regla",
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconocido {{filterId}}",
|
||||
"install_welcome_title": "¡Bienvenido a AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home es un servidor DNS para bloqueo de anuncios y rastreadores a nivel de red. Su propósito 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ón",
|
||||
"install_settings_listen": "Interfaz de escucha",
|
||||
"install_settings_port": "Puerto",
|
||||
"install_settings_interface_link": "Su interfaz web de administración de AdGuard Home estará disponible en las siguientes direcciones:",
|
||||
"form_error_port": "Ingrese un valor de puerto válido",
|
||||
"install_settings_dns": "Servidor DNS",
|
||||
"install_settings_dns_desc": "Deberá 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ón",
|
||||
"install_auth_desc": "Se recomienda encarecidamente configurar la autenticación por contraseña para la interfaz web de administración de AdGuard Home. Incluso si solo es accesible en su red local, es importante que esté protegido contra el acceso no autorizado.",
|
||||
"install_auth_username": "Usuario",
|
||||
"install_auth_password": "Contraseña",
|
||||
"install_auth_confirm": "Confirmar contraseña",
|
||||
"install_auth_username_enter": "Ingrese su nombre de usuario",
|
||||
"install_auth_password_enter": "Ingrese su contraseña",
|
||||
"install_step": "Paso",
|
||||
"install_devices_title": "Configure sus dispositivos",
|
||||
"install_devices_desc": "Para comenzar a utilizar AdGuard Home, debe configurar sus dispositivos para usarlo.",
|
||||
"install_submit_title": "¡Felicitaciones!",
|
||||
"install_submit_desc": "El proceso de configuración ha finalizado y está listo para comenzar a usar AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Esta configuración cubrirá automáticamente todos los dispositivos conectados a su router doméstico y no necesitará configurar cada uno de ellos manualmente.",
|
||||
"install_devices_address": "El servidor DNS de AdGuard Home está escuchando en las siguientes direcciones",
|
||||
"install_devices_router_list_1": "Abra las preferencias de su router. Por lo general, puede acceder a él desde su navegador a través de una URL (como http://192.168.0.1/ o http://192.168.1.1/). Se le puede pedir que ingrese la contraseña. Si no lo recuerda, a menudo puede restablecer la contraseña presionando un botón en el router. Algunos routers requieren una aplicación específica, que en ese caso ya debería estar instalada en su computadora/teléfono.",
|
||||
"install_devices_router_list_2": "Busque la configuración de DHCP/DNS. Busque las letras DNS junto a un campo que permita ingresar dos o tres grupos de números, cada uno dividido en cuatro grupos de uno a tres dígitos.",
|
||||
"install_devices_router_list_3": "Ingrese las direcciones de su servidor AdGuard Home allí.",
|
||||
"install_devices_windows_list_1": "Abra el Panel de control a través del menú Inicio o en el buscador de Windows.",
|
||||
"install_devices_windows_list_2": "Vaya a la categoría Redes e Internet, luego a Centro de redes y recursos compartidos.",
|
||||
"install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busque Cambiar configuración del adaptador y luego haga clic en él.",
|
||||
"install_devices_windows_list_4": "Seleccione su conexión activa, haga clic derecho sobre ella y elija Propiedades.",
|
||||
"install_devices_windows_list_5": "Busque en la lista el Protocolo de Internet versión 4 (TCP/IP), selecciónelo y vuelva a hacer clic en Propiedades.",
|
||||
"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ón de la lista y haga clic en Avanzado.",
|
||||
"install_devices_macos_list_4": "Seleccione la pestaña DNS e ingrese las direcciones de su servidor AdGuard Home.",
|
||||
"install_devices_android_list_1": "En la pantalla de inicio del menú Android, pulse en Configuración.",
|
||||
"install_devices_android_list_2": "Pulse Wi-Fi en el menú. Aparecerá la pantalla que lista todas las redes disponibles (es imposible configurar DNS personalizados para la conexión móvil).",
|
||||
"install_devices_android_list_3": "Mantenga presionada la red a la que está conectado y pulse Modificar red.",
|
||||
"install_devices_android_list_4": "En algunos dispositivos, es posible que deba marcar la casilla Avanzado para ver más configuraciones. Para ajustar la configuración DNS de su Android, deberá cambiar la configuración de IP de DHCP a Estática.",
|
||||
"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ón.",
|
||||
"install_devices_ios_list_2": "Elija Wi-Fi en el menú de la izquierda (es imposible configurar DNS para redes móviles).",
|
||||
"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ón",
|
||||
"encryption_config_saved": "Configuración 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áticamente",
|
||||
"encryption_redirect_desc": "Si está marcado, AdGuard Home redireccionará automáticamente de HTTP a las direcciones HTTPS.",
|
||||
"encryption_https": "Puerto HTTPS",
|
||||
"encryption_https_desc": "Si el puerto HTTPS está configurado, la interfaz de administración de AdGuard Home será accesible a través de HTTPS, y también proporcionará DNS mediante HTTPS en la ubicación '/dns-query'.",
|
||||
"encryption_dot": "Puerto DNS mediante TLS",
|
||||
"encryption_dot_desc": "Si este puerto está configurado, AdGuard Home ejecutará 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álida para su dominio. Puede obtener un certificado gratuito en <0>{{link}}</0> o puede comprarlo en una de las autoridades de certificación de confianza.",
|
||||
"encryption_certificates_input": "Copie/pegue aquí sus certificados codificados PEM.",
|
||||
"encryption_status": "Estado",
|
||||
"encryption_expire": "Expira",
|
||||
"encryption_key": "Clave privada",
|
||||
"encryption_key_input": "Copie/pegue aquí 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á habilitado, la interfaz de administración de AdGuard Home funcionará a través de HTTPS, y el servidor DNS escuchará las peticiones DNS mediante HTTPS y DNS mediante TLS.",
|
||||
"encryption_chain_valid": "La cadena de certificado es válida",
|
||||
"encryption_chain_invalid": "La cadena de certificado no es válida",
|
||||
"encryption_key_valid": "Esta es una clave privada {{type}} válida",
|
||||
"encryption_key_invalid": "Esta es una clave privada {{type}} no válida",
|
||||
"encryption_subject": "Asunto",
|
||||
"encryption_issuer": "Emisor",
|
||||
"encryption_hostnames": "Nombres de hosts",
|
||||
"encryption_reset": "¿Está seguro de que desea restablecer la configuración de cifrado?",
|
||||
"topline_expiring_certificate": "Su certificado SSL está a punto de expirar. Actualice la <0>configuración del cifrado</0>.",
|
||||
"topline_expired_certificate": "Su certificado SSL ha expirado. Actualice la <0>configuración 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ía ser igual",
|
||||
"form_error_password": "La contraseña no coincide",
|
||||
"reset_settings": "Restablecer configuración",
|
||||
"update_announcement": "¡AdGuard Home {{version}} ya está disponible! <0>Haga clic aquí</0> para más información.",
|
||||
"setup_guide": "Guía de configuración",
|
||||
"dns_addresses": "Direcciones DNS",
|
||||
"down": "Abajo",
|
||||
"fix": "Corregir",
|
||||
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
|
||||
"update_now": "Actualizar ahora",
|
||||
"update_failed": "Error en la actualización automática. 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á 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ñadir cliente",
|
||||
"client_new": "Cliente nuevo",
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Dirección IP",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP o MAC. Tenga en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home también 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ón global",
|
||||
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
|
||||
"client_added": "Cliente \"{{key}}\" añadido correctamente",
|
||||
"client_updated": "Cliente \"{{key}}\" actualizado correctamente",
|
||||
"table_statistics": "Número de peticiones (últimas 24 horas)",
|
||||
"clients_not_found": "No se han encontrado clientes",
|
||||
"client_confirm_delete": "¿Está seguro de que desea eliminar el cliente \"{{key}}\"?",
|
||||
"filter_confirm_delete": "¿Está seguro de que desea eliminar el filtro?",
|
||||
"auto_clients_title": "Clientes (activos)",
|
||||
"auto_clients_desc": "Datos de los clientes que utilizan AdGuard Home, pero no se almacenan en la configuración",
|
||||
"access_title": "Configuración de acceso",
|
||||
"access_desc": "Aquí puede configurar las reglas de acceso para el servidor DNS de AdGuard Home.",
|
||||
"access_allowed_title": "Clientes permitidos",
|
||||
"access_allowed_desc": "Lista de CIDR o direcciones IP. Si está configurado, AdGuard Home solo aceptará peticiones de estas direcciones IP.",
|
||||
"access_disallowed_title": "Clientes no permitidos",
|
||||
"access_disallowed_desc": "Lista de CIDR o direcciones IP. Si está configurado, AdGuard Home eliminará las peticiones de estas direcciones IP.",
|
||||
"access_blocked_title": "Dominios bloqueados",
|
||||
"access_blocked_desc": "No confundas esto con filtros. AdGuard Home eliminará las consultas DNS con estos dominios en la pregunta de la consulta.",
|
||||
"access_settings_saved": "Configuración de acceso guardado correctamente",
|
||||
"updates_checked": "Actualizaciones comprobadas correctamente",
|
||||
"updates_version_equal": "AdGuard Home está actualizado",
|
||||
"check_updates_now": "Buscar actualizaciones ahora",
|
||||
"dns_privacy": "DNS con privacidad",
|
||||
"setup_dns_privacy_1": "<0>DNS mediante TLS:</0> Utilice la cadena <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS mediante HTTPS:</0> Utilice la cadena <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_3": "<0>Tenga en cuenta que los protocolos DNS cifrados solo son compatibles con Android 9. Por lo tanto, necesita instalar software adicional para otros sistemas operativos.</0><0>Aquí hay una lista de software que puedes usar.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 soporta DNS mediante TLS de forma nativa. Para configurarlo, vaya a Configuración → Red e Internet → Avanzado → DNS privado e ingrese su nombre de dominio allí.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard para Android</0> soporta <1>DNS mediante HTTPS</1> y <1>DNS mediante TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> añade soporte a Android para <1>DNS mediante HTTPS</1>.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> soporta <1>DNS mediante HTTPS</1>, pero para configurarlo y que use tu propio servidor, necesitará generar un <2>DNS Stamp</2> para ello.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard para iOS</0> soporta la configuración <1>DNS mediante HTTPS</1> y <1>DNS mediante TLS</1>.",
|
||||
"setup_dns_privacy_other_title": "Otras implementaciones",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home en sí mismo puede ser un cliente DNS seguro en cualquier plataforma.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> soporta todos los protocolos DNS seguros conocidos.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> soporta <1>DNS mediante HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Encontrará más implementaciones <0>aquí</0> y <1>aquí</1>.",
|
||||
"setup_dns_notice": "Para utilizar <1>DNS mediante HTTPS</1> o <1>DNS mediante TLS</1>, debe <0>configurar el cifrado</0> en la configuración de AdGuard Home.",
|
||||
"rewrite_added": "Reescritura DNS para \"{{key}}\" añadido correctamente",
|
||||
"rewrite_deleted": "Reescritura DNS para \"{{key}}\" eliminado correctamente",
|
||||
"rewrite_add": "Añadir reescritura DNS",
|
||||
"rewrite_not_found": "No se han encontrado reescrituras DNS",
|
||||
"rewrite_confirm_delete": "¿Está seguro de que desea eliminar la reescritura DNS para \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurar fácilmente la respuesta DNS personalizada para un nombre de dominio específico.",
|
||||
"rewrite_applied": "Regla de reescritura aplicada",
|
||||
"dns_rewrites": "Reescrituras DNS",
|
||||
"form_domain": "Ingrese el dominio",
|
||||
"form_answer": "Ingrese la dirección IP o el nombre del dominio",
|
||||
"form_error_domain_format": "Formato de dominio no válido",
|
||||
"form_error_answer_format": "Formato de respuesta no válido",
|
||||
"configure": "Configurar",
|
||||
"main_settings": "Configuración principal",
|
||||
"block_services": "Bloquear servicios específicos",
|
||||
"blocked_services": "Servicios bloqueados",
|
||||
"blocked_services_desc": "Permite bloquear rápidamente sitios y servicios populares.",
|
||||
"blocked_services_saved": "Servicios bloqueados guardado correctamente",
|
||||
"blocked_services_global": "Usar servicios bloqueados globalmente",
|
||||
"blocked_service": "Servicio bloqueado",
|
||||
"block_all": "Bloquear todo",
|
||||
"unblock_all": "Desbloquear todo"
|
||||
}
|
||||
173
client/src/__locales/fr.json
Normal file
173
client/src/__locales/fr.json
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"client_settings": "Paramètres du client",
|
||||
"example_upstream_reserved": "vous pouvez spécifier un DNS upstream <0>pour un/des domaine(s) spécifique(s)</0>",
|
||||
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs upstream",
|
||||
"bootstrap_dns": "Serveurs DNS d'amorçage",
|
||||
"bootstrap_dns_desc": "Les serveurs DNS d'amorçage sont utilisés pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme upstream.",
|
||||
"url_added_successfully": "Url ajoutée",
|
||||
"check_dhcp_servers": "Rechercher les serveurs DHCP",
|
||||
"save_config": "Sauvegarder la configuration",
|
||||
"enabled_dhcp": "Serveur DHCP activé",
|
||||
"disabled_dhcp": "Serveur DHCP désactivé",
|
||||
"dhcp_title": "Serveur DHCP (experimental !)",
|
||||
"dhcp_description": "Si votre routeur ne fonctionne pas avec les réglages DHCP, vous pouvez utiliser le serveur DHCP par défaut d'AdGuard.",
|
||||
"dhcp_enable": "Activer le serveur DHCP",
|
||||
"dhcp_disable": "Désactiver le serveur DHCP",
|
||||
"dhcp_not_found": "Aucun serveur DHCP actif trouvé sur le réseau. Vous pouvez activer le serveur DHCP intégré.",
|
||||
"dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le réseau. Ce n'est pas prudent d'activer le serveur DHCP intégré en ce moment.",
|
||||
"dhcp_leases": "Locations des serveurs DHCP",
|
||||
"dhcp_leases_not_found": "Aucune location des serveurs DHCP trouvée",
|
||||
"dhcp_config_saved": "La configuration du serveur DHCP est sauvegardée",
|
||||
"form_error_required": "Champ requis",
|
||||
"form_error_ip_format": "Format IPv4 invalide",
|
||||
"form_error_positive": "Doit être supérieur à 0",
|
||||
"dhcp_form_gateway_input": "IP de la passerelle",
|
||||
"dhcp_form_subnet_input": "Masque de sous-réseau",
|
||||
"dhcp_form_range_title": "Rangée des adresses IP",
|
||||
"dhcp_form_range_start": "Début de la rangée",
|
||||
"dhcp_form_range_end": "Fin de la rangée",
|
||||
"dhcp_form_lease_title": "Période de location du serveur DHCP (secondes)",
|
||||
"dhcp_form_lease_input": "Durée de la location",
|
||||
"dhcp_interface_select": "Sélectionner 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ètres",
|
||||
"filters": "Filtres",
|
||||
"query_log": "Journal des requêtes",
|
||||
"version": "version",
|
||||
"address": "addresse",
|
||||
"on": "Activé",
|
||||
"off": "Éteint",
|
||||
"homepage": "Page d'accueil",
|
||||
"report_an_issue": "Signaler un problème",
|
||||
"enable_protection": "Activer la protection",
|
||||
"enabled_protection": "Protection activée",
|
||||
"disable_protection": "Désactiver la protection",
|
||||
"disabled_protection": "Protection désactivée",
|
||||
"refresh_statics": "Renouveler les statistiques",
|
||||
"dns_query": "Requêtes DNS",
|
||||
"blocked_by": "Bloqué par Filtres",
|
||||
"stats_malware_phishing": "Tentative de malware/hammeçonnage bloquée",
|
||||
"stats_adult": "Sites à contenu adulte bloqués",
|
||||
"stats_query_domain": "Domaines les plus recherchés",
|
||||
"for_last_24_hours": "pendant les dernières 24 heures",
|
||||
"no_domains_found": "Pas de domaines trouvés",
|
||||
"requests_count": "Nombre de requêtes",
|
||||
"top_blocked_domains": "Les domaines les plus fréquemment bloqués",
|
||||
"top_clients": "Meilleurs clients",
|
||||
"no_clients_found": "Pas de clients trouvés",
|
||||
"general_statistics": "Statistiques générales",
|
||||
"number_of_dns_query_24_hours": "Un nombre de requêtes DNS quieries traitées pendant les 24 heures dernières",
|
||||
"number_of_dns_query_blocked_24_hours": "Un nombre de requêtes DNS bloquées par les filtres adblock et les listes de blocage des hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Un nombre de requêtes DNS bloquées par le module Sécurité de navigation d'AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Un nombre de sites à contenu adulte bloqués",
|
||||
"enforced_save_search": "Recherche sécurisée renforcée",
|
||||
"number_of_dns_query_to_safe_search": "Un nombre de requêtes DNS faites avec la Recherche securisée",
|
||||
"average_processing_time": "Temps moyen de traitement",
|
||||
"average_processing_time_hint": "Temps moyen (en millisecondes) de traitement d'une requête DNS",
|
||||
"block_domain_use_filters_and_hosts": "Bloquez les domaines à l'aide des filtres et fichiers hosts",
|
||||
"filters_block_toggle_hint": "Vous pouvez configurer les règles de filtrage dans les paramètres des <a href='#filters'>Filtres</a>.",
|
||||
"use_adguard_browsing_sec": "Utilisez le service Sécurité de navigation d'AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home va vérifier si le domaine est dans la liste noire du service de sécurité de navigation. Pour cela il va utiliser un lookup API discret : le préfixe court du hash du nom de domaine SHA256 sera envoyé au serveur.",
|
||||
"use_adguard_parental": "Utiliser le contrôle parental d'AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.",
|
||||
"enforce_safe_search": "Renforcer la recherche sécurisée",
|
||||
"enforce_save_search_hint": "AdGuard Home peut renforcer la Recherche sécurisée dans les moteurs de recherche suivants : Google, Youtube, Bing et Yandex.",
|
||||
"no_servers_specified": "Pas de serveurs spécifiés",
|
||||
"no_settings": "Pas de paramètres",
|
||||
"general_settings": "Paramètres généraux",
|
||||
"upstream_dns": "Serveurs DNS upstream",
|
||||
"upstream_dns_hint": "Si vous laissez ce champ vide, AdGuard Home va utiliser <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> somme upstream. Utilisez le préfixe tls:// pour DNS via les serveurs TLS .",
|
||||
"test_upstream_btn": "Tester les upstreams",
|
||||
"apply_btn": "Appliquer",
|
||||
"disabled_filtering_toast": "Filtrage désactivé",
|
||||
"enabled_filtering_toast": "Filtrage activé",
|
||||
"disabled_safe_browsing_toast": "Surfing sécurisé désactivé",
|
||||
"enabled_safe_browsing_toast": "Surfing sécurisé activé",
|
||||
"disabled_parental_toast": "Contrôle parental désactivé",
|
||||
"enabled_parental_toast": "Contrôle parental activé",
|
||||
"disabled_safe_search_toast": "Recherche sécurisée désactivée",
|
||||
"enabled_save_search_toast": "Recherche sécurisée activée",
|
||||
"enabled_table_header": "Activé",
|
||||
"name_table_header": "Nom",
|
||||
"filter_url_table_header": "URL du filtre",
|
||||
"rules_count_table_header": "Nombre des règles",
|
||||
"last_time_updated_table_header": "Dernière mise à jour",
|
||||
"delete_table_action": "Supprimer",
|
||||
"filters_and_hosts": "Listes de blocage des filtres et hosts",
|
||||
"filters_and_hosts_hint": "AdGuard Home comprend les règles basiques de blocage ainsi que la syntaxe des fichiers hosts.",
|
||||
"no_filters_added": "Aucun filtre ajouté",
|
||||
"add_filter_btn": "Ajouter filtre",
|
||||
"cancel_btn": "Annuler",
|
||||
"enter_name_hint": "Saisir nom",
|
||||
"enter_url_hint": "Saisir URL",
|
||||
"check_updates_btn": "Vérifier les mises à jour",
|
||||
"new_filter_btn": "Abonnement à un nouveau filtre",
|
||||
"enter_valid_filter_url": "Saisir un URL valide pour s'abonner au filtre ou à un fichier host.",
|
||||
"custom_filter_rules": "Règles de filtrage d'utilisateur",
|
||||
"custom_filter_rules_hint": "Saisissez la règle en une ligne. C'est possible d'utiliser les règles de blocage ou la syntaxe des fichiers hosts.",
|
||||
"examples_title": "Exemples",
|
||||
"example_meaning_filter_block": "bloquer l'accés au domaine exemple.org et à tous ses sous-domaines",
|
||||
"example_meaning_filter_whitelist": "débloquer l'accés au domaine exemple.org et à tous ses sous-domaines",
|
||||
"example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines).",
|
||||
"example_comment": "! Voici comment ajouter une déscription",
|
||||
"example_comment_meaning": "commentaire",
|
||||
"example_comment_hash": "# Et comme ça aussi on peut laisser des commentaires",
|
||||
"example_regex_meaning": "block access to the domains matching the specified regular expression",
|
||||
"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é",
|
||||
"example_upstream_doh": "<a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS</a> chiffré",
|
||||
"example_upstream_sdns": "vous pouvez utiliser <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> pour <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> ou les resolveurs <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS</a>",
|
||||
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
||||
"all_filters_up_to_date_toast": "Tous les filtres sont mis à jour",
|
||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis à jour",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent de manière incorrecte",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur \"{{key}}\": veuillez vérifier si le nom saisi est bien correct",
|
||||
"unblock_btn": "Débloquer",
|
||||
"block_btn": "Bloquer",
|
||||
"time_table_header": "Temps",
|
||||
"domain_name_table_header": "Nom de domaine",
|
||||
"response_table_header": "Réponse",
|
||||
"empty_response_status": "Vide",
|
||||
"show_all_filter_type": "Montrer tout",
|
||||
"show_filtered_type": "Montrer les sites filtrés",
|
||||
"no_logs_found": "Aucun journal trouvé",
|
||||
"disabled_log_btn": "Désactiver le journal",
|
||||
"download_log_file_btn": "Télécharger le fichier de journal",
|
||||
"refresh_btn": "Actualiser",
|
||||
"enabled_log_btn": "Activer le journal",
|
||||
"last_dns_queries": "5000 dernières requêtes DNS",
|
||||
"previous_btn": "Précédent",
|
||||
"next_btn": "Suivant",
|
||||
"loading_table_status": "Chargement en cours ...",
|
||||
"of_table_footer_text": "de",
|
||||
"rows_table_footer_text": "lignes",
|
||||
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur",
|
||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur",
|
||||
"query_log_disabled_toast": "Journal de requêtes désactivé",
|
||||
"query_log_enabled_toast": "Journal de requêtes activé",
|
||||
"found_in_known_domain_db": "Trouvé dans la base de données des domaines connus",
|
||||
"category_label": "Catégorie",
|
||||
"rule_label": "Règle",
|
||||
"filter_label": "Filtre",
|
||||
"unknown_filter": "Filtre inconnu {{filterId}}",
|
||||
"install_devices_desc": "Pour commencer à utiliser AdGuard Home, vous devez configurer vos appareils.",
|
||||
"install_submit_title": "Félicitations !",
|
||||
"install_submit_desc": "La procédure d'installation est terminée et vous êtes prêt(e) pour commencer à utiliser AdGuard Home.",
|
||||
"install_devices_router": "Routeur",
|
||||
"install_devices_router_desc": "Cette installation impactera automatiquement tous les appareils connectés au routeur de votre maison et vous n'aurez pas besoin de configurer manuellement chaque appareil.",
|
||||
"install_devices_address": "Le serveur DNS AdGuard Home écoute sur les adresses suivantes",
|
||||
"install_devices_router_list_1": "Ouvrez les préférences de votre routeur. Normalement, vous pouvez y accéder depuis votre navigateur Web via une URL (exemple http://192.168.0.1/ ou http://192.168.1.1/). Vous devrez peut-être saisir le mot de passe. Si vous ne vous en rappelez plus, vous pouvez le réinitialiser en appuyant sur le bouton du routeur. Certains routeurs fonctionnent sous une application spécifique, qui devrait être déjà installée sur votre ordinateur/téléphone.",
|
||||
"install_devices_router_list_2": "Trouvez les paramètres DHCP/DNS. Recherchez les lettres DNS près d'une zone qui permet la saisie de 2 ou 3 blocs de chiffres, chacun composé de 4 parties de 1 à 3 chiffres.",
|
||||
"install_devices_router_list_3": "Saisissez vos adresses de serveur AdGuard Home ici.",
|
||||
"install_devices_windows_list_1": "Ouvrez votre Panneau de configuration depuis le menu Démarrer ou la recherche Windows.",
|
||||
"install_devices_windows_list_2": "Allez dans la catégorie Réseau et Internet et ensuite dans le Centre Réseau et Partage.",
|
||||
"install_devices_windows_list_3": "Sur la partie gauche de l'écran, recherchez Modifier les paramètres de la carte et cliquez dessus.",
|
||||
"install_devices_windows_list_4": "Sélectionnez votre connexion active, clic droit dessus et sélectionnez Propriétés.",
|
||||
"install_devices_windows_list_5": "Recherchez la version du protocole Internet 4 (TCP/IP) dans la liste, sélectionnez-la puis cliquez à nouveau sur Propriétés."
|
||||
}
|
||||
357
client/src/__locales/ja.json
Normal file
357
client/src/__locales/ja.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"client_settings": "クライアント設定",
|
||||
"example_upstream_reserved": "<0>特定のドメイン</0>に対して上流DNSを指定できます",
|
||||
"upstream_parallel": "すべての上流サーバに同時に照会することで解決をスピードアップするため、並列クエリを使用する",
|
||||
"bootstrap_dns": "ブートストラップDNSサーバ",
|
||||
"bootstrap_dns_desc": "ブートストラップDNSサーバは、上流として指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されます。",
|
||||
"url_added_successfully": "URLの追加に成功しました",
|
||||
"check_dhcp_servers": "DHCPサーバをチェックする",
|
||||
"save_config": "設定を保存する",
|
||||
"enabled_dhcp": "DHCPサーバを有効にしました",
|
||||
"disabled_dhcp": "DHCPサーバを無効にしました",
|
||||
"dhcp_title": "DHCPサーバ(実験的!)",
|
||||
"dhcp_description": "あなたのルータがDHCPの設定を提供していないのなら、AdGuardに内蔵されているDHCPサーバを利用できます。",
|
||||
"dhcp_enable": "DHCPサーバを有効にする",
|
||||
"dhcp_disable": "DHCPサーバを無効にする",
|
||||
"dhcp_not_found": "ネットワーク内に動作しているDHCPサーバはありません。内蔵されているDHCPサーバを有効にしても安全です。",
|
||||
"dhcp_found": "ネットワーク内に動作しているDHCPサーバが見つかりました。内臓されているDHCPサーバを有効にするのは安全ではありません。",
|
||||
"dhcp_leases": "DHCP割り当て",
|
||||
"dhcp_static_leases": "DHCP静的割り当て",
|
||||
"dhcp_leases_not_found": "DHCP割当はありません",
|
||||
"dhcp_config_saved": "DHCPサーバの設定を保存しました",
|
||||
"form_error_required": "必須項目",
|
||||
"form_error_ip_format": "IPv4フォーマットではありません",
|
||||
"form_error_mac_format": "MACフォーマットではありません",
|
||||
"form_error_positive": "0より大きい必要があります",
|
||||
"dhcp_form_gateway_input": "ゲートウェイIP",
|
||||
"dhcp_form_subnet_input": "サブネットマスク",
|
||||
"dhcp_form_range_title": "IPアドレスの範囲",
|
||||
"dhcp_form_range_start": "範囲の開始",
|
||||
"dhcp_form_range_end": "範囲の終了",
|
||||
"dhcp_form_lease_title": "DHCP割当時間(秒単位)",
|
||||
"dhcp_form_lease_input": "割当期間",
|
||||
"dhcp_interface_select": "DHCPインタフェースの選択",
|
||||
"dhcp_hardware_address": "MACアドレス",
|
||||
"dhcp_ip_addresses": "IPアドレス",
|
||||
"dhcp_table_hostname": "ホスト名",
|
||||
"dhcp_table_expires": "有効期限",
|
||||
"dhcp_warning": "内蔵しているDHCPサーバを有効にしたい場合は、稼働中のDHCPサーバがないことを確認してください。そうでなければ、接続されたデバイスのためにインターネットを壊すかもしれません!",
|
||||
"dhcp_error": "ネットワーク内に別のDHCPサーバがあるかどうかを判断できませんでした。",
|
||||
"dhcp_static_ip_error": "DHCPサーバを使用するには、静的IPアドレスを設定する必要があります。このネットワークインターフェイスが静的IPアドレスを使用するように設定されているかどうかを判断できませんでした。手動で静的IPアドレスを設定してください。",
|
||||
"dhcp_dynamic_ip_found": "ご使用のシステムは、インターフェース<0>{{interfaceName}}</0>に動的IPアドレス構成が使用されています。DHCPサーバを使用するには、静的IPアドレスで設定する必要があります。あなたの現在のIPアドレスは<0>{{ipAddress}}</0>です。「DHCPサーバを有効にする」ボタンを押すと、このIPアドレスを静的IPアドレスに自動設定されます。",
|
||||
"dhcp_lease_added": "静的割り当て \"{{key}}\" の追加に成功しました",
|
||||
"dhcp_lease_deleted": "静的割り当て \"{{key}}\" の削除に成功しました",
|
||||
"dhcp_new_static_lease": "新規静的割り当て",
|
||||
"dhcp_static_leases_not_found": "DHCP静的割り当てはありません",
|
||||
"dhcp_add_static_lease": "静的割り当てを追加する",
|
||||
"delete_confirm": "\"{{key}}\" を削除してもよろしいですか?",
|
||||
"form_enter_hostname": "ホスト名を入力してください",
|
||||
"error_details": "エラー詳細",
|
||||
"back": "戻る",
|
||||
"dashboard": "ダッシュボード",
|
||||
"settings": "設定",
|
||||
"filters": "フィルタ",
|
||||
"query_log": "クエリ・ログ",
|
||||
"faq": "よくある質問",
|
||||
"version": "バージョン",
|
||||
"address": "アドレス",
|
||||
"on": "オン",
|
||||
"off": "オフ",
|
||||
"copyright": "著作権",
|
||||
"homepage": "ホームページ",
|
||||
"report_an_issue": "問題を報告する",
|
||||
"privacy_policy": "プライバシーポリシー",
|
||||
"enable_protection": "保護を有効にする",
|
||||
"enabled_protection": "保護を有効にしました",
|
||||
"disable_protection": "保護を無効にする",
|
||||
"disabled_protection": "保護を無効にしました",
|
||||
"refresh_statics": "統計データを最新にする",
|
||||
"dns_query": "DNSクエリ",
|
||||
"blocked_by": "フィルタにブロックされたDNSクエリ",
|
||||
"stats_malware_phishing": "ブロックされたマルウェア/フィッシング",
|
||||
"stats_adult": "ブロックされたアダルトウェブサイト",
|
||||
"stats_query_domain": "最も問合せされたドメイン",
|
||||
"for_last_24_hours": "過去24時間以内",
|
||||
"no_domains_found": "ドメイン情報はありません",
|
||||
"requests_count": "リクエスト数",
|
||||
"top_blocked_domains": "最もブロックされたドメイン",
|
||||
"top_clients": "トップクライアント",
|
||||
"no_clients_found": "クライアント情報はありません",
|
||||
"general_statistics": "全般的な統計",
|
||||
"number_of_dns_query_24_hours": "過去24時間に処理されたDNSクエリの数",
|
||||
"number_of_dns_query_blocked_24_hours": "広告ブロックフィルタとhostsブロックリストによってブロックされたDNSリクエストの数",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuardブラウジングセキュリティモジュールによってブロックされたDNSリクエストの数",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "ブロックされたアダルトウェブサイトの数",
|
||||
"enforced_save_search": "強制されたセーフサーチ",
|
||||
"number_of_dns_query_to_safe_search": "セーフサーチが強制された検索エンジンに対するDNSリクエストの数",
|
||||
"average_processing_time": "平均処理時間",
|
||||
"average_processing_time_hint": "DNSリクエストの処理にかかる平均時間(ミリ秒単位)",
|
||||
"block_domain_use_filters_and_hosts": "フィルタとhostsファイルを使用してドメインをブロックする",
|
||||
"filters_block_toggle_hint": "<a href='#filters'>フィルタ</a>の設定でブロックするルールを設定することができます。",
|
||||
"use_adguard_browsing_sec": "AdGuardブラウジングセキュリティ・ウェブサービスを使用する",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Homeは、ブラウジングセキュリティ・ウェブサービスによってドメインがブラックリストに登録されているかどうかを確認します。 これはプライバシーを考慮したAPIを使用してチェックを実行します。ドメイン名SHA256ハッシュの短いプレフィックスのみがサーバに送信されます。",
|
||||
"use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する",
|
||||
"use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。",
|
||||
"enforce_safe_search": "セーフサーチを強制する",
|
||||
"enforce_save_search_hint": "AdGuard Homeは、Google、Youtube、Bing、Yandexの検索エンジンでセーフサーチを強制できます。",
|
||||
"no_servers_specified": "サーバが指定されていません",
|
||||
"no_settings": "設定なし",
|
||||
"general_settings": "一般設定",
|
||||
"dns_settings": "DNS設定",
|
||||
"encryption_settings": "暗号化設定",
|
||||
"dhcp_settings": "DHCP設定",
|
||||
"upstream_dns": "上流DNSサーバ",
|
||||
"upstream_dns_hint": "このフィールドを未入力のままにすると、AdGuard Homeは上流として<a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a>を使用します。DNS over TLSサーバには、「tls://」プレフィックスを使用してください。",
|
||||
"test_upstream_btn": "上流サーバをテストする",
|
||||
"apply_btn": "適用する",
|
||||
"disabled_filtering_toast": "フィルタリングを無効にしました",
|
||||
"enabled_filtering_toast": "フィルタリングを有効にしました",
|
||||
"disabled_safe_browsing_toast": "セーフブラウジングを無効にしました",
|
||||
"enabled_safe_browsing_toast": "セーフブラウジングを有効にしました",
|
||||
"disabled_parental_toast": "ペアレンタルコントロールを無効にしました",
|
||||
"enabled_parental_toast": "ペアレンタルコントロールを有効にしました",
|
||||
"disabled_safe_search_toast": "セーフサーチを無効にしました",
|
||||
"enabled_save_search_toast": "セーフサーチを有効にしました",
|
||||
"enabled_table_header": "有効",
|
||||
"name_table_header": "名称",
|
||||
"filter_url_table_header": "フィルタのURL",
|
||||
"rules_count_table_header": "ルール数",
|
||||
"last_time_updated_table_header": "最終更新時刻",
|
||||
"actions_table_header": "操作",
|
||||
"edit_table_action": "編集する",
|
||||
"delete_table_action": "削除する",
|
||||
"filters_and_hosts": "フィルタとhostsブロックリスト",
|
||||
"filters_and_hosts_hint": "AdGuard Homeは、基本的な広告ブロックルールとhostsファイルの構文を理解します。",
|
||||
"no_filters_added": "フィルタは追加されませんでした",
|
||||
"add_filter_btn": "フィルタを追加する",
|
||||
"cancel_btn": "キャンセル",
|
||||
"enter_name_hint": "名称を入力",
|
||||
"enter_url_hint": "URLを入力",
|
||||
"check_updates_btn": "アップデートを確認する",
|
||||
"new_filter_btn": "新しいフィルタ・サブスクリプション",
|
||||
"enter_valid_filter_url": "フィルタ・サブスクリプションもしくはhostsファイルの有効なURLを入力してください。",
|
||||
"custom_filter_rules": "カスタム・フィルタリングルール",
|
||||
"custom_filter_rules_hint": "1つの行に1つのルールを入力してください。 広告ブロックルールやhostsファイル構文を使用できます。",
|
||||
"examples_title": "例",
|
||||
"example_meaning_filter_block": "example.orgドメインとそのすべてのサブドメインへのアクセスをブロックする",
|
||||
"example_meaning_filter_whitelist": "example.orgドメインとそのすべてのサブドメインへのアクセスのブロックを解除する",
|
||||
"example_meaning_host_block": "AdGuard Homeは、example.orgドメイン(サブドメインを除く)に対して127.0.0.1のアドレスを返すようになります。",
|
||||
"example_comment": "! ここにはコメントが入ります",
|
||||
"example_comment_meaning": "ただのコメントです",
|
||||
"example_comment_hash": "# ここもコメントです",
|
||||
"example_regex_meaning": "<0>指定の正規表現</0>に一致するドメインへのアクセスをブロックします",
|
||||
"example_upstream_regular": "通常のDNS(UDPでの問い合わせ)",
|
||||
"example_upstream_dot": "暗号化されている <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "暗号化されている <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> または <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> リゾルバのために <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> を使えます",
|
||||
"example_upstream_tcp": "通常のDNS(TCPでの問い合わせ)",
|
||||
"all_filters_up_to_date_toast": "すべてのフィルタは既に最新です",
|
||||
"updated_upstream_dns_toast": "上流DNSサーバを更新しました",
|
||||
"dns_test_ok_toast": "指定されたDNSサーバは正しく動作しています",
|
||||
"dns_test_not_ok_toast": "サーバ \"{{key}}\": 使用できませんでした。正しく入力されているかどうかを確認してください",
|
||||
"unblock_btn": "ブロック解除",
|
||||
"block_btn": "ブロックする",
|
||||
"time_table_header": "時刻",
|
||||
"domain_name_table_header": "ドメイン名",
|
||||
"type_table_header": "種類",
|
||||
"response_table_header": "応答",
|
||||
"client_table_header": "クライアント",
|
||||
"empty_response_status": "未定義",
|
||||
"show_all_filter_type": "すべて表示",
|
||||
"show_filtered_type": "フィルタされたログを表示",
|
||||
"no_logs_found": "ログはありません",
|
||||
"disabled_log_btn": "ログを無効にする",
|
||||
"download_log_file_btn": "ログファイルをダウンロードする",
|
||||
"refresh_btn": "最新にする",
|
||||
"enabled_log_btn": "ログを有効にする",
|
||||
"last_dns_queries": "最新5000件分のDNSクエリ",
|
||||
"previous_btn": "前へ",
|
||||
"next_btn": "次へ",
|
||||
"loading_table_status": "読み込み中…",
|
||||
"page_table_footer_text": "ページ",
|
||||
"of_table_footer_text": "/",
|
||||
"rows_table_footer_text": "行",
|
||||
"updated_custom_filtering_toast": "カスタム・フィルタリングルールを更新しました",
|
||||
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました",
|
||||
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました",
|
||||
"query_log_disabled_toast": "クエリ・ログを無効にしました",
|
||||
"query_log_enabled_toast": "クエリ・ログを有効にしました",
|
||||
"source_label": "ソース",
|
||||
"found_in_known_domain_db": "既知のドメインデータベースに見つかりました。",
|
||||
"category_label": "カテゴリ",
|
||||
"rule_label": "ルール",
|
||||
"filter_label": "フィルタ",
|
||||
"unknown_filter": "不明なフィルタ {{filterId}}",
|
||||
"install_welcome_title": "ようこそ、AdGuard Home へ!",
|
||||
"install_welcome_desc": "AdGuard Homeは、ネットワーク全体で広告と追跡をブロックするDNSサーバです。その目的は、ネットワークとデバイスのすべてをあなたが制御できるようにすることであり、クライアント側のプログラムを使用する必要はありません。",
|
||||
"install_settings_title": "管理用ウェブインターフェイス",
|
||||
"install_settings_listen": "待ち受けインターフェイス",
|
||||
"install_settings_port": "ポート",
|
||||
"install_settings_interface_link": "AdGuard Homeの管理ウェブインターフェイスは、次のアドレスで利用可能になります:",
|
||||
"form_error_port": "有効なポート番号を入力してください",
|
||||
"install_settings_dns": "DNSサーバ",
|
||||
"install_settings_dns_desc": "次のアドレスでDNSサーバを使用するようにあなたのデバイスまたはルータを設定する必要があります:",
|
||||
"install_settings_all_interfaces": "すべてのインターフェイス",
|
||||
"install_auth_title": "認証",
|
||||
"install_auth_desc": "AdGuard Homeの管理ウェブインターフェースにパスワード認証を設定することを強くお勧めします。ローカルネットワークでのみアクセス可能であっても、制限のないアクセスから保護することは重要です。",
|
||||
"install_auth_username": "ユーザ名",
|
||||
"install_auth_password": "パスワード",
|
||||
"install_auth_confirm": "パスワード(確認用)",
|
||||
"install_auth_username_enter": "ユーザ名を入力してください",
|
||||
"install_auth_password_enter": "パスワードを入力してください",
|
||||
"install_step": "手順",
|
||||
"install_devices_title": "あなたのデバイスの設定",
|
||||
"install_devices_desc": "AdGuard Homeの利用を開始するには、あなたのデバイスがAdGuard Homeを利用するように設定する必要があります。",
|
||||
"install_submit_title": "おめでとうございます!",
|
||||
"install_submit_desc": "設定手順は完了し、AdGuard Homeの利用を開始する準備が整いました。",
|
||||
"install_devices_router": "ルータ",
|
||||
"install_devices_router_desc": "この設定では、ルータに接続されているすべてのデバイスを自動的にカバーしますので、各デバイスを手動で設定する必要はありません。",
|
||||
"install_devices_address": "AdGuard HomeのDNSサーバは次のアドレスで待ち受けています",
|
||||
"install_devices_router_list_1": "ルータの設定を開きます。通常は、URL(http://192.168.0.1/ または http://192.168.1.1/ など)を介してブラウザからアクセスできます。パスワードの入力を求められることがあります。パスワードを覚えていない場合は、ルータにあるボタンを押してパスワードをリセットできます。一部のルータは特定のアプリケーションを必要とします。その場合、アプリケーションはあなたのコンピュータ/電話に既にインストールされているはずです。",
|
||||
"install_devices_router_list_2": "DHCP/DNSの設定を見つけます。DNSの文字のある入力欄を探します。それは、1〜3桁の数字で4つのグループに分けられた入力欄で、2〜3セットを許可されている欄です。",
|
||||
"install_devices_router_list_3": "そこにAdGuard Homeサーバのアドレスを入力します。",
|
||||
"install_devices_windows_list_1": "「スタート」メニューまたはWindowsの検索から「設定」を開きます。",
|
||||
"install_devices_windows_list_2": "「ネットワークとインターネット」カテゴリに移動し、さらに「ネットワークと共有センター」へ移動します。",
|
||||
"install_devices_windows_list_3": "画面の左側にある「アダプターの設定の変更」を見つけてクリックします。",
|
||||
"install_devices_windows_list_4": "動作中の接続を選択して右クリックし、「プロパティ」を選択します。",
|
||||
"install_devices_windows_list_5": "一覧から「インターネット プロトコル バージョン4(TCP/IPv4)」を見つけ、それを選択してから、もう一度プロパティをクリックします。",
|
||||
"install_devices_windows_list_6": "「次のDNSサーバのアドレスを使う」を選択して、AdGuard Homeサーバのアドレスを入力します。",
|
||||
"install_devices_macos_list_1": "Apple アイコンをクリックして「システム環境設定」へ行きます。",
|
||||
"install_devices_macos_list_2": "「ネットワーク」をクリックします。",
|
||||
"install_devices_macos_list_3": "一覧の最初の接続を選択して「詳細...」をクリックします。",
|
||||
"install_devices_macos_list_4": "「DNS」タブを選択して、AdGuard Homeサーバのアドレスを入力します。",
|
||||
"install_devices_android_list_1": "Androidメニューのホーム画面から、「設定」をタップします。",
|
||||
"install_devices_android_list_2": "メニューの「Wi-Fi」をタップします。利用可能なすべてのネットワークの一覧が表示されます(モバイル接続用にカスタムDNSを設定することは不可能です)。",
|
||||
"install_devices_android_list_3": "接続しているネットワークを長押しして、「ネットワークの変更」をタップします。",
|
||||
"install_devices_android_list_4": "一部のデバイスでは、詳細設定のボックスをチェックして詳細設定を確認する必要があります。AndroidのDNS設定を調整するには、IP設定を「DHCP」から「静的IP」へ切り替える必要があります。",
|
||||
"install_devices_android_list_5": "DNS 1とDNS 2の値をAdGuard Homeサーバのアドレスへ変更します。",
|
||||
"install_devices_ios_list_1": "ホーム画面から「設定」をタップします。",
|
||||
"install_devices_ios_list_2": "左側のメニューで「Wi-Fi」を選択します(モバイルネットワーク用にDNSを設定することは不可能です)。",
|
||||
"install_devices_ios_list_3": "現在使用中のネットワークの名前をタップします。",
|
||||
"install_devices_ios_list_4": "「DNS」欄に、AdGuard Homeサーバのアドレスを入力します。",
|
||||
"get_started": "始めましょう",
|
||||
"next": "次へ",
|
||||
"open_dashboard": "ダッシュボードを開きます",
|
||||
"install_saved": "保存に成功しました",
|
||||
"encryption_title": "暗号化",
|
||||
"encryption_desc": "DNSと管理ウェブインターフェースの両方に対する暗号化(HTTPS/TLS)をサポートします",
|
||||
"encryption_config_saved": "暗号化の設定を保存しました",
|
||||
"encryption_server": "サーバ名",
|
||||
"encryption_server_enter": "ドメイン名を入力してください",
|
||||
"encryption_server_desc": "HTTPSを使用するには、SSL証明書と一致するサーバ名を入力する必要があります。",
|
||||
"encryption_redirect": "HTTPSに自動的にリダイレクト",
|
||||
"encryption_redirect_desc": "チェックすると、AdGuard Homeは自動的にHTTPからHTTPSアドレスへリダイレクトします。",
|
||||
"encryption_https": "HTTPS ポート",
|
||||
"encryption_https_desc": "HTTPSポートが設定されていると、AdGuard Home 管理インターフェースはHTTPS経由でアクセス可能になり、そして「/dns-query」の場所にDNS-over-HTTPSも提供されます。",
|
||||
"encryption_dot": "DNS-over-TLS ポート",
|
||||
"encryption_dot_desc": "このポートが設定されていると、AdGuard HomeはこのポートでDNS-over-TLSサーバを実行します。",
|
||||
"encryption_certificates": "証明書",
|
||||
"encryption_certificates_desc": "暗号化を使用するには、ドメインに有効なSSL証明書チェーンを提供する必要があります。無料の証明書は<0> {{link}} </0>で入手できます。または、信頼できる認証局のいずれかから購入することもできます。",
|
||||
"encryption_certificates_input": "ここにPEM形式の証明書をコピー/ペーストしてください。",
|
||||
"encryption_status": "ステータス",
|
||||
"encryption_expire": "有効期限",
|
||||
"encryption_key": "秘密鍵",
|
||||
"encryption_key_input": "ここに証明書のためのPEM形式の秘密鍵をコピー/ペーストしてください。",
|
||||
"encryption_enable": "暗号化を有効にする(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
|
||||
"encryption_enable_desc": "暗号化が有効になっていると、AdGuard Home 管理インターフェースはHTTPS経由で動作し、DNSサーバはDNS-over-HTTPSおよびDNS-over-TLS経由で要求を待ち受けます。",
|
||||
"encryption_chain_valid": "証明書チェーンは有効です",
|
||||
"encryption_chain_invalid": "証明書チェーンは無効です",
|
||||
"encryption_key_valid": "これは有効な{{type}}秘密鍵です",
|
||||
"encryption_key_invalid": "これは無効な{{type}}秘密鍵です",
|
||||
"encryption_subject": "件名",
|
||||
"encryption_issuer": "発行者",
|
||||
"encryption_hostnames": "ホスト名",
|
||||
"encryption_reset": "暗号化設定をリセットして良いですか?",
|
||||
"topline_expiring_certificate": "SSL証明書は期限切れになります。<0>暗号化設定</0>を更新します。",
|
||||
"topline_expired_certificate": "SSL証明書は期限切れです。<0>暗号化設定</0>を更新します。",
|
||||
"form_error_port_range": "80〜65535の範囲でポート番号を入力してください",
|
||||
"form_error_port_unsafe": "これは危険なポートです",
|
||||
"form_error_equal": "等しくないはずです",
|
||||
"form_error_password": "パスワードが不一致です",
|
||||
"reset_settings": "設定をリセットする",
|
||||
"update_announcement": "AdGuard Home {{version}}がリリースされました。詳しくは<0>こちらをクリック</0>してください。",
|
||||
"setup_guide": "セットアップガイド",
|
||||
"dns_addresses": "DNSアドレス",
|
||||
"down": "ダウン",
|
||||
"fix": "改善",
|
||||
"dns_providers": "こちらは、選択可能な<0>既知のDNSプロバイダの一覧</0>です。",
|
||||
"update_now": "今すぐ更新する",
|
||||
"update_failed": "自動更新に失敗しました。手動で更新するには、<a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>手順に従って</a>ください。",
|
||||
"processing_update": "AdGuard Homeを更新しています。しばらくお待ちください",
|
||||
"clients_title": "クライアント",
|
||||
"clients_desc": "AdGuard Homeに接続されているデバイスを設定します",
|
||||
"settings_global": "グローバル",
|
||||
"settings_custom": "カスタム",
|
||||
"table_client": "クライアント",
|
||||
"table_name": "名前",
|
||||
"save_btn": "保存する",
|
||||
"client_add": "クライアントを追加する",
|
||||
"client_new": "新規クライアント",
|
||||
"client_edit": "クライアントの編集",
|
||||
"client_identifier": "識別子",
|
||||
"ip_address": "IPアドレス",
|
||||
"client_identifier_desc": "クライアントはIPアドレスまたはMACアドレスで識別できます。AdGuard Homeが<0>DHCPサーバ</0>でもある場合にのみ、識別子としてMACを使用することが可能であることにご注意ください。",
|
||||
"form_enter_ip": "IPアドレスを入力してください",
|
||||
"form_enter_mac": "MACアドレスを入力してください",
|
||||
"form_client_name": "クライアント名を入力してください",
|
||||
"client_global_settings": "グローバル設定を使用する",
|
||||
"client_deleted": "クライアント \"{{key}}\" の削除に成功しました",
|
||||
"client_added": "クライアント \"{{key}}\" の追加に成功しました",
|
||||
"client_updated": "クライアント \"{{key}}\" の更新に成功しました",
|
||||
"table_statistics": "リクエスト数(過去24時間)",
|
||||
"clients_not_found": "クライアント情報はありません",
|
||||
"client_confirm_delete": "クライアント \"{{key}}\" を削除してもよろしいですか?",
|
||||
"filter_confirm_delete": "フィルターを削除してもよろしいですか?",
|
||||
"auto_clients_title": "クライアント(実行時)",
|
||||
"auto_clients_desc": "AdGuard Homeで使用しているが設定に保存されていないクライアント上のデータ",
|
||||
"access_title": "アクセス設定",
|
||||
"access_desc": "ここで、AdGuard Home DNSサーバのアクセスルールを設定できます。",
|
||||
"access_allowed_title": "許可されたクライアント",
|
||||
"access_allowed_desc": "CIDRまたはIPアドレスのリスト。設定されると、AdGuard HomeはこれらのIPアドレスからのリクエストのみを許可します。",
|
||||
"access_disallowed_title": "拒否するクライアント",
|
||||
"access_disallowed_desc": "CIDRまたはIPアドレスのリスト。設定されると、AdGuard HomeはこれらのIPアドレスからのリクエストを破棄します。",
|
||||
"access_blocked_title": "ブロックするドメイン",
|
||||
"access_blocked_desc": "これをフィルタと混同しないでください。AdGuard Homeは、これらのドメインを含むDNSクエリを破棄します。",
|
||||
"access_settings_saved": "アクセス設定の保存に成功しました",
|
||||
"updates_checked": "アップデートの確認に成功しました",
|
||||
"updates_version_equal": "AdGuard Homeは既に最新です",
|
||||
"check_updates_now": "今すぐアップデートを確認する",
|
||||
"dns_privacy": "DNSプライバシー",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1>という文字列を使用してください。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1>という文字列を使用してください。",
|
||||
"setup_dns_privacy_3": "<0>暗号化されたDNSプロトコルはAndroid 9でのみサポートされていることに注意してください。そのため、他のオペレーティングシステムでは追加のソフトウェアをインストールする必要があります。</0> <0>使用できるソフトウェアの一覧です。</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9はDNS-over-TLSをネイティブにサポートします。設定するには、設定 → ネットワークとインターネット → 詳細設定 → プライベートDNS へ遷移し、そこにドメイン名を入力してください。",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0>は、<1>DNS-over-HTTPS</1>と<1>DNS-over-TLS</1>をサポートしています。",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0>は、Androidに<1>DNS-over-HTTPS</1>サポートを追加します。",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0>は<1>DNS-over-HTTPS</1>をサポートしますが、自身のサーバで使用するように設定するには、<2>DNS Stamp</2>を生成する必要があります。",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0>は、<1>DNS-over-HTTPS</1>と<1>DNS-over-TLS</1>の設定をサポートしています。",
|
||||
"setup_dns_privacy_other_title": "その他の機能",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home 自身は、どのプラットフォームでも安全なDNSクライアントになることができます。",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0>は、既知のすべてのセキュアDNSプロトコルをサポートしています。",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0>は<1>DNS-over-HTTPS</1>をサポートします。",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0>は<1>DNS-over-HTTPS</1>をサポートしています。",
|
||||
"setup_dns_privacy_other_5": "もっと多くの実装を<0>ここ</0>や<1>ここ</1>で見つけられます。",
|
||||
"setup_dns_notice": "<1>DNS-over-HTTPS</1>または<1>DNS-over-TLS</1>を使用するには、AdGuard Home 設定の<0>暗号化設定</0>が必要です。",
|
||||
"rewrite_added": "\"{{key}}\" のためのDNS書き換え情報を追加完了しました",
|
||||
"rewrite_deleted": "\"{{key}}\" のためのDNS書き換え情報を削除完了しました",
|
||||
"rewrite_add": "DNS書き換え情報を追加する",
|
||||
"rewrite_not_found": "DNS書き換え情報はありません",
|
||||
"rewrite_confirm_delete": "\"{{key}}\" のためのDNS書き換え情報を削除してもよろしいですか?",
|
||||
"rewrite_desc": "特定のドメイン名に対するDNS応答を簡単にカスタマイズすることを可能にします。",
|
||||
"rewrite_applied": "書き換えルールを適用済み",
|
||||
"dns_rewrites": "DNS書き換え",
|
||||
"form_domain": "ドメイン名を入力",
|
||||
"form_answer": "IPアドレスかドメイン名を入力",
|
||||
"form_error_domain_format": "ドメイン名のフォーマットが間違っています",
|
||||
"form_error_answer_format": "応答フォーマットが間違っています",
|
||||
"configure": "保存",
|
||||
"main_settings": "メイン設定",
|
||||
"block_services": "特定のサービスをブロックする",
|
||||
"blocked_services": "ブロックするサービス",
|
||||
"blocked_services_desc": "人気のあるサイトやサービスを一気にブロック。",
|
||||
"blocked_services_saved": "ブロックするサービスを保存完了しました。",
|
||||
"blocked_services_global": "ブロックするサービスに対しグローバル設定を使用する",
|
||||
"blocked_service": "ブロックするサービス",
|
||||
"block_all": "すべてブロック",
|
||||
"unblock_all": "すべてのブロックを解除"
|
||||
}
|
||||
357
client/src/__locales/pt-br.json
Normal file
357
client/src/__locales/pt-br.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"client_settings": "Configurações do cliente",
|
||||
"example_upstream_reserved": "Você pode especificar um DNS upstream <0>para um domínio(s) especifico</0>",
|
||||
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores upstream",
|
||||
"bootstrap_dns": "Servidores DNS de inicialização",
|
||||
"bootstrap_dns_desc": "Servidores DNS de inicialização são usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams.",
|
||||
"url_added_successfully": "URL adicionada com sucesso",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Salvar configuração",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
"disabled_dhcp": "Servidor DHCP desativado",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Se o seu roteador não fornecer configurações de DHCP, você poderá usar o servidor DHCP integrado do AdGuard.",
|
||||
"dhcp_enable": "Ativar servidor DHCP",
|
||||
"dhcp_disable": "Desativar servidor DHCP",
|
||||
"dhcp_not_found": "É seguro ativar o servidor DHCP integrado - não encontramos nenhum servidor DHCP ativo na rede. No entanto, recomendamos que você faça manualmente uma nova verificação, pois nosso teste automático não oferece 100% de garantia.",
|
||||
"dhcp_found": "Um servidor DHCP ativo foi encontrado na rede. Não é seguro ativar o servidor DHCP incorporado.",
|
||||
"dhcp_leases": "Concessões DHCP",
|
||||
"dhcp_static_leases": "Concessões de DHCP estático",
|
||||
"dhcp_leases_not_found": "Nenhuma concessão DHCP encontrada",
|
||||
"dhcp_config_saved": "Salvar configurações do servidor DHCP",
|
||||
"form_error_required": "Campo obrigatório",
|
||||
"form_error_ip_format": "formato de endereço IPv4 inválido",
|
||||
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||
"form_error_positive": "Deve ser maior que 0",
|
||||
"dhcp_form_gateway_input": "IP do gateway",
|
||||
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
||||
"dhcp_form_range_title": "Faixa de endereços IP",
|
||||
"dhcp_form_range_start": "Início da faixa",
|
||||
"dhcp_form_range_end": "Final da faixa",
|
||||
"dhcp_form_lease_title": "Tempo de concessão do DHCP (em segundos)",
|
||||
"dhcp_form_lease_input": "Duração da concessão",
|
||||
"dhcp_interface_select": "Selecione a interface DHCP",
|
||||
"dhcp_hardware_address": "Endereço de hardware",
|
||||
"dhcp_ip_addresses": "Endereço de IP",
|
||||
"dhcp_table_hostname": "Nome do servidor",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"dhcp_warning": "Se você quiser ativar o servidor DHCP, verifique se não há outro servidor DHCP ativo na sua rede. Caso contrário, a internet pode parar de funcionar para outros dispositivos conectados!",
|
||||
"dhcp_error": "Não foi possível determinar se existe outro servidor DHCP na rede.",
|
||||
"dhcp_static_ip_error": "Para usar o servidor DHCP, você deve definir um endereço IP estático. Não conseguimos determinar se essa interface de rede está configurada usando o endereço de IP estático. Por favor, defina um endereço IP estático manualmente.",
|
||||
"dhcp_dynamic_ip_found": "Seu sistema usa a configuração de endereço IP dinâmico para a interface <0>{{interfaceName}}</0>. Para usar o servidor DHCP, você deve definir um endereço de IP estático. Seu endereço IP atual é <0> {{ipAddress}} </ 0>. Vamos definir automaticamente este endereço IP como estático se você pressionar o botão Ativar DHCP.",
|
||||
"dhcp_lease_added": "Concessão estática \"{{key}}\" adicionada com sucesso",
|
||||
"dhcp_lease_deleted": "Concessão estática \"{{key}}\" excluída com sucesso",
|
||||
"dhcp_new_static_lease": "Nova concessão estática",
|
||||
"dhcp_static_leases_not_found": "Nenhuma concessão DHCP estática foi encontrada",
|
||||
"dhcp_add_static_lease": "Adicionar nova concessão estática",
|
||||
"delete_confirm": "Você tem certeza de que deseja excluir \"{{key}}\"?",
|
||||
"form_enter_hostname": "Digite o hostname",
|
||||
"error_details": "Detalhes do erro",
|
||||
"back": "Voltar",
|
||||
"dashboard": "Painel",
|
||||
"settings": "Configurações",
|
||||
"filters": "Filtros",
|
||||
"query_log": "Registro de consultas",
|
||||
"faq": "FAQ",
|
||||
"version": "Versão",
|
||||
"address": "endereço",
|
||||
"on": "Ligado",
|
||||
"off": "Desligado",
|
||||
"copyright": "Copyright",
|
||||
"homepage": "Página inicial",
|
||||
"report_an_issue": "Reportar um problema",
|
||||
"privacy_policy": "Política de privacidade",
|
||||
"enable_protection": "Ativar proteção",
|
||||
"enabled_protection": "Proteção ativada",
|
||||
"disable_protection": "Desativar proteção",
|
||||
"disabled_protection": "Proteção desativada",
|
||||
"refresh_statics": "Atualizar estatísticas",
|
||||
"dns_query": "Consultas de DNS",
|
||||
"blocked_by": "Bloqueador por filtros",
|
||||
"stats_malware_phishing": "Bloqueado malware/phishing",
|
||||
"stats_adult": "Bloqueado sites adultos",
|
||||
"stats_query_domain": "Principais domínios consultados",
|
||||
"for_last_24_hours": "nas últimas 24 horas",
|
||||
"no_domains_found": "Nenhum domínio encontrado",
|
||||
"requests_count": "Contagem de solicitações",
|
||||
"top_blocked_domains": "Principais domínios bloqueados",
|
||||
"top_clients": "Principais clientes",
|
||||
"no_clients_found": "Nenhuma cliente encontrado",
|
||||
"general_statistics": "Estatísticas gerais",
|
||||
"number_of_dns_query_24_hours": "O número de consultas DNS processadas nas últimas 24 horas",
|
||||
"number_of_dns_query_blocked_24_hours": "Várias solicitações DNS bloqueadas por filtros de bloqueio de anúncios e listas de bloqueio de hosts",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Várias solicitações de DNS bloqueadas pelo módulo de segurança da navegação do AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Vários sites adultos bloqueados",
|
||||
"enforced_save_search": "Forçar pesquisa segura",
|
||||
"number_of_dns_query_to_safe_search": "Várias solicitações de DNS para motores de busca para os quais a pesquisa segura foi aplicada",
|
||||
"average_processing_time": "Tempo médio de processamento",
|
||||
"average_processing_time_hint": "Tempo médio em milissegundos no processamento de uma solicitação DNS",
|
||||
"block_domain_use_filters_and_hosts": "Bloquear domínios usando arquivos de filtros e hosts",
|
||||
"filters_block_toggle_hint": "Você pode configurar as regras de bloqueio nas configurações de <a href='#filters'>Filtros</a>.",
|
||||
"use_adguard_browsing_sec": "Usar o serviço de segurança da navegação do AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "O AdGuard Home irá verificar se o domínio está na lista negra do serviço de segurança da navegação. Ele usará a API de pesquisa de privacidade para executar a verificação: apenas um prefixo curto do hash do nome de domínio SHA256 é enviado para o servidor.",
|
||||
"use_adguard_parental": "Usar o serviço de controle parental do AdGuard",
|
||||
"use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Ele usa a mesma API amigável de privacidade que o serviço de segurança da navegação.",
|
||||
"enforce_safe_search": "Forçar pesquisa segura",
|
||||
"enforce_save_search_hint": "O AdGuard Home pode forçar a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing e Yandex.",
|
||||
"no_servers_specified": "Nenhum servidor especificado",
|
||||
"no_settings": "Não configurado",
|
||||
"general_settings": "Configurações gerais",
|
||||
"dns_settings": "Configurações de DNS",
|
||||
"encryption_settings": "Configurações de criptografia",
|
||||
"dhcp_settings": "Configurações de DHCP",
|
||||
"upstream_dns": "Servidores DNS upstream",
|
||||
"upstream_dns_hint": "Se você deixar este campo vazio, o AdGuard Home irá 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",
|
||||
"enabled_filtering_toast": "Filtragem ativada",
|
||||
"disabled_safe_browsing_toast": "Navegação segura desativada",
|
||||
"enabled_safe_browsing_toast": "Navegação segura ativada",
|
||||
"disabled_parental_toast": "Controle parental desativado",
|
||||
"enabled_parental_toast": "Controle parental ativado",
|
||||
"disabled_safe_search_toast": "Pesquisa segura desativada",
|
||||
"enabled_save_search_toast": "Pesquisa segura ativada",
|
||||
"enabled_table_header": "Ativado",
|
||||
"name_table_header": "Nome",
|
||||
"filter_url_table_header": "URL do filtro",
|
||||
"rules_count_table_header": "Quantidade de regras",
|
||||
"last_time_updated_table_header": "Última atualização",
|
||||
"actions_table_header": "Ações",
|
||||
"edit_table_action": "Editar",
|
||||
"delete_table_action": "Excluir",
|
||||
"filters_and_hosts": "Filtros e listas de bloqueio de hosts",
|
||||
"filters_and_hosts_hint": "O AdGuard Home entende regras básicas de bloqueio de anúncios e a sintaxe de arquivos de hosts.",
|
||||
"no_filters_added": "Nenhum filtro adicionado",
|
||||
"add_filter_btn": "Adicionar filtro",
|
||||
"cancel_btn": "Cancelar",
|
||||
"enter_name_hint": "Digite o nome",
|
||||
"enter_url_hint": "Digite a URL",
|
||||
"check_updates_btn": "Verificar atualizações",
|
||||
"new_filter_btn": "Nova inscrição de filtro",
|
||||
"enter_valid_filter_url": "Digite a URL válida para efetuar a inscrição de filtro ou um arquivo de hosts.",
|
||||
"custom_filter_rules": "Regras de filtragem personalizadas",
|
||||
"custom_filter_rules_hint": "Digite uma regra por linha. Você pode usar regras de bloqueio de anúncios ou a sintaxe de arquivos de hosts.",
|
||||
"examples_title": "Exemplos",
|
||||
"example_meaning_filter_block": "bloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
|
||||
"example_meaning_filter_whitelist": "desbloqueia o acesso ao domínio exemplo.org e a todos os seus subdomínios",
|
||||
"example_meaning_host_block": "O AdGuard Home irá retornar o endereço 127.0.0.1 para o domínio exemplo.org (exceto seus subdomínios).",
|
||||
"example_comment": "! Aqui vai um comentário",
|
||||
"example_comment_meaning": "apenas um comentário",
|
||||
"example_comment_hash": "# Também um comentário",
|
||||
"example_regex_meaning": "bloqueia o acesso aos domínios que correspondem à <0>expressão regular especificada</0>",
|
||||
"example_upstream_regular": "DNS regular (através do UDP)",
|
||||
"example_upstream_dot": "<0>DNS-sobre-TLS</0> criptografado",
|
||||
"example_upstream_doh": "<0>DNS-sobre-HTTPS</0> criptografado",
|
||||
"example_upstream_sdns": "Você 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és do TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos os filtros já estão atualizados",
|
||||
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
|
||||
"dns_test_ok_toast": "Os servidores DNS especificados estão funcionando corretamente",
|
||||
"dns_test_not_ok_toast": "O servidor \"{{key}}\": não pôde ser utilizado. Por favor, verifique se você escreveu corretamente",
|
||||
"unblock_btn": "Desbloquear",
|
||||
"block_btn": "Bloquear",
|
||||
"time_table_header": "Data",
|
||||
"domain_name_table_header": "Nome de domínio",
|
||||
"type_table_header": "Tipo",
|
||||
"response_table_header": "Resposta",
|
||||
"client_table_header": "Cliente",
|
||||
"empty_response_status": "Vazio",
|
||||
"show_all_filter_type": "Mostrar todos",
|
||||
"show_filtered_type": "Mostrar filtrados",
|
||||
"no_logs_found": "Nenhum registro encontrado",
|
||||
"disabled_log_btn": "Desativar registros",
|
||||
"download_log_file_btn": "Baixar arquivo de registros",
|
||||
"refresh_btn": "Atualizar",
|
||||
"enabled_log_btn": "Ativar registros",
|
||||
"last_dns_queries": "Últimas 5000 consultas DNS",
|
||||
"previous_btn": "Anterior",
|
||||
"next_btn": "Próximo",
|
||||
"loading_table_status": "Carregando",
|
||||
"page_table_footer_text": "Página",
|
||||
"of_table_footer_text": "de",
|
||||
"rows_table_footer_text": "linhas",
|
||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
|
||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas",
|
||||
"query_log_disabled_toast": "Registros de consultas desativado",
|
||||
"query_log_enabled_toast": "Registros de consultas ativado",
|
||||
"source_label": "Fonte",
|
||||
"found_in_known_domain_db": "Encontrado no banco de dados de domínios conhecidos.",
|
||||
"category_label": "Categoria",
|
||||
"rule_label": "Regra",
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
||||
"install_welcome_title": "Bem-vindo(a) ao AdGuard Home!",
|
||||
"install_welcome_desc": "O AdGuard Home é um servidor de DNS para bloqueio de anúncios e rastreamento em toda a rede. Sua finalidade é permitir que você 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á disponível nos seguintes endereços:",
|
||||
"form_error_port": "Digite uma porta válida",
|
||||
"install_settings_dns": "Servidor DNS",
|
||||
"install_settings_dns_desc": "Você precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endereços:",
|
||||
"install_settings_all_interfaces": "Todas interfaces",
|
||||
"install_auth_title": "Autenticação",
|
||||
"install_auth_desc": "É altamente recomendável configurar uma senha de autenticação na interface web de administrador do AdGuard Home. Mesmo que seja acessível apenas em sua rede local, ainda é importante protegê-lo contra o acesso irrestrito.",
|
||||
"install_auth_username": "Nome de usuário",
|
||||
"install_auth_password": "Senha",
|
||||
"install_auth_confirm": "Confirmar senha",
|
||||
"install_auth_username_enter": "Digite o nome de usuário",
|
||||
"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ê precisa configurar seus dispositivos para usá-lo.",
|
||||
"install_submit_title": "Parabéns!",
|
||||
"install_submit_desc": "O procedimento de configuração está concluído e você está pronto para começar a usar o AdGuard Home.",
|
||||
"install_devices_router": "Roteador",
|
||||
"install_devices_router_desc": "Esta configuração cobrirá automaticamente todos os dispositivos conectados ao seu roteador doméstico e você não irá precisar configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home está capturando os seguintes endereços",
|
||||
"install_devices_router_list_1": "Abra as configurações do seu roteador\nNo navegador digite o IP do roteador, o padrão é (http://192.168.0.1/ ou http://192.168.1.1/), e o login e senha é admin/admin; Se você não se lembra da senha, você pode redefinir a senha rapidamente pressionando um botão no próprio roteador. Alguns roteadores têm um aplicativo específico que já deve estar instalado em seu computador/telefone.",
|
||||
"install_devices_router_list_2": "Encontre as Configurações de DNS. Procure as letras DNS ao lado de um campo que permite dois ou três conjuntos de números, cada um dividido em quatro grupos de um a três números.",
|
||||
"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ções do adaptador.",
|
||||
"install_devices_windows_list_4": "Selecione sua atual conexão, clique nela com o botão 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ços de servidor DNS e digite os endereços do servidores do AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Clique na ícone da Apple e depois em Preferências do Sistema.",
|
||||
"install_devices_macos_list_2": "Clique em Rede.",
|
||||
"install_devices_macos_list_3": "Selecione a primeira conexão da lista e clique em Avançado.",
|
||||
"install_devices_macos_list_4": "Selecione a guia DNS e digite os endereços dos servidores do AdGuard Home.",
|
||||
"install_devices_android_list_1": "Na tela inicial do menu Android, toque em Configurações.",
|
||||
"install_devices_android_list_2": "Toque em Wi-Fi. A tela listando todas as redes será exibida (não é possível configurar DNS personalizado para uma conexão de dados móveis)",
|
||||
"install_devices_android_list_3": "Pressione prolongadamente a rede para a qual você está conectado e toque em Modificar rede",
|
||||
"install_devices_android_list_4": "Em alguns dispositivos, talvez seja necessário marcar a caixa Avançado para ver as outras configurações. Para ajustar suas configurações de DNS do Android, você precisará alternar as configurações de IP de DHCP para Estático.",
|
||||
"install_devices_android_list_5": "Altere o conjunto dos valores DNS 1 e DNS 2 para os endereços 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ão é possível configurar o DNS em conexões de dados móveis).",
|
||||
"install_devices_ios_list_3": "Toque no nome da rede atualmente ativa.",
|
||||
"install_devices_ios_list_4": "No campo DNS, digite os endereços dos servidores do AdGuard Home.",
|
||||
"get_started": "Começar",
|
||||
"next": "Próximo",
|
||||
"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ção web",
|
||||
"encryption_config_saved": "Configuração de criptografia salva",
|
||||
"encryption_server": "Nome do servidor",
|
||||
"encryption_server_enter": "Digite seu nome de domínio",
|
||||
"encryption_server_desc": "Para usar o protocolo HTTPS, você 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á redirecionar automaticamente os endereços HTTP para HTTPS.",
|
||||
"encryption_https": "Porta HTTPS",
|
||||
"encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home será acessível via HTTPS e também fornecerá 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á executar o servidor DNS-sobre- TSL nesta porta.",
|
||||
"encryption_certificates": "Certificados",
|
||||
"encryption_certificates_desc": "Para usar criptografia, você precisa fornecer uma cadeia de certificados SSL válida para seu domínio. Você pode obter um certificado gratuito em <0> {{link}}</0> ou pode comprá-lo de uma das autoridades de certificação confiáveis.",
|
||||
"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á em HTTPS, o servidor DNS irá capturar as solicitações por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
|
||||
"encryption_chain_valid": "Cadeia de chave válida.",
|
||||
"encryption_chain_invalid": "A cadeia de certificado é inválida",
|
||||
"encryption_key_valid": "Esta é uma chave privada {{type}} válida",
|
||||
"encryption_key_invalid": "Esta é uma chave privada {{type}} inválida",
|
||||
"encryption_subject": "Assunto",
|
||||
"encryption_issuer": "Emissor",
|
||||
"encryption_hostnames": "Nomes dos servidores",
|
||||
"encryption_reset": "Você tem certeza de que deseja redefinir a configuração de criptografia?",
|
||||
"topline_expiring_certificate": "Seu certificado SSL está prestes a expirar. Atualize suas <0>configurações de criptografia</]0>",
|
||||
"topline_expired_certificate": "Seu certificado SSL está expirado. Atualize suas <0>configurações de criptografia</0>",
|
||||
"form_error_port_range": "Digite um porta entre 80 e 65535",
|
||||
"form_error_port_unsafe": "Esta porta não é segura",
|
||||
"form_error_equal": "Não deve ser igual",
|
||||
"form_error_password": "Senhas não coincidem",
|
||||
"reset_settings": "Redefinir configurações",
|
||||
"update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
|
||||
"setup_guide": "Guia de configuração",
|
||||
"dns_addresses": "Endereços DNS",
|
||||
"down": "Caiu",
|
||||
"fix": "Corrigido",
|
||||
"dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
|
||||
"update_now": "Atualizar agora",
|
||||
"update_failed": "A atualização automática falhou. Por favor, <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>siga estes passos</a> para atualizar manualmente.",
|
||||
"processing_update": "Por favor, aguarde enquanto o AdGuard Home está sendo atualizado",
|
||||
"clients_title": "Clientes",
|
||||
"clients_desc": "Configure dispositivos conectados ao AdGuard",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Personalizado",
|
||||
"table_client": "Cliente",
|
||||
"table_name": "Nome",
|
||||
"save_btn": "Salvar",
|
||||
"client_add": "Adicionar cliente",
|
||||
"client_new": "Novo cliente",
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Endereço de IP",
|
||||
"client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
||||
"form_enter_ip": "Digite o endereço de IP",
|
||||
"form_enter_mac": "Digite o endereço MAC",
|
||||
"form_client_name": "Digite o nome do cliente",
|
||||
"client_global_settings": "Usar configurações global",
|
||||
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
|
||||
"client_added": "Cliente \"{{key}}\" adicionado com sucesso",
|
||||
"client_updated": "Cliente \"{{key}}\" atualizado com sucesso",
|
||||
"table_statistics": "Contagem de solicitações (últimas 24 horas)",
|
||||
"clients_not_found": "Nenhum cliente foi encontrado",
|
||||
"client_confirm_delete": "Você tem certeza de que deseja excluir o cliente \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Você tem certeza de que deseja excluir o filtro?",
|
||||
"auto_clients_title": "Clientes (tempo de execução)",
|
||||
"auto_clients_desc": "Dados dos clientes que usam o AdGuard Home, que não são armazenados na configuração",
|
||||
"access_title": "Configurações de acessos",
|
||||
"access_desc": "Aqui você pode configurar as regras de acesso para o servidores de DNS do AdGuard Home.",
|
||||
"access_allowed_title": "Clientes permitidos",
|
||||
"access_allowed_desc": "Uma lista de endereços IP ou CIDR. Ao configurar, o AdGuard Home irá permitir solicitações apenas desses endereços de IP.",
|
||||
"access_disallowed_title": "Clientes não permitidos",
|
||||
"access_disallowed_desc": "Uma lista de endereços IP ou CIDR. Ao configurar, o AdGuard Home irá descartar as solicitações desses endereços de IP.",
|
||||
"access_blocked_title": "Domínios bloqueados",
|
||||
"access_blocked_desc": "Não confunda isso com os filtros. O AdGuard Home irá descartar as consultas DNS com esses domínios.",
|
||||
"access_settings_saved": "Configurações de acesso foram salvas com sucesso",
|
||||
"updates_checked": "Atualizações verificadas com sucesso",
|
||||
"updates_version_equal": "O AdGuard Home está atualizado.",
|
||||
"check_updates_now": "Verificar atualizações",
|
||||
"dns_privacy": "Privacidade de DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-sobre-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-sobre-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_3": "<0>Por favor, note que os protocolos de DNS criptografados são suportados apenas no Android 9. Então, você irá precisa instalar um software adicional em outros sistemas operacionais.</0><0>Aqui está a lista de softwares que você pode usar.</0>",
|
||||
"setup_dns_privacy_android_1": "O Android 9 suporta o DNS-sobre-TLS de forma nativa. Para configurá-lo, vá para Configurações → Rede e internet → Avançado → DNS privado e digite seu nome de domínio lá.",
|
||||
"setup_dns_privacy_android_2": "O <0>AdGuard para Android</0> suporta <1>DNS-sobre-HTTPS</1> e <1>DNS-sobre-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> adiciona o suporte <1>DNS-sobre-HTTPS</1> para o Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> suporta <1>DNS-sobre-HTTPS</1>, mas para configurá-lo para usar seu próprio servidor, você precisará gerar um <2>DNS Stamp</2>.",
|
||||
"setup_dns_privacy_ios_2": "O <0>AdGuard para iOS</0> suporta a configuração do <1>DNS-sobre-HTTPS</1> e <1>DNS-sobre-TLS</1>.",
|
||||
"setup_dns_privacy_other_title": "Outras implementações",
|
||||
"setup_dns_privacy_other_1": "O próprio AdGuard Home pode ser usado como um cliente DNS seguro em qualquer plataforma.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> suporta todos os protocolos de DNS seguros conhecidos.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> suporta <1>DNS-sobre-HTTPS</1>",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> suporta <1>DNS-sobre-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Você encontrará mais implementações <0>aqui</0> e <1>aqui</1>.",
|
||||
"setup_dns_notice": "Para usar o <1>DNS-sobre-HTTPS</1> ou <1>DNS-sobre-TLS</1>, você precisa <0>configurar a criptografia</0> nas configurações do AdGuard Home.",
|
||||
"rewrite_added": "Reescrita de DNS para \"{{key}}\" adicionada com sucesso",
|
||||
"rewrite_deleted": "Reescrita de DNS para \"{{key}}\" excluída com sucesso",
|
||||
"rewrite_add": "Adicionar reescrita de DNS",
|
||||
"rewrite_not_found": "Nenhuma reescrita de DNS foi encontrada",
|
||||
"rewrite_confirm_delete": "Você tem certeza de que deseja excluir a reescrita de DNS para \"{{key}}\"?",
|
||||
"rewrite_desc": "Permite configurar uma resposta personalizada do DNS para um nome de domínio específico.",
|
||||
"rewrite_applied": "Regra de reescrita aplicada",
|
||||
"dns_rewrites": "Reescritas de DNS",
|
||||
"form_domain": "Digite o domínio",
|
||||
"form_answer": "Digite o endereço de IP ou nome de domínio",
|
||||
"form_error_domain_format": "Formato de domínio inválido",
|
||||
"form_error_answer_format": "Formato de resposta inválido",
|
||||
"configure": "Configurar",
|
||||
"main_settings": "Configurações principais",
|
||||
"block_services": "Bloquear serviços específicos",
|
||||
"blocked_services": "Serviços bloqueados",
|
||||
"blocked_services_desc": "Permite o bloqueio rápido de sites e serviços populares.",
|
||||
"blocked_services_saved": "Serviços bloqueados salvos com sucesso",
|
||||
"blocked_services_global": "Usar serviços bloqueados globais",
|
||||
"blocked_service": "Serviço bloqueado",
|
||||
"block_all": "Bloquear tudo",
|
||||
"unblock_all": "Desbloquear todos"
|
||||
}
|
||||
357
client/src/__locales/ru.json
Normal file
357
client/src/__locales/ru.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"client_settings": "Настройки клиентов",
|
||||
"example_upstream_reserved": "вы можете указать DNS-сервер <0>для конкретного домена(ов)</0>",
|
||||
"upstream_parallel": "Использовать одновременные запроссы ко всем серверам для ускорения обработки запроса",
|
||||
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS-серверы используются для поиска IP-адресов DoH/DoT серверов, которые вы указали.",
|
||||
"url_added_successfully": "URL успешно добавлен",
|
||||
"check_dhcp_servers": "Проверить DHCP-серверы",
|
||||
"save_config": "Сохранить конфигурацию",
|
||||
"enabled_dhcp": "DHCP-сервер включен",
|
||||
"disabled_dhcp": "DHCP-сервер отключен",
|
||||
"dhcp_title": "DHCP-сервер (экспериментальный!)",
|
||||
"dhcp_description": "Если ваш роутер не предоставляет настройки DHCP, вы можете использовать собственный встроенный DHCP-сервер AdGuard.",
|
||||
"dhcp_enable": "Включить DHCP-сервер",
|
||||
"dhcp_disable": "Отключить DHCP-сервер",
|
||||
"dhcp_not_found": "Активные DHCP-серверы в сети не найдены. Вы можете безопасно включить встроенный сервер DHCP.",
|
||||
"dhcp_found": "Некоторые активные DHCP-серверы найдены в сети. Включение встроенного DHCP-сервера небезопасно.",
|
||||
"dhcp_leases": "Аренда DHCP",
|
||||
"dhcp_static_leases": "Статические аренды DHCP",
|
||||
"dhcp_leases_not_found": "Аренда DHCP не обнаружена",
|
||||
"dhcp_config_saved": "Сохраненная конфигурация DHCP-сервера",
|
||||
"form_error_required": "Обязательное поле",
|
||||
"form_error_ip_format": "Неверный формат IPv4",
|
||||
"form_error_mac_format": "Некорректный формат MAC",
|
||||
"form_error_positive": "Должно быть больше 0",
|
||||
"dhcp_form_gateway_input": "IP-адрес шлюза",
|
||||
"dhcp_form_subnet_input": "Маска подсети",
|
||||
"dhcp_form_range_title": "Диапазон IP-адресов",
|
||||
"dhcp_form_range_start": "Начало диапазона",
|
||||
"dhcp_form_range_end": "Конец диапазона",
|
||||
"dhcp_form_lease_title": "Время аренды DHCP (в секундах)",
|
||||
"dhcp_form_lease_input": "Срок аренды",
|
||||
"dhcp_interface_select": "Выбрать интерфейс DHCP",
|
||||
"dhcp_hardware_address": "Аппаратный адрес",
|
||||
"dhcp_ip_addresses": "IP-адреса",
|
||||
"dhcp_table_hostname": "Имя хоста",
|
||||
"dhcp_table_expires": "Истекает",
|
||||
"dhcp_warning": "Если вы все равно хотите включить DHCP-сервер, убедитесь, что в сети больше нет активных DHCP-серверов. Иначе это может сломать доступ в сеть для подключенных устройств!",
|
||||
"dhcp_error": "Мы не смогли определить присутствие других DHCP-серверов в сети.",
|
||||
"dhcp_static_ip_error": "Для того, чтобы использовать DHCP-сервер, должен быть установлен статический IP-адрес. Мы не смогли определить, использует ли этот сетевой интерфейс статический IP-адрес. Пожалуйста, установите его вручную.",
|
||||
"dhcp_dynamic_ip_found": "Ваша система использует динамический IP-адрес для интерфейса <0>{{interfaceName}}</0>. Чтобы использовать DHCP-сервер необходимо установить статический IP-адрес. Ваш текущий IP-адрес - <0>{{ipAddress}}</0>. Мы автоматически установим его как статический ессли вы нажмете кнопку Включить DHCP.",
|
||||
"dhcp_lease_added": "Статическая аренда \"{{key}}\" успешно добавлена",
|
||||
"dhcp_lease_deleted": "Статическая аренда \"{{key}}\" успешно удалена",
|
||||
"dhcp_new_static_lease": "Новая статическая аренда",
|
||||
"dhcp_static_leases_not_found": "Не найдено статических аренд DHCP",
|
||||
"dhcp_add_static_lease": "Добавить статическую аренду",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Введите имя хоста",
|
||||
"error_details": "Детализация ошибки",
|
||||
"back": "Назад",
|
||||
"dashboard": "Панель управления",
|
||||
"settings": "Настройки",
|
||||
"filters": "Фильтры",
|
||||
"query_log": "Журнал",
|
||||
"faq": "FAQ",
|
||||
"version": "версия",
|
||||
"address": "адрес",
|
||||
"on": "Вкл",
|
||||
"off": "Выкл",
|
||||
"copyright": "Все права защищены",
|
||||
"homepage": "Главная",
|
||||
"report_an_issue": "Сообщить о проблеме",
|
||||
"privacy_policy": "Политика конфиденциальности",
|
||||
"enable_protection": "Включить защиту",
|
||||
"enabled_protection": "Защита вкл.",
|
||||
"disable_protection": "Отключить защиту",
|
||||
"disabled_protection": "Защита выкл.",
|
||||
"refresh_statics": "Обновить статистику",
|
||||
"dns_query": "DNS-запросы",
|
||||
"blocked_by": "Заблокировано фильтрами",
|
||||
"stats_malware_phishing": "Заблокированные вредоносные и фишинговые сайты",
|
||||
"stats_adult": "Заблокированные \"взрослые\" сайты",
|
||||
"stats_query_domain": "Часто запрашиваемые домены",
|
||||
"for_last_24_hours": "за 24 часа",
|
||||
"no_domains_found": "Домены не найдены",
|
||||
"requests_count": "Количество запросов",
|
||||
"top_blocked_domains": "Часто блокируемые домены",
|
||||
"top_clients": "Частые клиенты",
|
||||
"no_clients_found": "Клиентов не найдено",
|
||||
"general_statistics": "Общая статистика",
|
||||
"number_of_dns_query_24_hours": "Количество DNS-запросов за 24 часа",
|
||||
"number_of_dns_query_blocked_24_hours": "Количество DNS-запросов, заблокированных фильтрами и блок-списками",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Количество DNS-запросов, заблокированных модулем Антифишинга AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Количество заблокированных \"сайтов для взрослых\"",
|
||||
"enforced_save_search": "Применен безопасный поиск",
|
||||
"number_of_dns_query_to_safe_search": "Количество запросов DNS для поисковых систем, для которых был применен Безопасный поиск",
|
||||
"average_processing_time": "Среднее время обработки запроса",
|
||||
"average_processing_time_hint": "Среднее время для обработки запроса DNS в миллисекундах",
|
||||
"block_domain_use_filters_and_hosts": "Блокировать домены с использованием фильтров и файлов хостов",
|
||||
"filters_block_toggle_hint": "Вы можете настроить правила блокировки в <a href='#filters'> \"Фильтрах\"</a>.",
|
||||
"use_adguard_browsing_sec": "Использовать Безопасную навигацию AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home проверит, включен ли домен в веб-службу безопасности браузера. Он будет использовать API, чтобы выполнить проверку: на сервер отправляется только короткий префикс имени домена SHA256.",
|
||||
"use_adguard_parental": "Используйте модуль Родительского контроля AdGuard ",
|
||||
"use_adguard_parental_hint": "AdGuard Home проверит, содержит ли домен материалы 18+. Он использует тот же API для обеспечения конфиденциальности, что и веб-служба безопасности браузера.",
|
||||
"enforce_safe_search": "Усилить безопасный поиск",
|
||||
"enforce_save_search_hint": "AdGuard Home может обеспечить безопасный поиск в следующих системах: Google, Youtube, Bing и Yandex.",
|
||||
"no_servers_specified": "Нет указанных серверов",
|
||||
"no_settings": "Нет настроек",
|
||||
"general_settings": "Основные настройки",
|
||||
"dns_settings": "Настройки DNS",
|
||||
"encryption_settings": "Настройки шифрования",
|
||||
"dhcp_settings": "Настройки DHCP",
|
||||
"upstream_dns": "Upstream DNS-серверы",
|
||||
"upstream_dns_hint": "Если вы оставите это поле пустым, то AdGuard Home использует <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> в качестве upstream. Используйте tls:// для DNS через серверы TLS.",
|
||||
"test_upstream_btn": "Тест upstream серверов",
|
||||
"apply_btn": "Применить",
|
||||
"disabled_filtering_toast": "Фильтрация выкл.",
|
||||
"enabled_filtering_toast": "Фильтрация вкл.",
|
||||
"disabled_safe_browsing_toast": "Безопасная навигация выкл.",
|
||||
"enabled_safe_browsing_toast": "Безопасная навигация вкл.",
|
||||
"disabled_parental_toast": "Родительский контроль выкл.",
|
||||
"enabled_parental_toast": "Родительский контроль вкл.",
|
||||
"disabled_safe_search_toast": "Безопасный поиск выкл.",
|
||||
"enabled_save_search_toast": "Безопасный поиск вкл.",
|
||||
"enabled_table_header": "Вкл.",
|
||||
"name_table_header": "Имя",
|
||||
"filter_url_table_header": "URL фильтра",
|
||||
"rules_count_table_header": "Количество правил:",
|
||||
"last_time_updated_table_header": "Последнее обновление",
|
||||
"actions_table_header": "Действия",
|
||||
"edit_table_action": "Редактировать",
|
||||
"delete_table_action": "Удалить",
|
||||
"filters_and_hosts": "Фильтры и черные списки hosts",
|
||||
"filters_and_hosts_hint": "AdGuard Home распознает базовые правила блокировки и синтаксис файлов hosts.",
|
||||
"no_filters_added": "Фильтры не добавлены",
|
||||
"add_filter_btn": "Добавить фильтр",
|
||||
"cancel_btn": "Отмена",
|
||||
"enter_name_hint": "Введите имя",
|
||||
"enter_url_hint": "Введите URL",
|
||||
"check_updates_btn": "Проверить обновления",
|
||||
"new_filter_btn": "Добавление нового фильтра",
|
||||
"enter_valid_filter_url": "Введите действующий URL для подписки на фильтр или файл hosts.",
|
||||
"custom_filter_rules": "Пользовательское правило фильтрации",
|
||||
"custom_filter_rules_hint": "Вводите по одному правилу на строчку. Вы можете использовать правила блокировки или синтаксис файлов hosts.",
|
||||
"examples_title": "Примеры",
|
||||
"example_meaning_filter_block": "заблокировать доступ к домену example.org и всем его поддоменам",
|
||||
"example_meaning_filter_whitelist": "разблокировать доступ к домену example.org и всем его поддоменам",
|
||||
"example_meaning_host_block": "Теперь AdGuard Home вернет 127.0.0.1 для домена example.org (но не для его поддоменов).",
|
||||
"example_comment": "! Так можно добавлять описание",
|
||||
"example_comment_meaning": "комментарий",
|
||||
"example_comment_hash": "# И вот так тоже",
|
||||
"example_regex_meaning": "блокирует доступ к доменам, соответствующим <0>заданному регулярному выражению</0>",
|
||||
"example_upstream_regular": "обычный DNS (поверх UDP)",
|
||||
"example_upstream_dot": "зашифрованный <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-поверх-TLS</a>",
|
||||
"example_upstream_doh": "зашифрованный <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-поверх-HTTPS</a>",
|
||||
"example_upstream_sdns": "вы можете использовать <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> для <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> или <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> резолверов",
|
||||
"example_upstream_tcp": "обычный DNS (поверх TCP)",
|
||||
"all_filters_up_to_date_toast": "Все фильтры обновлены",
|
||||
"updated_upstream_dns_toast": "Upstream DNS-серверы обновлены",
|
||||
"dns_test_ok_toast": "Указанные серверы DNS работают корректно",
|
||||
"dns_test_not_ok_toast": "Сервер \"{{key}}\": невозможно использовать, проверьте правильность написания",
|
||||
"unblock_btn": "Разблокировать",
|
||||
"block_btn": "Заблокировать",
|
||||
"time_table_header": "Время",
|
||||
"domain_name_table_header": "Домен",
|
||||
"type_table_header": "Тип",
|
||||
"response_table_header": "Ответ",
|
||||
"client_table_header": "Клиент",
|
||||
"empty_response_status": "Пусто",
|
||||
"show_all_filter_type": "Показать все",
|
||||
"show_filtered_type": "Показать отфильтрованные",
|
||||
"no_logs_found": "Логи не найдены",
|
||||
"disabled_log_btn": "Журнал фильтрации выкл.",
|
||||
"download_log_file_btn": "Загрузить отчёт",
|
||||
"refresh_btn": "Обновить",
|
||||
"enabled_log_btn": "Журнал фильтрации вкл.",
|
||||
"last_dns_queries": "Последние 5000 DNS-запросов",
|
||||
"previous_btn": "Назад",
|
||||
"next_btn": "Вперёд",
|
||||
"loading_table_status": "Загрузка...",
|
||||
"page_table_footer_text": "Страница",
|
||||
"of_table_footer_text": "из",
|
||||
"rows_table_footer_text": "строк",
|
||||
"updated_custom_filtering_toast": "Внесены изменения в пользовательские правила",
|
||||
"rule_removed_from_custom_filtering_toast": "Правило удалено из авторского списка правил фильтрации",
|
||||
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено",
|
||||
"query_log_disabled_toast": "Журнал запросов выкл.",
|
||||
"query_log_enabled_toast": "Журнал запросов вкл.",
|
||||
"source_label": "Источник",
|
||||
"found_in_known_domain_db": "Найден в базе известных доменов.",
|
||||
"category_label": "Категория",
|
||||
"rule_label": "Правило",
|
||||
"filter_label": "Фильтр",
|
||||
"unknown_filter": "Неизвестный фильтр {{filterId}}",
|
||||
"install_welcome_title": "Добро пожаловать в AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home - это DNS-сервер, блокирующий рекламу и трекинг. Его цель - дать вам возможность контролировать всю вашу сеть и все подключенные устройства. Он не требует установки клиентских программ.",
|
||||
"install_settings_title": "Веб-интерфейс администрирования",
|
||||
"install_settings_listen": "Сетевой интерфейс",
|
||||
"install_settings_port": "Порт",
|
||||
"install_settings_interface_link": "Ваш веб-интерфейс администрирования AdGuard Home будет доступен по следующим адресам:",
|
||||
"form_error_port": "Введите корректный порт",
|
||||
"install_settings_dns": "DNS-сервер",
|
||||
"install_settings_dns_desc": "Вам будет нужно настроить свои устройства или роутер на использование DNS-сервера на одном из следующих адресов:",
|
||||
"install_settings_all_interfaces": "Все интерфейсы",
|
||||
"install_auth_title": "Авторизация",
|
||||
"install_auth_desc": "Рекомендуется настроить авторизацию по паролю для вашего веб-интерфейса администрирования AdGuard Home. Важно ограничить доступ к нему даже если он доступен только в вашей локальной сети.",
|
||||
"install_auth_username": "Имя пользователя",
|
||||
"install_auth_password": "Пароль",
|
||||
"install_auth_confirm": "Подтвердить пароль",
|
||||
"install_auth_username_enter": "Введите имя пользователя",
|
||||
"install_auth_password_enter": "Введите пароль",
|
||||
"install_step": "Шаг",
|
||||
"install_devices_title": "Настройте ваши устройства",
|
||||
"install_devices_desc": "Для того, чтобы использовать AdGuard Home, вам нужно настроить ваши устройства на его использование.",
|
||||
"install_submit_title": "Поздравляем!",
|
||||
"install_submit_desc": "Процедура настройки завершена и вы готовы начать использование AdGuard Home.",
|
||||
"install_devices_router": "Роутер",
|
||||
"install_devices_router_desc": "Такая настройка автоматически покроет все устройства, использующие ваш домашний роутер, и вам не нужно будет настраивать каждое из них в отдельности.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home доступен по следующим адресам",
|
||||
"install_devices_router_list_1": "Откройте настройки вашего роутера. Обычно вы можете открыть их в вашем браузере (например, http://192.168.0.1/ или http://192.168.1.1/). Вас могут попросить ввести пароль. Если вы не помните его, пароль часто можно сбросить, нажав на кнопку на самом роутере. Некоторые роутеры требуют специального приложения, которое в этом случае должно быть уже установлено на ваш компьютер или телефон.",
|
||||
"install_devices_router_list_2": "Найдите настройки DHCP или DNS. Найдите буквы \"DNS\" рядом с текстовым полем, в которое можно ввести два или три ряда цифр, разделенных на 4 группы от одной до трёх цифр.",
|
||||
"install_devices_router_list_3": "Введите туда адрес вашего AdGuard Home.",
|
||||
"install_devices_windows_list_1": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
||||
"install_devices_windows_list_2": "Откройте Панель управления через меню \"Пуск\" или через поиск Windows.",
|
||||
"install_devices_windows_list_3": "В левой стороне экрана найдите \"Изменение параметров адаптера\" и кликните по нему.",
|
||||
"install_devices_windows_list_4": "Выделите ваше активное подключение, затем кликните по нему правой клавишей мыши и выберите \"Свойства\".",
|
||||
"install_devices_windows_list_5": "Найдите в списке пункт \"IP версии 4 (TCP/IP)\", выделите его и затем снова нажмите \"Свойства\".",
|
||||
"install_devices_windows_list_6": "Выберите \"Использовать следующие адреса DNS-серверов\" и введите адрес AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Кликните по иконке Apple и перейдите в «Системные настройки».",
|
||||
"install_devices_macos_list_2": "Кликните по иконке «Сеть».",
|
||||
"install_devices_macos_list_3": "Выберите первое подключение в списке и нажмите кнопку «Дополнительно».",
|
||||
"install_devices_macos_list_4": "Выберите вкладку «DNS» и добавьте адреса AdGuard Home.",
|
||||
"install_devices_android_list_1": "В меню управления нажмите иконку «Настройки».",
|
||||
"install_devices_android_list_2": "Выберите пункт «Wi-Fi». Появится экран со списком доступных сетей (настройка DNS недоступна для мобильных сетей).",
|
||||
"install_devices_android_list_3": "Долгим нажатием по текущей сети вызовите меню, в котором нажмите «Изменить сеть».",
|
||||
"install_devices_android_list_4": "На некоторых устройствах может потребоваться нажать «Расширенные настройки». Чтобы получить возможность изменять настройки DNS, вам потребуется переключить «Настройки IP» на «Пользовательские».",
|
||||
"install_devices_android_list_5": "Теперь можно изменить поля «DNS 1» и «DNS 2». Введите в них адреса AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Войдите в меню настроек устройства.",
|
||||
"install_devices_ios_list_2": "Выберите пункт «Wi-Fi» (для мобильных сетей ручная настройка DNS невозможна).",
|
||||
"install_devices_ios_list_3": "Нажмите на название сети, к которой устройство подключено в данный момент.",
|
||||
"install_devices_ios_list_4": "В поле «DNS» введите введите адреса AdGuard Home.",
|
||||
"get_started": "Поехали",
|
||||
"next": "Дальше",
|
||||
"open_dashboard": "Открыть Панель управления",
|
||||
"install_saved": "Успешно сохранено",
|
||||
"encryption_title": "Шифрование",
|
||||
"encryption_desc": "Поддержка шифрования (HTTPS/TLS) для DNS и веб-интерфейса администрирования",
|
||||
"encryption_config_saved": "Настройки шифрования сохранены",
|
||||
"encryption_server": "Имя сервера",
|
||||
"encryption_server_enter": "Введите ваше доменное имя",
|
||||
"encryption_server_desc": "Для использования HTTPS вам необходимо ввести имя сервера, которое подходит вашему SSL-сертификату.",
|
||||
"encryption_redirect": "Автоматически перенаправлять на HTTPS",
|
||||
"encryption_redirect_desc": "Если включено, AdGuard Home будет автоматически перенаправлять вас с HTTP на HTTPS адрес.",
|
||||
"encryption_https": "Порт HTTPS",
|
||||
"encryption_https_desc": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.",
|
||||
"encryption_dot": "Порт DNS-over-TLS",
|
||||
"encryption_dot_desc": "Если этот порт настроен, AdGuard Home запустит DNS-over-TLS-сервер на этому порту.",
|
||||
"encryption_certificates": "Сертификаты",
|
||||
"encryption_certificates_desc": "Для использования шифрования вам необходимо предоставить валидную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на <0>{{link}}</0> или вы можете купить его у одного из доверенных Центров Сертификации.",
|
||||
"encryption_certificates_input": "Скопируйте сюда сертификаты в PEM-кодировке.",
|
||||
"encryption_status": "Статус",
|
||||
"encryption_expire": "Истекает",
|
||||
"encryption_key": "Приватный ключ",
|
||||
"encryption_key_input": "Скопируйте сюда приватный ключ в PEM-кодировке.",
|
||||
"encryption_enable": "Включить шифрование (HTTPS, DNS-over-HTTPS и DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Если шифрование включено, веб-интерфейс AdGuard Home будет работать по HTTPS, а DNS-сервер будет также работать по DNS-over-HTTPS и DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Цепочка сертификатов валидна",
|
||||
"encryption_chain_invalid": "Цепочка сертификатов не валидна",
|
||||
"encryption_key_valid": "Валидный {{type}} приватный ключ",
|
||||
"encryption_key_invalid": "Невалидный {{type}} приватный ключ",
|
||||
"encryption_subject": "Субъект",
|
||||
"encryption_issuer": "Издатель",
|
||||
"encryption_hostnames": "Имена хостов",
|
||||
"encryption_reset": "Вы уверены, что хотите сбросить настройки шифрования?",
|
||||
"topline_expiring_certificate": "Ваш SSL-сертификат скоро истекает. Обновите <0>Настройки шифрования</0>.",
|
||||
"topline_expired_certificate": "Ваш SSL-сертификат истек. Обновите <0>Настройки шифрования</0>.",
|
||||
"form_error_port_range": "Введите значение порта из интервала 80-65535",
|
||||
"form_error_port_unsafe": "Это - небезопасный порт",
|
||||
"form_error_equal": "Не должны быть равны",
|
||||
"form_error_password": "Пароли не совпадают",
|
||||
"reset_settings": "Сбросить настройки",
|
||||
"update_announcement": "AdGuard Home {{version}} уже доступна! <0>Нажмите сюда</0>, чтобы узнать больше.",
|
||||
"setup_guide": "Инструкция по настройке",
|
||||
"dns_addresses": "Адреса DNS",
|
||||
"down": "Вниз",
|
||||
"fix": "Исправить",
|
||||
"dns_providers": "<0>Список известных DNS-провайдеров</0> на выбор.",
|
||||
"update_now": "Обновить сейчас",
|
||||
"update_failed": "Ошибка авто-обновления. Пожалуйста, <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>следуйте инструкции</a> для обновления вручную.",
|
||||
"processing_update": "Пожалуйста, подождите, AdGuard Home обновляется",
|
||||
"clients_title": "Клиенты",
|
||||
"clients_desc": "Настройте устройства, использующие AdGuard Home",
|
||||
"settings_global": "Глобальные",
|
||||
"settings_custom": "Свои",
|
||||
"table_client": "Клиент",
|
||||
"table_name": "Имя",
|
||||
"save_btn": "Сохранить",
|
||||
"client_add": "Добавить клиента",
|
||||
"client_new": "Новый клиент",
|
||||
"client_edit": "Редактировать клиента",
|
||||
"client_identifier": "Идентификатор",
|
||||
"ip_address": "IP-адрес",
|
||||
"client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу или MAC-адресу. Обратите внимание, что использование MAC как идентификатора, возможно только если AdGuard Home также является и <0>DHCP-сервером</0>",
|
||||
"form_enter_ip": "Введите IP",
|
||||
"form_enter_mac": "Введите MAC",
|
||||
"form_client_name": "Введите имя клиента",
|
||||
"client_global_settings": "Использовать глобальные настройки",
|
||||
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
||||
"client_added": "Клиент \"{{key}}\" успешно добавлен",
|
||||
"client_updated": "Клиент \"{{key}}\" успешно обновлен",
|
||||
"table_statistics": "Количество запросов (последние 24 часа)",
|
||||
"clients_not_found": "Клиентов не найдено",
|
||||
"client_confirm_delete": "Вы уверены, что хотите удалить клиента \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Вы уверены, что хотите удалить фильтр?",
|
||||
"auto_clients_title": "Клиенты (runtime)",
|
||||
"auto_clients_desc": "Данные о клиентах, которые используют AdGuard Home, но не хранятся в настройках",
|
||||
"access_title": "Настройки доступа",
|
||||
"access_desc": "Здесь вы можете настроить правила доступа к DNS-серверу AdGuard Home.",
|
||||
"access_allowed_title": "Разрешенные клиенты",
|
||||
"access_allowed_desc": "Список CIDR- или IP-адресов. Если он настроен, AdGuard Home будет принимать запросы только с этих IP-адресов.",
|
||||
"access_disallowed_title": "Запрещенные клиенты",
|
||||
"access_disallowed_desc": "Список CIDR- или IP-адресов. Если он настроек, AdGuard Home будет игнорировать запросы с этих IP-адресов.",
|
||||
"access_blocked_title": "Заблокированные домены",
|
||||
"access_blocked_desc": "Не путайте это с фильтрами. AdGuard Home будет игнорировать DNS-запросы с этими доменами.",
|
||||
"access_settings_saved": "Настройки доступа успешно сохранены",
|
||||
"updates_checked": "Проверка обновлений прошла успешно",
|
||||
"updates_version_equal": "Версия AdGuard Home актуальна",
|
||||
"check_updates_now": "Проверить обновления",
|
||||
"dns_privacy": "Зашифрованный DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Используйте строку <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Используйте строку <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_3": "<0>Обратите внимание, что зашифрованный DNS-протокол поддерживается только на Android 9. Для других операционных систем вам нужно будет установить дополнительное ПО.</0><0>Вот список ПО, которое вы можете использовать.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 нативно поддерживает DNS-over-TLS. Для настройки, перейдите в Настройки → Сеть и Интернет → Дополнительно → Персональный DNS сервер, и введите туда ваше доменное имя.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard для Android</0> поддерживает <1>DNS-over-HTTPS</1> и <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> добавляет поддержка <1>DNS-over-HTTPS</1> на Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> поддерживает <1>DNS-over-HTTPS</1>, но для настройки его, вам будет нужно сгенерировать для него <2>DNS-отпечаток</2>.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard для iOS</0> поддерживает <1>DNS-over-HTTPS</1> и <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_other_title": "Другие решения",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home сам может быть клиентом зашифрованного DNS на любой платформе.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> поддерживает все известные зашифрованные DNS-протоколы.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> поддерживает <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Вы можете найти еще варианты <0>тут</0> и <1>тут</1>.",
|
||||
"setup_dns_notice": "Чтобы использовать <1>DNS-over-HTTPS</1> или <1>DNS-over-TLS</1>, вам нужно <0>настроить шифрование</0> в настройках AdGuard Home.",
|
||||
"rewrite_added": "Правило перенаправления DNS для \"{{key}}\" успешно добавлено",
|
||||
"rewrite_deleted": "Правило перенаправления DNS для \"{{key}}\" успешно удалено",
|
||||
"rewrite_add": "Добавить правило перенаправления DNS",
|
||||
"rewrite_not_found": "Не найдено правил перенаправления DNS",
|
||||
"rewrite_confirm_delete": "Вы уверены, что хотите удалить правило перенаправления DNS для \"{{key}}\"?",
|
||||
"rewrite_desc": "Позволяет легко настроить пользовательский DNS-ответ для определеннного домена.",
|
||||
"rewrite_applied": "Применено правило перенаправления",
|
||||
"dns_rewrites": "Перенаправления DNS",
|
||||
"form_domain": "Введите домен",
|
||||
"form_answer": "Введите IP адрес или домен",
|
||||
"form_error_domain_format": "Неверный формат домена",
|
||||
"form_error_answer_format": "Неверный формат ответа",
|
||||
"configure": "Настроить",
|
||||
"main_settings": "Основные настройки",
|
||||
"block_services": "Выбрать заблокированные сервисы",
|
||||
"blocked_services": "Заблокированные сервисы",
|
||||
"blocked_services_desc": "Позволяет быстро заблокировать популярные сайты и сервисы.",
|
||||
"blocked_services_saved": "Заблокированные сервисы успешно сохранены",
|
||||
"blocked_services_global": "Использовать глобальные заблокированные сервисы",
|
||||
"blocked_service": "Заблокированный сервис",
|
||||
"block_all": "Заблокировать все",
|
||||
"unblock_all": "Разблокировать все"
|
||||
}
|
||||
254
client/src/__locales/sv.json
Normal file
254
client/src/__locales/sv.json
Normal file
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"example_upstream_reserved": "du kan specificera DNS-uppström <0>för en specifik domän</0>",
|
||||
"upstream_parallel": "Använd parallella förfrågningar för att snabba upp dessa på uppströmsservrar.",
|
||||
"bootstrap_dns": "Bootstrap-DNS-servrar",
|
||||
"bootstrap_dns_desc": "Bootstrap-DNS-servrar används för att slå upp DoH/DoT-resolvrarnas IP-adresser som du specificerat som uppström.",
|
||||
"url_added_successfully": "URL tillagd utan fel",
|
||||
"check_dhcp_servers": "Letar efter DHCP-servrar",
|
||||
"save_config": "Spara inställningar",
|
||||
"enabled_dhcp": "DHCP-server aktiverad",
|
||||
"disabled_dhcp": "Dhcp-server avaktiverad",
|
||||
"dhcp_title": "DHCP-server (experimentell)",
|
||||
"dhcp_description": "Om din router inte har inställningar för DHCP kan du använda AdGuards inbyggda server.",
|
||||
"dhcp_enable": "Aktivera DHCP.-server",
|
||||
"dhcp_disable": "Avaktivera DHCP-server",
|
||||
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i nätverkat.",
|
||||
"dhcp_found": "Några aktiva DHCP-servar upptäcktes. Det är inte säkert att aktivera inbyggda DHCP-servrar.",
|
||||
"dhcp_leases": "DHCP-lease",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||
"dhcp_config_saved": "Sparade inställningar för DHCP-servern",
|
||||
"form_error_required": "Obligatoriskt fält",
|
||||
"form_error_ip_format": "Ogiltigt IPv4-format",
|
||||
"form_error_positive": "Måste vara större än noll",
|
||||
"dhcp_form_gateway_input": "Gateway-IP",
|
||||
"dhcp_form_subnet_input": "Subnetmask",
|
||||
"dhcp_form_range_title": "IP-adressgränser",
|
||||
"dhcp_form_range_start": "Startgräns",
|
||||
"dhcp_form_range_end": "Gränsslut",
|
||||
"dhcp_form_lease_title": "DHCP-leasetid (i sekunder)",
|
||||
"dhcp_form_lease_input": "Leasetid",
|
||||
"dhcp_interface_select": "Välj DHCP-gränssnitt",
|
||||
"dhcp_hardware_address": "Hårdvaruadress",
|
||||
"dhcp_ip_addresses": "IP-adresser",
|
||||
"dhcp_table_hostname": "Värdnamn",
|
||||
"dhcp_table_expires": "Utgår",
|
||||
"dhcp_warning": "Om du vill använda den inbyggda DHCP-servern ändå, se till att det inte finns några andra aktiva DHCP-servrar. Annars kan den störa internetanslutningen för anslutna enheter!",
|
||||
"dhcp_error": "Vi kunde inte avgöra om det finns en till DHCP-server på nätverket.",
|
||||
"dhcp_static_ip_error": "För att kunna använda en DHCP-server måste det finnas en statisk IP-adress. Vi kunde inte avgöra om nätverksgränssnittet är konfigurerat med en statisk IP-adress. Ställ in en statistik IP-adress manuellt.",
|
||||
"dhcp_dynamic_ip_found": "Din enhet använder en dynamisk IP-adress för gränssnittet <0>{{interfaceName}}</0>. För att kunna använda DHCP-servern behövs en statisk IP-adress. Din nuvarande IP-adress är <0>{{ipAddress}}</0>. Vi kommer att göra denna IP-adress statisk automatiskt om du trycker på knappen \"Aktivera DHCP\".",
|
||||
"error_details": "Felinformation",
|
||||
"back": "Tiilbaka",
|
||||
"dashboard": "Kontrollpanel",
|
||||
"settings": "Inställningar",
|
||||
"filters": "Filter",
|
||||
"query_log": "Förfrågningslogg",
|
||||
"version": "version",
|
||||
"address": "adress",
|
||||
"on": "PÅ",
|
||||
"off": "AV",
|
||||
"homepage": "Hemsida",
|
||||
"report_an_issue": "Rapportera ett problem",
|
||||
"privacy_policy": "Integritetspolicy",
|
||||
"enable_protection": "Koppla på skydd",
|
||||
"enabled_protection": "Kopplade på skydd",
|
||||
"disable_protection": "Koppla bort skydd",
|
||||
"disabled_protection": "Kopplade bort skydd",
|
||||
"refresh_statics": "Uppdatera statistik",
|
||||
"dns_query": "DNS-förfrågningar",
|
||||
"blocked_by": "Blockerat av filter",
|
||||
"stats_malware_phishing": "Blockerad skadekod/phising",
|
||||
"stats_adult": "Blockerade vuxensajter",
|
||||
"stats_query_domain": "Mest eftersökta domäner",
|
||||
"for_last_24_hours": "under de senaste 24 timamrna",
|
||||
"no_domains_found": "Inga domäner hittade",
|
||||
"requests_count": "Förfrågningsantal",
|
||||
"top_blocked_domains": "Flest blockerade domäner",
|
||||
"top_clients": "Toppklienter",
|
||||
"no_clients_found": "Inga hitatde klienter",
|
||||
"general_statistics": "Allmän statistik",
|
||||
"number_of_dns_query_24_hours": "Ett antal DNS-förfrågningar utfördes under de senaste 244 timamrna",
|
||||
"number_of_dns_query_blocked_24_hours": "Ett antal DNS-förfrågningar blockerades av annonsfilter och värdens bloceringsklistor",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Ett antal DNS-förfrågningar blockerades av AdGuards modul för surfsäkerhet",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Ett anta vuxensajter blockerades",
|
||||
"enforced_save_search": "Aktivering av Säker surf",
|
||||
"number_of_dns_query_to_safe_search": "Ett antal DNS-förfrågningar genomfördes på sökmotorer med Säker surf aktiverat",
|
||||
"average_processing_time": "Genomsnittlig processtid",
|
||||
"average_processing_time_hint": "Genomsnittlig processtid i millisekunder för DNS-förfrågning",
|
||||
"block_domain_use_filters_and_hosts": "Blockera domäner med filter- och värdfiler",
|
||||
"filters_block_toggle_hint": "Du kan ställa in egna blockerings regler i <a href='#filters'>Filterinställningar</a>.",
|
||||
"use_adguard_browsing_sec": "Amvänd AdGuards webbservice för surfsäkerhet",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home kommer att kontrollera om en domän är svartlistad i webbservicens surfsäkerhet. Med en integritetsvänlig metod görs en API-lookup för att kontrollera : endast en kort prefix i domännamnet SHA256 hash skickas till servern.",
|
||||
"use_adguard_parental": "Använda AdGuards webbservice för färäldrakontroll",
|
||||
"use_adguard_parental_hint": "AdGuard Home kommer att kontrollera domäner för innehåll av vuxenmaterial . Samma integritetsvänliga metod för API-lookup som tillämpas i webbservicens surfsäkerhet används.",
|
||||
"enforce_safe_search": "Tillämpa Säker surf",
|
||||
"enforce_save_search_hint": "AdGuard Home kan framtvinga säker surf i följande sökmoterer: Google, Youtube, Bing, och Yandex.",
|
||||
"no_servers_specified": "Inga servrar angivna",
|
||||
"no_settings": "Inga inställningar",
|
||||
"general_settings": "Allmänna inställningar",
|
||||
"upstream_dns": "Upstream DNS-servrar",
|
||||
"upstream_dns_hint": "Om du låter fältet vara tomt kommer AdGuard Home att använda <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> för uppström.",
|
||||
"test_upstream_btn": "Testa uppströmmar",
|
||||
"apply_btn": "Tillämpa",
|
||||
"disabled_filtering_toast": "Filtrering bortkopplad",
|
||||
"enabled_filtering_toast": "Filtrering inkopplad",
|
||||
"disabled_safe_browsing_toast": "Säker surfning bortkopplat",
|
||||
"enabled_safe_browsing_toast": "Säker surfning inkopplat",
|
||||
"disabled_parental_toast": "Föräldrakontroll bortkopplat",
|
||||
"enabled_parental_toast": "Föräldrakontroll inkopplat",
|
||||
"disabled_safe_search_toast": "Säker webbsökning bortkopplat",
|
||||
"enabled_save_search_toast": "Säker webbsökning inkopplat",
|
||||
"enabled_table_header": "Inkopplat",
|
||||
"name_table_header": "Namn",
|
||||
"filter_url_table_header": "Filtrerar URL",
|
||||
"rules_count_table_header": "Regelantal",
|
||||
"last_time_updated_table_header": "Uppdaterades senast",
|
||||
"actions_table_header": "Åtgärder",
|
||||
"delete_table_action": "Ta bort",
|
||||
"filters_and_hosts": "Filtrerings- och värdlistor för blockering",
|
||||
"filters_and_hosts_hint": "AdGuard tillämpar grundläggande annonsblockeringsregler och värdfiltersyntaxer",
|
||||
"no_filters_added": "Inga filter tillagda",
|
||||
"add_filter_btn": "Lägg till filter",
|
||||
"cancel_btn": "Avbryt",
|
||||
"enter_name_hint": "Skriv in namn",
|
||||
"enter_url_hint": "Skriv in URL",
|
||||
"check_updates_btn": "Sök efter uppdateringar",
|
||||
"new_filter_btn": "Nytt filterabonemang",
|
||||
"enter_valid_filter_url": "Skriv in en giltigt URL till ett filterabonnemang eller värdfil.",
|
||||
"custom_filter_rules": "Egna filterregler",
|
||||
"custom_filter_rules_hint": "Skriv en regel per rad. Du kan använda antingen annonsblockeringsregler eller värdfilssyntax.",
|
||||
"examples_title": "Exempel",
|
||||
"example_meaning_filter_block": "blockera åtkomst till domän example.org domain och alla dess subdomäner",
|
||||
"example_meaning_filter_whitelist": "avblockera åtkomst till domän example.org domain och alla dess subdomäner",
|
||||
"example_meaning_host_block": "AdGuard Home kommer nu att returnera adress 127.0.0.1 för domänexemplet example.org (dock utan dess subdomäner).",
|
||||
"example_comment": "! Här kommer en kommentar",
|
||||
"example_comment_meaning": "Endast en kommentar",
|
||||
"example_comment_hash": "# Också en kommentar",
|
||||
"example_upstream_regular": "vanlig DNS (över 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ända <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS-stamps</a> för <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> eller <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-över-HTTPS</a>\n-resolvers",
|
||||
"example_upstream_tcp": "vanlig DNS (över UDP)",
|
||||
"all_filters_up_to_date_toast": "Alla filter är redan aktuella",
|
||||
"updated_upstream_dns_toast": "Uppdaterade uppströms-dns-servrar",
|
||||
"dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte användas. Var snäll och kolla att du skrivit in rätt",
|
||||
"unblock_btn": "Avblockera",
|
||||
"block_btn": "Blockera",
|
||||
"time_table_header": "Tid",
|
||||
"domain_name_table_header": "Domännamn",
|
||||
"type_table_header": "Typ",
|
||||
"response_table_header": "Svar",
|
||||
"client_table_header": "Klient",
|
||||
"empty_response_status": "Tomt",
|
||||
"show_all_filter_type": "Visa alla",
|
||||
"show_filtered_type": "Visa filtrerade",
|
||||
"no_logs_found": "Inga logga funna",
|
||||
"disabled_log_btn": "Koppla bort logg",
|
||||
"download_log_file_btn": "Ladda ner loggfil",
|
||||
"refresh_btn": "Läs in igen",
|
||||
"enabled_log_btn": "Koppla in logg",
|
||||
"last_dns_queries": "De senaste 5000 DNS-anropen",
|
||||
"previous_btn": "Föregående",
|
||||
"next_btn": "Nästa",
|
||||
"loading_table_status": "Läser in...",
|
||||
"page_table_footer_text": "Sida",
|
||||
"of_table_footer_text": "av",
|
||||
"rows_table_footer_text": "rader",
|
||||
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna",
|
||||
"query_log_disabled_toast": "Förfrågningsloggen bortkopplad",
|
||||
"query_log_enabled_toast": "Förfrågningsloggen inkopplad",
|
||||
"source_label": "Källa",
|
||||
"found_in_known_domain_db": "Hittad i domändatabas.",
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Regel",
|
||||
"unknown_filter": "Okänt filter {{filterId}}",
|
||||
"install_welcome_title": "Välkommen till AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home är en DNS-server för nätverkstäckande annons- och spårningsblockering. Dess syfte är att de dig kontroll över hela nätverket och alla dina enheter, utan behov av att använda klientbaserade program.",
|
||||
"install_settings_title": "Administratörens webbgränssnitt",
|
||||
"install_settings_listen": "Övervakningsgränssnitt",
|
||||
"install_settings_interface_link": "Din administratörssida för AdGuard Home finns på följande adresser:",
|
||||
"form_error_port": "Skriv in ett giltigt portnummer",
|
||||
"install_settings_dns": "DNS-server",
|
||||
"install_settings_dns_desc": "Du behöver ställa in dina enheter eller din router för att använda DNS-server på följande adresser.",
|
||||
"install_settings_all_interfaces": "Alla gränssnitt",
|
||||
"install_auth_title": "Autentisering",
|
||||
"install_auth_desc": "Det rekommenderas starkt att ställa in lösenordsskydd till webbgränssnittets administrativa del i ditt AdGuard Home. Även om den endast är åtkomlig på ditt lokala nätverk rekommenderas det ändå att skydda det mot oönskad åtkomst.",
|
||||
"install_auth_username": "Användarnamn",
|
||||
"install_auth_password": "Lösenord",
|
||||
"install_auth_confirm": "Bekräfta lösenord",
|
||||
"install_auth_username_enter": "Skriv in användarnamn",
|
||||
"install_auth_password_enter": "Skriv in lösenord",
|
||||
"install_step": "Steg",
|
||||
"install_devices_title": "Ställ in dina enheter",
|
||||
"install_devices_desc": "För att kunna använda AdGuard Home måste du sälla in dina enheter för att utnyttja den.",
|
||||
"install_submit_title": "Grattis!",
|
||||
"install_submit_desc": "Inställningsproceduren är klar och du kan börja använda AdGuard Home.",
|
||||
"install_devices_router_desc": "Den här anpassningen kommer att automatiskt täcka in alla de enheter som är anslutna till din hemmarouter och du behöver därför inte konfigurera var och en individuellt.",
|
||||
"install_devices_address": "AdGuard Home DNS-server täcker följande adresser",
|
||||
"install_devices_router_list_1": "Öppna routern Inställningar. Vanligtvis får man åtkomst via en URL (http://192.168.0.1 eller 192.168.1.1)- Du kommer att bli ombes att ange ett lösenord. Lösenordet kan stå angivet på routerns bak- eller undersida. Om lösenordet ändrats och du inte känner till det kan du återställa med Reset-knappen. En del routrar kräver en särskild applikation som skall finnas på antingen din dator eller i din mobil.",
|
||||
"install_devices_router_list_2": "Leta upp DHCP/DNS-inställningarna. Titta efter DNS-tecken intill ett fält med två eller tre uppsättningar 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": "Öppna Kontrollpanelen via Start eller Windows Sök.",
|
||||
"install_devices_windows_list_2": "Välj Nätverks och delningscenter, Nätverk och Internet.",
|
||||
"install_devices_windows_list_3": "Leta upp Ändra nätverkskortsalternativ",
|
||||
"install_devices_windows_list_4": "Markera din aktiva anslutning. Högerklicka på den och välj Egenskaper.",
|
||||
"install_devices_windows_list_5": "Markera Internet Protocol Version 4 (TCP/IP) och klicka på knappen Egenskaper.",
|
||||
"install_devices_windows_list_6": "Markera Använd följande DNS-serveradresser och skriv in adresserna till ditt AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Klicka på Apple-ikonen och välj Systemalternativ.",
|
||||
"install_devices_macos_list_2": "Klicka på Nätverk.",
|
||||
"install_devices_macos_list_3": "Välj den första anslutningen i listan och klicka på Avancerat.",
|
||||
"install_devices_macos_list_4": "Klicka på DNS-fliken och skriv in AdGuard Homes serveradresser",
|
||||
"install_devices_android_list_1": "Välj Inställningar från Androids hemknapp",
|
||||
"install_devices_android_list_2": "Tryck på Nätverk och Internet, Wi-Fi. Alla tillgängliga nätverk visas i en lista (det går inte all välja egen DNS på mobilnätverk.",
|
||||
"install_devices_android_list_3": "Håll ner på nätverksnamnet som du är ansluten till och välj Ändra nätverk.",
|
||||
"install_devices_android_list_4": "På en del enheter kan du behöva välja Avancerat för att komma åt ytterligare inställningar. För att ändra på DNS-inställningar måste du byta IP-inställning från DHCP till Statisk. På Android Pie väljs Privat DNS på Nätverk och internet.",
|
||||
"install_devices_android_list_5": "Ändra DNS 1 och DNS 2 till serveradresserna för AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Tryck Inställningar från hemskärmen.",
|
||||
"install_devices_ios_list_2": "Välj Wi_Fi på den vänstra menyn (det går inte att ställa in egen DNS för mobila nätverk).",
|
||||
"install_devices_ios_list_3": "Tryck på namnet på den aktiva anslutningen.",
|
||||
"install_devices_ios_list_4": "Skriv in AdGuard Homes serveradresser i DNS-fälten.",
|
||||
"get_started": "Kom igång",
|
||||
"next": "Nästa",
|
||||
"open_dashboard": "Öppna Kontrollbordet",
|
||||
"install_saved": "Sparat utan fel",
|
||||
"encryption_title": "Kryptering",
|
||||
"encryption_desc": "Krypteringsstöd (HTTPS/TLS) för både DNS och adminwebbgränssnitt.",
|
||||
"encryption_config_saved": "Krypteringsinställningar sparade",
|
||||
"encryption_server": "Servernamn",
|
||||
"encryption_server_enter": "Skriv in ditt domännamn",
|
||||
"encryption_server_desc": "För att använda HTTPS behöver du skriva in servernamnet som stämmer överens med ditt SSL-certifikat.",
|
||||
"encryption_redirect": "Omdirigera till HTTPS automatiskt",
|
||||
"encryption_redirect_desc": "Om bockad kommer AdGuard Home automatiskt att omdirigera dig från HTTP till HTTPS-adresser.",
|
||||
"encryption_https": "HTTPS-port",
|
||||
"encryption_https_desc": "Om en HTTPS-port är inställd kommer gränssnittet till AdGuard Home administrering att kunna nås via HTTPS och kommer också att erbjuda DNS-over-HTTPS på '/dns-query' plats.",
|
||||
"encryption_dot": "DNS-över-TLS port",
|
||||
"encryption_dot_desc": "Om den här porten ställs in kommer AdGuard Home att använda DNS-over-TLS-server på porten.",
|
||||
"encryption_certificates": "Certifikat",
|
||||
"encryption_certificates_desc": "För att använda kryptering måste du ange ett giltigt SSL-certifikat för din domän. Du kan skaffa ett certifikat gratis på <0>{{link}}</0> eller köpa ett från någon av de godkända certifikatutfärdare.",
|
||||
"encryption_certificates_input": "Kopiera/klistra in dina PEM-kodade certifikat här.",
|
||||
"encryption_expire": "Utgår",
|
||||
"encryption_key": "Privat nyckel",
|
||||
"encryption_key_input": "Kopiera/klistra in din PEM-kodade privata nyckel för ditt certifikat här.",
|
||||
"encryption_enable": "Aktivera kryptering (HTTPS, DNS-över-HTTPS och DNS-över-TLS)",
|
||||
"encryption_enable_desc": "Om kryptering är aktiverat kommer administratörsgränssnittet till AdGuard Home att köras över HTTPS och DNS-servern kommer att lyssna på förfrågningar över DNS-over-HTTPS och DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certifikatkedjan är giltig",
|
||||
"encryption_chain_invalid": "Certifikatkedjan är ogiltig",
|
||||
"encryption_key_valid": "Det här är en giltig {{type}} privat nyckel",
|
||||
"encryption_key_invalid": "Det här är en ogiltig {{type}} privat nyckel",
|
||||
"encryption_subject": "Subjekt",
|
||||
"encryption_issuer": "Utgivare",
|
||||
"encryption_hostnames": "Värdnamn",
|
||||
"encryption_reset": "Är du säker på att du vill återställa krypteringsinställningarna?",
|
||||
"topline_expiring_certificate": "Ditt SSL-certifikat håller på att gå ut. <0>Krypteringsinställningar</0>.",
|
||||
"topline_expired_certificate": "Ditt SSL-certifikat har gått ut. Uppdatera <0>Krypteringsinställningar</0>-",
|
||||
"form_error_port_range": "Ange ett portnummer inom värdena 80-65535",
|
||||
"form_error_port_unsafe": "Det här är en osäker port",
|
||||
"form_error_equal": "Skall inte vara lika",
|
||||
"form_error_password": "Lösenorden överensstämmer inte",
|
||||
"reset_settings": "Återställ inställningar",
|
||||
"update_announcement": "AdGuard Home {{version}} är nu tillgänglig! <0>Klicka här</0> för mer information.",
|
||||
"setup_guide": "Inställningsguide",
|
||||
"dns_addresses": "DNS-adresser"
|
||||
}
|
||||
144
client/src/__locales/vi.json
Normal file
144
client/src/__locales/vi.json
Normal file
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"check_dhcp_servers": "Kiểm tra máy chủ DHCP",
|
||||
"save_config": "Lưu thiết lập",
|
||||
"enabled_dhcp": "Máy chủ DHCP đã kích hoạt",
|
||||
"disabled_dhcp": "Máy chủ DHCP đã tắt",
|
||||
"dhcp_title": "Máy chủ DHCP (thử nghiệm!)",
|
||||
"dhcp_description": "Nếu bộ định tuyến không trợ cài đặt DHCP, bạn có thể dùng máy chủ DHCP dựng sẵn của AdGuard",
|
||||
"dhcp_enable": "Bật máy chủ DHCP",
|
||||
"dhcp_disable": "Tắt máy chủ DHCP",
|
||||
"dhcp_not_found": "Không có máy chủ DHCP nào được tìm thấy trong mạng. Có thể bật máy chủ DHCP một cách an toàn",
|
||||
"dhcp_found": "Đã tìm thấy máy chủ DHCP trong mạng. Có thể có rủi ro nếu kích hoạt máy chủ DHCP dựng sẵn",
|
||||
"form_error_positive": "Phải lớn hơn 0",
|
||||
"dhcp_form_range_end": "IP kết thúc",
|
||||
"dhcp_interface_select": "Chọn một card mạng",
|
||||
"back": "Quay lại",
|
||||
"dashboard": "Tổng quan",
|
||||
"settings": "Cài đặt",
|
||||
"filters": "Bộ lọc",
|
||||
"query_log": "Lịch sử truy vấn",
|
||||
"faq": "Hỏi đáp",
|
||||
"version": "phiên bản",
|
||||
"address": "địa chỉ",
|
||||
"on": "Đang bật",
|
||||
"off": "Đang tắt",
|
||||
"copyright": "Bản quyền",
|
||||
"homepage": "Trang chủ",
|
||||
"report_an_issue": "Báo lỗi",
|
||||
"enable_protection": "Bật bảo vệ",
|
||||
"enabled_protection": "Đã bật bảo vệ",
|
||||
"disable_protection": "Tắt bảo vệ",
|
||||
"disabled_protection": "Đã tắt bảo vệ",
|
||||
"refresh_statics": "Làm mới thống kê",
|
||||
"dns_query": "Truy vấn DNS",
|
||||
"blocked_by": "Chặn bởi bộ lọc",
|
||||
"stats_malware_phishing": "Mã độc/lừa đảo đã chặn",
|
||||
"stats_adult": "Website người lớn đã chặn",
|
||||
"stats_query_domain": "Tên miền truy vấn nhiều",
|
||||
"for_last_24_hours": "trong 24 giờ qua",
|
||||
"no_domains_found": "Không có tên miền nào",
|
||||
"requests_count": "Số lần yêu cầu",
|
||||
"top_blocked_domains": "Tên miền chặn nhiều",
|
||||
"top_clients": "Client dùng nhiều",
|
||||
"no_clients_found": "Không có client nào",
|
||||
"general_statistics": "Thống kê chung",
|
||||
"number_of_dns_query_24_hours": "Số yêu cầu DNS đã xử lý trong 24 giờ qua",
|
||||
"number_of_dns_query_blocked_24_hours": "Số yêu cầu DNS bị chặn bởi bộ lọc quảng cáo và danh sách chặn host",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Số yêu cầu DNS bị chặn bởi chế độ bảo vệ duyệt web AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Số website người lớn đã chặn",
|
||||
"enforced_save_search": "Tìm kiếm an toàn",
|
||||
"number_of_dns_query_to_safe_search": "Số yêu cầu DNS tới công cụ tìm kiếm đã chuyển thành tìm kiếm an toàn",
|
||||
"average_processing_time": "Thời gian xử lý trung bình",
|
||||
"average_processing_time_hint": "Thời gian trung bình cho một yêu cầu DNS tính bằng mili giây",
|
||||
"block_domain_use_filters_and_hosts": "Chặn tên miền sử dụng các bộ lọc và file hosts",
|
||||
"filters_block_toggle_hint": "Bạn có thể thiết lập quy tắc chặn tại cài đặt <a href='#filters'>Bộ lọc</a>.",
|
||||
"use_adguard_browsing_sec": "Sử dụng dịch vụ bảo vệ duyệt web AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home sẽ kiểm tra tên miền với dịch vụ bảo vệ duyệt web. Tính năng sử dụng một API thân thiện với quyền riêng tư: chỉ một phần ngắn tiền tố mã băm SHA256 được gửi đến máy chủ",
|
||||
"use_adguard_parental": "Sử dụng dịch vụ quản lý của phụ huynh AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web",
|
||||
"enforce_safe_search": "Bắt buộc tìm kiếm an toàn",
|
||||
"enforce_save_search_hint": "AdGuard Home có thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Youtube, Bing, Yandex.",
|
||||
"no_servers_specified": "Không có máy chủ nào được liệt kê",
|
||||
"no_settings": "Không có cài đặt nào",
|
||||
"general_settings": "Cài đặt chung",
|
||||
"upstream_dns": "Máy chủ DNS tìm kiếm",
|
||||
"upstream_dns_hint": "Nếu bạn để trống mục này, AdGuard Home sẽ sử dụng <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> để tìm kiếm. Sử dụng tiền tố tls:// cho các máy chủ DNS dựa trên TLS.",
|
||||
"test_upstream_btn": "Kiểm tra",
|
||||
"apply_btn": "Áp dụng",
|
||||
"disabled_filtering_toast": "Đã tắt chặn quảng cáo",
|
||||
"enabled_filtering_toast": "Đã bật chặn quảng cáo",
|
||||
"disabled_safe_browsing_toast": "Đã tắt bảo vệ duyệt web",
|
||||
"enabled_safe_browsing_toast": "Đã bật bảo vệ duyệt web",
|
||||
"disabled_parental_toast": "Đã tắt quản lý của phụ huynh",
|
||||
"enabled_parental_toast": "Đã bật quản lý của phụ huynh",
|
||||
"disabled_safe_search_toast": "Đã tắt tìm kiếm an toàn",
|
||||
"enabled_save_search_toast": "Đã bật tìm kiếm an toàn",
|
||||
"enabled_table_header": "Kích hoạt",
|
||||
"name_table_header": "Tên",
|
||||
"filter_url_table_header": "URL bộ lọc",
|
||||
"rules_count_table_header": "Số quy tắc",
|
||||
"last_time_updated_table_header": "Cập nhật cuối",
|
||||
"actions_table_header": "Thao tác",
|
||||
"delete_table_action": "Xoá",
|
||||
"filters_and_hosts": "Danh sách bộ lọc và hosts",
|
||||
"filters_and_hosts_hint": "AdGuard home hiểu các quy tắc chặn quảng cáo đơn giản và cú pháp file hosts",
|
||||
"no_filters_added": "Không có bộ lọc nào được thêm",
|
||||
"add_filter_btn": "Thêm bộ lọc",
|
||||
"cancel_btn": "Huỷ",
|
||||
"enter_name_hint": "Nhập tên",
|
||||
"enter_url_hint": "Nhập URL",
|
||||
"check_updates_btn": "Kiểm tra cập nhật",
|
||||
"new_filter_btn": "Đăng ký bộ lọc mới",
|
||||
"enter_valid_filter_url": "Nhập URL hợp lệ của bộ lọc hoặc file hosts",
|
||||
"custom_filter_rules": "Quy tắc lọc tuỳ chỉnh",
|
||||
"custom_filter_rules_hint": "Nhập mỗi quy tắc 1 dòng. Có thể sử dụng quy tắc chặn quảng cáo hoặc cú pháp file host",
|
||||
"examples_title": "Ví dụ",
|
||||
"example_meaning_filter_block": "Chặn truy cập tới tên miền example.org và tất cả tên miền con",
|
||||
"example_meaning_filter_whitelist": "Không chặn truy cập tới tên miền example.org và tất cả tên miền con",
|
||||
"example_meaning_host_block": "AdGuard Home sẽ phản hồi địa chỉ IP 127.0.0.1 cho tên miền example.org (không áp dụng tên miền con)",
|
||||
"example_comment": "! Đây là một chú thích",
|
||||
"example_comment_meaning": "Chỉ là một chú thích",
|
||||
"example_comment_hash": "# Cũng là một chú thích",
|
||||
"example_upstream_regular": "DNS thông thường (dùng UDP)",
|
||||
"example_upstream_dot": "được mã hoá <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "được mã hoá <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
"example_upstream_sdns": "bạn có thể sử dụng <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> for <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> hoặc<a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> ",
|
||||
"example_upstream_tcp": "DNS thông thường(dùng TCP)",
|
||||
"all_filters_up_to_date_toast": "Tất cả bộ lọc đã được cập nhật",
|
||||
"updated_upstream_dns_toast": "Đã cập nhật máy chủ DNS tìm kiếm",
|
||||
"dns_test_ok_toast": "Máy chủ DNS có thể sử dụng",
|
||||
"dns_test_not_ok_toast": "Máy chủ \"\"': không thể sử dụng, vui lòng kiểm tra lại",
|
||||
"unblock_btn": "Bỏ chặn",
|
||||
"block_btn": "Chặn",
|
||||
"time_table_header": "Thời gian",
|
||||
"domain_name_table_header": "Tên miền",
|
||||
"type_table_header": "Loại",
|
||||
"response_table_header": "Phản hồi",
|
||||
"client_table_header": "Người dùng cuối",
|
||||
"empty_response_status": "Rỗng",
|
||||
"show_all_filter_type": "Hiện tất cả",
|
||||
"show_filtered_type": "Chỉ hiện đã lọc",
|
||||
"no_logs_found": "Không có lịch sử truy vấn",
|
||||
"disabled_log_btn": "Tắt lịch sử truy vấn",
|
||||
"download_log_file_btn": "Tải tập tin lịch sử truy vấn",
|
||||
"refresh_btn": "Làm mới",
|
||||
"enabled_log_btn": "Bật lịch sử truy vấn",
|
||||
"last_dns_queries": "5000 truy vấn DNS gần nhất",
|
||||
"previous_btn": "Trang trước",
|
||||
"next_btn": "Trang sau",
|
||||
"loading_table_status": "Đang tải...",
|
||||
"page_table_footer_text": "Trang",
|
||||
"of_table_footer_text": "của",
|
||||
"rows_table_footer_text": "hàng",
|
||||
"updated_custom_filtering_toast": "Đã cập nhật quy tắc lọc tuỳ chỉnh",
|
||||
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh",
|
||||
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh",
|
||||
"query_log_disabled_toast": "Đã tắt lịch sử truy vấn",
|
||||
"query_log_enabled_toast": "Đã bật lịch sử truy vấn",
|
||||
"source_label": "Nguồn",
|
||||
"found_in_known_domain_db": "Tìm thấy trong cơ sở dữ liệu tên miền",
|
||||
"category_label": "Thể loại",
|
||||
"rule_label": "Quy tắc",
|
||||
"filter_label": "Bộ lọc",
|
||||
"unknown_filter": "Bộ lọc không rõ {{filterId}}"
|
||||
}
|
||||
334
client/src/__locales/zh-cn.json
Normal file
334
client/src/__locales/zh-cn.json
Normal file
@@ -0,0 +1,334 @@
|
||||
{
|
||||
"client_settings": "客户端设置",
|
||||
"example_upstream_reserved": "您可以<0>为特定域</0>指定上游 DNS",
|
||||
"upstream_parallel": "通过同时查询所有上流服务器以使用并行查询加速解析",
|
||||
"bootstrap_dns": "Bootstrap DNS 服务器",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS 服务器用于解析您指定为上游的 DoH / DoT 解析器的 IP 地址。",
|
||||
"url_added_successfully": "网址添加成功",
|
||||
"check_dhcp_servers": "检查 DHCP 服务器",
|
||||
"save_config": "保存配置",
|
||||
"enabled_dhcp": "DHCP 服务器已启用",
|
||||
"disabled_dhcp": "DHCP 服务器已禁用",
|
||||
"dhcp_title": "DHCP 服务器(实验性)",
|
||||
"dhcp_description": "如果你的路由器没有提供动态主机设置协议(DHCP)设置,你可以使用 AdGuard 内置的 DHCP 服务器。",
|
||||
"dhcp_enable": "启用 DHCP 服务器",
|
||||
"dhcp_disable": "禁用 DHCP 服务器",
|
||||
"dhcp_not_found": "在当前网络中未检测到 DHCP 服务器。您可以安全地启用内置 DHCP 服务器。",
|
||||
"dhcp_found": "在当前网络中检测到 DHCP 服务器。如果启用内置的 DHCP 服务器可能不安全。",
|
||||
"dhcp_leases": "DHCP 租约",
|
||||
"dhcp_static_leases": "DHCP 静态租约",
|
||||
"dhcp_leases_not_found": "未检测到 DHCP 租约",
|
||||
"dhcp_config_saved": "保存 DHCP 服务器配置",
|
||||
"form_error_required": "必填字段",
|
||||
"form_error_ip_format": "无效的 IPv4 格式",
|
||||
"form_error_mac_format": "无效的 MAC 格式",
|
||||
"form_error_positive": "必须大于 0",
|
||||
"dhcp_form_gateway_input": "网关 IP",
|
||||
"dhcp_form_subnet_input": "子网掩码",
|
||||
"dhcp_form_range_title": "IP 地址范围",
|
||||
"dhcp_form_range_start": "起始 IP 地址",
|
||||
"dhcp_form_range_end": "末尾 IP 地址",
|
||||
"dhcp_form_lease_title": "DHCP 租约时间(秒)",
|
||||
"dhcp_form_lease_input": "租期",
|
||||
"dhcp_interface_select": "选择 DHCP 接口",
|
||||
"dhcp_hardware_address": "硬件地址",
|
||||
"dhcp_ip_addresses": "IP 地址",
|
||||
"dhcp_table_hostname": "主机名",
|
||||
"dhcp_table_expires": "到期",
|
||||
"dhcp_warning": "如果你想要启用内置的 DHCP 服务器,请确保在当前网络中没有其它活动的 DHCP 服务器。否则,此操作可能会破坏已连接设备的网络连接!",
|
||||
"dhcp_error": "我们无法确定网络上是否有其它 DHCP 服务器。",
|
||||
"dhcp_static_ip_error": "要使用 DHCP 服务器,则必须设置静态 IP。我们无法确定此网络接口是否是使用静态 IP 配置的。请手动设置静态 IP 地址。",
|
||||
"dhcp_dynamic_ip_found": "您的系统使用了动态 IP 地址配置 <0>{{interfaceName}}</0> 接口。要使用 DHCP 服务器,则必须设置为静态 IP 地址。您当前的 IP 地址为 <0>{{ipAddress}}</0>。如您点击 开启 DHCP 按钮,则我们会自动设置此 IP 地址为静态。",
|
||||
"dhcp_lease_added": "静态租约 \"{{key}}\" 添加成功",
|
||||
"dhcp_lease_deleted": "静态租约 \"{{key}}\" 删除成功",
|
||||
"dhcp_new_static_lease": "新建静态租约",
|
||||
"dhcp_static_leases_not_found": "未找到 DHCP 静态租约",
|
||||
"dhcp_add_static_lease": "添加静态租约",
|
||||
"delete_confirm": "您确定要删除 \"{{key}}\"?",
|
||||
"form_enter_hostname": "输入主机名称",
|
||||
"error_details": "详细错误信息",
|
||||
"back": "返回",
|
||||
"dashboard": "仪表盘",
|
||||
"settings": "设置",
|
||||
"filters": "过滤器",
|
||||
"query_log": "查询日志",
|
||||
"faq": "常见问题",
|
||||
"version": "版本",
|
||||
"address": "地址",
|
||||
"on": "启用中",
|
||||
"off": "禁用中",
|
||||
"copyright": "版权",
|
||||
"homepage": "主页",
|
||||
"report_an_issue": "问题反馈",
|
||||
"privacy_policy": "隐私策略",
|
||||
"enable_protection": "启用保护",
|
||||
"enabled_protection": "保护已启用",
|
||||
"disable_protection": "禁用保护",
|
||||
"disabled_protection": "保护已禁用",
|
||||
"refresh_statics": "刷新状态",
|
||||
"dns_query": "DNS查询",
|
||||
"blocked_by": "已被过滤器拦截",
|
||||
"stats_malware_phishing": "被拦截的恶意/钓鱼网站",
|
||||
"stats_adult": "被拦截的成人网站",
|
||||
"stats_query_domain": "请求域名排行",
|
||||
"for_last_24_hours": "在过去 24 小时",
|
||||
"no_domains_found": "未找到域名",
|
||||
"requests_count": "请求数",
|
||||
"top_blocked_domains": "被拦截域名排行",
|
||||
"top_clients": "客户端排行",
|
||||
"no_clients_found": "未找到客户端",
|
||||
"general_statistics": "概况统计",
|
||||
"number_of_dns_query_24_hours": "过去 24 小时内处理的 DNS 请求总数",
|
||||
"number_of_dns_query_blocked_24_hours": "被广告过滤器和 Hosts 拦截清单拦截的 DNS 请求总数",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "被 AdGuard 安全浏览模块拦截的 DNS 请求总数",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "被拦截的成人网站总数",
|
||||
"enforced_save_search": "强制安全搜索",
|
||||
"number_of_dns_query_to_safe_search": "启用强制安全搜索后对搜索引擎的 DNS 请求总数",
|
||||
"average_processing_time": "平均处理时间",
|
||||
"average_processing_time_hint": "处理 DNS 请求的平均时间(毫秒)",
|
||||
"block_domain_use_filters_and_hosts": "使用过滤器和 Hosts 文件以拦截指定域名",
|
||||
"filters_block_toggle_hint": "你可以在 <a href='#filters'>过滤器</a> 设置中添加过滤规则。",
|
||||
"use_adguard_browsing_sec": "使用 AdGuard【浏览安全】网页服务",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home 将检查域名是否被浏览安全服务列入黑名单。它将使用隐私性强的检索 API 来执行检查,只有域名的 SHA256 的短前缀会被发送到服务器。",
|
||||
"use_adguard_parental": "使用 AdGuard 【家长控制】服务",
|
||||
"use_adguard_parental_hint": "AdGuard Home 将使用与浏览安全服务相同的隐私性强的 API 来检查域名指向的网站是否包含成人内容。",
|
||||
"enforce_safe_search": "强制安全搜索",
|
||||
"enforce_save_search_hint": "AdGuard Home 将对以下搜索引擎强制启用安全搜索:Google、YouTube、Bing 和 Yandex。",
|
||||
"no_servers_specified": "未找到指定的服务器",
|
||||
"no_settings": "未找到设置",
|
||||
"general_settings": "常规设置",
|
||||
"dns_settings": "DNS 设置",
|
||||
"encryption_settings": "加密设置",
|
||||
"dhcp_settings": "DHCP 设置",
|
||||
"upstream_dns": "上游 DNS 服务器",
|
||||
"upstream_dns_hint": "如果此处留空,AdGuard Home 将会使用 <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> 作为上游 DNS。如果想要使用使用 DNS over TLS,请以 tls:// 为开头。",
|
||||
"test_upstream_btn": "测试上游 DNS",
|
||||
"apply_btn": "应用",
|
||||
"disabled_filtering_toast": "过滤器已禁用",
|
||||
"enabled_filtering_toast": "过滤器已启用",
|
||||
"disabled_safe_browsing_toast": "安全浏览已禁用",
|
||||
"enabled_safe_browsing_toast": "安全浏览已启用",
|
||||
"disabled_parental_toast": "家长控制已禁用",
|
||||
"enabled_parental_toast": "家长控制已启用",
|
||||
"disabled_safe_search_toast": "安全搜索已禁用",
|
||||
"enabled_save_search_toast": "安全搜索已启用",
|
||||
"enabled_table_header": "已启用",
|
||||
"name_table_header": "名称",
|
||||
"filter_url_table_header": "过滤器地址",
|
||||
"rules_count_table_header": "规则数",
|
||||
"last_time_updated_table_header": "上次更新时间",
|
||||
"actions_table_header": "活跃状态",
|
||||
"edit_table_action": "编辑",
|
||||
"delete_table_action": "删除",
|
||||
"filters_and_hosts": "过滤器和 Hosts 拦截清单",
|
||||
"filters_and_hosts_hint": "AdGuard Home 可以解析基础的 adblock 规则和 Hosts 语法。",
|
||||
"no_filters_added": "未添加任何过滤器",
|
||||
"add_filter_btn": "添加过滤器",
|
||||
"cancel_btn": "取消",
|
||||
"enter_name_hint": "输入名称",
|
||||
"enter_url_hint": "输入 URL",
|
||||
"check_updates_btn": "检查更新",
|
||||
"new_filter_btn": "订阅新的过滤器",
|
||||
"enter_valid_filter_url": "输入一个过滤器订阅或 Hosts 文件的有效 URL",
|
||||
"custom_filter_rules": "自定义过滤器规则",
|
||||
"custom_filter_rules_hint": "请确保每行只输入一条规则。你可以输入符合 adblock 语法或 Hosts 语法的规则。",
|
||||
"examples_title": "范例",
|
||||
"example_meaning_filter_block": "拦截 example.org 域名及其所有子域名",
|
||||
"example_meaning_filter_whitelist": "放行 example.org 及其所有子域名",
|
||||
"example_meaning_host_block": "AdGuard Home 现在将会把 example.org(但不包括它的子域名)解析到 127.0.0.1。",
|
||||
"example_comment": "! 这是一行注释",
|
||||
"example_comment_meaning": "只是一条注释",
|
||||
"example_comment_hash": "# 这也是一行注释",
|
||||
"example_upstream_regular": "常规 DNS(基于 UDP)",
|
||||
"example_upstream_dot": "加密 <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "加密 <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> 的 <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> 或者 <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> 解析器",
|
||||
"example_upstream_tcp": "常规 DNS(基于 TCP )",
|
||||
"all_filters_up_to_date_toast": "所有过滤器已更新至最新",
|
||||
"updated_upstream_dns_toast": "上游 DNS 已更新",
|
||||
"dns_test_ok_toast": "指定的 DNS 服务器现已正常运行",
|
||||
"dns_test_not_ok_toast": "服务器 \"{{key}}\":无法使用,请检查你输入的是否正确",
|
||||
"unblock_btn": "放行",
|
||||
"block_btn": "拦截",
|
||||
"time_table_header": "时间",
|
||||
"domain_name_table_header": "域名",
|
||||
"type_table_header": "类型",
|
||||
"response_table_header": "响应",
|
||||
"client_table_header": "客户端",
|
||||
"empty_response_status": "空",
|
||||
"show_all_filter_type": "显示所有",
|
||||
"show_filtered_type": "显示被拦截的",
|
||||
"no_logs_found": "未找到日志",
|
||||
"disabled_log_btn": "禁用日志",
|
||||
"download_log_file_btn": "下载日志文件",
|
||||
"refresh_btn": "刷新",
|
||||
"enabled_log_btn": "启用日志",
|
||||
"last_dns_queries": "最近的 5000 个 DNS 请求",
|
||||
"previous_btn": "上一页",
|
||||
"next_btn": "下一页",
|
||||
"loading_table_status": "加载中……",
|
||||
"page_table_footer_text": "页",
|
||||
"of_table_footer_text": "在",
|
||||
"rows_table_footer_text": "行",
|
||||
"updated_custom_filtering_toast": "自定义过滤规则已更新",
|
||||
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除",
|
||||
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中",
|
||||
"query_log_disabled_toast": "查询日志已禁用",
|
||||
"query_log_enabled_toast": "查询日志已启用",
|
||||
"source_label": "源",
|
||||
"found_in_known_domain_db": "成功在已知域名数据库中查询到",
|
||||
"category_label": "类别",
|
||||
"rule_label": "规则",
|
||||
"filter_label": "过滤器",
|
||||
"unknown_filter": "未知过滤器 {{filterId}}",
|
||||
"install_welcome_title": "欢迎使用 AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home 是一个可在特定网络范围内拦截所有广告和跟踪器的 DNS 服务器。它的目的是让您控制整个网络和您的所有设备,且不需要使用任何客户端程序。",
|
||||
"install_settings_title": "网页管理界面",
|
||||
"install_settings_listen": "监听接口",
|
||||
"install_settings_port": "端口",
|
||||
"install_settings_interface_link": "您可以通过以下地址访问您的 AdGuard Home 网页管理界面:",
|
||||
"form_error_port": "输入有效的端口值",
|
||||
"install_settings_dns": "DNS 服务器",
|
||||
"install_settings_dns_desc": "您将需要使用以下地址来设置您的设备或路由器的 DNS 服务器:",
|
||||
"install_settings_all_interfaces": "所有接口",
|
||||
"install_auth_title": "身份认证",
|
||||
"install_auth_desc": "我们强烈建议您为 AdGuard Home 的网页管理界面配置密码。尽管该页面只能通过您的本地网络访问,但避免它被不加限制地访问仍十分重要。",
|
||||
"install_auth_username": "用户名",
|
||||
"install_auth_password": "密码",
|
||||
"install_auth_confirm": "确认密码",
|
||||
"install_auth_username_enter": "输入用户名",
|
||||
"install_auth_password_enter": "输入密码",
|
||||
"install_step": "步骤",
|
||||
"install_devices_title": "配置您的设备",
|
||||
"install_devices_desc": "为保证 AdGuard Home 可以开始正常工作,您需要在设备上对其进行配置。",
|
||||
"install_submit_title": "恭喜您!",
|
||||
"install_submit_desc": "安装过程已经完成,您可以开始使用 AdGuard Home 了。",
|
||||
"install_devices_router": "路由器",
|
||||
"install_devices_router_desc": "此设置将自动覆盖连接到您的家庭路由器的所有设备,您不需要手动配置它们。",
|
||||
"install_devices_address": "AdGuard Home DNS 服务器正在监听以下地址",
|
||||
"install_devices_router_list_1": "打开您的路由器配置界面。通常情况下,您可以通过浏览器访问地址(如 http://192.168.0.1/ 或 http://192.168.1.1 )。打开后您可能需要输入密码以进入配置界面。如果您不记得密码,通常可以通过按下路由器上的重置按钮来重设密码。一些路由器可能需要通过特定的应用来进行这一操作,请确保您已经在计算机或手机上安装了相关应用。",
|
||||
"install_devices_router_list_2": "找到路由器的 DHCP/DNS 设置页面。您会在 DNS 这一单词旁边找到两到三行允许输入的输入框,每一行输入框分为四组,每组允许输入一到三个数字。",
|
||||
"install_devices_router_list_3": "请在此处输入您的 AdGuard Home 服务器地址。",
|
||||
"install_devices_windows_list_1": "通过开始菜单或 Windows 搜索功能打开控制面板。",
|
||||
"install_devices_windows_list_2": "点击进入 ”网络和 Internet“ 后,再次点击进入 “网络和共享中心”",
|
||||
"install_devices_windows_list_3": "在窗口的左侧找到 ”更改适配器设置“ 并点击进入。",
|
||||
"install_devices_windows_list_4": "选择您正在连接的网络设备,右击它并选择 ”属性“ 。",
|
||||
"install_devices_windows_list_5": "在列表中找到 ”Internet 协议版本 4 (TCP/IPv4)“ ,选择并再次点击 ”属性“ 。",
|
||||
"install_devices_windows_list_6": "选择 ”使用下面的 DNS 服务器地址“ ,并输入您的 AdGuard Home 服务器地址。",
|
||||
"install_devices_macos_list_1": "点击苹果图标,进入 ”系统首选项“。",
|
||||
"install_devices_macos_list_2": "点击 ”网络“ 。",
|
||||
"install_devices_macos_list_3": "选择在列表中的第一个连接,并点击 ”高级“ 。",
|
||||
"install_devices_macos_list_4": "选择 ”DNS“ 选项卡,并输入您的 AdGuard Home 服务器地址。",
|
||||
"install_devices_android_list_1": "在安卓主屏幕菜单中点击设置。",
|
||||
"install_devices_android_list_2": "点击菜单上的 ”无线局域网“ 选项。在屏幕上将列出所有可用的网络(蜂窝移动网络不支持修改 DNS )。",
|
||||
"install_devices_android_list_3": "长按当前已连接的网络,然后点击 ”修改网络设置“ 。",
|
||||
"install_devices_android_list_4": "在某些设备上,您可能需要选中 ”高级“ 复选框以查看进一步的设置。您可能需要调整您安卓设备的 DNS 设置,或是需要将 IP 设置从 DHCP 切换到静态。",
|
||||
"install_devices_android_list_5": "将 \"DNS 1 / 主 DNS\" 和 ”DNS 2 / 副 DNS“ 的值改为您的 AdGuard Home 服务器地址。",
|
||||
"install_devices_ios_list_1": "从主屏幕中点击 ”设置“ 。",
|
||||
"install_devices_ios_list_2": "从左侧目录中选择 ”无线局域网“(移动数据网络环境下不支持修改 DNS )。",
|
||||
"install_devices_ios_list_3": "点击当前已连接网络的名称。",
|
||||
"install_devices_ios_list_4": "在 DNS 字段中输入您的 AdGuard Home 服务器地址。",
|
||||
"get_started": "开始配置",
|
||||
"next": "下一步",
|
||||
"open_dashboard": "打开仪表盘",
|
||||
"install_saved": "保存成功",
|
||||
"encryption_title": "加密",
|
||||
"encryption_desc": "为 DNS 与网页管理界面启用加密(HTTPS/TLS)",
|
||||
"encryption_config_saved": "加密配置已保存",
|
||||
"encryption_server": "服务器名称",
|
||||
"encryption_server_enter": "输入您的域名",
|
||||
"encryption_server_desc": "若要使用 HTTPS ,您需要输入与 SSL 证书相匹配的服务器名称。",
|
||||
"encryption_redirect": "HTTPS 自动重定向",
|
||||
"encryption_redirect_desc": "如果勾选此选项,AdGuard Home 将自动将您从 HTTP 重定向到 HTTPS 地址。",
|
||||
"encryption_https": "HTTPS 端口",
|
||||
"encryption_https_desc": "如果配置了 HTTPS 端口,AdGuard Home 管理界面将可以通过 HTTPS 访问,它还将在在 '/dns-query' 位置提供 DNS-over-HTTPS 。",
|
||||
"encryption_dot": "DNS-over-TLS 端口",
|
||||
"encryption_dot_desc": "如果配置了此端口,AdGuard Home 将在此端口上运行一个 DNS-over-TLS 服务器。",
|
||||
"encryption_certificates": "证书",
|
||||
"encryption_certificates_desc": "为了使用加密,您需要为域提供有效的 SSL 证书链。您可以在 <0>{{link}}</0> 上获得免费证书,也可以从受信任的证书颁发机构购买证书。",
|
||||
"encryption_certificates_input": "将您以 PEM 格式编码的证书复制粘贴到此处。",
|
||||
"encryption_status": "状态",
|
||||
"encryption_expire": "有效期",
|
||||
"encryption_key": "私钥",
|
||||
"encryption_key_input": "将您以 PEM 格式编码的证书私钥复制粘贴到此处。",
|
||||
"encryption_enable": "启用加密(HTTPS、DNS-over-HTTPS、DNS-over-TLS)",
|
||||
"encryption_enable_desc": "如果启用加密选项,AdGuard Home 的网页管理界面将通过 HTTPS 连接访问,同时 DNS 服务器将监听通过 DNS-over-HTTPS 与 DNS-over-TLS 发送的请求。",
|
||||
"encryption_chain_valid": "证书链验证有效",
|
||||
"encryption_chain_invalid": "证书链验证无效",
|
||||
"encryption_key_valid": "该 {{type}} 私钥验证有效",
|
||||
"encryption_key_invalid": "该 {{type}} 私钥验证无效",
|
||||
"encryption_subject": "使用者",
|
||||
"encryption_issuer": "颁发者",
|
||||
"encryption_hostnames": "主机名",
|
||||
"encryption_reset": "您确定想要重置加密设置?",
|
||||
"topline_expiring_certificate": "您的 SSL 证书即将过期。请更新 <0>加密设置</0> 。",
|
||||
"topline_expired_certificate": "您的 SSL 证书已过期。请更新 <0>加密设置</0> 。",
|
||||
"form_error_port_range": "输入 80 - 65535 范围内的端口值",
|
||||
"form_error_port_unsafe": "这是一个不安全的端口",
|
||||
"form_error_equal": "不应该相同",
|
||||
"form_error_password": "密码不匹配",
|
||||
"reset_settings": "重置设置",
|
||||
"update_announcement": "AdGuard Home {{version}} 现已发布! <0>点击此处</0> 以获取详细信息。",
|
||||
"setup_guide": "设置指导",
|
||||
"dns_addresses": "DNS 地址",
|
||||
"down": "下移",
|
||||
"fix": "修复",
|
||||
"dns_providers": "此为可从中选择的<0>已知 DNS 提供商列表</0>。",
|
||||
"update_now": "立即更新",
|
||||
"update_failed": "自动更新失败。请<a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>跟随这些步骤</a>以手动更新。",
|
||||
"processing_update": "正在更新 AdGuard Home,请稍侯",
|
||||
"clients_title": "客户端",
|
||||
"clients_desc": "配置已连接到 AdGuard Home 的设备",
|
||||
"settings_global": "全局",
|
||||
"settings_custom": "自定义",
|
||||
"table_client": "客户端",
|
||||
"table_name": "名称",
|
||||
"save_btn": "保存",
|
||||
"client_add": "添加客户端",
|
||||
"client_new": "新建客户端",
|
||||
"client_edit": "编辑客户端",
|
||||
"client_identifier": "标识符",
|
||||
"ip_address": "IP 地址",
|
||||
"client_identifier_desc": "客户端可通过 IP 地址或 MAC 地址识别。请注意,如 AdGuard Home 也是 <0>DHCP 服务器</0>,则仅能将 MAC 用作标识符",
|
||||
"form_enter_ip": "输入 IP",
|
||||
"form_enter_mac": "输入 MAC",
|
||||
"form_client_name": "输入客户端名称",
|
||||
"client_global_settings": "使用全局设置",
|
||||
"client_deleted": "客户端 \"{{key}}\" 删除成功",
|
||||
"client_added": "客户端 \"{{key}}\" 添加成功",
|
||||
"client_updated": "客户端 \"{{key}}\" 更新成功",
|
||||
"table_statistics": "请求次数(最后 24 小时)",
|
||||
"clients_not_found": "未找到客户端",
|
||||
"client_confirm_delete": "您确定要删除客户端 \"{{key}}\"?",
|
||||
"filter_confirm_delete": "您确定是要删除过滤器?",
|
||||
"auto_clients_title": "客户端(运行时间)",
|
||||
"auto_clients_desc": "使用 Adguard Home 但未存储在配置中的客户端上的数据",
|
||||
"access_title": "访问设置",
|
||||
"access_desc": "您可在此处配置 AdGuard Home DNS 服务器的访问规则。",
|
||||
"access_allowed_title": "允许的客户端",
|
||||
"access_allowed_desc": "CIDR 或 IP 地址列表。如配置,则 AdGuard Home 仅会接受源自这些 IP 地址的请求。",
|
||||
"access_disallowed_title": "不允许的客户端",
|
||||
"access_disallowed_desc": "CIDR 或 IP 地址列表。如配置,则 AdGuard Home 会放弃源自这些 IP 地址的请求。",
|
||||
"access_blocked_title": "拦截的域",
|
||||
"access_blocked_desc": "不要与过滤器混淆。在查询问题时 AdGuard Home 会放弃源自这些域的 DNS 查询。",
|
||||
"access_settings_saved": "访问设置保存成功",
|
||||
"updates_checked": "检查更新成功",
|
||||
"updates_version_equal": "AdGuard Home已经是最新版本",
|
||||
"check_updates_now": "立即检查更新",
|
||||
"dns_privacy": "DNS 隐私",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> 使用 <1>{{address}}</1> 字符串。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> 使用 <1>{{address}}</1> 字符串。",
|
||||
"setup_dns_privacy_3": "<0>请注意,加密的 DNS 协议仅适用于 Android 9。所以您需要为其他操作系统上安装额外的软件。</0><0>以下是您可以使用软件的列表。</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 原生支持 DNS-over-TLS。 要进行配置,请转到 设置 → 网络和互联网 → 高级 → 私有 DNS,然后在那里输入您的域名。",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> 支持 <1>DNS-over-HTTPS</1> 和 <1>DNS-over-TLS</1>。",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> 为 Android 提供了 <1>DNS-over-HTTPS</1> 的支持。",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> 支持 <1>DNS-over-HTTPS</1> ,但为了设置使用您自己的服务器,您需要为了它生成一个 <2>DNS Stamp</2> 。",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> 支持 <1>DNS-over-HTTPS</1> 和 <1>DNS-over-TLS</1>。",
|
||||
"setup_dns_privacy_other_title": "其他实施方案",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home 本身可以作为任何平台上的安全 DNS 客户端。",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> 支持所有已知的安全 DNS 协议。",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支持 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支持 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_5": "您可以从 <0>这里</0> 和 <1>这里</1> 找到更多的实施方案。",
|
||||
"setup_dns_notice": "为了使用 <1>DNS-over-HTTPS</1> 或者 <1>DNS-over-TLS</1> ,您需要在 AdGuard Home 设置中 <0>配置加密</0> 。"
|
||||
}
|
||||
357
client/src/__locales/zh-tw.json
Normal file
357
client/src/__locales/zh-tw.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"client_settings": "用戶端設定",
|
||||
"example_upstream_reserved": "您可明確指定<0>用於特定的網域</0>之 DNS 上游",
|
||||
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析",
|
||||
"bootstrap_dns": "自我啟動(Bootstrap)DNS 伺服器",
|
||||
"bootstrap_dns_desc": "自我啟動(Bootstrap)DNS 伺服器被用於解析您明確指定作為上游的 DoH/DoT 解析器之 IP 位址。",
|
||||
"url_added_successfully": "網址被成功地加入",
|
||||
"check_dhcp_servers": "檢查動態主機設定協定(DHCP)伺服器",
|
||||
"save_config": "儲存配置",
|
||||
"enabled_dhcp": "動態主機設定協定(DHCP)伺服器被啟用",
|
||||
"disabled_dhcp": "動態主機設定協定(DHCP)伺服器被禁用",
|
||||
"dhcp_title": "動態主機設定協定(DHCP)伺服器(實驗性的!)",
|
||||
"dhcp_description": "如果您的路由器未提供動態主機設定協定(DHCP)設定,您可使用 AdGuard 自身內建的 DHCP 伺服器。",
|
||||
"dhcp_enable": "啟用動態主機設定協定(DHCP)伺服器",
|
||||
"dhcp_disable": "禁用動態主機設定協定(DHCP)伺服器",
|
||||
"dhcp_not_found": "啟用內建的動態主機設定協定(DHCP)伺服器為安全的 - 於該網路上,我們未發現任何現行的 DHCP 伺服器。然而,我們鼓勵您手動地重新檢查它,因為我們的自動之測試目前不予 100% 保證。",
|
||||
"dhcp_found": "於該網路上,一個現行的動態主機設定協定(DHCP)伺服器被發現。啟用內建的 DHCP 伺服器為不安全的。",
|
||||
"dhcp_leases": "動態主機設定協定(DHCP)租賃",
|
||||
"dhcp_static_leases": "動態主機設定協定(DHCP)靜態租賃",
|
||||
"dhcp_leases_not_found": "無已發現之動態主機設定協定(DHCP)租賃",
|
||||
"dhcp_config_saved": "已儲存動態主機設定協定(DHCP)伺服器配置",
|
||||
"form_error_required": "必填的欄位",
|
||||
"form_error_ip_format": "無效的 IPv4 格式",
|
||||
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
||||
"form_error_positive": "必須大於 0",
|
||||
"dhcp_form_gateway_input": "閘道 IP",
|
||||
"dhcp_form_subnet_input": "子網路遮罩",
|
||||
"dhcp_form_range_title": "IP 位址範圍",
|
||||
"dhcp_form_range_start": "範圍開始",
|
||||
"dhcp_form_range_end": "範圍結束",
|
||||
"dhcp_form_lease_title": "動態主機設定協定(DHCP)租賃時間(以秒數)",
|
||||
"dhcp_form_lease_input": "租賃持續時間",
|
||||
"dhcp_interface_select": "選擇動態主機設定協定(DHCP)介面",
|
||||
"dhcp_hardware_address": "硬體位址",
|
||||
"dhcp_ip_addresses": "IP 位址",
|
||||
"dhcp_table_hostname": "主機名稱",
|
||||
"dhcp_table_expires": "到期",
|
||||
"dhcp_warning": "如果您無論如何想要啟用動態主機設定協定(DHCP)伺服器,確保在您的網路無其它現行的 DHCP 伺服器。否則,它可能會破壞供已連線的裝置之網際網路!",
|
||||
"dhcp_error": "我們無法確定在該網路是否有另外的動態主機設定協定(DHCP)伺服器。",
|
||||
"dhcp_static_ip_error": "為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。我們未能確定該網路介面是否被配置使用靜態 IP 位址。請手動地設定靜態 IP 位址。",
|
||||
"dhcp_dynamic_ip_found": "您的系統使用動態 IP 位址配置供介面 <0>{{interfaceName}}</0>。為了使用動態主機設定協定(DHCP)伺服器,靜態 IP 位址必須被設定。您現行的 IP 位址為 <0>{{ipAddress}}</0>。如果您按啟用 DHCP 按鈕,我們將自動地設定此 IP 位址作為靜態。",
|
||||
"dhcp_lease_added": "靜態租賃 \"{{key}}\" 被成功地加入",
|
||||
"dhcp_lease_deleted": "靜態租賃 \"{{key}}\" 被成功地刪除",
|
||||
"dhcp_new_static_lease": "新的靜態租賃",
|
||||
"dhcp_static_leases_not_found": "無已發現之動態主機設定協定(DHCP)靜態租賃",
|
||||
"dhcp_add_static_lease": "增加靜態租賃",
|
||||
"delete_confirm": "您確定您想要刪除 \"{{key}}\" 嗎?",
|
||||
"form_enter_hostname": "輸入主機名稱",
|
||||
"error_details": "錯誤細節",
|
||||
"back": "返回",
|
||||
"dashboard": "儀表板",
|
||||
"settings": "設定",
|
||||
"filters": "過濾器",
|
||||
"query_log": "查詢記錄",
|
||||
"faq": "常見問答集",
|
||||
"version": "版本",
|
||||
"address": "位址",
|
||||
"on": "開著",
|
||||
"off": "關著",
|
||||
"copyright": "版權",
|
||||
"homepage": "首頁",
|
||||
"report_an_issue": "報告問題",
|
||||
"privacy_policy": "隱私政策",
|
||||
"enable_protection": "啟用防護",
|
||||
"enabled_protection": "已啟用防護",
|
||||
"disable_protection": "禁用防護",
|
||||
"disabled_protection": "已禁用防護",
|
||||
"refresh_statics": "重新整理統計資料",
|
||||
"dns_query": "DNS 查詢",
|
||||
"blocked_by": "被過濾器封鎖",
|
||||
"stats_malware_phishing": "已封鎖的惡意軟體/網路釣魚",
|
||||
"stats_adult": "已封鎖的成人網站",
|
||||
"stats_query_domain": "熱門已查詢的網域",
|
||||
"for_last_24_hours": "在最近的 24 小時內",
|
||||
"no_domains_found": "無已發現之網域",
|
||||
"requests_count": "請求總數",
|
||||
"top_blocked_domains": "熱門已封鎖的網域",
|
||||
"top_clients": "熱門用戶端",
|
||||
"no_clients_found": "無已發現之用戶端",
|
||||
"general_statistics": "一般的統計資料",
|
||||
"number_of_dns_query_24_hours": "在最近的 24 小時內已處理的 DNS 查詢之數量",
|
||||
"number_of_dns_query_blocked_24_hours": "被廣告封鎖過濾器和主機封鎖清單封鎖的 DNS 請求之數量",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "被 AdGuard 瀏覽安全模組封鎖的 DNS 請求之數量",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "已封鎖的成人網站之數量",
|
||||
"enforced_save_search": "已強制執行的安全搜尋",
|
||||
"number_of_dns_query_to_safe_search": "安全搜尋已被強制執行之屬於搜尋引擎的 DNS 請求之數量",
|
||||
"average_processing_time": "平均的處理時間",
|
||||
"average_processing_time_hint": "於處理一項 DNS 請求上以毫秒(ms)計之平均的時間",
|
||||
"block_domain_use_filters_and_hosts": "透過過濾器和主機檔案封鎖網域",
|
||||
"filters_block_toggle_hint": "您可在<a href='#filters'>過濾器</a>設定中設置封鎖規則。",
|
||||
"use_adguard_browsing_sec": "使用 AdGuard 瀏覽安全網路服務",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home 將檢查網域是否被瀏覽安全網路服務列入黑名單。它將使用友好的隱私查找應用程式介面(API)以執行檢查:僅域名 SHA256 雜湊的短前綴被傳送到伺服器。",
|
||||
"use_adguard_parental": "使用 AdGuard 家長監控之網路服務",
|
||||
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之友好的隱私應用程式介面(API)。",
|
||||
"enforce_safe_search": "強制執行安全搜尋",
|
||||
"enforce_save_search_hint": "AdGuard Home 可在下列的搜尋引擎:Google、YouTube、Bing、DuckDuckGo 和 Yandex 中強制執行安全搜尋。",
|
||||
"no_servers_specified": "無已明確指定的伺服器",
|
||||
"no_settings": "無設定",
|
||||
"general_settings": "一般的設定",
|
||||
"dns_settings": "DNS 設定",
|
||||
"encryption_settings": "加密設定",
|
||||
"dhcp_settings": "動態主機設定協定(DHCP)設定",
|
||||
"upstream_dns": "上游的 DNS 伺服器",
|
||||
"upstream_dns_hint": "如果您將該欄位留空,AdGuard Home 將使用 <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> 作為上游。",
|
||||
"test_upstream_btn": "測試上行資料流",
|
||||
"apply_btn": "套用",
|
||||
"disabled_filtering_toast": "已禁用過濾",
|
||||
"enabled_filtering_toast": "已啟用過濾",
|
||||
"disabled_safe_browsing_toast": "已禁用安全瀏覽",
|
||||
"enabled_safe_browsing_toast": "已啟用安全瀏覽",
|
||||
"disabled_parental_toast": "已禁用家長監控",
|
||||
"enabled_parental_toast": "已啟用家長監控",
|
||||
"disabled_safe_search_toast": "已禁用安全搜尋",
|
||||
"enabled_save_search_toast": "已啟用安全搜尋",
|
||||
"enabled_table_header": "已啟用的",
|
||||
"name_table_header": "名稱",
|
||||
"filter_url_table_header": "過濾器網址",
|
||||
"rules_count_table_header": "規則總數",
|
||||
"last_time_updated_table_header": "最近的更新時間",
|
||||
"actions_table_header": "行動",
|
||||
"edit_table_action": "編輯",
|
||||
"delete_table_action": "刪除",
|
||||
"filters_and_hosts": "過濾器和主機封鎖清單",
|
||||
"filters_and_hosts_hint": "AdGuard Home 懂得基本的廣告封鎖規則和主機檔案語法。",
|
||||
"no_filters_added": "無已加入的過濾器",
|
||||
"add_filter_btn": "增加過濾器",
|
||||
"cancel_btn": "取消",
|
||||
"enter_name_hint": "輸入名稱",
|
||||
"enter_url_hint": "輸入網址",
|
||||
"check_updates_btn": "檢查更新",
|
||||
"new_filter_btn": "新的過濾器訂閱",
|
||||
"enter_valid_filter_url": "輸入關於過濾器訂閱或主機檔案之有效的網址。",
|
||||
"custom_filter_rules": "自訂的過濾規則",
|
||||
"custom_filter_rules_hint": "於一行上輸入一個規則。您可使用廣告封鎖規則或主機檔案語法。",
|
||||
"examples_title": "範例",
|
||||
"example_meaning_filter_block": "封鎖至 example.org 網域及其所有的子網域之存取",
|
||||
"example_meaning_filter_whitelist": "解除封鎖至 example.org 網域及其所有的子網域之存取",
|
||||
"example_meaning_host_block": "AdGuard Home 現在將對 example.org 網域返回 127.0.0.1 位址(但非其子網域)。",
|
||||
"example_comment": "! 看,一個註解",
|
||||
"example_comment_meaning": "只是一個註解",
|
||||
"example_comment_hash": "# 也是一個註解",
|
||||
"example_regex_meaning": "封鎖至與<0>已明確指定的規則運算式</0>(Regular Expression)相符的網域之存取",
|
||||
"example_upstream_regular": "一般的 DNS(透過 UDP)",
|
||||
"example_upstream_dot": "加密的 <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "加密的 <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "您可使用關於 <1>DNSCrypt</1> 或 <2>DNS-over-HTTPS</2> 解析器之 <0>DNS 戳記</0>",
|
||||
"example_upstream_tcp": "一般的 DNS(透過 TCP)",
|
||||
"all_filters_up_to_date_toast": "所有的過濾器已是最新的",
|
||||
"updated_upstream_dns_toast": "已更新上游的 DNS 伺服器",
|
||||
"dns_test_ok_toast": "已明確指定的 DNS 伺服器正在正確地運作",
|
||||
"dns_test_not_ok_toast": "伺服器 \"{{key}}\":無法被使用,請檢查您已正確地填寫它",
|
||||
"unblock_btn": "解除封鎖",
|
||||
"block_btn": "封鎖",
|
||||
"time_table_header": "時間",
|
||||
"domain_name_table_header": "域名",
|
||||
"type_table_header": "類型",
|
||||
"response_table_header": "回應",
|
||||
"client_table_header": "用戶端",
|
||||
"empty_response_status": "空白的",
|
||||
"show_all_filter_type": "顯示全部",
|
||||
"show_filtered_type": "顯示已過濾的",
|
||||
"no_logs_found": "無已發現之記錄",
|
||||
"disabled_log_btn": "禁用記錄",
|
||||
"download_log_file_btn": "下載記錄檔案",
|
||||
"refresh_btn": "重新整理",
|
||||
"enabled_log_btn": "啟用記錄",
|
||||
"last_dns_queries": "最近的 5000 筆 DNS 查詢",
|
||||
"previous_btn": "上一頁",
|
||||
"next_btn": "下一頁",
|
||||
"loading_table_status": "正在載入…",
|
||||
"page_table_footer_text": "頁面",
|
||||
"of_table_footer_text": "之",
|
||||
"rows_table_footer_text": "列",
|
||||
"updated_custom_filtering_toast": "已更新自訂的過濾規則",
|
||||
"rule_removed_from_custom_filtering_toast": "規則從自訂的過濾規則中被移除",
|
||||
"rule_added_to_custom_filtering_toast": "規則被加至自訂的過濾規則中",
|
||||
"query_log_disabled_toast": "查詢記錄被禁用",
|
||||
"query_log_enabled_toast": "查詢記錄被啟用",
|
||||
"source_label": "來源",
|
||||
"found_in_known_domain_db": "在已知的域名資料庫中被發現。",
|
||||
"category_label": "類別",
|
||||
"rule_label": "規則",
|
||||
"filter_label": "過濾器",
|
||||
"unknown_filter": "未知的過濾器 {{filterId}}",
|
||||
"install_welcome_title": "歡迎至 AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home 是全網路範圍廣告和追蹤器封鎖的 DNS 伺服器。它的目的為讓您控制您整個的網路和所有您的裝置,且不需要使用用戶端程式。",
|
||||
"install_settings_title": "管理員網路介面",
|
||||
"install_settings_listen": "監聽介面",
|
||||
"install_settings_port": "連接埠",
|
||||
"install_settings_interface_link": "您的 AdGuard Home 管理員網路介面將於下列的位址上為可用的:",
|
||||
"form_error_port": "輸入有效的連接埠值",
|
||||
"install_settings_dns": "DNS 伺服器",
|
||||
"install_settings_dns_desc": "您將需要配置您的裝置或路由器以使用於下列的位址上之 DNS 伺服器:",
|
||||
"install_settings_all_interfaces": "所有的介面",
|
||||
"install_auth_title": "驗證",
|
||||
"install_auth_desc": "被非常建議配置屬於您的 AdGuard Home 管理員網路介面之密碼驗證。即使它僅在您的區域網路中為可存取的,讓它受保護免於不受限制的存取為仍然重要的。",
|
||||
"install_auth_username": "使用者名稱",
|
||||
"install_auth_password": "密碼",
|
||||
"install_auth_confirm": "確認密碼",
|
||||
"install_auth_username_enter": "輸入使用者名稱",
|
||||
"install_auth_password_enter": "輸入密碼",
|
||||
"install_step": "步驟",
|
||||
"install_devices_title": "配置您的裝置",
|
||||
"install_devices_desc": "為了開始使用 AdGuard Home,您需要配置您的裝置以使用它。",
|
||||
"install_submit_title": "恭喜!",
|
||||
"install_submit_desc": "該設置程序被完成,且您準備好開始使用 AdGuard Home。",
|
||||
"install_devices_router": "路由器",
|
||||
"install_devices_router_desc": "該設置將自動地涵蓋被連線至您的家庭路由器之所有的裝置,且您將無需手動地配置它們每個。",
|
||||
"install_devices_address": "AdGuard Home DNS 伺服器正在監聽下列的位址",
|
||||
"install_devices_router_list_1": "開啟關於您的路由器之偏好設定。通常地,您可透過網址(如 http://192.168.0.1/ 或 http://192.168.1.1/)從您的瀏覽器中存取它。您可能被要求輸入該密碼。如果您不記得它,您經常可透過按壓於該路由器本身上的按鈕來重置密碼。某些路由器需要特定的應用程式,既然如此其應已被安裝於您的電腦/手機上。",
|
||||
"install_devices_router_list_2": "找到 DHCP/DNS 設定。尋找緊鄰著允許兩組或三組數字集的欄位之 DNS 字母,每組被拆成四個含有一至三個數字的群集。",
|
||||
"install_devices_router_list_3": "在那裡輸入您的 AdGuard Home 伺服器位址。",
|
||||
"install_devices_windows_list_1": "通過開始功能表或 Windows 搜尋,開啟控制台。",
|
||||
"install_devices_windows_list_2": "去網路和網際網路類別,然後去網路和共用中心。",
|
||||
"install_devices_windows_list_3": "於畫面之左側上找到變更介面卡設定並於它上點擊。",
|
||||
"install_devices_windows_list_4": "選擇您現行的連線,於它上點擊滑鼠右鍵,然後選擇內容。",
|
||||
"install_devices_windows_list_5": "在清單中找到網際網路通訊協定第 4 版(TCP/IPv4),選擇它,然後再次於內容上點擊。",
|
||||
"install_devices_windows_list_6": "選擇使用下列的 DNS 伺服器位址,然後輸入您的 AdGuard Home 伺服器位址。",
|
||||
"install_devices_macos_list_1": "於 Apple 圖像上點擊,然後去系統偏好設定。",
|
||||
"install_devices_macos_list_2": "於網路上點擊。",
|
||||
"install_devices_macos_list_3": "選擇在您的清單中之首要的連線,然後點擊進階的。",
|
||||
"install_devices_macos_list_4": "選擇該 DNS 分頁,然後輸入您的 AdGuard Home 伺服器位址。",
|
||||
"install_devices_android_list_1": "從 Android 選單主畫面中,輕觸設定。",
|
||||
"install_devices_android_list_2": "於該選單上輕觸 Wi-Fi。正在列出所有可用的網路之畫面將被顯示(不可能為行動連線設定自訂的 DNS)。",
|
||||
"install_devices_android_list_3": "長按您所連線至的網路,然後輕觸修改網路。",
|
||||
"install_devices_android_list_4": "於某些裝置上,您可能需要檢查關於進階的方框以查看進一步的設定。為了調整您的 Android DNS 設定,您將需要把 IP 設定從 DHCP 轉換成靜態。",
|
||||
"install_devices_android_list_5": "更改 DNS 1 和 DNS 2 位置的值為您的 AdGuard Home 伺服器位址。",
|
||||
"install_devices_ios_list_1": "從主畫面中,輕觸設定。",
|
||||
"install_devices_ios_list_2": "在左側的選單中選擇 Wi-Fi(不可能為行動網路配置 DNS)。",
|
||||
"install_devices_ios_list_3": "於目前現行的網路之名稱上輕觸。",
|
||||
"install_devices_ios_list_4": "在該 DNS 欄位中,輸入您的 AdGuard Home 伺服器位址。",
|
||||
"get_started": "開始吧",
|
||||
"next": "下一頁",
|
||||
"open_dashboard": "開啟儀表板",
|
||||
"install_saved": "已成功地儲存",
|
||||
"encryption_title": "加密",
|
||||
"encryption_desc": "供 DNS 和管理員網路介面兩者之加密(HTTPS/TLS)支援",
|
||||
"encryption_config_saved": "加密配置被儲存",
|
||||
"encryption_server": "伺服器名稱",
|
||||
"encryption_server_enter": "輸入您的域名",
|
||||
"encryption_server_desc": "為了使用 HTTPS,您需要輸入與您的安全通訊端層(SSL)憑證相符的伺服器名稱。",
|
||||
"encryption_redirect": "自動地重新導向到 HTTPS",
|
||||
"encryption_redirect_desc": "如果被勾選,AdGuard Home 將自動地重新導向您從 HTTP 到 HTTPS 位址。",
|
||||
"encryption_https": "HTTPS 連接埠",
|
||||
"encryption_https_desc": "如果 HTTPS 連接埠被配置,AdGuard Home 管理員介面透過 HTTPS 將為可存取的,且它也將於 '/dns-query' 位置上提供 DNS-over-HTTPS。",
|
||||
"encryption_dot": "DNS-over-TLS 連接埠",
|
||||
"encryption_dot_desc": "如果該連接埠被配置,AdGuard Home 將於此連接埠上運行 DNS-over-TLS 伺服器。",
|
||||
"encryption_certificates": "憑證",
|
||||
"encryption_certificates_desc": "為了使用加密,您需要提供有效的安全通訊端層(SSL)憑證鏈結供您的網域。於 <0>{{link}}</0> 上您可取得免費的憑證或您可從受信任的憑證授權單位之一購買它。",
|
||||
"encryption_certificates_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)憑證。",
|
||||
"encryption_status": "狀態",
|
||||
"encryption_expire": "到期",
|
||||
"encryption_key": "私密金鑰",
|
||||
"encryption_key_input": "於此複製/貼上您的隱私增強郵件編碼之(PEM-encoded)私密金鑰供您的憑證。",
|
||||
"encryption_enable": "啟用加密(HTTPS、DNS-over-HTTPS 和 DNS-over-TLS)",
|
||||
"encryption_enable_desc": "如果加密被啟用,AdGuard Home 管理員介面透過 HTTPS 將運作,且該 DNS 伺服器將留心監聽透過 DNS-over-HTTPS 和 DNS-over-TLS 之請求。",
|
||||
"encryption_chain_valid": "憑證鏈結為有效的",
|
||||
"encryption_chain_invalid": "憑證鏈結為無效的",
|
||||
"encryption_key_valid": "此為有效的 {{type}} 私密金鑰",
|
||||
"encryption_key_invalid": "此為無效的 {{type}} 私密金鑰",
|
||||
"encryption_subject": "物件",
|
||||
"encryption_issuer": "簽發者",
|
||||
"encryption_hostnames": "主機名稱",
|
||||
"encryption_reset": "您確定您想要重置加密設定嗎?",
|
||||
"topline_expiring_certificate": "您的安全通訊端層(SSL)憑證即將到期。更新<0>加密設定</0>。",
|
||||
"topline_expired_certificate": "您的安全通訊端層(SSL)憑證為已到期的。更新<0>加密設定</0>。",
|
||||
"form_error_port_range": "在 80-65535 之範圍內輸入連接埠值",
|
||||
"form_error_port_unsafe": "此為不安全的連接埠",
|
||||
"form_error_equal": "不應為相等的",
|
||||
"form_error_password": "不相符的密碼",
|
||||
"reset_settings": "重置設定",
|
||||
"update_announcement": "AdGuard Home {{version}} 現為可用的!關於更多的資訊,<0>點擊這裡</0>。",
|
||||
"setup_guide": "安裝指南",
|
||||
"dns_addresses": "DNS 位址",
|
||||
"down": "停止運作的",
|
||||
"fix": "修復",
|
||||
"dns_providers": "這裡是一個從中選擇之<0>已知的 DNS 供應商之清單</0>。",
|
||||
"update_now": "立即更新",
|
||||
"update_failed": "自動更新已失敗。請<a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>遵循這些步驟</a>以手動地更新。",
|
||||
"processing_update": "請等待,AdGuard Home 正被更新",
|
||||
"clients_title": "用戶端",
|
||||
"clients_desc": "配置被連線到 AdGuard Home 的裝置",
|
||||
"settings_global": "全域的",
|
||||
"settings_custom": "自訂的",
|
||||
"table_client": "用戶端",
|
||||
"table_name": "名稱",
|
||||
"save_btn": "儲存",
|
||||
"client_add": "增加用戶端",
|
||||
"client_new": "新的用戶端",
|
||||
"client_edit": "編輯用戶端",
|
||||
"client_identifier": "識別碼",
|
||||
"ip_address": "IP 位址",
|
||||
"client_identifier_desc": "用戶端可被 IP 位址或媒體存取控制(MAC)位址識別。請注意,僅若 AdGuard Home 也是<0>動態主機設定協定(DHCP)伺服器</0>,使用 MAC 作為識別碼是可能的",
|
||||
"form_enter_ip": "輸入 IP",
|
||||
"form_enter_mac": "輸入媒體存取控制(MAC)",
|
||||
"form_client_name": "輸入用戶端名稱",
|
||||
"client_global_settings": "使用全域的設定",
|
||||
"client_deleted": "用戶端 \"{{key}}\" 被成功地刪除",
|
||||
"client_added": "用戶端 \"{{key}}\" 被成功地加入",
|
||||
"client_updated": "用戶端 \"{{key}}\" 被成功地更新",
|
||||
"table_statistics": "請求總數(最近的 24 小時)",
|
||||
"clients_not_found": "無已發現之用戶端",
|
||||
"client_confirm_delete": "您確定您想要刪除用戶端 \"{{key}}\" 嗎?",
|
||||
"filter_confirm_delete": "您確定您想要刪除該過濾器嗎?",
|
||||
"auto_clients_title": "用戶端(執行時期)",
|
||||
"auto_clients_desc": "使用 AdGuard Home 但未被儲存在配置中之關於用戶端的資料",
|
||||
"access_title": "存取設定",
|
||||
"access_desc": "於此您可配置用於 AdGuard Home DNS 伺服器之存取規則。",
|
||||
"access_allowed_title": "已允許的用戶端",
|
||||
"access_allowed_desc": "無類別網域間路由(CIDR)或 IP 位址之清單。如果被配置,AdGuard Home 將僅從這些 IP 位址中接受請求。",
|
||||
"access_disallowed_title": "不允許的用戶端",
|
||||
"access_disallowed_desc": "無類別網域間路由(CIDR)或 IP 位址之清單。如果被配置,AdGuard Home 將從這些 IP 位址中排除請求。",
|
||||
"access_blocked_title": "已封鎖的網域",
|
||||
"access_blocked_desc": "不要把這個和過濾器混淆。AdGuard Home 將從查詢的詢問中排除有這些網域的 DNS 查詢。",
|
||||
"access_settings_saved": "存取設定被成功地儲存",
|
||||
"updates_checked": "更新被成功地檢查",
|
||||
"updates_version_equal": "AdGuard Home 為最新的",
|
||||
"check_updates_now": "立即檢查更新",
|
||||
"dns_privacy": "DNS 隱私",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0>使用 <1>{{address}}</1> 字串。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0>使用 <1>{{address}}</1> 字串。",
|
||||
"setup_dns_privacy_3": "<0>請注意,加密的 DNS 協定僅於 Android 9 上被支援。所以您需要安裝額外的軟體供其它的作業系統。</0><0>這裡是您可使用的軟體之清單。</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 原生地支援 DNS-over-TLS。為了配置它,去設定 → 網路 & 網際網路 → 進階 → 私人 DNS 並在那輸入您的域名。",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> 支援 <1>DNS-over-HTTPS</1> 和 <1>DNS-over-TLS</1>。",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> 對 Android 增加 <1>DNS-over-HTTPS</1> 支援。",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> 支援 <1>DNS-over-HTTPS</1>,但為了配置它以使用您自己的伺服器,您將需要為它產生一個 <2>DNS 戳記</2>。",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> 支援 <1>DNS-over-HTTPS</1> 和 <1>DNS-over-TLS</1> 設置。",
|
||||
"setup_dns_privacy_other_title": "其它的執行",
|
||||
"setup_dns_privacy_other_1": "於任何的平台上,AdGuard Home 它本身可以是安全的 DNS 用戶端。",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> 支援所有已知安全的 DNS 協定。",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> 支援 <1>DNS-over-HTTPS</1>。",
|
||||
"setup_dns_privacy_other_5": "在<0>這裡</0>和<1>這裡</1>,您將發現更多的執行。",
|
||||
"setup_dns_notice": "為了使用 <1>DNS-over-HTTPS</1> 或 <1>DNS-over-TLS</1>,您需要在 AdGuard Home 設定裡<0>配置加密</0>。",
|
||||
"rewrite_added": "對於 \"{{key}}\" 之 DNS 改寫被成功地加入",
|
||||
"rewrite_deleted": "對於 \"{{key}}\" 之 DNS 改寫被成功地刪除",
|
||||
"rewrite_add": "增加 DNS 改寫",
|
||||
"rewrite_not_found": "無已發現之 DNS 改寫",
|
||||
"rewrite_confirm_delete": "您確定您想要刪除對於 \"{{key}}\" 之 DNS 改寫嗎?",
|
||||
"rewrite_desc": "允許輕易地配置自訂的 DNS 回應供特定的域名。",
|
||||
"rewrite_applied": "已套用的改寫規則",
|
||||
"dns_rewrites": "DNS 改寫",
|
||||
"form_domain": "輸入網域",
|
||||
"form_answer": "輸入 IP 位址或域名",
|
||||
"form_error_domain_format": "無效的網域格式",
|
||||
"form_error_answer_format": "無效的回應格式",
|
||||
"configure": "配置",
|
||||
"main_settings": "主設定",
|
||||
"block_services": "封鎖特定的服務",
|
||||
"blocked_services": "已封鎖的服務",
|
||||
"blocked_services_desc": "允許立即封鎖熱門的網站和服務。",
|
||||
"blocked_services_saved": "已封鎖的服務被成功地儲存",
|
||||
"blocked_services_global": "使用全域已封鎖的服務",
|
||||
"blocked_service": "已封鎖的服務",
|
||||
"block_all": "封鎖全部",
|
||||
"unblock_all": "解除封鎖全部"
|
||||
}
|
||||
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());
|
||||
}
|
||||
};
|
||||
@@ -1,50 +1,75 @@
|
||||
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 } 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');
|
||||
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||
|
||||
export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
switch (settingKey) {
|
||||
case 'filtering':
|
||||
if (status) {
|
||||
await apiClient.disableFiltering();
|
||||
} else {
|
||||
await apiClient.enableFiltering();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safebrowsing':
|
||||
if (status) {
|
||||
await apiClient.disableSafebrowsing();
|
||||
} else {
|
||||
await apiClient.enableSafebrowsing();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'parental':
|
||||
if (status) {
|
||||
await apiClient.disableParentalControl();
|
||||
} else {
|
||||
await apiClient.enableParentalControl();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safesearch':
|
||||
if (status) {
|
||||
await apiClient.disableSafesearch();
|
||||
} else {
|
||||
await apiClient.enableSafesearch();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
let successMessage = '';
|
||||
try {
|
||||
switch (settingKey) {
|
||||
case SETTINGS_NAMES.filtering:
|
||||
if (status) {
|
||||
successMessage = 'disabled_filtering_toast';
|
||||
await apiClient.disableFiltering();
|
||||
} else {
|
||||
successMessage = 'enabled_filtering_toast';
|
||||
await apiClient.enableFiltering();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case SETTINGS_NAMES.safebrowsing:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_browsing_toast';
|
||||
await apiClient.disableSafebrowsing();
|
||||
} else {
|
||||
successMessage = 'enabled_safe_browsing_toast';
|
||||
await apiClient.enableSafebrowsing();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case SETTINGS_NAMES.parental:
|
||||
if (status) {
|
||||
successMessage = 'disabled_parental_toast';
|
||||
await apiClient.disableParentalControl();
|
||||
} else {
|
||||
successMessage = 'enabled_parental_toast';
|
||||
await apiClient.enableParentalControl();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case SETTINGS_NAMES.safesearch:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_search_toast';
|
||||
await apiClient.disableSafesearch();
|
||||
} else {
|
||||
successMessage = 'enabled_save_search_toast';
|
||||
await apiClient.enableSafesearch();
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dispatch(addSuccessToast(successMessage));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,11 +98,175 @@ export const initSettings = settingsList => async (dispatch) => {
|
||||
};
|
||||
dispatch(initSettingsSuccess({ settingsList: newSettingsList }));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(initSettingsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getFilteringRequest = createAction('GET_FILTERING_REQUEST');
|
||||
export const getFilteringFailure = createAction('GET_FILTERING_FAILURE');
|
||||
export const getFilteringSuccess = createAction('GET_FILTERING_SUCCESS');
|
||||
|
||||
export const getFiltering = () => async (dispatch) => {
|
||||
dispatch(getFilteringRequest());
|
||||
try {
|
||||
const filteringStatus = await apiClient.getFilteringStatus();
|
||||
dispatch(getFilteringSuccess(filteringStatus.enabled));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getFilteringFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
|
||||
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
|
||||
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
|
||||
|
||||
export const toggleProtection = status => async (dispatch) => {
|
||||
dispatch(toggleProtectionRequest());
|
||||
let successMessage = '';
|
||||
|
||||
try {
|
||||
if (status) {
|
||||
successMessage = 'disabled_protection';
|
||||
await apiClient.disableGlobalProtection();
|
||||
} else {
|
||||
successMessage = 'enabled_protection';
|
||||
await apiClient.enableGlobalProtection();
|
||||
}
|
||||
|
||||
dispatch(addSuccessToast(successMessage));
|
||||
dispatch(toggleProtectionSuccess());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleProtectionFailure());
|
||||
}
|
||||
};
|
||||
|
||||
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');
|
||||
@@ -87,8 +276,10 @@ export const getDnsStatus = () => async (dispatch) => {
|
||||
try {
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
dispatch(getVersion());
|
||||
dispatch(getTlsStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(initSettingsFailure());
|
||||
}
|
||||
};
|
||||
@@ -103,7 +294,7 @@ export const enableDns = () => async (dispatch) => {
|
||||
await apiClient.startGlobalFiltering();
|
||||
dispatch(enableDnsSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(enableDnsFailure());
|
||||
}
|
||||
};
|
||||
@@ -118,8 +309,8 @@ export const disableDns = () => async (dispatch) => {
|
||||
await apiClient.stopGlobalFiltering();
|
||||
dispatch(disableDnsSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(disableDnsFailure());
|
||||
dispatch(disableDnsFailure(error));
|
||||
dispatch(addErrorToast({ error }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,51 +330,30 @@ export const getStats = () => async (dispatch) => {
|
||||
|
||||
dispatch(getStatsSuccess(processedStats));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getStatsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
try {
|
||||
const state = getState();
|
||||
const timer = setInterval(async () => {
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
const stats = await apiClient.getGlobalStatsTop();
|
||||
dispatch(getTopStatsSuccess(stats));
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getTopStatsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||
|
||||
export const getLogs = () => async (dispatch, getState) => {
|
||||
dispatch(getLogsRequest());
|
||||
try {
|
||||
const timer = setInterval(async () => {
|
||||
const state = getState();
|
||||
const timer = setInterval(async () => {
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
const logs = await apiClient.getQueryLog();
|
||||
if (state.dashboard.isCoreRunning) {
|
||||
clearInterval(timer);
|
||||
try {
|
||||
const logs = normalizeLogs(await apiClient.getQueryLog());
|
||||
dispatch(getLogsSuccess(logs));
|
||||
clearInterval(timer);
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getLogsFailure(error));
|
||||
}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(getLogsFailure());
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
|
||||
@@ -193,16 +363,20 @@ export const toggleLogStatusSuccess = createAction('TOGGLE_LOGS_SUCCESS');
|
||||
export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
|
||||
dispatch(toggleLogStatusRequest());
|
||||
let toggleMethod;
|
||||
let successMessage;
|
||||
if (queryLogEnabled) {
|
||||
toggleMethod = apiClient.disableQueryLog.bind(apiClient);
|
||||
successMessage = 'query_log_disabled_toast';
|
||||
} else {
|
||||
toggleMethod = apiClient.enableQueryLog.bind(apiClient);
|
||||
successMessage = 'query_log_enabled_toast';
|
||||
}
|
||||
try {
|
||||
await toggleMethod();
|
||||
dispatch(addSuccessToast(successMessage));
|
||||
dispatch(toggleLogStatusSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleLogStatusFailure());
|
||||
}
|
||||
};
|
||||
@@ -214,10 +388,14 @@ export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
|
||||
export const setRules = rules => async (dispatch) => {
|
||||
dispatch(setRulesRequest());
|
||||
try {
|
||||
await apiClient.setRules(rules);
|
||||
const replacedLineEndings = rules
|
||||
.replace(/^\n/g, '')
|
||||
.replace(/\n\s*\n/g, '\n');
|
||||
await apiClient.setRules(replacedLineEndings);
|
||||
dispatch(addSuccessToast('updated_custom_filtering_toast'));
|
||||
dispatch(setRulesSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setRulesFailure());
|
||||
}
|
||||
};
|
||||
@@ -232,7 +410,7 @@ export const getFilteringStatus = () => async (dispatch) => {
|
||||
const status = await apiClient.getFilteringStatus();
|
||||
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getFilteringStatusFailure());
|
||||
}
|
||||
};
|
||||
@@ -258,7 +436,7 @@ export const toggleFilterStatus = url => async (dispatch, getState) => {
|
||||
dispatch(toggleFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleFilterFailure());
|
||||
}
|
||||
};
|
||||
@@ -268,14 +446,28 @@ 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 {
|
||||
await apiClient.refreshFilters();
|
||||
dispatch(refreshFiltersSuccess);
|
||||
const refreshText = await apiClient.refreshFilters();
|
||||
dispatch(refreshFiltersSuccess());
|
||||
|
||||
if (refreshText.includes('OK')) {
|
||||
if (refreshText.includes('OK 0')) {
|
||||
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
|
||||
} else {
|
||||
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
|
||||
}
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: refreshText }));
|
||||
}
|
||||
|
||||
dispatch(getFilteringStatus());
|
||||
dispatch(hideLoading());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(refreshFiltersFailure());
|
||||
dispatch(hideLoading());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,7 +484,7 @@ export const getStatsHistory = () => async (dispatch) => {
|
||||
const normalizedHistory = normalizeHistory(statsHistory);
|
||||
dispatch(getStatsHistorySuccess(normalizedHistory));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getStatsHistoryFailure());
|
||||
}
|
||||
};
|
||||
@@ -301,14 +493,14 @@ export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
|
||||
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
|
||||
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
|
||||
|
||||
export const addFilter = url => async (dispatch) => {
|
||||
export const addFilter = (url, name) => async (dispatch) => {
|
||||
dispatch(addFilterRequest());
|
||||
try {
|
||||
await apiClient.addFilter(url);
|
||||
await apiClient.addFilter(url, name);
|
||||
dispatch(addFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(addFilterFailure());
|
||||
}
|
||||
};
|
||||
@@ -325,7 +517,7 @@ export const removeFilter = url => async (dispatch) => {
|
||||
dispatch(removeFilterSuccess(url));
|
||||
dispatch(getFilteringStatus());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(removeFilterFailure());
|
||||
}
|
||||
};
|
||||
@@ -336,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());
|
||||
@@ -344,7 +535,7 @@ export const downloadQueryLog = () => async (dispatch) => {
|
||||
data = await apiClient.downloadQueryLog();
|
||||
dispatch(downloadQueryLogSuccess());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(downloadQueryLogFailure());
|
||||
}
|
||||
return data;
|
||||
@@ -355,13 +546,213 @@ 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) {
|
||||
console.error(error);
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setUpstreamFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
|
||||
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
|
||||
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
||||
|
||||
export const testUpstream = config => async (dispatch) => {
|
||||
dispatch(testUpstreamRequest());
|
||||
try {
|
||||
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') {
|
||||
dispatch(addErrorToast({ error: t('dns_test_not_ok_toast', { key }) }));
|
||||
}
|
||||
return message;
|
||||
});
|
||||
|
||||
if (testMessages.every(message => message === 'OK')) {
|
||||
dispatch(addSuccessToast('dns_test_ok_toast'));
|
||||
}
|
||||
|
||||
dispatch(testUpstreamSuccess());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(testUpstreamFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
|
||||
export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE');
|
||||
export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS');
|
||||
|
||||
export const changeLanguage = lang => async (dispatch) => {
|
||||
dispatch(changeLanguageRequest());
|
||||
try {
|
||||
await apiClient.changeLanguage(lang);
|
||||
dispatch(changeLanguageSuccess());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(changeLanguageFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getLanguageRequest = createAction('GET_LANGUAGE_REQUEST');
|
||||
export const getLanguageFailure = createAction('GET_LANGUAGE_FAILURE');
|
||||
export const getLanguageSuccess = createAction('GET_LANGUAGE_SUCCESS');
|
||||
|
||||
export const getLanguage = () => async (dispatch) => {
|
||||
dispatch(getLanguageRequest());
|
||||
try {
|
||||
const language = await apiClient.getCurrentLanguage();
|
||||
dispatch(getLanguageSuccess(language));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getLanguageFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST');
|
||||
export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS');
|
||||
export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
|
||||
|
||||
export const getDhcpStatus = () => async (dispatch) => {
|
||||
dispatch(getDhcpStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getDhcpStatus();
|
||||
dispatch(getDhcpStatusSuccess(status));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDhcpStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getDhcpInterfacesRequest = createAction('GET_DHCP_INTERFACES_REQUEST');
|
||||
export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS');
|
||||
export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE');
|
||||
|
||||
export const getDhcpInterfaces = () => async (dispatch) => {
|
||||
dispatch(getDhcpInterfacesRequest());
|
||||
try {
|
||||
const interfaces = await apiClient.getDhcpInterfaces();
|
||||
dispatch(getDhcpInterfacesSuccess(interfaces));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDhcpInterfacesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
|
||||
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
|
||||
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
|
||||
|
||||
export const findActiveDhcp = name => async (dispatch) => {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
|
||||
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
|
||||
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
|
||||
|
||||
export const setDhcpConfig = values => async (dispatch, getState) => {
|
||||
const { config } = getState().dhcp;
|
||||
const updatedConfig = { ...config, ...values };
|
||||
dispatch(setDhcpConfigRequest());
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
try {
|
||||
await apiClient.setDhcpConfig(updatedConfig);
|
||||
dispatch(setDhcpConfigSuccess(updatedConfig));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
|
||||
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
|
||||
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
|
||||
|
||||
export const toggleDhcp = values => async (dispatch) => {
|
||||
dispatch(toggleDhcpRequest());
|
||||
let config = { ...values, enabled: false };
|
||||
let successMessage = 'disabled_dhcp';
|
||||
|
||||
if (!values.enabled) {
|
||||
config = { ...values, enabled: true };
|
||||
successMessage = 'enabled_dhcp';
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
58
client/src/actions/rewrites.js
Normal file
58
client/src/actions/rewrites.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { t } from 'i18next';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const toggleRewritesModal = createAction('TOGGLE_REWRITES_MODAL');
|
||||
|
||||
export const getRewritesListRequest = createAction('GET_REWRITES_LIST_REQUEST');
|
||||
export const getRewritesListFailure = createAction('GET_REWRITES_LIST_FAILURE');
|
||||
export const getRewritesListSuccess = createAction('GET_REWRITES_LIST_SUCCESS');
|
||||
|
||||
export const getRewritesList = () => async (dispatch) => {
|
||||
dispatch(getRewritesListRequest());
|
||||
try {
|
||||
const data = await apiClient.getRewritesList();
|
||||
dispatch(getRewritesListSuccess(data));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getRewritesListFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const addRewriteRequest = createAction('ADD_REWRITE_REQUEST');
|
||||
export const addRewriteFailure = createAction('ADD_REWRITE_FAILURE');
|
||||
export const addRewriteSuccess = createAction('ADD_REWRITE_SUCCESS');
|
||||
|
||||
export const addRewrite = config => async (dispatch) => {
|
||||
dispatch(addRewriteRequest());
|
||||
try {
|
||||
await apiClient.addRewrite(config);
|
||||
dispatch(addRewriteSuccess(config));
|
||||
dispatch(toggleRewritesModal());
|
||||
dispatch(getRewritesList());
|
||||
dispatch(addSuccessToast(t('rewrite_added', { key: config.domain })));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(addRewriteFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRewriteRequest = createAction('DELETE_REWRITE_REQUEST');
|
||||
export const deleteRewriteFailure = createAction('DELETE_REWRITE_FAILURE');
|
||||
export const deleteRewriteSuccess = createAction('DELETE_REWRITE_SUCCESS');
|
||||
|
||||
export const deleteRewrite = config => async (dispatch) => {
|
||||
dispatch(deleteRewriteRequest());
|
||||
try {
|
||||
await apiClient.deleteRewrite(config);
|
||||
dispatch(deleteRewriteSuccess());
|
||||
dispatch(getRewritesList());
|
||||
dispatch(addSuccessToast(t('rewrite_deleted', { key: config.domain })));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(deleteRewriteFailure());
|
||||
}
|
||||
};
|
||||
37
client/src/actions/services.js
Normal file
37
client/src/actions/services.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST');
|
||||
export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE');
|
||||
export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS');
|
||||
|
||||
export const getBlockedServices = () => async (dispatch) => {
|
||||
dispatch(getBlockedServicesRequest());
|
||||
try {
|
||||
const data = await apiClient.getBlockedServices();
|
||||
dispatch(getBlockedServicesSuccess(data));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getBlockedServicesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
|
||||
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
|
||||
export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');
|
||||
|
||||
export const setBlockedServices = values => async (dispatch) => {
|
||||
dispatch(setBlockedServicesRequest());
|
||||
try {
|
||||
await apiClient.setBlockedServices(values);
|
||||
dispatch(setBlockedServicesSuccess());
|
||||
dispatch(getBlockedServices());
|
||||
dispatch(addSuccessToast('blocked_services_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setBlockedServicesFailure());
|
||||
}
|
||||
};
|
||||
@@ -1,18 +1,26 @@
|
||||
import axios from 'axios';
|
||||
import startOfToday from 'date-fns/start_of_today';
|
||||
import endOfToday from 'date-fns/end_of_today';
|
||||
import subHours from 'date-fns/sub_hours';
|
||||
import dateFormat from 'date-fns/format';
|
||||
|
||||
export default class Api {
|
||||
baseUrl = 'control';
|
||||
|
||||
async makeRequest(path, method = 'POST', config) {
|
||||
const response = await axios({
|
||||
url: `${this.baseUrl}/${path}`,
|
||||
method,
|
||||
...config,
|
||||
});
|
||||
return response.data;
|
||||
try {
|
||||
const response = await axios({
|
||||
url: `${this.baseUrl}/${path}`,
|
||||
method,
|
||||
...config,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Global methods
|
||||
@@ -26,7 +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: '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;
|
||||
@@ -51,13 +64,12 @@ export default class Api {
|
||||
getGlobalStatsHistory() {
|
||||
const { path, method } = this.GLOBAL_STATS_HISTORY;
|
||||
const format = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||
const todayStart = dateFormat(startOfToday(), format);
|
||||
const todayEnd = dateFormat(endOfToday(), format);
|
||||
const dateNow = Date.now();
|
||||
|
||||
const config = {
|
||||
params: {
|
||||
start_time: todayStart,
|
||||
end_time: todayEnd,
|
||||
start_time: dateFormat(subHours(dateNow, 24), format),
|
||||
end_time: dateFormat(dateNow, format),
|
||||
time_unit: 'hours',
|
||||
},
|
||||
};
|
||||
@@ -99,18 +111,51 @@ 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);
|
||||
}
|
||||
|
||||
testUpstream(servers) {
|
||||
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: servers,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
getGlobalVersion(data) {
|
||||
const { path, method } = this.GLOBAL_VERSION;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
|
||||
enableGlobalProtection() {
|
||||
const { path, method } = this.GLOBAL_ENABLE_PROTECTION;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
disableGlobalProtection() {
|
||||
const { path, method } = this.GLOBAL_DISABLE_PROTECTION;
|
||||
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' };
|
||||
@@ -136,26 +181,25 @@ export default class Api {
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
addFilter(url) {
|
||||
addFilter(url, name) {
|
||||
const { path, method } = this.FILTERING_ADD_FILTER;
|
||||
const parameter = 'url';
|
||||
const requestBody = `${parameter}=${url}`;
|
||||
const config = {
|
||||
data: requestBody,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
data: {
|
||||
name,
|
||||
url,
|
||||
},
|
||||
};
|
||||
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) {
|
||||
@@ -253,4 +297,234 @@ export default class Api {
|
||||
const { path, method } = this.SAFESEARCH_DISABLE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Language
|
||||
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' };
|
||||
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' };
|
||||
|
||||
getCurrentLanguage() {
|
||||
const { path, method } = this.CURRENT_LANGUAGE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
changeLanguage(lang) {
|
||||
const { path, method } = this.CHANGE_LANGUAGE;
|
||||
const parameters = {
|
||||
data: lang,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DHCP
|
||||
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
|
||||
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;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getDhcpInterfaces() {
|
||||
const { path, method } = this.DHCP_INTERFACES;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setDhcpConfig(config) {
|
||||
const { path, method } = this.DHCP_SET_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
findActiveDhcp(name) {
|
||||
const { path, method } = this.DHCP_FIND_ACTIVE;
|
||||
const parameters = {
|
||||
data: name,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
// DNS rewrites
|
||||
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' };
|
||||
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
|
||||
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
|
||||
|
||||
getRewritesList() {
|
||||
const { path, method } = this.REWRITES_LIST;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
addRewrite(config) {
|
||||
const { path, method } = this.REWRITE_ADD;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
deleteRewrite(config) {
|
||||
const { path, method } = this.REWRITE_DELETE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// Blocked services
|
||||
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
|
||||
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
|
||||
|
||||
getBlockedServices() {
|
||||
const { path, method } = this.BLOCKED_SERVICES_LIST;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setBlockedServices(config) {
|
||||
const { path, method } = this.BLOCKED_SERVICES_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 {
|
||||
@@ -17,3 +17,16 @@ body {
|
||||
min-height: calc(100vh - 117px);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
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,8 @@
|
||||
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';
|
||||
import '../ui/Tabler.css';
|
||||
@@ -11,44 +13,105 @@ import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Filters from '../../containers/Filters';
|
||||
import Logs from '../../containers/Logs';
|
||||
import Footer from '../ui/Footer';
|
||||
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
import Dhcp from '../../containers/Dhcp';
|
||||
import Clients from '../../containers/Clients';
|
||||
|
||||
import Logs from '../../containers/Logs';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import Toasts from '../Toasts';
|
||||
import Footer from '../ui/Footer';
|
||||
import Status from '../ui/Status';
|
||||
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();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.dashboard.language !== prevProps.dashboard.language) {
|
||||
this.setLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
handleStatusChange = () => {
|
||||
this.props.enableDns();
|
||||
};
|
||||
|
||||
handleUpdate = () => {
|
||||
this.props.getUpdate();
|
||||
};
|
||||
|
||||
setLanguage = () => {
|
||||
const { processing, language } = this.props.dashboard;
|
||||
|
||||
if (!processing) {
|
||||
if (language) {
|
||||
i18n.changeLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
i18n.on('languageChanged', (lang) => {
|
||||
this.props.changeLanguage(lang);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, encryption } = this.props;
|
||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
|
||||
return (
|
||||
<HashRouter hashType='noslash'>
|
||||
<HashRouter hashType="noslash">
|
||||
<Fragment>
|
||||
{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>
|
||||
);
|
||||
@@ -57,9 +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,
|
||||
changeLanguage: PropTypes.func,
|
||||
encryption: PropTypes.object,
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default withNamespaces()(App);
|
||||
|
||||
@@ -1,34 +1,79 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
import Popover from '../ui/Popover';
|
||||
|
||||
const Clients = props => (
|
||||
<Card title="Top blocked domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topBlockedDomains, (value, prop) => (
|
||||
{ ip: prop, domain: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Domain name',
|
||||
accessor: 'domain',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No domains found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import { getPercent } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
Clients.propTypes = {
|
||||
class BlockedDomains extends Component {
|
||||
columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: (row) => {
|
||||
const { value } = row;
|
||||
const trackerData = getTrackerData(value);
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<div className="logs__text" title={value}>
|
||||
{value}
|
||||
</div>
|
||||
{trackerData && <Popover data={trackerData} />}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
accessor: 'domain',
|
||||
maxWidth: 190,
|
||||
Cell: ({ value }) => {
|
||||
const {
|
||||
blockedFiltering,
|
||||
replacedSafebrowsing,
|
||||
replacedParental,
|
||||
} = this.props;
|
||||
const blocked = blockedFiltering + replacedSafebrowsing + replacedParental;
|
||||
const percent = getPercent(blocked, value);
|
||||
|
||||
return (
|
||||
<Cell value={value} percent={percent} color={STATUS_COLORS.red} />
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card title={ t('top_blocked_domains') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(this.props.topBlockedDomains, (value, prop) => (
|
||||
{ ip: prop, domain: value }
|
||||
))}
|
||||
columns={this.columns}
|
||||
showPagination={false}
|
||||
noDataText={ t('no_domains_found') }
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow stats__table"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BlockedDomains.propTypes = {
|
||||
topBlockedDomains: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
blockedFiltering: PropTypes.number.isRequired,
|
||||
replacedSafebrowsing: PropTypes.number.isRequired,
|
||||
replacedParental: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Clients;
|
||||
export default withNamespaces()(BlockedDomains);
|
||||
|
||||
@@ -1,34 +1,87 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
|
||||
const Clients = props => (
|
||||
<Card title="Top clients" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topClients, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Request count',
|
||||
accessor: 'count',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No clients found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
import { getPercent, getClientName } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
class Clients extends Component {
|
||||
getPercentColor = (percent) => {
|
||||
if (percent > 50) {
|
||||
return STATUS_COLORS.green;
|
||||
} else if (percent > 10) {
|
||||
return STATUS_COLORS.yellow;
|
||||
}
|
||||
return STATUS_COLORS.red;
|
||||
}
|
||||
|
||||
columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
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>,
|
||||
accessor: 'count',
|
||||
Cell: ({ value }) => {
|
||||
const percent = getPercent(this.props.dnsQueries, value);
|
||||
const percentColor = this.getPercentColor(percent);
|
||||
|
||||
return (
|
||||
<Cell value={value} percent={percent} color={percentColor} />
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card title={ t('top_clients') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(this.props.topClients, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={this.columns}
|
||||
showPagination={false}
|
||||
noDataText={ t('no_clients_found') }
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Clients.propTypes = {
|
||||
topClients: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
clients: PropTypes.array.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Clients;
|
||||
export default withNamespaces()(Clients);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
|
||||
const tooltipType = 'tooltip-custom--narrow';
|
||||
|
||||
const Counters = props => (
|
||||
<Card title="General counters" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<Card title={ props.t('general_statistics') } subtitle={ props.t('for_last_24_hours') } bodyType="card-table" refresh={props.refreshButton}>
|
||||
<table className="table card-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
DNS Queries
|
||||
<Tooltip text="A number of DNS quieries processed in the last 3 minutes" />
|
||||
<Trans>dns_query</Trans>
|
||||
<Tooltip text={ props.t('number_of_dns_query_24_hours') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -21,8 +24,10 @@ const Counters = props => (
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked by filters
|
||||
<Tooltip text="A number of DNS requests blocked by filters" />
|
||||
<a href="#filters">
|
||||
<Trans>blocked_by</Trans>
|
||||
</a>
|
||||
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -32,8 +37,8 @@ const Counters = props => (
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked malware/phishing
|
||||
<Tooltip text="A number of DNS requests blocked" />
|
||||
<Trans>stats_malware_phishing</Trans>
|
||||
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_by_sec') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -43,8 +48,8 @@ const Counters = props => (
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Blocked adult websites
|
||||
<Tooltip text="A number of adult websites blocked" />
|
||||
<Trans>stats_adult</Trans>
|
||||
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_adult') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -54,8 +59,8 @@ const Counters = props => (
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Enforced safe search
|
||||
<Tooltip text="A number of DNS requests to search engines for which Safe Search was enforced" />
|
||||
<Trans>enforced_save_search</Trans>
|
||||
<Tooltip text={ props.t('number_of_dns_query_to_safe_search') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -65,8 +70,8 @@ const Counters = props => (
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Average processing time
|
||||
<Tooltip text="Average time in milliseconds on processing a DNS request" />
|
||||
<Trans>average_processing_time</Trans>
|
||||
<Tooltip text={ props.t('average_processing_time_hint') } type={tooltipType} />
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="text-muted">
|
||||
@@ -86,7 +91,8 @@ Counters.propTypes = {
|
||||
replacedParental: PropTypes.number.isRequired,
|
||||
replacedSafesearch: PropTypes.number.isRequired,
|
||||
avgProcessingTime: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Counters;
|
||||
export default withNamespaces()(Counters);
|
||||
|
||||
22
client/src/components/Dashboard/Dashboard.css
Normal file
22
client/src/components/Dashboard/Dashboard.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.stats__table .popover__body {
|
||||
left: 0;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.stats__table .popover__body:after {
|
||||
left: 13px;
|
||||
}
|
||||
|
||||
.stats__table .rt-tr-group:first-child .popover__body,
|
||||
.stats__table .rt-tr-group:nth-child(2) .popover__body {
|
||||
top: calc(100% + 5px);
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stats__table .rt-tr-group:first-child .popover__body:after,
|
||||
.stats__table .rt-tr-group:nth-child(2) .popover__body:after {
|
||||
top: -11px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
@@ -1,34 +1,81 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import map from 'lodash/map';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
import Popover from '../ui/Popover';
|
||||
|
||||
const QueriedDomains = props => (
|
||||
<Card title="Top queried domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(props.topQueriedDomains, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={[{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Request count',
|
||||
accessor: 'count',
|
||||
}]}
|
||||
showPagination={false}
|
||||
noDataText="No domains found"
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import { getPercent } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
class QueriedDomains extends Component {
|
||||
getPercentColor = (percent) => {
|
||||
if (percent > 10) {
|
||||
return STATUS_COLORS.red;
|
||||
} else if (percent > 5) {
|
||||
return STATUS_COLORS.yellow;
|
||||
}
|
||||
return STATUS_COLORS.green;
|
||||
}
|
||||
|
||||
columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: (row) => {
|
||||
const { value } = row;
|
||||
const trackerData = getTrackerData(value);
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<div className="logs__text" title={value}>
|
||||
{value}
|
||||
</div>
|
||||
{trackerData && <Popover data={trackerData} />}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
accessor: 'count',
|
||||
maxWidth: 190,
|
||||
Cell: ({ value }) => {
|
||||
const percent = getPercent(this.props.dnsQueries, value);
|
||||
const percentColor = this.getPercentColor(percent);
|
||||
|
||||
return (
|
||||
<Cell value={value} percent={percent} color={percentColor} />
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card title={ t('stats_query_domain') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
|
||||
<ReactTable
|
||||
data={map(this.props.topQueriedDomains, (value, prop) => (
|
||||
{ ip: prop, count: value }
|
||||
))}
|
||||
columns={this.columns}
|
||||
showPagination={false}
|
||||
noDataText={ t('no_domains_found') }
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow stats__table"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QueriedDomains.propTypes = {
|
||||
topQueriedDomains: PropTypes.object.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default QueriedDomains;
|
||||
export default withNamespaces()(QueriedDomains);
|
||||
|
||||
@@ -1,61 +1,112 @@
|
||||
import React from 'react';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
import Line from '../ui/Line';
|
||||
|
||||
const Statistics = props => (
|
||||
<Card title="Statistics" subtitle="Today" bodyType="card-graph" refresh={props.refreshButton}>
|
||||
{props.history ?
|
||||
<ResponsiveLine
|
||||
data={props.history}
|
||||
margin={{
|
||||
top: 50,
|
||||
right: 40,
|
||||
bottom: 80,
|
||||
left: 80,
|
||||
}}
|
||||
minY="auto"
|
||||
stacked={false}
|
||||
curve='monotoneX'
|
||||
axisBottom={{
|
||||
orient: 'bottom',
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: -45,
|
||||
legend: 'time',
|
||||
legendOffset: 50,
|
||||
legendPosition: 'center',
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: 'left',
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: 'count',
|
||||
legendOffset: -40,
|
||||
legendPosition: 'center',
|
||||
}}
|
||||
enableArea={true}
|
||||
dotSize={10}
|
||||
dotColor="inherit:darker(0.3)"
|
||||
dotBorderWidth={2}
|
||||
dotBorderColor="#ffffff"
|
||||
dotLabel="y"
|
||||
dotLabelYOffset={-12}
|
||||
animate={true}
|
||||
motionStiffness={90}
|
||||
motionDamping={15}
|
||||
/>
|
||||
:
|
||||
<h2 className="text-muted">Empty data</h2>
|
||||
}
|
||||
</Card>
|
||||
);
|
||||
import { getPercent } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
class Statistics extends Component {
|
||||
render() {
|
||||
const {
|
||||
dnsQueries,
|
||||
blockedFiltering,
|
||||
replacedSafebrowsing,
|
||||
replacedParental,
|
||||
} = this.props;
|
||||
|
||||
const filteringData = [this.props.history[1]];
|
||||
const queriesData = [this.props.history[2]];
|
||||
const parentalData = [this.props.history[3]];
|
||||
const safebrowsingData = [this.props.history[4]];
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-6 col-lg-3">
|
||||
<Card type="card--full" bodyType="card-wrap">
|
||||
<div className="card-body-stats">
|
||||
<div className="card-value card-value-stats text-blue">
|
||||
{dnsQueries}
|
||||
</div>
|
||||
<div className="card-title-stats">
|
||||
<Trans>dns_query</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-chart-bg">
|
||||
<Line data={queriesData} color={STATUS_COLORS.blue}/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="col-sm-6 col-lg-3">
|
||||
<Card type="card--full" bodyType="card-wrap">
|
||||
<div className="card-body-stats">
|
||||
<div className="card-value card-value-stats text-red">
|
||||
{blockedFiltering}
|
||||
</div>
|
||||
<div className="card-value card-value-percent text-red">
|
||||
{getPercent(dnsQueries, blockedFiltering)}
|
||||
</div>
|
||||
<div className="card-title-stats">
|
||||
<a href="#filters">
|
||||
<Trans>blocked_by</Trans>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-chart-bg">
|
||||
<Line data={filteringData} color={STATUS_COLORS.red}/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="col-sm-6 col-lg-3">
|
||||
<Card type="card--full" bodyType="card-wrap">
|
||||
<div className="card-body-stats">
|
||||
<div className="card-value card-value-stats text-green">
|
||||
{replacedSafebrowsing}
|
||||
</div>
|
||||
<div className="card-value card-value-percent text-green">
|
||||
{getPercent(dnsQueries, replacedSafebrowsing)}
|
||||
</div>
|
||||
<div className="card-title-stats">
|
||||
<Trans>stats_malware_phishing</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-chart-bg">
|
||||
<Line data={safebrowsingData} color={STATUS_COLORS.green}/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="col-sm-6 col-lg-3">
|
||||
<Card type="card--full" bodyType="card-wrap">
|
||||
<div className="card-body-stats">
|
||||
<div className="card-value card-value-stats text-yellow">
|
||||
{replacedParental}
|
||||
</div>
|
||||
<div className="card-value card-value-percent text-yellow">
|
||||
{getPercent(dnsQueries, replacedParental)}
|
||||
</div>
|
||||
<div className="card-title-stats">
|
||||
<Trans>stats_adult</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-chart-bg">
|
||||
<Line data={parentalData} color={STATUS_COLORS.yellow}/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Statistics.propTypes = {
|
||||
history: PropTypes.array.isRequired,
|
||||
refreshButton: PropTypes.node,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
blockedFiltering: PropTypes.number.isRequired,
|
||||
replacedSafebrowsing: PropTypes.number.isRequired,
|
||||
replacedParental: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default Statistics;
|
||||
export default withNamespaces()(Statistics);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'whatwg-fetch';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Statistics from './Statistics';
|
||||
import Counters from './Counters';
|
||||
@@ -10,31 +10,72 @@ import BlockedDomains from './BlockedDomains';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Loading from '../ui/Loading';
|
||||
import './Dashboard.css';
|
||||
|
||||
class Dashboard extends Component {
|
||||
componentDidMount() {
|
||||
this.getAllStats();
|
||||
}
|
||||
|
||||
getAllStats = () => {
|
||||
this.props.getStats();
|
||||
this.props.getStatsHistory();
|
||||
this.props.getTopStats();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
getToggleFilteringButton = () => {
|
||||
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)}
|
||||
disabled={processingProtection}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, t } = this.props;
|
||||
const dashboardProcessing =
|
||||
dashboard.processing ||
|
||||
dashboard.processingStats ||
|
||||
dashboard.processingStatsHistory ||
|
||||
dashboard.processingClients ||
|
||||
dashboard.processingTopStats;
|
||||
|
||||
const disableButton = <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={() => this.props.disableDns()}>Disable DNS</button>;
|
||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.props.getStats()}>Refresh statistics</button>;
|
||||
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.props.getStats()}></button>;
|
||||
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>
|
||||
<PageTitle title="Dashboard">
|
||||
<PageTitle title={ t('dashboard') }>
|
||||
<div className="page-title__actions">
|
||||
{disableButton}
|
||||
{this.getToggleFilteringButton()}
|
||||
{refreshFullButton}
|
||||
</div>
|
||||
</PageTitle>
|
||||
@@ -46,6 +87,10 @@ class Dashboard extends Component {
|
||||
<Statistics
|
||||
history={dashboard.statsHistory}
|
||||
refreshButton={refreshButton}
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
blockedFiltering={dashboard.stats.blocked_filtering}
|
||||
replacedSafebrowsing={dashboard.stats.replaced_safebrowsing}
|
||||
replacedParental={dashboard.stats.replaced_parental}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -66,12 +111,16 @@ class Dashboard extends Component {
|
||||
<Fragment>
|
||||
<div className="col-lg-6">
|
||||
<Clients
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
refreshButton={refreshButton}
|
||||
topClients={dashboard.topStats.top_clients}
|
||||
clients={dashboard.clients}
|
||||
autoClients={dashboard.autoClients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<QueriedDomains
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
refreshButton={refreshButton}
|
||||
topQueriedDomains={dashboard.topStats.top_queried_domains}
|
||||
/>
|
||||
@@ -80,6 +129,9 @@ class Dashboard extends Component {
|
||||
<BlockedDomains
|
||||
refreshButton={refreshButton}
|
||||
topBlockedDomains={dashboard.topStats.top_blocked_domains}
|
||||
blockedFiltering={dashboard.stats.blocked_filtering}
|
||||
replacedSafebrowsing={dashboard.stats.replaced_safebrowsing}
|
||||
replacedParental={dashboard.stats.replaced_parental}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
@@ -95,9 +147,13 @@ Dashboard.propTypes = {
|
||||
getStats: PropTypes.func,
|
||||
getStatsHistory: PropTypes.func,
|
||||
getTopStats: PropTypes.func,
|
||||
disableDns: PropTypes.func,
|
||||
dashboard: PropTypes.object,
|
||||
isCoreRunning: PropTypes.bool,
|
||||
getFiltering: PropTypes.func,
|
||||
toggleProtection: PropTypes.func,
|
||||
getClients: PropTypes.func,
|
||||
processingProtection: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
export default withNamespaces()(Dashboard);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -2,21 +2,22 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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');
|
||||
|
||||
export default class Modal extends Component {
|
||||
state = {
|
||||
url: '',
|
||||
isUrlValid: false,
|
||||
};
|
||||
const initialState = {
|
||||
url: '',
|
||||
name: '',
|
||||
isUrlValid: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
isUrlValid = url => {
|
||||
return R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
};
|
||||
class Modal extends Component {
|
||||
state = initialState;
|
||||
|
||||
isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
|
||||
|
||||
handleUrlChange = async (e) => {
|
||||
const { value: url } = e.currentTarget;
|
||||
@@ -27,33 +28,49 @@ export default class Modal extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleNameChange = (e) => {
|
||||
const { value: name } = e.currentTarget;
|
||||
this.setState({ ...this.state, name });
|
||||
};
|
||||
|
||||
handleNext = () => {
|
||||
this.props.addFilter(this.state.url);
|
||||
this.props.addFilter(this.state.url, this.state.name);
|
||||
setTimeout(() => {
|
||||
if (this.props.isFilterAdded) {
|
||||
this.props.toggleModal();
|
||||
this.closeModal();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
closeModal = () => {
|
||||
this.props.toggleModal();
|
||||
this.setState({ ...this.state, ...initialState });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
toggleModal,
|
||||
title,
|
||||
inputDescription,
|
||||
processingAddFilter,
|
||||
} = this.props;
|
||||
const { isUrlValid, url } = this.state;
|
||||
const inputClass = classnames({
|
||||
const { isUrlValid, url, name } = this.state;
|
||||
const inputUrlClass = classnames({
|
||||
'form-control mb-2': true,
|
||||
'is-invalid': url.length > 0 && !isUrlValid,
|
||||
'is-valid': url.length > 0 && isUrlValid,
|
||||
});
|
||||
const inputNameClass = classnames({
|
||||
'form-control mb-2': true,
|
||||
'is-valid': name.length > 0,
|
||||
});
|
||||
|
||||
const renderBody = () => {
|
||||
if (!this.props.isFilterAdded) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input type="text" className={inputClass} placeholder="Enter URL or path" 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}
|
||||
@@ -63,38 +80,50 @@ export default class Modal extends Component {
|
||||
}
|
||||
return (
|
||||
<div className="description">
|
||||
Url added successfully
|
||||
<Trans>url_added_successfully</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isValidForSubmit = !(url.length > 0 && isUrlValid);
|
||||
const isValidForSubmit = !(url.length > 0 && isUrlValid && name.length > 0);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={ isOpen }
|
||||
onRequestClose={toggleModal}
|
||||
isOpen={isOpen}
|
||||
onRequestClose={this.closeModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
{title}
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={toggleModal}>
|
||||
<button type="button" className="close" onClick={this.closeModal}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{ renderBody()}
|
||||
{renderBody()}
|
||||
</div>
|
||||
{
|
||||
!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={toggleModal}>Cancel</button>
|
||||
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}>Add filter</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>
|
||||
@@ -109,4 +138,8 @@ Modal.propTypes = {
|
||||
inputDescription: PropTypes.string,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
processingAddFilter: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Modal);
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
export default class UserRules extends Component {
|
||||
class UserRules extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleRulesChange(value);
|
||||
@@ -14,44 +15,61 @@ export default class UserRules extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<Card
|
||||
title="Custom filtering rules"
|
||||
subtitle="Enter one rule on a line. You can use either adblock rules or hosts files syntax."
|
||||
>
|
||||
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<textarea className="form-control" 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}
|
||||
>
|
||||
Apply...
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<hr />
|
||||
<div className="list leading-loose">
|
||||
Examples:
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>||example.org^</code> - block access to the example.org domain
|
||||
and all its subdomains
|
||||
<code>||example.org^</code> – {t('example_meaning_filter_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code> @@||example.org^</code> - unblock access to the example.org
|
||||
domain and all its subdomains
|
||||
<code> @@||example.org^</code> – {t('example_meaning_filter_whitelist')}
|
||||
</li>
|
||||
<li>
|
||||
<code>example.org 127.0.0.1</code> - AdGuard DNS will now return
|
||||
127.0.0.1 address for the example.org domain (but not its subdomains).
|
||||
<code>127.0.0.1 example.org</code> – {t('example_meaning_host_block')}
|
||||
</li>
|
||||
<li>
|
||||
<code>! Here goes a comment</code> - just a comment
|
||||
<code>{t('example_comment')}</code> – {t('example_comment_meaning')}
|
||||
</li>
|
||||
<li>
|
||||
<code># Also a comment</code> - just a comment
|
||||
<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>
|
||||
@@ -64,4 +82,7 @@ UserRules.propTypes = {
|
||||
userRules: PropTypes.string,
|
||||
handleRulesChange: PropTypes.func,
|
||||
handleRulesSubmit: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(UserRules);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from '../ui/Modal';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
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() {
|
||||
@@ -32,58 +32,101 @@ 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: 'Enabled',
|
||||
Header: <Trans>enabled_table_header</Trans>,
|
||||
accessor: 'enabled',
|
||||
Cell: this.renderCheckbox,
|
||||
width: 90,
|
||||
className: 'text-center',
|
||||
}, {
|
||||
Header: 'Filter name',
|
||||
Header: <Trans>name_table_header</Trans>,
|
||||
accessor: 'name',
|
||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
|
||||
}, {
|
||||
Header: 'Host file URL',
|
||||
Header: <Trans>filter_url_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>),
|
||||
}, {
|
||||
Header: 'Rules count',
|
||||
Header: <Trans>rules_count_table_header</Trans>,
|
||||
accessor: 'rulesCount',
|
||||
className: 'text-center',
|
||||
Cell: props => props.value.toLocaleString(),
|
||||
}, {
|
||||
Header: 'Last time update',
|
||||
Header: <Trans>last_time_updated_table_header</Trans>,
|
||||
accessor: 'lastUpdated',
|
||||
className: 'text-center',
|
||||
}, {
|
||||
Header: 'Actions',
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'url',
|
||||
Cell: ({ value }) => (<span 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: 75,
|
||||
width: 80,
|
||||
sortable: false,
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const { filters, userRules } = this.props.filtering;
|
||||
const { t } = this.props;
|
||||
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title="Filters" />
|
||||
<PageTitle title={ t('filters') } />
|
||||
<div className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<Card
|
||||
title="Blocking filters and hosts files"
|
||||
subtitle="AdGuard DNS understands basic adblock rules and hosts files syntax."
|
||||
title={ t('filters_and_hosts') }
|
||||
subtitle={ t('filters_and_hosts_hint') }
|
||||
>
|
||||
<ReactTable
|
||||
data={filters}
|
||||
columns={this.columns}
|
||||
showPagination={false}
|
||||
noDataText="No filters added"
|
||||
minRows={4} // TODO find out what to show if rules.length is 0
|
||||
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') }
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}>Add filter</button>
|
||||
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}>Check updates</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>
|
||||
@@ -101,8 +144,9 @@ class Filters extends Component {
|
||||
toggleModal={this.props.toggleFilteringModal}
|
||||
addFilter={this.props.addFilter}
|
||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
||||
title="New filter subscription"
|
||||
inputDescription="Enter valid URL or file path of the filter into field above. You will be subscribed to that filter."
|
||||
processingAddFilter={this.props.filtering.processingAddFilter}
|
||||
title={ t('new_filter_btn') }
|
||||
inputDescription={ t('enter_valid_filter_url') }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -117,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,
|
||||
@@ -124,7 +170,8 @@ Filters.propTypes = {
|
||||
toggleFilteringModal: PropTypes.func.isRequired,
|
||||
handleRulesChange: PropTypes.func.isRequired,
|
||||
refreshFilters: PropTypes.func.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
export default Filters;
|
||||
export default withNamespaces()(Filters);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -67,13 +69,47 @@
|
||||
}
|
||||
|
||||
.nav-version {
|
||||
padding: 16px 0;
|
||||
font-size: 0.85rem;
|
||||
padding: 7px 0;
|
||||
font-size: 0.80rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.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: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-item.show .nav-link {
|
||||
color: #66b574;
|
||||
background-color: #fff;
|
||||
border-bottom-color: #66b574;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
@@ -84,6 +120,8 @@
|
||||
.nav-tabs .nav-link {
|
||||
width: auto;
|
||||
border-bottom: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
@@ -103,7 +141,24 @@
|
||||
|
||||
.nav-version {
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
.nav-tabs .nav-link {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-version {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import enhanceWithClickOutside from 'react-click-outside';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { SETTINGS_URLS } from '../../helpers/constants';
|
||||
import Dropdown from '../ui/Dropdown';
|
||||
|
||||
class Menu extends Component {
|
||||
handleClickOutside = () => {
|
||||
@@ -13,44 +17,87 @@ 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 mobile-menu': true,
|
||||
'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">
|
||||
<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>
|
||||
Back
|
||||
<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>
|
||||
Dashboard
|
||||
</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>
|
||||
Settings
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#dashboard" />
|
||||
</svg>
|
||||
<Trans>dashboard</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>
|
||||
Filters
|
||||
<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>
|
||||
Query Log
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#log" />
|
||||
</svg>
|
||||
<Trans>query_log</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/guide" className="nav-link">
|
||||
<svg className="nav-icon">
|
||||
<use xlinkHref="#setup" />
|
||||
</svg>
|
||||
<Trans>setup_guide</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -64,6 +111,8 @@ Menu.propTypes = {
|
||||
isMenuOpen: PropTypes.bool,
|
||||
closeMenu: PropTypes.func,
|
||||
toggleMenuOpen: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default enhanceWithClickOutside(Menu);
|
||||
export default withNamespaces()(enhanceWithClickOutside(Menu));
|
||||
|
||||
@@ -1,17 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
const Version = (props) => {
|
||||
const {
|
||||
dnsVersion, dnsAddresses, processingVersion, t,
|
||||
} = props;
|
||||
|
||||
export default function Version(props) {
|
||||
const { dnsVersion, dnsAddress, dnsPort } = props;
|
||||
return (
|
||||
<div className="nav-version">
|
||||
v.{dnsVersion} / address: {dnsAddress}:{dnsPort}
|
||||
<div className="nav-version__text">
|
||||
<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__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);
|
||||
|
||||
@@ -2,16 +2,16 @@ import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
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 = () => {
|
||||
@@ -23,11 +23,12 @@ 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.isCoreRunning,
|
||||
'badge-danger': !dashboard.isCoreRunning,
|
||||
'badge-success': dashboard.protectionEnabled,
|
||||
'badge-danger': !dashboard.protectionEnabled,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -42,24 +43,27 @@ class Header extends Component {
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!dashboard.proccessing &&
|
||||
{!dashboard.proccessing && dashboard.isCoreRunning &&
|
||||
<span className={badgeClass}>
|
||||
{dashboard.isCoreRunning ? 'ON' : 'OFF'}
|
||||
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
|
||||
</span>
|
||||
}
|
||||
</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>
|
||||
@@ -68,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 Header;
|
||||
export default withNamespaces()(Header);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 118 26" xmlns="http://www.w3.org/2000/svg"><path fill="#232323" d="M92.535 18.314l-.897-2.259h-4.47l-.849 2.259h-3.034L88.13 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.468 3.949h2.904L89.435 9.88zm-6.607 4.095c0 .693-.117 1.324-.35 1.893a4.115 4.115 0 0 1-1.004 1.463 4.63 4.63 0 0 1-1.574.95c-.614.228-1.297.341-2.047.341-.761 0-1.447-.113-2.056-.34a4.468 4.468 0 0 1-1.55-.951 4.126 4.126 0 0 1-.978-1.463 5.038 5.038 0 0 1-.343-1.893V6.809H75.7v6.939c0 .314.041.612.123.893.081.282.206.534.375.756.169.222.392.398.669.528s.612.195 1.003.195c.392 0 .726-.065 1.003-.195a1.83 1.83 0 0 0 .677-.528 2.1 2.1 0 0 0 .376-.756c.076-.281.114-.58.114-.893v-6.94h2.79v7.167zm-11.446 3.64a8.898 8.898 0 0 1-1.982.715 10.43 10.43 0 0 1-2.472.276c-.924 0-1.775-.146-2.553-.439a5.895 5.895 0 0 1-2.006-1.235 5.63 5.63 0 0 1-1.314-1.909c-.315-.742-.473-1.568-.473-2.478 0-.92.16-1.755.482-2.502a5.567 5.567 0 0 1 1.33-1.91 5.893 5.893 0 0 1 1.99-1.21 7.044 7.044 0 0 1 2.463-.423c.913 0 1.762.138 2.545.414.783.277 1.419.648 1.908 1.114l-1.762 1.998a3.05 3.05 0 0 0-1.076-.772c-.446-.2-.952-.3-1.517-.3-.49 0-.941.09-1.354.268a3.256 3.256 0 0 0-1.077.747 3.39 3.39 0 0 0-.71 1.138 3.977 3.977 0 0 0-.253 1.438c0 .53.077 1.018.229 1.463.152.444.378.826.677 1.145.299.32.669.569 1.11.748.44.178.943.268 1.508.268.326 0 .636-.025.93-.073.294-.05.566-.128.816-.236v-2.096h-2.203V11.52h4.764v6.094zm46.107-5.086c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.208-.544 5.955 5.955 0 0 0-1.394-.163h-1.387v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-55.226 0c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.207-.544 5.955 5.955 0 0 0-1.395-.163H51.3v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-11.86 5.785l-.897-2.259h-4.47l-.848 2.259h-3.034L40.19 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.467 3.949h2.903L41.496 9.88zm61.203 8.434l-2.496-4.566h-.946v4.566h-2.74V6.809h4.404c.555 0 1.096.057 1.623.17.528.114 1 .306 1.42.577.418.271.752.629 1.003 1.073.25.444.375.996.375 1.657 0 .78-.212 1.436-.636 1.966-.425.531-1.012.91-1.762 1.138l3.018 4.924h-3.263zm-.114-7.979c0-.27-.057-.49-.171-.658a1.172 1.172 0 0 0-.44-.39 1.919 1.919 0 0 0-.604-.187 4.469 4.469 0 0 0-.645-.049H99.24v2.681h1.321c.228 0 .462-.018.701-.056.24-.038.457-.106.653-.204.196-.097.356-.238.481-.422s.188-.422.188-.715z"/><path fill="#68bc71" d="M12.651 0C8.697 0 3.927.93 0 2.977c0 4.42-.054 15.433 12.651 22.958C25.357 18.41 25.303 7.397 25.303 2.977 21.376.93 16.606 0 12.651 0z"/><path fill="#67b279" d="M12.638 25.927C-.054 18.403 0 7.396 0 2.977 3.923.932 8.687.002 12.638 0v25.927z"/><path fill="#fff" d="M12.19 17.305l7.65-10.311c-.56-.45-1.052-.133-1.323.113h-.01l-6.379 6.636-2.403-2.892c-1.147-1.325-2.705-.314-3.07-.047l5.535 6.5"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
105
client/src/components/Logs/Logs.css
Normal file
105
client/src/components/Logs/Logs.css
Normal file
@@ -0,0 +1,105 @@
|
||||
.logs__row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.logs__row--center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logs__row--overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__row--column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__row--icons {
|
||||
max-width: 180px;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.logs__row .list-unstyled {
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__text,
|
||||
.logs__row .list-unstyled li {
|
||||
padding: 0 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__row .tooltip-custom {
|
||||
top: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.logs__action {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.logs__table .rt-td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr:hover .logs__action {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .tooltip-custom:before {
|
||||
top: calc(100% + 12px);
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .tooltip-custom:after {
|
||||
top: initial;
|
||||
bottom: -4px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .popover__body {
|
||||
top: calc(100% + 5px);
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .popover__body:after {
|
||||
top: -11px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-filters input,
|
||||
.logs__table .rt-thead.-filters select {
|
||||
padding: 6px 7px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
color: #495057;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-filters input:focus,
|
||||
.logs__table .rt-thead.-filters select:focus {
|
||||
border-color: #1991eb;
|
||||
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { saveAs } from 'file-saver/FileSaver';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { HashLink as Link } from 'react-router-hash-link';
|
||||
|
||||
import { formatTime, getClientName } from '../../helpers/helpers';
|
||||
import { SERVICES } from '../../helpers/constants';
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import Loading from '../ui/Loading';
|
||||
import { normalizeLogs } from '../../helpers/helpers';
|
||||
|
||||
import PopoverFiltered from '../ui/PopoverFilter';
|
||||
import Popover from '../ui/Popover';
|
||||
import './Logs.css';
|
||||
|
||||
const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
|
||||
|
||||
class Logs extends Component {
|
||||
componentDidMount() {
|
||||
// get logs on initialization if queryLogIsEnabled
|
||||
if (this.props.dashboard.queryLogEnabled) {
|
||||
this.props.getLogs();
|
||||
}
|
||||
this.getLogs();
|
||||
this.props.getFilteringStatus();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -25,46 +33,276 @@ class Logs extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getLogs = () => {
|
||||
// get logs on initialization if queryLogIsEnabled
|
||||
if (this.props.dashboard.queryLogEnabled) {
|
||||
this.props.getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
renderTooltip = (isFiltered, rule, filter, service) =>
|
||||
isFiltered && <PopoverFiltered rule={rule} filter={filter} service={service} />;
|
||||
|
||||
toggleBlocking = (type, domain) => {
|
||||
const { userRules } = this.props.filtering;
|
||||
const { t } = this.props;
|
||||
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
||||
const baseRule = `||${domain}^$important`;
|
||||
const baseUnblocking = `@@${baseRule}`;
|
||||
const blockingRule = type === 'block' ? baseUnblocking : baseRule;
|
||||
const unblockingRule = type === 'block' ? baseRule : baseUnblocking;
|
||||
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
|
||||
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
|
||||
|
||||
if (userRules.match(preparedBlockingRule)) {
|
||||
this.props.setRules(userRules.replace(`${blockingRule}`, ''));
|
||||
this.props.addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
|
||||
} else if (!userRules.match(preparedUnblockingRule)) {
|
||||
this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
|
||||
this.props.addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
|
||||
}
|
||||
|
||||
this.props.getFilteringStatus();
|
||||
}
|
||||
|
||||
renderBlockingButton(isFiltered, domain) {
|
||||
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
|
||||
const buttonText = isFiltered ? 'unblock_btn' : 'block_btn';
|
||||
const buttonType = isFiltered ? 'unblock' : 'block';
|
||||
|
||||
return (
|
||||
<div className="logs__action">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonClass}`}
|
||||
onClick={() => this.toggleBlocking(buttonType, domain)}
|
||||
disabled={this.props.filtering.processingRules}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLogs(logs) {
|
||||
const { t, dashboard } = this.props;
|
||||
const columns = [{
|
||||
Header: 'Time',
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
maxWidth: 150,
|
||||
maxWidth: 110,
|
||||
filterable: false,
|
||||
Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>),
|
||||
}, {
|
||||
Header: 'Domain name',
|
||||
Header: t('domain_name_table_header'),
|
||||
accessor: 'domain',
|
||||
Cell: (row) => {
|
||||
const response = row.value;
|
||||
const trackerData = getTrackerData(response);
|
||||
|
||||
return (
|
||||
<div className="logs__row" title={response}>
|
||||
<div className="logs__text">
|
||||
{response}
|
||||
</div>
|
||||
{trackerData && <Popover data={trackerData}/>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
Header: 'Type',
|
||||
Header: t('type_table_header'),
|
||||
accessor: 'type',
|
||||
maxWidth: 100,
|
||||
maxWidth: 60,
|
||||
}, {
|
||||
Header: 'Response',
|
||||
Header: t('response_table_header'),
|
||||
accessor: 'response',
|
||||
Cell: (row) => {
|
||||
const responses = row.value;
|
||||
const { reason } = row.original;
|
||||
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
|
||||
const parsedFilteredReason = reason.replace('Filtered', 'Filtered by ');
|
||||
const rule = row && row.original && row.original.rule;
|
||||
const { filterId } = row.original;
|
||||
const { filters } = this.props.filtering;
|
||||
const isRewrite = reason && reason === 'Rewrite';
|
||||
let filterName = '';
|
||||
|
||||
if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
|
||||
if (filterId === 0) {
|
||||
filterName = t('custom_filter_rules');
|
||||
} else {
|
||||
const filterItem = Object.keys(filters)
|
||||
.filter(key => filters[key].id === filterId);
|
||||
|
||||
if (typeof filterItem !== 'undefined' && typeof filters[filterItem] !== 'undefined') {
|
||||
filterName = filters[filterItem].name;
|
||||
}
|
||||
|
||||
if (!filterName) {
|
||||
filterName = t('unknown_filter', { filterId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reason === 'FilteredBlockedService') {
|
||||
const getService = SERVICES
|
||||
.find(service => service.id === row.original.serviceName);
|
||||
const serviceName = getService && getService.name;
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<span className="logs__text" title={parsedFilteredReason}>
|
||||
{parsedFilteredReason}
|
||||
</span>
|
||||
{this.renderTooltip(isFiltered, '', '', serviceName)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isFiltered) {
|
||||
return (
|
||||
<div className="logs__row">
|
||||
<span className="logs__text" title={parsedFilteredReason}>
|
||||
{parsedFilteredReason}
|
||||
</span>
|
||||
{this.renderTooltip(isFiltered, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (responses.length > 0) {
|
||||
const liNodes = responses.map((response, index) =>
|
||||
(<li key={index}>{response}</li>));
|
||||
return (<ul className="list-unstyled">{liNodes}</ul>);
|
||||
(<li key={index} title={response}>{response}</li>));
|
||||
const isRenderTooltip = reason === 'NotFilteredWhiteList';
|
||||
|
||||
return (
|
||||
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
|
||||
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
|
||||
<ul className="list-unstyled">{liNodes}</ul>
|
||||
{this.renderTooltip(isRenderTooltip, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return 'Empty';
|
||||
return (
|
||||
<div className={`logs__row ${isRewrite && 'logs__row--column'}`}>
|
||||
{isRewrite && <strong><Trans>rewrite_applied</Trans></strong>}
|
||||
<span><Trans>empty_response_status</Trans></span>
|
||||
{this.renderTooltip(isFiltered, rule, filterName)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}];
|
||||
filterMethod: (filter, row) => {
|
||||
if (filter.value === 'filtered') {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
return row._original.reason.indexOf('Filtered') === 0 || row._original.reason === 'NotFilteredWhiteList';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
Filter: ({ filter, onChange }) =>
|
||||
<select
|
||||
onChange={event => onChange(event.target.value)}
|
||||
className="form-control"
|
||||
value={filter ? filter.value : 'all'}
|
||||
>
|
||||
<option value="all">{ t('show_all_filter_type') }</option>
|
||||
<option value="filtered">{ t('show_filtered_type') }</option>
|
||||
</select>,
|
||||
}, {
|
||||
Header: t('client_table_header'),
|
||||
accessor: 'client',
|
||||
maxWidth: 250,
|
||||
Cell: (row) => {
|
||||
const { reason } = row.original;
|
||||
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
|
||||
const isRewrite = reason && reason === 'Rewrite';
|
||||
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;
|
||||
}
|
||||
|
||||
if (isRewrite) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{client}
|
||||
</div>
|
||||
<div className="logs__action">
|
||||
<Link to="/dns#rewrites" className="btn btn-sm btn-outline-primary">
|
||||
<Trans>configure</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{client}
|
||||
</div>
|
||||
{this.renderBlockingButton(isFiltered, row.original.domain)}
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (logs) {
|
||||
const normalizedLogs = normalizeLogs(logs);
|
||||
return (<ReactTable
|
||||
data={normalizedLogs}
|
||||
className='logs__table'
|
||||
filterable
|
||||
data={logs}
|
||||
columns={columns}
|
||||
showPagination={false}
|
||||
showPagination={true}
|
||||
defaultPageSize={50}
|
||||
minRows={7}
|
||||
noDataText="No logs found"
|
||||
// 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_logs_found') }
|
||||
defaultFilterMethod={(filter, row) => {
|
||||
const id = filter.pivotId || filter.id;
|
||||
return row[id] !== undefined ?
|
||||
String(row[id]).indexOf(filter.value) !== -1 : true;
|
||||
}}
|
||||
defaultSorted={[
|
||||
{
|
||||
id: 'time',
|
||||
desc: true,
|
||||
},
|
||||
]}
|
||||
getTrProps={(_state, rowInfo) => {
|
||||
// highlight filtered requests
|
||||
if (!rowInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (rowInfo.original.reason.indexOf('Filtered') === 0) {
|
||||
return {
|
||||
className: 'red',
|
||||
};
|
||||
} else if (rowInfo.original.reason === 'NotFilteredWhiteList') {
|
||||
return {
|
||||
className: 'green',
|
||||
};
|
||||
} else if (rowInfo.original.reason === 'Rewrite') {
|
||||
return {
|
||||
className: 'blue',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
className: '',
|
||||
};
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
return undefined;
|
||||
@@ -78,35 +316,65 @@ class Logs extends Component {
|
||||
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
|
||||
};
|
||||
|
||||
renderButtons(queryLogEnabled) {
|
||||
return (<div className="card-actions-top">
|
||||
<button
|
||||
className="btn btn-success btn-standart mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
>{queryLogEnabled ? 'Disable log' : 'Enable log'}</button>
|
||||
{queryLogEnabled &&
|
||||
<button
|
||||
className="btn btn-primary btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleDownloadButton}
|
||||
>Download log file</button> }
|
||||
</div>);
|
||||
renderButtons(queryLogEnabled, logStatusProcessing) {
|
||||
if (queryLogEnabled) {
|
||||
return (
|
||||
<Fragment>
|
||||
<button
|
||||
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"
|
||||
type="submit"
|
||||
onClick={this.handleDownloadButton}
|
||||
><Trans>download_log_file_btn</Trans></button>
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
type="submit"
|
||||
onClick={this.getLogs}
|
||||
><Trans>refresh_btn</Trans></button>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className="btn btn-success btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>enabled_log_btn</Trans></button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { queryLogs, dashboard } = this.props;
|
||||
const { queryLogs, dashboard, t } = this.props;
|
||||
const { queryLogEnabled } = dashboard;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title="Query Log" subtitle="DNS queries log" />
|
||||
<Fragment>
|
||||
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
|
||||
<div className="page-title__actions">
|
||||
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
|
||||
</div>
|
||||
</PageTitle>
|
||||
<Card>
|
||||
{this.renderButtons(queryLogEnabled)}
|
||||
{queryLogEnabled && queryLogs.processing && <Loading />}
|
||||
{queryLogEnabled && !queryLogs.processing &&
|
||||
this.renderLogs(queryLogs.logs)}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& queryLogs.getLogsProcessing
|
||||
&& dashboard.processingClients
|
||||
&& <Loading />
|
||||
}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& !queryLogs.getLogsProcessing
|
||||
&& !dashboard.processingClients
|
||||
&& this.renderLogs(queryLogs.logs)
|
||||
}
|
||||
</Card>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -117,6 +385,15 @@ Logs.propTypes = {
|
||||
dashboard: PropTypes.object,
|
||||
toggleLogStatus: PropTypes.func,
|
||||
downloadQueryLog: PropTypes.func,
|
||||
getFilteringStatus: PropTypes.func,
|
||||
filtering: PropTypes.object,
|
||||
userRules: PropTypes.string,
|
||||
setRules: PropTypes.func,
|
||||
addSuccessToast: PropTypes.func,
|
||||
processingRules: PropTypes.bool,
|
||||
logStatusProcessing: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Logs;
|
||||
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);
|
||||
292
client/src/components/Settings/Clients/ClientsTable.js
Normal file
292
client/src/components/Settings/Clients/ClientsTable.js
Normal file
@@ -0,0 +1,292 @@
|
||||
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) => {
|
||||
let config = values;
|
||||
|
||||
if (values && values.blocked_services) {
|
||||
const blocked_services = Object
|
||||
.keys(values.blocked_services)
|
||||
.filter(service => values.blocked_services[service]);
|
||||
config = { ...values, blocked_services };
|
||||
}
|
||||
|
||||
if (this.props.modalType === MODAL_TYPE.EDIT) {
|
||||
this.handleFormUpdate(config, this.props.modalClientName);
|
||||
} else {
|
||||
this.handleFormAdd(config);
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
use_global_blocked_services: true,
|
||||
...client,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: CLIENT_ID.IP,
|
||||
use_global_settings: true,
|
||||
use_global_blocked_services: 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('blocked_services'),
|
||||
accessor: 'blocked_services',
|
||||
Cell: (row) => {
|
||||
const { value, original } = row;
|
||||
|
||||
if (original.use_global_blocked_services) {
|
||||
return <Trans>settings_global</Trans>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--icons">
|
||||
{value && value.length > 0 ? value.map(service => (
|
||||
<svg className="service__icon service__icon--table" title={service} key={service}>
|
||||
<use xlinkHref={`#service_${service}`} />
|
||||
</svg>
|
||||
)) : '–'}
|
||||
</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);
|
||||
257
client/src/components/Settings/Clients/Form.js
Normal file
257
client/src/components/Settings/Clients/Form.js
Normal file
@@ -0,0 +1,257 @@
|
||||
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 Tabs from '../../ui/Tabs';
|
||||
import { toggleAllServices } from '../../../helpers/helpers';
|
||||
import { renderField, renderRadioField, renderSelectField, renderServiceField, ipv4, mac, required } from '../../../helpers/form';
|
||||
import { CLIENT_ID, SERVICES } from '../../../helpers/constants';
|
||||
import './Service.css';
|
||||
|
||||
const settingsCheckboxes = [
|
||||
{
|
||||
name: 'use_global_settings',
|
||||
placeholder: 'client_global_settings',
|
||||
},
|
||||
{
|
||||
name: 'filtering_enabled',
|
||||
placeholder: 'block_domain_use_filters_and_hosts',
|
||||
},
|
||||
{
|
||||
name: 'safebrowsing_enabled',
|
||||
placeholder: 'use_adguard_browsing_sec',
|
||||
},
|
||||
{
|
||||
name: 'parental_enabled',
|
||||
placeholder: 'use_adguard_parental',
|
||||
},
|
||||
{
|
||||
name: 'safesearch_enabled',
|
||||
placeholder: 'enforce_safe_search',
|
||||
},
|
||||
];
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
reset,
|
||||
change,
|
||||
pristine,
|
||||
submitting,
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
useGlobalServices,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form__group">
|
||||
<div className="form__inline mb-2">
|
||||
<strong className="mr-3">
|
||||
<Trans>client_identifier</Trans>
|
||||
</strong>
|
||||
<div className="custom-controls-stacked">
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderRadioField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="ip"
|
||||
placeholder={t('ip_address')}
|
||||
/>
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderRadioField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="mac"
|
||||
placeholder="MAC"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col col-sm-6">
|
||||
{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>
|
||||
<div className="col col-sm-6">
|
||||
<Field
|
||||
id="name"
|
||||
name="name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_client_name')}
|
||||
validate={[required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__desc">
|
||||
<Trans
|
||||
components={[
|
||||
<a href="#dhcp" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
client_identifier_desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs controlClass="form">
|
||||
<div label="settings" title={props.t('main_settings')}>
|
||||
{settingsCheckboxes.map(setting => (
|
||||
<div className="form__group" key={setting.name}>
|
||||
<Field
|
||||
name={setting.name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(setting.placeholder)}
|
||||
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div label="services" title={props.t('block_services')}>
|
||||
<div className="form__group">
|
||||
<Field
|
||||
name="use_global_blocked_services"
|
||||
type="checkbox"
|
||||
component={renderServiceField}
|
||||
placeholder={t('blocked_services_global')}
|
||||
modifier="service--global"
|
||||
/>
|
||||
<div className="row mb-4">
|
||||
<div className="col-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-block"
|
||||
disabled={useGlobalServices}
|
||||
onClick={() => toggleAllServices(SERVICES, change, true)}
|
||||
>
|
||||
<Trans>block_all</Trans>
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-block"
|
||||
disabled={useGlobalServices}
|
||||
onClick={() => toggleAllServices(SERVICES, change, false)}
|
||||
>
|
||||
<Trans>unblock_all</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="services">
|
||||
{SERVICES.map(service => (
|
||||
<Field
|
||||
key={service.id}
|
||||
icon={`service_${service.id}`}
|
||||
name={`blocked_services.${service.id}`}
|
||||
type="checkbox"
|
||||
component={renderServiceField}
|
||||
placeholder={service.name}
|
||||
disabled={useGlobalServices}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</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,
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
clientIdentifier: PropTypes.string,
|
||||
useGlobalSettings: PropTypes.bool,
|
||||
useGlobalServices: 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');
|
||||
const useGlobalServices = selector(state, 'use_global_blocked_services');
|
||||
return {
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
useGlobalServices,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'clientForm',
|
||||
enableReinitialize: true,
|
||||
}),
|
||||
])(Form);
|
||||
81
client/src/components/Settings/Clients/Modal.js
Normal file
81
client/src/components/Settings/Clients/Modal.js
Normal file
@@ -0,0 +1,81 @@
|
||||
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 getInitialData = (initial) => {
|
||||
if (initial && initial.blocked_services) {
|
||||
const { blocked_services } = initial;
|
||||
const blocked = {};
|
||||
|
||||
blocked_services.forEach((service) => {
|
||||
blocked[service] = true;
|
||||
});
|
||||
|
||||
return {
|
||||
...initial,
|
||||
blocked_services: blocked,
|
||||
};
|
||||
}
|
||||
|
||||
return initial;
|
||||
};
|
||||
|
||||
const Modal = (props) => {
|
||||
const {
|
||||
isModalOpen,
|
||||
modalType,
|
||||
currentClientData,
|
||||
handleSubmit,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
} = props;
|
||||
const initialData = getInitialData(currentClientData);
|
||||
|
||||
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={{ ...initialData }}
|
||||
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);
|
||||
79
client/src/components/Settings/Clients/Service.css
Normal file
79
client/src/components/Settings/Clients/Service.css
Normal file
@@ -0,0 +1,79 @@
|
||||
.service {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.services {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.service {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: calc(99.9% * 4/12 - (30px - 30px * 4/12));
|
||||
max-width: calc(99.9% * 4/12 - (30px - 30px * 4/12));
|
||||
width: calc(99.9% * 4/12 - (30px - 30px * 4/12));
|
||||
}
|
||||
|
||||
.service--global {
|
||||
flex-basis: 1;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.service:nth-child(1n) {
|
||||
margin-right: 30px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.service:nth-child(3n) {
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.service__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.service--global .service__icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service__icon--table {
|
||||
margin-bottom: 3px;
|
||||
color: #9aa0ac;
|
||||
}
|
||||
|
||||
.service__switch {
|
||||
margin-left: auto;
|
||||
border: 1px solid rgba(150, 150, 150, 0.12);
|
||||
}
|
||||
|
||||
.custom-switch-input:checked ~ .service__switch {
|
||||
background-color: #cd201f;
|
||||
}
|
||||
|
||||
.custom-switch-input:focus ~ .service__switch {
|
||||
box-shadow: 0 0 0 2px #cd201f3b;
|
||||
border-color: #ec4241;
|
||||
}
|
||||
|
||||
.custom-switch-input:disabled ~ .service__switch,
|
||||
.custom-switch-input:disabled ~ .service__text,
|
||||
.custom-switch-input:disabled ~ .service__icon {
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
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);
|
||||
200
client/src/components/Settings/Dhcp/Form.js
Normal file
200
client/src/components/Settings/Dhcp/Form.js
Normal file
@@ -0,0 +1,200 @@
|
||||
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, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
|
||||
|
||||
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 Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
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--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
name="gateway_ip"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_gateway_input')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
name="subnet_mask"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_subnet_input')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="range_start"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_start')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="range_end"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_end')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="lease_duration"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_lease_input')}
|
||||
validate={[required, isPositive]}
|
||||
normalize={toNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
interfaces: PropTypes.object,
|
||||
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' }),
|
||||
])(Form);
|
||||
53
client/src/components/Settings/Dhcp/Leases.js
Normal file
53
client/src/components/Settings/Dhcp/Leases.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
class Leases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
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,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Leases);
|
||||
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);
|
||||
275
client/src/components/Settings/Dhcp/index.js
Normal file
275
client/src/components/Settings/Dhcp/index.js
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
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 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 {
|
||||
componentDidMount() {
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
if (values.interface_name) {
|
||||
this.props.setDhcpConfig(values);
|
||||
}
|
||||
};
|
||||
|
||||
handleToggle = (config) => {
|
||||
this.props.toggleDhcp(config);
|
||||
};
|
||||
|
||||
getToggleDhcpButton = () => {
|
||||
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' || key === 'icmp_timeout_msec') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return config[key];
|
||||
});
|
||||
|
||||
if (config.enabled) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standard mr-2 btn-gray"
|
||||
onClick={() => this.props.toggleDhcp(config)}
|
||||
disabled={processingDhcp || processingConfig}
|
||||
>
|
||||
<Trans>dhcp_disable</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standard mr-2 btn-success"
|
||||
onClick={() => this.handleToggle(config)}
|
||||
disabled={
|
||||
!filledConfig || !check || otherDhcpFound || processingDhcp || processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>dhcp_enable</Trans>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
getActiveDhcpMessage = (t, check) => {
|
||||
const { found } = check.otherServer;
|
||||
|
||||
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>
|
||||
<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>
|
||||
<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-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
const { enabled, interface_name, ...values } = dhcp.config;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<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>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dhcp.propTypes = {
|
||||
dhcp: PropTypes.object,
|
||||
toggleDhcp: PropTypes.func,
|
||||
getDhcpStatus: PropTypes.func,
|
||||
setDhcpConfig: PropTypes.func,
|
||||
findActiveDhcp: PropTypes.func,
|
||||
addStaticLease: PropTypes.func,
|
||||
removeStaticLease: PropTypes.func,
|
||||
toggleLeaseModal: PropTypes.func,
|
||||
getDhcpInterfaces: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Dhcp);
|
||||
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);
|
||||
89
client/src/components/Settings/Dns/Rewrites/Form.js
Normal file
89
client/src/components/Settings/Dns/Rewrites/Form.js
Normal file
@@ -0,0 +1,89 @@
|
||||
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, required, domain, answer } from '../../../../helpers/form';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
reset,
|
||||
pristine,
|
||||
submitting,
|
||||
toggleRewritesModal,
|
||||
processingAdd,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="domain"
|
||||
name="domain"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_domain')}
|
||||
validate={[required, domain]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="answer"
|
||||
name="answer"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_answer')}
|
||||
validate={[required, answer]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standard"
|
||||
disabled={submitting || processingAdd}
|
||||
onClick={() => {
|
||||
reset();
|
||||
toggleRewritesModal();
|
||||
}}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || pristine || processingAdd}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'rewritesForm',
|
||||
enableReinitialize: true,
|
||||
}),
|
||||
])(Form);
|
||||
52
client/src/components/Settings/Dns/Rewrites/Modal.js
Normal file
52
client/src/components/Settings/Dns/Rewrites/Modal.js
Normal file
@@ -0,0 +1,52 @@
|
||||
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,
|
||||
toggleRewritesModal,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => toggleRewritesModal()}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
<Trans>Add DNS rewrite</Trans>
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleRewritesModal()}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
/>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
processingDelete: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Modal);
|
||||
87
client/src/components/Settings/Dns/Rewrites/Table.js
Normal file
87
client/src/components/Settings/Dns/Rewrites/Table.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
class Table extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
columns = [
|
||||
{
|
||||
Header: 'Domain',
|
||||
accessor: 'domain',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: 'Answer',
|
||||
accessor: 'answer',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('actions_table_header'),
|
||||
accessor: 'actions',
|
||||
maxWidth: 100,
|
||||
Cell: value => (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||
onClick={() =>
|
||||
this.props.handleDelete({
|
||||
answer: value.row.answer,
|
||||
domain: value.row.domain,
|
||||
})
|
||||
}
|
||||
title={this.props.t('delete_table_action')}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
const {
|
||||
t, list, processing, processingAdd, processingDelete,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
data={list || []}
|
||||
columns={this.columns}
|
||||
loading={processing || processingAdd || processingDelete}
|
||||
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('rewrite_not_found')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
list: PropTypes.array.isRequired,
|
||||
processing: PropTypes.bool.isRequired,
|
||||
processingAdd: PropTypes.bool.isRequired,
|
||||
processingDelete: PropTypes.bool.isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Table);
|
||||
83
client/src/components/Settings/Dns/Rewrites/index.js
Normal file
83
client/src/components/Settings/Dns/Rewrites/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Table from './Table';
|
||||
import Modal from './Modal';
|
||||
import Card from '../../../ui/Card';
|
||||
|
||||
class Rewrites extends Component {
|
||||
handleSubmit = (values) => {
|
||||
this.props.addRewrite(values);
|
||||
};
|
||||
|
||||
handleDelete = (values) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('rewrite_confirm_delete', { key: values.domain }))) {
|
||||
this.props.deleteRewrite(values);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
rewrites,
|
||||
toggleRewritesModal,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
list,
|
||||
isModalOpen,
|
||||
processing,
|
||||
processingAdd,
|
||||
processingDelete,
|
||||
} = rewrites;
|
||||
|
||||
return (
|
||||
<Card
|
||||
id="rewrites"
|
||||
title={t('dns_rewrites')}
|
||||
subtitle={t('rewrite_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Fragment>
|
||||
<Table
|
||||
list={list}
|
||||
processing={processing}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
handleDelete={this.handleDelete}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleRewritesModal()}
|
||||
disabled={processingAdd}
|
||||
>
|
||||
<Trans>rewrite_add</Trans>
|
||||
</button>
|
||||
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdd={processingAdd}
|
||||
processingDelete={processingDelete}
|
||||
/>
|
||||
</Fragment>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Rewrites.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
getRewritesList: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Rewrites);
|
||||
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);
|
||||
79
client/src/components/Settings/Dns/index.js
Normal file
79
client/src/components/Settings/Dns/index.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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 Rewrites from './Rewrites';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
class Dns extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getAccessList();
|
||||
this.props.getRewritesList();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
dashboard,
|
||||
settings,
|
||||
access,
|
||||
rewrites,
|
||||
setAccessList,
|
||||
testUpstream,
|
||||
setUpstream,
|
||||
getRewritesList,
|
||||
addRewrite,
|
||||
deleteRewrite,
|
||||
toggleRewritesModal,
|
||||
} = 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} />
|
||||
<Rewrites
|
||||
rewrites={rewrites}
|
||||
getRewritesList={getRewritesList}
|
||||
addRewrite={addRewrite}
|
||||
deleteRewrite={deleteRewrite}
|
||||
toggleRewritesModal={toggleRewritesModal}
|
||||
/>
|
||||
</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,
|
||||
rewrites: PropTypes.object.isRequired,
|
||||
getRewritesList: PropTypes.func.isRequired,
|
||||
addRewrite: PropTypes.func.isRequired,
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.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);
|
||||
90
client/src/components/Settings/Services/Form.js
Normal file
90
client/src/components/Settings/Services/Form.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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 { toggleAllServices } from '../../../helpers/helpers';
|
||||
import { renderServiceField } from '../../../helpers/form';
|
||||
import { SERVICES } from '../../../helpers/constants';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
change,
|
||||
pristine,
|
||||
submitting,
|
||||
processing,
|
||||
processingSet,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form__group">
|
||||
<div className="row mb-4">
|
||||
<div className="col-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-block"
|
||||
disabled={processing || processingSet}
|
||||
onClick={() => toggleAllServices(SERVICES, change, true)}
|
||||
>
|
||||
<Trans>block_all</Trans>
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-block"
|
||||
disabled={processing || processingSet}
|
||||
onClick={() => toggleAllServices(SERVICES, change, false)}
|
||||
>
|
||||
<Trans>unblock_all</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="services">
|
||||
{SERVICES.map(service => (
|
||||
<Field
|
||||
key={service.id}
|
||||
icon={`service_${service.id}`}
|
||||
name={`blocked_services.${service.id}`}
|
||||
type="checkbox"
|
||||
component={renderServiceField}
|
||||
placeholder={service.name}
|
||||
disabled={processing || processingSet}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || pristine || processing || processingSet}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
processing: PropTypes.bool.isRequired,
|
||||
processingSet: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'servicesForm',
|
||||
enableReinitialize: true,
|
||||
}),
|
||||
])(Form);
|
||||
69
client/src/components/Settings/Services/index.js
Normal file
69
client/src/components/Settings/Services/index.js
Normal file
@@ -0,0 +1,69 @@
|
||||
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 Services extends Component {
|
||||
handleSubmit = (values) => {
|
||||
let config = values;
|
||||
|
||||
if (values && values.blocked_services) {
|
||||
const blocked_services = Object
|
||||
.keys(values.blocked_services)
|
||||
.filter(service => values.blocked_services[service]);
|
||||
config = blocked_services;
|
||||
}
|
||||
|
||||
this.props.setBlockedServices(config);
|
||||
};
|
||||
|
||||
|
||||
getInitialDataForServices = (initial) => {
|
||||
if (initial) {
|
||||
const blocked = {};
|
||||
|
||||
initial.forEach((service) => {
|
||||
blocked[service] = true;
|
||||
});
|
||||
|
||||
return {
|
||||
blocked_services: blocked,
|
||||
};
|
||||
}
|
||||
|
||||
return initial;
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
const { services, t } = this.props;
|
||||
const initialData = this.getInitialDataForServices(services.list);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('blocked_services')}
|
||||
subtitle={t('Allows to quickly block popular sites.')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="form">
|
||||
<Form
|
||||
initialValues={{ ...initialData }}
|
||||
processing={services.processing}
|
||||
processingSet={services.processingSet}
|
||||
onSubmit={this.handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Services.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
services: PropTypes.object.isRequired,
|
||||
setBlockedServices: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Services);
|
||||
@@ -1,4 +1,5 @@
|
||||
.form__group {
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@@ -6,7 +7,100 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-standart {
|
||||
.form__group--settings:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form__inline {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.btn-standard {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.form-control--textarea {
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
.form-control--textarea-large {
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
.form__message {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.form__message--error {
|
||||
color: #cd201f;
|
||||
}
|
||||
|
||||
.interface__title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.interface__ip:after {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
.interface__ip:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.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,52 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
export default class Upstream extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleUpstreamChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleUpstreamSubmit();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Card
|
||||
title="Upstream DNS servers"
|
||||
subtitle="If you keep this field empty, AdGuard will use <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> as an upstream."
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<form>
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={this.props.upstream}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstream: PropTypes.string,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
handleUpstreamSubmit: PropTypes.func,
|
||||
};
|
||||
@@ -1,89 +1,92 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Upstream from './Upstream';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
import Services from './Services';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
import './Settings.css';
|
||||
|
||||
export default class Settings extends Component {
|
||||
class Settings extends Component {
|
||||
settings = {
|
||||
filtering: {
|
||||
enabled: false,
|
||||
title: 'Block domains using filters and hosts files',
|
||||
subtitle: 'You can setup blocking rules in the <a href="#filters">Filters</a> settings.',
|
||||
title: 'block_domain_use_filters_and_hosts',
|
||||
subtitle: 'filters_block_toggle_hint',
|
||||
},
|
||||
safebrowsing: {
|
||||
enabled: false,
|
||||
title: 'Use AdGuard browsing security web service',
|
||||
subtitle: 'AdGuard DNS will check if domain is blacklisted by the browsing security web service (sb.adtidy.org). It will use privacy-safe lookup API to do the check.',
|
||||
title: 'use_adguard_browsing_sec',
|
||||
subtitle: 'use_adguard_browsing_sec_hint',
|
||||
},
|
||||
parental: {
|
||||
enabled: false,
|
||||
title: 'Use AdGuard parental control web service',
|
||||
subtitle: 'AdGuard DNS will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.',
|
||||
title: 'use_adguard_parental',
|
||||
subtitle: 'use_adguard_parental_hint',
|
||||
},
|
||||
safesearch: {
|
||||
enabled: false,
|
||||
title: 'Enforce safe search',
|
||||
subtitle: 'AdGuard DNS can enforce safe search in the major search engines: Google, Bing, Yandex.',
|
||||
title: 'enforce_safe_search',
|
||||
subtitle: 'enforce_save_search_hint',
|
||||
},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.initSettings(this.settings);
|
||||
this.props.getBlockedServices();
|
||||
}
|
||||
|
||||
handleUpstreamChange = (value) => {
|
||||
this.props.handleUpstreamChange({ upstream: value });
|
||||
};
|
||||
|
||||
handleUpstreamSubmit = () => {
|
||||
this.props.setUpstream(this.props.settings.upstream);
|
||||
};
|
||||
|
||||
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>No settings</div>
|
||||
<div>
|
||||
<Trans>no_settings</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { settings, upstream } = this.props;
|
||||
const {
|
||||
settings, services, setBlockedServices, t,
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title="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="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
|
||||
upstream={upstream}
|
||||
handleUpstreamChange={this.handleUpstreamChange}
|
||||
handleUpstreamSubmit={this.handleUpstreamSubmit}
|
||||
</div>
|
||||
<div className="col-md-12">
|
||||
<Services
|
||||
services={services}
|
||||
setBlockedServices={setBlockedServices}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -94,7 +97,7 @@ Settings.propTypes = {
|
||||
settings: PropTypes.object,
|
||||
settingsList: PropTypes.object,
|
||||
toggleSetting: PropTypes.func,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
setUpstream: PropTypes.func,
|
||||
upstream: PropTypes.string,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Settings);
|
||||
|
||||
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);
|
||||
66
client/src/components/Toasts/Toast.css
Normal file
66
client/src/components/Toasts/Toast.css
Normal file
@@ -0,0 +1,66 @@
|
||||
.toasts {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 24px;
|
||||
z-index: 105;
|
||||
width: 345px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
padding: 16px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(236, 53, 53, 0.75);
|
||||
}
|
||||
|
||||
.toast--success {
|
||||
background-color: rgba(90, 173, 99, 0.75);
|
||||
}
|
||||
|
||||
.toast:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.toast__content {
|
||||
flex: 1 1 auto;
|
||||
margin: 0 12px 0 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast__content a {
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toast__dismiss {
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toast-enter {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
.toast-enter-active {
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.toast-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast-exit-active {
|
||||
opacity: 0.01;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
52
client/src/components/Toasts/Toast.js
Normal file
52
client/src/components/Toasts/Toast.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
class Toast extends Component {
|
||||
componentDidMount() {
|
||||
const timeout = this.props.type === 'success' ? 5000 : 30000;
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.removeToast(this.props.id);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
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--${type}`}>
|
||||
<p className="toast__content">
|
||||
{this.showMessage(t, type, message)}
|
||||
</p>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
removeToast: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Toast);
|
||||
42
client/src/components/Toasts/index.js
Normal file
42
client/src/components/Toasts/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import * as actionCreators from '../../actions';
|
||||
import Toast from './Toast';
|
||||
|
||||
import './Toast.css';
|
||||
|
||||
const Toasts = props => (
|
||||
<TransitionGroup className="toasts">
|
||||
{props.toasts.notices && props.toasts.notices.map((toast) => {
|
||||
const { id } = toast;
|
||||
return (
|
||||
<CSSTransition
|
||||
key={id}
|
||||
timeout={500}
|
||||
classNames="toast"
|
||||
>
|
||||
<Toast removeToast={props.removeToast} {...toast} />
|
||||
</CSSTransition>
|
||||
);
|
||||
})}
|
||||
</TransitionGroup>
|
||||
);
|
||||
|
||||
Toasts.propTypes = {
|
||||
toasts: PropTypes.object,
|
||||
removeToast: PropTypes.func,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { toasts } = state;
|
||||
const props = { toasts };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Toasts);
|
||||
|
||||
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;
|
||||
@@ -26,7 +26,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.card-body--status {
|
||||
@@ -34,17 +33,52 @@
|
||||
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-title-stats {
|
||||
font-size: 13px;
|
||||
color: #9aa0ac;
|
||||
}
|
||||
|
||||
.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-body-stats {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
height: calc(100% - 3rem);
|
||||
margin: 0;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.card-value-stats {
|
||||
display: block;
|
||||
font-size: 2.1rem;
|
||||
line-height: 2.7rem;
|
||||
height: 2.7rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-value-percent {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card-value-percent:after {
|
||||
content: "%";
|
||||
}
|
||||
|
||||
.card--full {
|
||||
height: calc(100% - 1.5rem);
|
||||
}
|
||||
|
||||
.card-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
.card-title-stats {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user