Compare commits

...

265 commits

Author SHA1 Message Date
Juan Gilsanz Polo
a4bf4cd3c5 Merge branch 'beta' 2025-03-23 19:52:20 +01:00
Juan Gilsanz Polo
61b0f724ba Updated app version 2025-03-23 19:51:58 +01:00
Juan Gilsanz Polo
52945b04ff Removed flutter displaymode 2025-03-23 19:46:52 +01:00
Juan Gilsanz Polo
8e5bbdbd4b Fixed update screen header color 2025-03-23 19:18:51 +01:00
Juan Gilsanz Polo
e6a01ac546 Disabled predictive back gesture 2025-03-23 19:18:43 +01:00
Juan Gilsanz Polo
e8440f7d1d Merge branch 'beta' 2025-03-17 19:13:25 +01:00
Juan Gilsanz Polo
6cc212751b Updated app version 2025-03-17 19:11:41 +01:00
Juan Gilsanz Polo
ae9d23d4a8 Support multiple subdomains 2025-03-17 19:10:06 +01:00
Juan
566254e617
Merge pull request #164 from mikropsoft/patch-1
Update app_tr.arb
2025-03-17 18:55:58 +01:00
𝗛𝗼𝗹𝗶
88339d1c40
Update app_tr.arb 2025-03-09 23:07:05 +03:00
𝗛𝗼𝗹𝗶
254bbcef57
Update app_tr.arb 2025-03-09 23:05:28 +03:00
𝗛𝗼𝗹𝗶
e7aacfbec1
Update app_tr.arb 2025-03-09 23:04:45 +03:00
Juan Gilsanz Polo
7a75a67701 Merge branch 'beta' 2025-03-09 20:37:16 +01:00
Juan Gilsanz Polo
7632d9ef87 Updated app version 2025-03-09 20:36:45 +01:00
Juan Gilsanz Polo
eded494024 Support TLD greater than 6 characters on domains 2025-03-09 20:28:00 +01:00
Juan Gilsanz Polo
e5979edf63 Added upstream dns timeout 2025-03-09 20:17:19 +01:00
Juan Gilsanz Polo
f27b17aad0 Fix filter logs by added client 2025-03-09 19:58:24 +01:00
Juan Gilsanz Polo
fd4daba2aa Merge branch 'master' into beta 2025-03-09 19:45:58 +01:00
Juan Gilsanz Polo
ce7a8e8cc5 Extracted some regexps to external file 2025-03-09 19:45:48 +01:00
Juan Gilsanz Polo
4282792ebd Updated stuff 2025-03-09 19:45:37 +01:00
Juan Gilsanz Polo
4766d1907f Merge branch 'beta' 2025-01-22 14:45:52 +01:00
Juan Gilsanz Polo
7f6f686b2b Updated app version 2025-01-22 14:45:38 +01:00
Juan Gilsanz Polo
02b659c1bc Changed my other apps text 2025-01-22 14:43:20 +01:00
Juan Gilsanz Polo
db6e63c4aa Fix bad performance debug mode 2025-01-22 14:41:04 +01:00
Juan Gilsanz Polo
a666d109d9 Bug fixes 2025-01-22 14:40:39 +01:00
Juan Gilsanz Polo
1f23f1f3ca Merge branch 'beta' 2025-01-22 00:00:36 +01:00
Juan Gilsanz Polo
51b8a6b610 Updated app version 2025-01-22 00:00:26 +01:00
Juan Gilsanz Polo
28034d4b74 Updated app version 2025-01-21 22:00:06 +01:00
Juan Gilsanz Polo
10ff5183f1 Added sorting options custom rules 2025-01-21 21:54:04 +01:00
Juan Gilsanz Polo
47b5313bf3 Removed unused imports 2025-01-21 20:56:56 +01:00
Juan Gilsanz Polo
791400f565 Added settings links 2025-01-21 20:55:54 +01:00
Juan Gilsanz Polo
06c9f7c771 Added support for custom time in logs settings 2025-01-21 20:47:24 +01:00
Juan Gilsanz Polo
2a0db84959 Updated libraries and other stuff 2025-01-21 20:47:10 +01:00
Juan Gilsanz Polo
74cade6553 Merge branch 'beta' 2024-10-01 18:50:46 +02:00
Juan Gilsanz Polo
1b5f258c96 Fixed windows installer builder 2024-10-01 18:50:37 +02:00
Juan Gilsanz Polo
aa511f8c42 Updated macos pods 2024-10-01 16:21:00 +02:00
Juan Gilsanz Polo
c28d2440b1 Merge branch 'beta' 2024-10-01 15:00:27 +02:00
Juan Gilsanz Polo
9d4c002813 Updated app version 2024-10-01 15:00:18 +02:00
Juan Gilsanz Polo
d7392e4b8d Updated compileSdkVersion to 35 2024-10-01 14:59:24 +02:00
Juan
4dc54794bd
Merge pull request #152 from navhaxs/master
Fix for not working 'path' parameter for reverse proxy
2024-09-29 22:26:37 +02:00
Jeremy Wong
a36335ef92 Fix missing 'path' in serverObj 2024-09-29 08:41:07 +10:00
Juan Gilsanz Polo
9096367843 Merge branch 'beta' 2024-09-12 14:47:31 +02:00
Juan Gilsanz Polo
0d0321a5ab Updated app version 2024-09-12 14:47:21 +02:00
Juan Gilsanz Polo
d903da0051 Removed app updates notifier due to a bug on the library 2024-09-12 14:33:32 +02:00
Juan Gilsanz Polo
b65dc35cb7 Exclude some sentry logs 2024-09-11 22:43:26 +02:00
Juan Gilsanz Polo
7bb7ad40c4 Fixed some bugs 2024-09-11 22:32:26 +02:00
Juan Gilsanz Polo
7579e2d580 Updated workflows 2024-09-11 21:14:58 +02:00
Juan Gilsanz Polo
6eb1d73ca4 Merge branch 'beta' 2024-09-11 19:46:53 +02:00
Juan Gilsanz Polo
3055c3582b Updated workflow 2024-09-11 19:46:45 +02:00
Juan Gilsanz Polo
f9f7e8c2c9 Merge branch 'beta' 2024-09-11 19:34:11 +02:00
Juan Gilsanz Polo
fc2305266f Updated workflow 2024-09-11 19:34:04 +02:00
Juan Gilsanz Polo
6df7d89867 Merge branch 'beta' 2024-09-11 19:27:11 +02:00
Juan Gilsanz Polo
67bc6a1716 Updated workflow 2024-09-11 19:27:04 +02:00
Juan Gilsanz Polo
391d1da1ff Merge branch 'beta' 2024-09-11 19:25:56 +02:00
Juan Gilsanz Polo
a171eda41c Updated workflow 2024-09-11 19:25:49 +02:00
Juan Gilsanz Polo
6e0f437c6b Merge branch 'beta' 2024-09-11 19:17:02 +02:00
Juan Gilsanz Polo
2f1e8d38a8 Updated app version 2024-09-11 19:16:41 +02:00
Juan Gilsanz Polo
c0f7449a95 Fixed some colors 2024-09-11 19:05:07 +02:00
Juan Gilsanz Polo
b223076dae Fix bottom padding top items list 2024-09-11 18:53:23 +02:00
Juan Gilsanz Polo
9eb200f5da Changed design top items screen 2024-09-11 18:51:57 +02:00
Juan Gilsanz Polo
f9149056fd Fixed logs search bar 2024-09-11 18:21:46 +02:00
Juan Gilsanz Polo
29af26f118 Added padding bottom list live logs 2024-09-11 18:18:20 +02:00
Juan Gilsanz Polo
f7c3ba0374 Fixed most of the warnings 2024-09-11 18:13:26 +02:00
Juan Gilsanz Polo
715ca0ab3f Updated readme 2024-09-08 19:30:50 +02:00
Juan Gilsanz Polo
ffcc0c2da0 Removed store checker and added install referrer 2024-09-08 19:27:34 +02:00
Juan Gilsanz Polo
8761652eaa Updated app version 2024-09-08 19:08:23 +02:00
Juan Gilsanz Polo
b7d4680f9c Updated macOS stuff 2024-09-08 19:07:05 +02:00
Juan Gilsanz Polo
d6b16b230c Added split view 2024-09-08 19:03:57 +02:00
Juan Gilsanz Polo
7d1845f806 Fix blocked services screen 2024-09-08 18:21:13 +02:00
Juan Gilsanz Polo
bd08c98849 Removed print 2024-09-08 18:17:26 +02:00
Juan Gilsanz Polo
7991f29707 Added realtime logs 2024-09-08 18:17:05 +02:00
Juan Gilsanz Polo
be3e76eafc Updated libraries 2024-09-08 17:16:07 +02:00
Juan Gilsanz Polo
5bf2db8a52 Merge branch 'beta' 2024-06-24 03:02:45 +02:00
Juan Gilsanz Polo
0eae951eed Updated app version 2024-06-24 03:02:33 +02:00
Juan Gilsanz Polo
f8f18be723 Fix windows installer builder 2024-06-24 03:00:02 +02:00
Juan Gilsanz Polo
b685010a03 Updated app version 2024-06-21 15:31:54 +02:00
Juan Gilsanz Polo
a8cbed5ca0 Added search button to added clients 2024-06-21 02:32:23 +02:00
Juan Gilsanz Polo
cce54c8ba5 Check if it's not ip to apply "" 2024-06-18 21:50:38 +02:00
Juan Gilsanz Polo
4f903d8e4c Updated app version 2024-06-18 01:32:04 +02:00
Juan Gilsanz Polo
9a747dd2fb Added added clients list to client filters on logs 2024-06-18 01:31:21 +02:00
Juan Gilsanz Polo
27b0c3a3a0 Updated macOS stuff 2024-06-18 01:30:54 +02:00
Juan Gilsanz Polo
b981f4a5cb Updated app version 2024-06-17 17:17:13 +02:00
Juan Gilsanz Polo
d34d881722 Added predictive navigation 2024-06-17 17:16:08 +02:00
Juan Gilsanz Polo
ad6e75b6fb Fixed on dark theme 2024-06-17 13:56:59 +02:00
Juan Gilsanz Polo
4dd77a39d8 Fixed wrong colors 2024-06-17 13:56:02 +02:00
Juan Gilsanz Polo
8801428167 Fixed missing language 2024-06-17 13:55:54 +02:00
Juan Gilsanz Polo
1dd23906c3 Added options to copy to clipboard on log details 2024-06-17 13:28:12 +02:00
Juan Gilsanz Polo
c5d2892ec2 Updated libraries 2024-06-17 13:28:00 +02:00
Juan Gilsanz Polo
1c5f0e46f5 Merge branch 'beta' 2024-06-08 21:45:56 +02:00
Juan Gilsanz Polo
5f5d79147f Updated readme 2024-06-08 21:45:49 +02:00
Juan Gilsanz Polo
38d2955a8c Merge branch 'beta' 2024-04-24 18:13:01 +02:00
Juan Gilsanz Polo
212e57149f Updated macos pods 2024-04-24 18:12:53 +02:00
Juan Gilsanz Polo
c4ee498d62 Merge branch 'beta' 2024-04-24 18:01:41 +02:00
Juan Gilsanz Polo
9295321cda Updated app version 2024-04-24 18:01:32 +02:00
Juan Gilsanz Polo
1e84fbfcb0 Merge branch 'beta' 2024-04-24 18:01:08 +02:00
Juan Gilsanz Polo
2326470578 Updated libraries 2024-04-24 17:59:25 +02:00
Juan Gilsanz Polo
3c397d208f Small bug fixes 2024-04-24 17:52:20 +02:00
Juan Gilsanz Polo
e4298cc062 Fixed chinese translation 2024-04-24 17:44:31 +02:00
Juan Gilsanz Polo
8b7b85fd61 Merge branch 'master' into beta 2024-04-24 17:41:42 +02:00
Juan Gilsanz Polo
a88dc21974
Merge pull request #141 from mikropsoft/master
Update app_tr.arb
2024-04-24 17:41:31 +02:00
Juan Gilsanz Polo
5e72d5579a
Merge pull request #143 from zutzo/patch-2
updata cn translate
2024-04-24 17:39:53 +02:00
Zutzo
f838302720
Update app_zh_CN.arb 2024-04-16 13:07:15 +00:00
Zutzo
18ee68d684
Update app_zh.arb 2024-04-16 13:06:09 +00:00
WINZORT
d3b6833e15 Update app_tr.arb 2024-03-15 19:43:51 +03:00
Juan Gilsanz Polo
b56234be91 Merge branch 'beta' 2024-03-10 21:53:16 +01:00
Juan Gilsanz Polo
d48687bfa0 Fixed innosetup 2024-03-10 21:53:06 +01:00
Juan Gilsanz Polo
6ab603d459 Merge branch 'beta' 2024-03-10 21:41:58 +01:00
Juan Gilsanz Polo
7e13285b7e Changed size 2024-03-10 21:30:25 +01:00
Juan Gilsanz Polo
999da666b4 Replaced window size with window manager 2024-03-10 21:24:17 +01:00
Juan Gilsanz Polo
fac793a3a2 Merge branch 'beta' 2024-03-10 21:15:31 +01:00
Juan Gilsanz Polo
5be5377110 Removed splitview package 2024-03-10 21:15:24 +01:00
Juan Gilsanz Polo
ecc9cf1073 Merge branch 'beta' 2024-03-10 20:31:36 +01:00
Juan Gilsanz Polo
fd25088791 Updated app version 2024-03-10 20:31:16 +01:00
Juan Gilsanz Polo
e013b1496e Bug fixes 2024-03-10 20:30:25 +01:00
Juan Gilsanz Polo
b270ca2b8c Added sliverappbar to more screens 2024-03-10 14:51:35 +01:00
Juan Gilsanz Polo
a3c63ffd9a Merge branch 'beta' of github.com:JGeek00/adguard-home-manager into beta
# Conflicts:
#	pubspec.yaml
2024-03-09 20:45:04 +01:00
Juan Gilsanz Polo
e88b61eb90 Updated app version 2024-03-09 20:43:55 +01:00
Juan Gilsanz Polo
ac5be8b500 Updated app version 2024-03-09 20:43:28 +01:00
Juan Gilsanz Polo
4b3129656b Updated libraries 2024-03-09 20:43:03 +01:00
Juan Gilsanz Polo
a8d0bd95a6 Changed options menu 2024-03-09 20:41:49 +01:00
Juan Gilsanz Polo
5fdeaa80b7 Added option to allow and disallow client 2024-03-09 20:33:14 +01:00
Juan Gilsanz Polo
806e74ca9f Added block unblock domain for a client 2024-03-09 20:07:24 +01:00
Juan Gilsanz Polo
31ad3fcf6a Fixed logs search bar 2024-03-09 14:40:02 +01:00
Juan Gilsanz Polo
d65ea6520c Added sliver app bar client logs 2024-03-09 14:14:49 +01:00
Juan Gilsanz Polo
ce8d38958e Fix desktop and sliver app bar add client 2024-03-09 13:57:15 +01:00
Juan Gilsanz Polo
ad6cb92d4b Add persistent client logs 2024-03-09 13:44:07 +01:00
Juan Gilsanz Polo
442c7d9264
Merge pull request #137 from mikropsoft/beta
Update app_tr.arb
2024-03-09 13:18:50 +01:00
WINZORT
a0b566446b Update app_tr.arb 2024-02-29 18:47:20 +03:00
Juan Gilsanz Polo
4d8660d363 Merge branch 'beta' 2024-02-29 15:44:57 +01:00
Juan Gilsanz Polo
a58b97a1bd Updated macos stuff 2024-02-29 15:44:49 +01:00
Juan Gilsanz Polo
2a075816b3 Merge branch 'beta' 2024-02-29 15:34:55 +01:00
Juan Gilsanz Polo
59a7a31a4f Updated app version 2024-02-29 15:34:11 +01:00
Juan Gilsanz Polo
654284b46a Added add comment fallback dns 2024-02-29 15:33:26 +01:00
Juan Gilsanz Polo
019367ca93 Fix hour mismatch home charts 2024-02-29 15:29:56 +01:00
Juan Gilsanz Polo
7c0b592715 Changed handling of comments fallback dns 2024-02-29 15:17:27 +01:00
Juan Gilsanz Polo
133d29aa91 Bump compileSdkVersion and targetSdkVersion android 2024-02-29 15:17:11 +01:00
Juan Gilsanz Polo
dac07edd89 Changes vscode rules 2024-02-29 15:16:50 +01:00
Juan Gilsanz Polo
ba2a27fef0 Fixed fallback dns regex 2024-02-29 14:55:40 +01:00
Juan Gilsanz Polo
9de9b0afec Fixed statistics config updated text 2024-02-29 14:49:30 +01:00
Juan Gilsanz Polo
0821fd4e0e Fix duplicated snackbars logs 2024-02-29 14:44:10 +01:00
Juan Gilsanz Polo
8859468a66 Updated libraries 2024-02-29 14:44:00 +01:00
Juan Gilsanz Polo
19ae91905f Added project specific vscode settings 2024-02-29 14:43:25 +01:00
Juan Gilsanz Polo
7087bd5aee Merge branch 'beta' 2024-02-21 13:16:47 +01:00
Juan Gilsanz Polo
f624fdbc43 Updated app version 2024-02-21 13:16:37 +01:00
Juan Gilsanz Polo
0a2a86ef81 Merge branch 'beta' 2024-02-21 13:16:09 +01:00
Juan Gilsanz Polo
693e66e125 Added loading indicator 2024-02-21 13:14:36 +01:00
Juan Gilsanz Polo
e603814d42 Removed unused imports 2024-02-21 10:50:33 +01:00
Juan Gilsanz Polo
7a89aea3a5 Changed dns addresses modal 2024-02-21 10:50:02 +01:00
Juan Gilsanz Polo
dcad63fe5c New update server screen 2024-02-21 02:34:36 +01:00
Juan Gilsanz Polo
bce93fa5ca Merge branch 'beta' 2024-02-18 21:41:31 +01:00
Juan Gilsanz Polo
e21e34668d Updated app version 2024-02-18 21:41:20 +01:00
Juan Gilsanz Polo
6629237548 Merge branch 'beta' 2024-02-18 21:40:42 +01:00
Juan Gilsanz Polo
923fb97f03 Added date log details 2024-02-18 21:37:06 +01:00
Juan Gilsanz Polo
568b879054 Added client name added clients desktop 2024-02-18 21:30:08 +01:00
Juan Gilsanz Polo
4702c8c427 Merge branch 'beta' 2024-02-09 03:01:17 +01:00
Juan Gilsanz Polo
c60b62345b Updated app version 2024-02-09 03:01:09 +01:00
Juan Gilsanz Polo
ca4fa5d7df Bug fixes 2024-02-09 03:00:42 +01:00
Juan Gilsanz Polo
dab9f69e69 Merge branch 'beta' 2024-02-09 01:49:06 +01:00
Juan Gilsanz Polo
eb703c6f58 Updated app version 2024-02-09 01:48:56 +01:00
Juan Gilsanz Polo
9cd480aa9a Disable plain dns on general disabled 2024-02-09 01:48:32 +01:00
Juan Gilsanz Polo
e2757d4b8f Upgraded android gradle and macos podfile 2024-02-09 01:45:02 +01:00
Juan Gilsanz Polo
6985efc4cb Small improvement 2024-02-09 01:44:31 +01:00
Juan Gilsanz Polo
34246d238b Updated libraries 2024-02-09 01:29:16 +01:00
Juan Gilsanz Polo
dba9c8b9ac Bug fixes 2024-02-09 01:29:11 +01:00
Juan Gilsanz Polo
3a210fb9fd Updated turkish translation 2024-02-09 01:22:05 +01:00
Juan Gilsanz Polo
b2c2a1452f Merge branch 'master' into beta 2024-02-08 01:29:08 +01:00
Juan Gilsanz Polo
00f77fc12c Updated templates 2024-02-08 01:28:38 +01:00
Juan Gilsanz Polo
0dc10abc29 Fix template 2024-02-08 01:21:53 +01:00
Juan Gilsanz Polo
a2d8e9d1c1 Added app feature request 2024-02-08 01:20:11 +01:00
Juan Gilsanz Polo
d51964de32 Added agh feature request 2024-02-08 01:14:09 +01:00
Juan Gilsanz Polo
c249b757b2 Updated bug template 2024-02-08 01:08:34 +01:00
Juan Gilsanz Polo
7fa5eee014 Updated bug template 2024-02-08 01:08:02 +01:00
Juan Gilsanz Polo
034280afa8 Updated bug template 2024-02-08 01:05:24 +01:00
Juan Gilsanz Polo
5dbe1e3f74 Updated bug template 2024-02-08 01:02:11 +01:00
Juan Gilsanz Polo
5f4580b6a0
Update issue templates 2024-02-08 00:54:07 +01:00
Juan Gilsanz Polo
6e8ecf11a0 Updated app version 2024-02-08 00:28:05 +01:00
Juan Gilsanz Polo
7d2da49bc8 Changed logs search 2024-02-08 00:25:58 +01:00
Juan Gilsanz Polo
2f4bf402fe Fix keyboard closes on edit encryption 2024-02-07 23:44:32 +01:00
Juan Gilsanz Polo
3f057062f1 Added plain dns control 2024-02-07 23:35:20 +01:00
Juan Gilsanz Polo
b977aed9bd Updated app version 2024-02-07 19:57:54 +01:00
Juan Gilsanz Polo
11dd6b23bd Added no items to reorder message 2024-02-07 19:55:44 +01:00
Juan Gilsanz Polo
5ca3c04c89 Improvements 2024-02-07 19:49:25 +01:00
Juan Gilsanz Polo
3e152db6ac Added show and hide top items 2024-02-07 19:46:35 +01:00
Juan Gilsanz Polo
ed0bc65285 Bug fixes 2024-02-04 21:28:08 +01:00
Juan Gilsanz Polo
c150d2ba23 Added edit custom rules 2024-02-04 21:23:45 +01:00
Juan Gilsanz Polo
2fca44f195 Updated app version 2024-02-03 20:00:06 +01:00
Juan Gilsanz Polo
1bc1efbe56 Added rate limit whitelist 2024-02-03 19:56:27 +01:00
Juan Gilsanz Polo
8df0781da6 Added ipv4 and ipv6 subnet prefix length fields 2024-02-03 14:38:38 +01:00
Juan Gilsanz Polo
5f448f0af0 Added search clients filter 2024-02-03 02:38:00 +01:00
Juan Gilsanz Polo
2585826a29 Show client name on logs 2024-02-03 01:58:45 +01:00
Juan Gilsanz Polo
9b1e36df5b Updated macos stuff 2024-02-02 20:40:42 +01:00
Juan Gilsanz Polo
794b690c91 Save settings on shared preferences 2024-02-02 20:37:27 +01:00
Juan Gilsanz Polo
f8f3914cad Merge branch 'beta' 2024-01-30 01:06:15 +01:00
Juan Gilsanz Polo
3eaf7143f2 Updated app version 2024-01-30 01:06:08 +01:00
Juan Gilsanz Polo
ae3e172033 Changed response status bottom sheet 2024-01-30 01:04:06 +01:00
Juan Gilsanz Polo
16f1d4664c Fix logs filtering by client 2024-01-30 00:43:57 +01:00
Juan Gilsanz Polo
1a7925f49d Merge branch 'beta' 2024-01-29 16:14:53 +01:00
Juan Gilsanz Polo
aed2500fa4 Updated app version 2024-01-29 16:14:34 +01:00
Juan Gilsanz Polo
d92d755381 Changed url launcher 2024-01-29 16:09:07 +01:00
Juan Gilsanz Polo
340ef4f00e Removed unused imports 2024-01-29 15:05:12 +01:00
Juan Gilsanz Polo
02b9bd034f Fixed duplicated snackbar desktop 2024-01-29 15:04:52 +01:00
Juan Gilsanz Polo
a4e8694cc1 Changed url launcher library 2024-01-29 02:37:45 +01:00
Juan Gilsanz Polo
406f7c832f Change android gradle 2024-01-29 02:36:46 +01:00
Juan Gilsanz Polo
2f95173d22 Updated libraries 2024-01-29 01:50:06 +01:00
Juan Gilsanz Polo
58bba7f1df Updated app version 2024-01-29 00:22:00 +01:00
Juan Gilsanz Polo
0d5aeaba42 Fix 2024-01-29 00:21:52 +01:00
Juan Gilsanz Polo
246d14151a Fix home top items type 2024-01-28 20:42:26 +01:00
Juan Gilsanz Polo
44d7da9977 Added statistics settings 2024-01-28 20:39:42 +01:00
Juan Gilsanz Polo
56943ec73d Fixed no data issue on home top items 2024-01-28 19:04:01 +01:00
Juan Gilsanz Polo
dd927bbd25 Fixed dhcp not available 2024-01-28 19:03:49 +01:00
Juan Gilsanz Polo
300738ca14 Changed some error texts 2024-01-28 17:13:18 +01:00
Juan Gilsanz Polo
69612cc3e5 Updated app version 2024-01-28 15:18:18 +01:00
Juan Gilsanz Polo
c87e3b4943 Updated splash screen icon 2024-01-28 15:09:41 +01:00
Juan Gilsanz Polo
46867b951a Updated app icon 2024-01-28 15:01:33 +01:00
Juan Gilsanz Polo
791c42317b Updated app version 2024-01-27 23:08:44 +01:00
Juan Gilsanz Polo
0b0f38cd2e Changed filter clients modal design 2024-01-27 23:08:04 +01:00
Juan Gilsanz Polo
7f77b3c6ca Updated ios pods 2024-01-27 23:07:00 +01:00
Juan Gilsanz Polo
3e206027de Changed general modals 2024-01-27 22:25:46 +01:00
Juan Gilsanz Polo
5d45829937 Changed from to buttons design 2024-01-27 22:04:54 +01:00
Juan Gilsanz Polo
ad3046a84b Updated app version 2024-01-26 21:39:54 +01:00
Juan Gilsanz Polo
74a4f65924 Added scrollbar 2024-01-26 21:38:21 +01:00
Juan Gilsanz Polo
91e3ea44f6 Fix timezone selection 2024-01-26 21:33:41 +01:00
Juan Gilsanz Polo
770366eae6 Added delete schedule and no schedule message 2024-01-26 21:23:37 +01:00
Juan Gilsanz Polo
283d4e5c41 Improved edit schedule 2024-01-26 21:18:04 +01:00
Juan Gilsanz Polo
d73ad93180 Created block time schedule settings 2024-01-25 22:34:30 +01:00
Juan Gilsanz Polo
5b2523158b Small change 2024-01-25 00:55:12 +01:00
Juan Gilsanz Polo
2d991ce9df Fixed showColor 2024-01-25 00:51:46 +01:00
Juan Gilsanz Polo
a40580f425 Removed top items entries animation 2024-01-25 00:50:35 +01:00
Juan Gilsanz Polo
2fd829021c Removed config variable 2024-01-25 00:47:20 +01:00
Juan Gilsanz Polo
9529cd6f56 Replaced ring chart with bar chart 2024-01-25 00:39:36 +01:00
Juan Gilsanz Polo
c12e8c5ad3 Added ignored domains list config 2024-01-24 15:14:19 +01:00
Juan Gilsanz Polo
211eab9f44 Moved logs settings to settings screen 2024-01-24 13:55:15 +01:00
Juan Gilsanz Polo
65ee702b89 Improved theme color selection for desktop 2024-01-24 13:07:25 +01:00
Juan Gilsanz Polo
0df32d3941
Merge pull request #115 from DmitryNG/russian_locale
Add Russian locale
2024-01-24 12:52:30 +01:00
Dmitry Nagibin
cf7bfa44f8 Russian_translation: fix for app version 2.14.1 2024-01-18 23:28:36 +05:00
Dmitry Nagibin
2693f96cf2 Russian_translation: updated for app version 2.14.1 2024-01-18 23:18:47 +05:00
Dmitry Nagibin
d0e77bc4ff Merge remote-tracking branch 'origin/master' into russian_locale
# Conflicts:
#	lib/main.dart
2024-01-18 20:54:11 +05:00
Juan Gilsanz Polo
781f6af0d6 Merge branch 'beta' 2024-01-16 19:06:56 +01:00
Juan Gilsanz Polo
d93eb504b0 Updated workflow 2024-01-16 19:06:47 +01:00
Juan Gilsanz Polo
b71b9cdb7f Merge branch 'beta' 2024-01-16 18:45:45 +01:00
Juan Gilsanz Polo
236124d246 Updated readme, added key properties sample and updated app version 2024-01-16 18:45:37 +01:00
Juan Gilsanz Polo
61d521f9f9 Change logs breaking width 2024-01-16 18:25:47 +01:00
Juan Gilsanz Polo
26086269e5 Fix save fallback dns when no dns is added 2024-01-16 18:25:38 +01:00
Juan Gilsanz Polo
0980641746 Updated libraries 2024-01-16 18:25:20 +01:00
Juan Gilsanz Polo
9a1cefdc26 Added redirect to https warning 2023-12-20 18:23:52 +01:00
Juan Gilsanz Polo
24881bc1e2 Merge branch 'beta' 2023-12-20 15:51:26 +01:00
Juan Gilsanz Polo
af2b9f2704 Updated app version 2023-12-20 15:51:17 +01:00
Juan Gilsanz Polo
3c42f790bd Fix 2023-12-20 15:50:39 +01:00
Juan Gilsanz Polo
565494e3f9 Fix parse ttl client 2023-12-20 15:47:12 +01:00
Juan Gilsanz Polo
62daa56b1b Fix copy log domain 2023-12-20 15:45:42 +01:00
Juan Gilsanz Polo
c78e5704bd Merge branch 'beta' 2023-12-14 15:24:25 +01:00
Juan Gilsanz Polo
3bc8fcec88 Merge branch 'beta' 2023-12-09 04:07:56 +01:00
Juan Gilsanz Polo
e2f3fe988e Merge branch 'beta' 2023-12-01 02:52:20 +01:00
Juan Gilsanz Polo
37e8aae1ef Merge branch 'beta' 2023-11-30 11:07:24 +01:00
Juan Gilsanz Polo
d86fc363aa Merge branch 'beta' 2023-11-30 11:06:05 +01:00
Juan Gilsanz Polo
fb7f1bcf3a Merge branch 'beta' 2023-11-27 14:46:00 +01:00
Juan Gilsanz Polo
969c84a722 Merge branch 'beta' 2023-11-27 14:45:30 +01:00
Juan Gilsanz Polo
aaa41c7b8a Merge branch 'beta' 2023-11-26 22:54:37 +01:00
Juan Gilsanz Polo
6ead07e464 Merge branch 'beta' 2023-11-26 22:53:24 +01:00
Juan Gilsanz Polo
65885c9498 Merge branch 'beta' 2023-11-24 01:59:14 +01:00
Juan Gilsanz Polo
cb1228f9f7 Merge branch 'beta' 2023-11-24 01:29:17 +01:00
dmitry
cd2c1b9cbd Russian_translation: added all translations 2023-09-24 22:42:35 +05:00
dmitry
b531531d2c Russian_translation: added some translations 2023-09-24 02:08:53 +05:00
dmitry
c57f18efb5 Merge branch 'beta' into russian_locale
# Conflicts:
#	lib/main.dart
2023-09-24 01:12:14 +05:00
dmitry
45d785ad72 Russian_translation: init 2023-09-24 00:29:22 +05:00
240 changed files with 10266 additions and 6077 deletions

51
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,51 @@
name: Report a bug
description: |
You detected something wrong on the application.
labels: ["bug"]
body:
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Steps to reproduce the problem you are running into.
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
id: expected-results
attributes:
label: Expected results
description: What is expected to happen.
validations:
required: true
- type: textarea
id: actual-results
attributes:
label: Actual results
description: What is actually happening.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots or Video
description: |
Upload any screenshots or video of the bug.
value: |
Screenshots or video demonstration
validations:
required: true
- type: textarea
id: app-os-details
attributes:
label: App and OS details
description: |
Some details about the app version and the OS where you are running the application.
value: |
- App version: (eg: v2.16.0)
- Device OS version: (eg: Android 14)
validations:
required: true

View file

@ -0,0 +1,32 @@
name: Request a new feature that has been added to AdGuard Home
description: |
The AdGuard Home team added recently a new feature that you want on the app.
labels: ["agh feature request"]
body:
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature that you want on the app.
placeholder: |
Write the details here...
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Attach some screenshots of where that new feature is located on the web administration panel.
placeholder: |
Screenshots here.
validations:
required: true
- type: textarea
id: version-introduced
attributes:
label: Version where feature was introduced
description: Version of the AdGuard Home server where this feature was introduced.
placeholder: |
- Version: (eg: v0.107.44)
validations:
required: true

View file

@ -0,0 +1,20 @@
name: Request a new feature or an improvement for the app itself
description: |
You want a new feature for the application, or an improvement for an existing one.
labels: ["app feature request"]
body:
- type: markdown
id: important-info
attributes:
value: |
IMPORTANT INFO
Please note that if the functionality is related to data representation, it may not be implemented, as AdGuard Home Manager depends on the capabilities of the AdGuard Home API.
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature that you want on the app.
placeholder: |
Write the details here...
validations:
required: true

19
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: You have a question about the app
description: |
You want ask something that's not related with a feature request or a bug.
labels: ["question"]
body:
- type: markdown
id: important-info
attributes:
value: |
Having problems while adding a connection to a server? Check out [this guide](https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection).
- type: textarea
id: question
attributes:
label: Question
description: Write your question here giving the maximum detail possible.
placeholder: |
Write your question here...
validations:
required: true

View file

@ -1,123 +0,0 @@
name: Compile and release beta build
on:
workflow_dispatch:
branches:
- beta
jobs:
build-android:
name: Build Android .apk and .aab
runs-on: ubuntu-latest
env:
ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release
ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release
outputs:
VERSION_NAME: ${{ steps.save_version.outputs.version_name }}
VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }}
steps:
- uses: actions/checkout@v3
with:
ref: beta
- name: Decode android/app/keystore.jks
run: echo "${{ secrets.KEYSTORE_JKS }}" | base64 --decode > android/app/keystore.jks
- name: Decode android/key.properties
run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties
- name: Decode .env
run: echo "${{ secrets.ENV }}" | base64 --decode > .env
- name: Read pubspec.yaml
uses: adore-me/read-yaml@v1.0.0
id: read_pubspec
with:
file: './pubspec.yaml'
key-path: '["version"]'
- name: Save version on env variable
id: save_version
run: |
version=${{ steps.read_pubspec.outputs.data }}
IFS='+'
read -r -a split <<< "$version"
echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV
echo "version_name=${split[0]}" >> $GITHUB_OUTPUT
echo "version_number=${split[1]}" >> $GITHUB_OUTPUT
- name: Update KeyStore password in gradle properties
run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties
- name: Update KeyStore key password in gradle properties
run: sed -i 's/#{KEYSTORE_KEY_PASS}#/${{ secrets.KEYSTORE_KEY_PASS }}/g' android/key.properties
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '18.x'
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build apk --release
- run: flutter build appbundle --release
- name: Rename apk
run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
- name: Rename aab
run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
- name: Copy apk to project root
run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
- name: Copy aab to project root
run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: android
path: |
AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
release-builds-github:
name: Release beta build to GitHub
runs-on: ubuntu-latest
needs: [build-android]
env:
VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }}
VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }}
steps:
- uses: actions/checkout@v3
with:
ref: beta
- name: Create builds directory
run: mkdir releases
- name: Download Android artifacts
uses: actions/download-artifact@v3
with:
name: android
path: releases/
- name: Release to GitHub
uses: ncipollo/release-action@v1
with:
artifacts: "releases/*"
token: ${{ secrets.GH_TOKEN }}
tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})'
name: v${{ env.VERSION_NAME }}
draft: true
prerelease: true
commit: ${{ github.sha }}
release-build-google-play:
name: Release Android beta build to the Google Play Store
runs-on: ubuntu-latest
needs: [build-android]
env:
VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }}
steps:
- uses: actions/checkout@v3
with:
ref: beta
- name: Download Android artifacts
uses: actions/download-artifact@v3
with:
name: android
- name: Release app to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: com.jgeek00.adguard_home_manager
releaseFiles: AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
track: beta
status: draft
releaseName: ${{ env.VERSION_NAME }}

View file

@ -1,302 +0,0 @@
name: Compile and release production build
on:
workflow_dispatch:
branches:
- master
jobs:
build-android:
name: Build Android .apk and .aab
runs-on: ubuntu-latest
env:
ANDROID_AAB_RELEASE_PATH: build/app/outputs/bundle/release
ANDROID_APK_RELEASE_PATH: build/app/outputs/apk/release
outputs:
VERSION_NAME: ${{ steps.save_version.outputs.version_name }}
VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }}
steps:
- uses: actions/checkout@v3
- name: Decode android/app/keystore.jks
run: echo "${{ secrets.KEYSTORE_JKS }}" | base64 --decode > android/app/keystore.jks
- name: Decode android/key.properties
run: echo "${{ secrets.KEY_PROPERTIES }}" | base64 --decode > android/key.properties
- name: Decode .env
run: echo "${{ secrets.ENV }}" | base64 --decode > .env
- name: Read pubspec.yaml
uses: adore-me/read-yaml@v1.0.0
id: read_pubspec
with:
file: './pubspec.yaml'
key-path: '["version"]'
- name: Save version on env variable
id: save_version
run: |
version=${{ steps.read_pubspec.outputs.data }}
IFS='+'
read -r -a split <<< "$version"
echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV
echo "version_name=${split[0]}" >> $GITHUB_OUTPUT
echo "version_number=${split[1]}" >> $GITHUB_OUTPUT
- name: Update KeyStore password in gradle properties
run: sed -i 's/#{KEYSTORE_PASS}#/${{ secrets.KEYSTORE_PASS }}/g' android/key.properties
- name: Update KeyStore key password in gradle properties
run: sed -i 's/#{KEYSTORE_KEY_PASS}#/${{ secrets.KEYSTORE_KEY_PASS }}/g' android/key.properties
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '18.x'
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build apk --release
- run: flutter build appbundle --release
- name: Rename apk
run: mv $ANDROID_APK_RELEASE_PATH/app-release.apk $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
- name: Rename aab
run: mv $ANDROID_AAB_RELEASE_PATH/app-release.aab $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
- name: Copy apk to project root
run: cp $ANDROID_APK_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
- name: Copy aab to project root
run: cp $ANDROID_AAB_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: android
path: |
AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.apk
build-macos:
name: Build macOS .dmg
runs-on: macos-latest
env:
MACOS_APP_RELEASE_PATH: build/macos/Build/Products/Release
outputs:
VERSION_NAME: ${{ steps.save_version.outputs.version_name }}
VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }}
steps:
- uses: actions/checkout@v3
- name: Decode .env
run: echo "${{ secrets.ENV }}" | base64 --decode > .env
- name: Read pubspec.yaml
uses: adore-me/read-yaml@v1.0.0
id: read_pubspec
with:
file: './pubspec.yaml'
key-path: '["version"]'
- name: Save version on env variable
id: save_version
run: |
version=${{ steps.read_pubspec.outputs.data }}
IFS='+'
read -r -a split <<< "$version"
echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV
echo "version_name=${split[0]}" >> $GITHUB_OUTPUT
echo "version_number=${split[1]}" >> $GITHUB_OUTPUT
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build macos --release
- name: Install the Apple certificate and sign the application
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PWD: ${{ secrets.APPLE_CERTIFICATE_PWD }}
APPLE_KEYCHAIN_PWD: ${{ secrets.APPLE_KEYCHAIN_PWD }}
APPLE_IDENTITY_ID: ${{ secrets.APPLE_IDENTITY_ID }}
run: |
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p $APPLE_KEYCHAIN_PWD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $APPLE_KEYCHAIN_PWD build.keychain
security import certificate.p12 -k build.keychain -P $APPLE_CERTIFICATE_PWD -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $APPLE_KEYCHAIN_PWD build.keychain
/usr/bin/codesign --force -s "$APPLE_IDENTITY_ID" $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager.app -v
- name: Create folder to build dmg
run: mkdir $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager
- name: Copy app into folder
run: cp -r $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager.app $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager/AdGuard\ Home\ Manager.app
- name: Generate symbolic link to Applications dir
run: ln -s /Applications $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager
- name: Generate dmg
run: hdiutil create -srcfolder $MACOS_APP_RELEASE_PATH/AdGuard\ Home\ Manager $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg
- name: Copy dmg to project root
run: cp $MACOS_APP_RELEASE_PATH/AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: macos
path: AdGuardHomeManager_${{ env.VERSION_NAME }}_macOS_Universal.dmg
build-linux:
name: Build Linux .tar.gz and .deb
runs-on: ubuntu-latest
outputs:
VERSION_NAME: ${{ steps.save_version.outputs.version_name }}
VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }}
steps:
- uses: actions/checkout@v3
- name: Decode .env
run: echo "${{ secrets.ENV }}" | base64 --decode > .env
- name: Read pubspec.yaml
uses: adore-me/read-yaml@v1.0.0
id: read_pubspec
with:
file: './pubspec.yaml'
key-path: '["version"]'
- name: Save version on env variable
id: save_version
run: |
version=${{ steps.read_pubspec.outputs.data }}
IFS='+'
read -r -a split <<< "$version"
echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV
echo "version_name=${split[0]}" >> $GITHUB_OUTPUT
echo "version_number=${split[1]}" >> $GITHUB_OUTPUT
- name: Update version in debian.yaml
run: sed -i 's/<REPLACE_VERSION_NUMBER_ACTIONS>/${{ env.VERSION_NAME }}/g' debian/debian.yaml
- name: Update dependencies list
run: sudo apt-get update
- name: Install dependencies
run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build linux --release
- name: Install flutter_to_debian
run: dart pub global activate flutter_to_debian
- name: Generate .deb package
run: flutter_to_debian
- name: Move .deb package to project root
run: mv debian/packages/AdGuardHomeManager_${{ env.VERSION_NAME }}_amd64.deb AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb
- name: Generate .tar.gz package
uses: a7ul/tar-action@v1.1.3
id: compress
with:
command: c
cwd: build/linux/x64/release/bundle
files: |
./data
./lib
./AdGuardHomeManager
outPath: AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: linux
path: |
AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux_amd64.deb
AdGuardHomeManager_${{ env.VERSION_NAME }}_Linux.tar.gz
build-windows:
name: Build Windows installer
runs-on: windows-latest
outputs:
VERSION_NAME: ${{ steps.save_version.outputs.version_name }}
VERSION_NUMBER: ${{ steps.save_version.outputs.version_number }}
steps:
- uses: actions/checkout@v3
- name: Decode .env
shell: pwsh
run: |
[IO.File]::WriteAllBytes('.env', [Convert]::FromBase64String('${{ secrets.ENV }}'))
- name: Read pubspec.yaml
uses: adore-me/read-yaml@v1.0.0
id: read_pubspec
with:
file: './pubspec.yaml'
key-path: '["version"]'
- name: Save version on env variable
shell: bash
id: save_version
run: |
version=${{ steps.read_pubspec.outputs.data }}
IFS='+'
read -r -a split <<< "$version"
echo "VERSION_NAME=$(echo ${split[0]})" >> $GITHUB_ENV
echo "version_name=${split[0]}" >> $GITHUB_OUTPUT
echo "version_number=${split[1]}" >> $GITHUB_OUTPUT
- name: Update version in innosetup config file
shell: pwsh
run: |
(Get-Content windows/innosetup_installer_builder.iss) -replace '<REPLACE_VERSION_ACTIONS>', '${{ env.VERSION_NAME }}' | Out-File -encoding ASCII windows/innosetup_installer_builder.iss
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build windows --release
- name: Build installer witn innosetup
run: iscc /Q windows/innosetup_installer_builder.iss
- name: Move installer file to root directory
run: move build/windows/aghm_installer.exe AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: windows
path: AdGuardHomeManager_${{ env.VERSION_NAME }}_Windows_x64.exe
release-builds-github:
name: Release builds to GitHub
runs-on: ubuntu-latest
needs: [build-android, build-macos, build-linux, build-windows]
env:
VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }}
VERSION_NUMBER: ${{ needs.build-android.outputs.VERSION_NUMBER }}
steps:
- uses: actions/checkout@v3
- name: Create builds directory
run: mkdir releases
- name: Download Android artifacts
uses: actions/download-artifact@v3
with:
name: android
path: releases/
- name: Download macOS artifacts
uses: actions/download-artifact@v3
with:
name: macos
path: releases/
- name: Download Linux artifacts
uses: actions/download-artifact@v3
with:
name: linux
path: releases/
- name: Download Windows artifacts
uses: actions/download-artifact@v3
with:
name: windows
path: releases/
- name: Release to GitHub
uses: ncipollo/release-action@v1
with:
artifacts: "releases/*"
token: ${{ secrets.GH_TOKEN }}
tag: '${{ env.VERSION_NAME }}_(${{ env.VERSION_NUMBER }})'
name: v${{ env.VERSION_NAME }}
draft: true
prerelease: false
commit: ${{ github.sha }}
release-build-google-play:
name: Release Android build to the Google Play Store
runs-on: ubuntu-latest
needs: [build-android, build-macos, build-linux, build-windows]
env:
VERSION_NAME: ${{ needs.build-android.outputs.VERSION_NAME }}
steps:
- uses: actions/checkout@v3
- name: Download Android artifacts
uses: actions/download-artifact@v3
with:
name: android
- name: Release app to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: com.jgeek00.adguard_home_manager
releaseFiles: AdGuardHomeManager_${{ env.VERSION_NAME }}_Android.aab
track: production
status: draft
releaseName: ${{ env.VERSION_NAME }}

6
.gitignore vendored
View file

@ -5,9 +5,11 @@
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.build/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
.swiftpm/
migrate_working_dir/ migrate_working_dir/
# Env # Env
@ -48,4 +50,6 @@ app.*.map.json
/debian/packages /debian/packages
untranslated.json untranslated.json
android/app/.cxx

20
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.formatOnType": false,
"editor.defaultFormatter": "Dart-Code.flutter",
"dart.lineLength": 120,
"[dart]": {
"editor.rulers": [
120
],
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": false,
"editor.formatOnPaste": false,
"editor.formatOnType": false
},
"cSpell.ignorePaths": [
"/pubspec.yaml",
"/.github/workflows"
],
}

View file

@ -40,22 +40,41 @@ Check the privacy policy [here](https://github.com/JGeek00/adguard-home-manager/
This is an unofficial application. The AdGuard Home team and the development of the AdGuard Home software is not related in any way with this application. This is an unofficial application. The AdGuard Home team and the development of the AdGuard Home software is not related in any way with this application.
## Recommended configuration and lists ## Recommended configuration and lists
On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a recommended configuration for AdGuard Home and some lists. Thanks to [JuanRodenas](https://github.com/JuanRodenas). On [this repository](https://github.com/juanico10/Pihole_list) you can find a recommended configuration for AdGuard Home and some lists. Thanks to [juanico10](https://github.com/juanico10).
## Donations
If you like the project and you want to contribute with the development, you can [become a sponsor on GitHub](https://github.com/sponsors/JGeek00), or you can donate using PayPal.
<div align="center">
<a href="https://www.paypal.com/donate/?hosted_button_id=T63UK6AVL3MG8">
<img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" alt="Donate with PayPal" height="100" />
</a>
</div>
## Generate production build ## Generate production build
<ul> <ul>
<li>
<b>Prerequisites</b>
<ol>
<li>Open <code>pubspec.yaml</code> and change the version name and the version number.</li>
<li>Run <code>flutter clean</code>.</li>
<li>Run <code>flutter pub get</code>.</li>
</ol>
</li>
<li>
<b>Android</b>
<ol>
<li>Make sure you have your <code>key.properties</code> file at <code>android/</code>, with all the required values of your signing key correctly set up.</li>
<li>Make sure you have your keystore file at <code>android/app</code>.</li>
<li>Run <code>flutter build apk --release</code> to compile the APK.</li>
<li>The .apk package is located at <code>build/app/outputs/flutter-apk/app-release.apk</code>.</li>
</ol>
</li>
<li> <li>
<b>macOS</b> <b>macOS</b>
<ol> <ol>
<li>flutter clean</li> <li>Run <code>flutter build macos --release</code> to compile the production build.</li>
<li>flutter pub get</li> <li>The .app package is located at <code>build/macos/Build/Products/Release/AdGuard Home Manager.app</code>.</li>
<li>flutter build macos --release</li>
<li>Open macos/Runner.xcworkspace on Xcode</li>
<li>Make sure all the pods have the minimum deployment version at 10.14</li>
<li>Select Runner > Targets Runner</li>
<li>Make sure the Version and Build numbers are correct</li>
<li>Click on Product menu and on Archive</li>
<li>Select the first on the list and click on Distribute app, select Copy App and click on Next</li>
</ol> </ol>
</li> </li>
<li> <li>
@ -67,24 +86,22 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
</ol> </ol>
<b>Build</b> <b>Build</b>
<ol> <ol>
<li>Open debian.yaml file inside debian/ and update the version number</li> <li>Open <code>debian.yaml</code> file inside debian/ and update the version number</li>
<li>run <code>rps build linux</code></li> <li>run <code>rps build linux</code></li>
<li>The .tar.gz is at build/linux/x64/release/bundle</li> <li>The .tar.gz is at <code>build/linux/x64/release/bundle</code></li>
<li>The .deb package is at debian/packages</li> <li>The .deb package is at <code>build/linux/x64/release/debian/</code></li>
</ol> </ol>
</ul> </ul>
</li> </li>
<li> <li>
<b>Windows</b> <b>Windows</b>
<ol> <ol>
<li>flutter clean</li> <li>Run <code>flutter build windows --release</code>.</li>
<li>flutter pub get</li>
<li>flutter build windows</li>
<li>Open Inno Setup Compiler application and load the script</li> <li>Open Inno Setup Compiler application and load the script</li>
<li>The script is located at windows/innosetup_installer_builder.iss</li> <li>The script is located at <code>windows/innosetup_installer_builder.iss</code></li>
<li>Update the version number and save the changes</li> <li>Update the version number and save the changes</li>
<li>Click on the Compile button</li> <li>Click on the Compile button</li>
<li>The installer will be generated at build/windows/aghm_installer.exe</li> <li>The installer will be generated at <code>build/windows/aghm_installer.exe</code>.</li>
</ol> </ol>
</li> </li>
</ul> </ul>
@ -96,7 +113,6 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
- [expandable](https://pub.dev/packages/expandable) - [expandable](https://pub.dev/packages/expandable)
- [package info plus](https://pub.dev/packages/package_info_plus) - [package info plus](https://pub.dev/packages/package_info_plus)
- [flutter phoenix](https://pub.dev/packages/flutter_phoenix) - [flutter phoenix](https://pub.dev/packages/flutter_phoenix)
- [flutter displaymode](https://pub.dev/packages/flutter_displaymode)
- [flutter launcher icons](https://pub.dev/packages/flutter_launcher_icons) - [flutter launcher icons](https://pub.dev/packages/flutter_launcher_icons)
- [flutter native splash](https://pub.dev/packages/flutter_native_splash) - [flutter native splash](https://pub.dev/packages/flutter_native_splash)
- [intl](https://pub.dev/packages/intl) - [intl](https://pub.dev/packages/intl)
@ -104,10 +120,8 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
- [dynamic color](https://pub.dev/packages/dynamic_color) - [dynamic color](https://pub.dev/packages/dynamic_color)
- [device info](https://pub.dev/packages/device_info) - [device info](https://pub.dev/packages/device_info)
- [fl chart](https://pub.dev/packages/fl_chart) - [fl chart](https://pub.dev/packages/fl_chart)
- [flutter web browser](https://pub.dev/packages/flutter_web_browser)
- [flutter svg](https://pub.dev/packages/flutter_svg) - [flutter svg](https://pub.dev/packages/flutter_svg)
- [percent indicator](https://pub.dev/packages/percent_indicator) - [percent indicator](https://pub.dev/packages/percent_indicator)
- [store checker](https://pub.dev/packages/store_checker)
- [flutter markdown](https://pub.dev/packages/flutter_markdown) - [flutter markdown](https://pub.dev/packages/flutter_markdown)
- [markdown](https://pub.dev/packages/markdown) - [markdown](https://pub.dev/packages/markdown)
- [html](https://pub.dev/packages/html) - [html](https://pub.dev/packages/html)
@ -122,6 +136,11 @@ On [this repository](https://github.com/JuanRodenas/Pihole_list) you can find a
- [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list) - [flutter reorderable list](https://pub.dev/packages/flutter_reorderable_list)
- [pie chart](https://pub.dev/packages/pie_chart) - [pie chart](https://pub.dev/packages/pie_chart)
- [segmented button slide](https://pub.dev/packages/segmented_button_slide) - [segmented button slide](https://pub.dev/packages/segmented_button_slide)
- [timezone](https://pub.dev/packages/timezone)
- [url launcher](https://pub.dev/packages/url_launcher)
- [flutter custom tabs](https://pub.dev/packages/flutter_custom_tabs)
- [shared preferences](https://pub.dev/packages/shared_preferences)
- [window manager](https://pub.dev/packages/window_manager)
<br> <br>

View file

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -21,10 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@ -32,8 +29,9 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 33 namespace "com.jgeek00.adguard_home_manager"
ndkVersion flutter.ndkVersion compileSdkVersion 35
ndkVersion "26.1.10909125"
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -49,12 +47,9 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.jgeek00.adguard_home_manager" applicationId "com.jgeek00.adguard_home_manager"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 33 targetSdkVersion 35
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
@ -80,5 +75,5 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" /> <monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon> </adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#ffffff</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View file

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.8.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View file

@ -0,0 +1,4 @@
storePassword= # keystore password #
keyPassword= # keystore key password #
keyAlias= # key alias #
storeFile= # ./keystore-file-name.jks #

View file

@ -1,11 +1,26 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties") includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
def properties = new Properties()
assert localPropertiesFile.exists() repositories {
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk") plugins {
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" id "com.android.application" version '8.6.0' apply false
id "org.jetbrains.kotlin.android" version "1.8.20" apply false
}
include ":app"

2
debian/debian.yaml vendored
View file

@ -5,7 +5,7 @@ flutter_app:
control: control:
Package: AdGuardHomeManager Package: AdGuardHomeManager
Version: <REPLACE_VERSION_NUMBER_ACTIONS> Version: 2.20.1
Architecture: amd64 Architecture: amd64
Essential: no Essential: no
Priority: optional Priority: optional

View file

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>11.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '11.0' # platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

67
ios/Podfile.lock Executable file → Normal file
View file

@ -2,60 +2,53 @@ PODS:
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_custom_tabs_ios (2.0.0):
- Flutter
- flutter_native_splash (0.0.1): - flutter_native_splash (0.0.1):
- Flutter - Flutter
- flutter_web_browser (0.17.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
- Sentry/HybridSDK (8.15.2): - Sentry/HybridSDK (8.18.0):
- SentryPrivate (= 8.15.2) - SentryPrivate (= 8.18.0)
- sentry_flutter (0.0.1): - sentry_flutter (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (= 8.15.2) - Sentry/HybridSDK (= 8.18.0)
- SentryPrivate (8.15.2) - SentryPrivate (8.18.0)
- sqflite (0.0.3): - sqflite (0.0.3):
- Flutter - Flutter
- FMDB (>= 2.7.5) - FlutterMacOS
- sqlite3 (3.44.0): - sqlite3 (3.45.0):
- sqlite3/common (= 3.44.0) - sqlite3/common (= 3.45.0)
- sqlite3/common (3.44.0) - sqlite3/common (3.45.0)
- sqlite3/fts5 (3.44.0): - sqlite3/fts5 (3.45.0):
- sqlite3/common - sqlite3/common
- sqlite3/perf-threadsafe (3.44.0): - sqlite3/perf-threadsafe (3.45.0):
- sqlite3/common - sqlite3/common
- sqlite3/rtree (3.44.0): - sqlite3/rtree (3.45.0):
- sqlite3/common - sqlite3/common
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- sqlite3 (~> 3.44.0) - sqlite3 (~> 3.45.0)
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/perf-threadsafe - sqlite3/perf-threadsafe
- sqlite3/rtree - sqlite3/rtree
- store_checker (0.0.1): - store_checker (0.0.1):
- Flutter - Flutter
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_web_browser (from `.symlinks/plugins/flutter_web_browser/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`) - sqflite (from `.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- store_checker (from `.symlinks/plugins/store_checker/ios`) - store_checker (from `.symlinks/plugins/store_checker/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- FMDB
- Sentry - Sentry
- SentryPrivate - SentryPrivate
- sqlite3 - sqlite3
@ -65,39 +58,35 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_custom_tabs_ios:
:path: ".symlinks/plugins/flutter_custom_tabs_ios/ios"
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_web_browser:
:path: ".symlinks/plugins/flutter_web_browser/ios"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
sentry_flutter: sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios" :path: ".symlinks/plugins/sentry_flutter/ios"
sqflite: sqflite:
:path: ".symlinks/plugins/sqflite/ios" :path: ".symlinks/plugins/sqflite/darwin"
sqlite3_flutter_libs: sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios" :path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
store_checker: store_checker:
:path: ".symlinks/plugins/store_checker/ios" :path: ".symlinks/plugins/store_checker/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_custom_tabs_ios: 62439c843b2691aae516fd50119a01eb9755fff7
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 Sentry: 8984a4ffb2b9bd2894d74fb36e6f5833865bc18e
sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b sentry_flutter: c87a0556eeb6cbf7f9f924d30e878bdedf22d364
SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 SentryPrivate: 2f0c9ba4c3fc993f70eab6ca95673509561e0085
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273 sqlite3: f307b6291c4db7b5086c38d6237446b98a738581
sqlite3_flutter_libs: eb769059df0356dc52ddda040f09cacc9391a7cf sqlite3_flutter_libs: aeb4d37509853dfa79d9b59386a2dac5dd079428
store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d store_checker: 359c5051d9ec30ff0a8fa39eb5ec9df021bb745d
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011
COCOAPODS: 1.14.3 COCOAPODS: 1.14.3

View file

@ -15,6 +15,24 @@ ThemeData lightTheme(ColorScheme? dynamicColorScheme) => ThemeData(
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1), iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(117, 117, 117, 1),
), ),
cardTheme: CardTheme(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
popupMenuTheme: PopupMenuThemeData(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
navigationBarTheme: NavigationBarThemeData(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
dialogTheme: DialogTheme(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
// DISABLE PREDICTIVE BACK GESTURE
// pageTransitionsTheme: const PageTransitionsTheme(
// builders: {
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
// }
// )
); );
ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData( ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
@ -33,6 +51,24 @@ ThemeData darkTheme(ColorScheme? dynamicColorScheme) => ThemeData(
textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), textColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1), iconColor: dynamicColorScheme != null ? dynamicColorScheme.onSurfaceVariant : const Color.fromRGBO(187, 187, 187, 1),
), ),
cardTheme: CardTheme(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
popupMenuTheme: PopupMenuThemeData(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
navigationBarTheme: NavigationBarThemeData(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
dialogTheme: DialogTheme(
surfaceTintColor: dynamicColorScheme?.surfaceTint
),
// DISABLE PREDICTIVE BACK GESTURE
// pageTransitionsTheme: const PageTransitionsTheme(
// builders: {
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
// }
// )
); );
ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData( ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
@ -51,6 +87,12 @@ ThemeData lightThemeOldVersions(MaterialColor primaryColor) => ThemeData(
iconColor: Color.fromRGBO(117, 117, 117, 1), iconColor: Color.fromRGBO(117, 117, 117, 1),
), ),
brightness: Brightness.light, brightness: Brightness.light,
// DISABLE PREDICTIVE BACK GESTURE
// pageTransitionsTheme: const PageTransitionsTheme(
// builders: {
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
// }
// )
); );
ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData( ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
@ -72,4 +114,10 @@ ThemeData darkThemeOldVersions(MaterialColor primaryColor) => ThemeData(
iconColor: Color.fromRGBO(187, 187, 187, 1), iconColor: Color.fromRGBO(187, 187, 187, 1),
), ),
brightness: Brightness.dark, brightness: Brightness.dark,
// DISABLE PREDICTIVE BACK GESTURE
// pageTransitionsTheme: const PageTransitionsTheme(
// builders: {
// TargetPlatform.android: PredictiveBackPageTransitionsBuilder()
// }
// )
); );

View file

@ -1,2 +1,3 @@
enum LoadStatus { loading, loaded, error } enum LoadStatus { loading, loaded, error }
enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime } enum HomeTopItems { queriedDomains, blockedDomains, recurrentClients, topUpstreams, avgUpstreamResponseTime }
enum CustomRulesSorting { topBottom, bottomTop }

View file

@ -0,0 +1,12 @@
class Regexps {
static final wildcardDomain = RegExp(r'^(\*\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,10}$');
static final domain = RegExp(r'^((?:(?:[a-zA-Z]{1})|(?:[a-zA-Z]{1}[a-zA-Z]{1})|(?:[a-zA-Z]{1}[0-9]{1})|(?:[0-9]{1}[a-zA-Z]{1})|(?:[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.)+([a-zA-Z]{2,10}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,10})$');
static final ipv4Address = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
static final ipv6Address = RegExp(r'(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))');
static final subroute = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$');
static final macAddress = RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$');
static final url = RegExp(r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$');
static final certificate = RegExp(r'(-{3,}(\bBEGIN CERTIFICATE\b))|(-{3,}-{3,}(\END CERTIFICATE\b)-{3,})', multiLine: true);
static final privateKey = RegExp(r'(-{3,}(\bBEGIN\b).*(PRIVATE KEY\b))|(-{3,}-{3,}(\bEND\b).*(PRIVATE KEY\b)-{3,})', multiLine: true);
static final path = RegExp(r'^(\/{0,1}(?!\/))[A-Za-z0-9\/\-_]+(\.([a-zA-Z]+))?$');
}

View file

@ -7,4 +7,6 @@ class Urls {
static const String adGuardHomeReleasesTags = "https://api.github.com/repos/AdGuardTeam/AdGuardHome/releases/tags"; static const String adGuardHomeReleasesTags = "https://api.github.com/repos/AdGuardTeam/AdGuardHome/releases/tags";
static const String googleSearchUrl = "https://www.google.com/search"; static const String googleSearchUrl = "https://www.google.com/search";
static const String connectionInstructions = "https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection"; static const String connectionInstructions = "https://github.com/JGeek00/adguard-home-manager/wiki/Create-a-connection";
static const String appDetailsWebpage = "https://apps.jgeek00.com/jlfed8mcgyz6laf";
static const String jgeek00AppsWebpage = "https://apps.jgeek00.com";
} }

View file

@ -1,61 +0,0 @@
import 'dart:io';
import 'package:store_checker/store_checker.dart';
import 'package:adguard_home_manager/functions/compare_versions.dart';
import 'package:adguard_home_manager/services/external_requests.dart';
import 'package:adguard_home_manager/models/github_release.dart';
Future<GitHubRelease?> checkAppUpdates({
required String currentBuildNumber,
required void Function(GitHubRelease?) setUpdateAvailable,
required Source installationSource,
required bool isBeta
}) async {
var result = isBeta
? await ExternalRequests.getReleasesGitHub()
: await ExternalRequests.getReleaseData();
if (result.successful == true) {
late GitHubRelease gitHubRelease;
if (isBeta) {
gitHubRelease = (result.content as List<GitHubRelease>).firstWhere((r) => r.prerelease == true);
}
else {
gitHubRelease = result.content as GitHubRelease;
}
final update = gitHubUpdateExists(
currentBuildNumber: currentBuildNumber,
gitHubRelease: gitHubRelease,
isBeta: isBeta
);
print(update);
if (update == true) {
setUpdateAvailable(gitHubRelease);
if (Platform.isAndroid) {
if (
installationSource == Source.IS_INSTALLED_FROM_LOCAL_SOURCE ||
installationSource == Source.IS_INSTALLED_FROM_PLAY_PACKAGE_INSTALLER ||
installationSource == Source.UNKNOWN
) {
return gitHubRelease;
}
else {
return null;
}
}
else if (Platform.isIOS) {
return null;
}
else {
return gitHubRelease;
}
}
else {
setUpdateAvailable(null);
}
}
return null;
}

10
lib/functions/is_ip.dart Normal file
View file

@ -0,0 +1,10 @@
import 'package:adguard_home_manager/constants/regexps.dart';
bool isIpAddress(String value) {
if (Regexps.ipv4Address.hasMatch(value) || Regexps.ipv6Address.hasMatch(value)) {
return true;
}
else {
return false;
}
}

View file

@ -1,30 +1,43 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter_web_browser/flutter_web_browser.dart'; import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as flutter_custom_tabs;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart' as url_launcher;
import 'package:sentry_flutter/sentry_flutter.dart';
void openUrl(String url) async { void openUrl(String url) async {
if (!(url.startsWith("http") || url.startsWith("https"))) {
try {
url_launcher.launchUrl(Uri.parse(url));
} catch (e, stackTrace) {
Sentry.captureException(e, stackTrace: stackTrace);
}
return;
}
if (Platform.isAndroid || Platform.isIOS) { if (Platform.isAndroid || Platform.isIOS) {
FlutterWebBrowser.openWebPage( try {
url: url, await flutter_custom_tabs.launchUrl(
customTabsOptions: const CustomTabsOptions( Uri.parse(url),
instantAppsEnabled: true, customTabsOptions: const flutter_custom_tabs.CustomTabsOptions(
showTitle: true, shareState: flutter_custom_tabs.CustomTabsShareState.browserDefault,
urlBarHidingEnabled: false, urlBarHidingEnabled: true,
), showTitle: true,
safariVCOptions: const SafariViewControllerOptions( ),
barCollapsingEnabled: true, safariVCOptions: const flutter_custom_tabs.SafariViewControllerOptions(
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, barCollapsingEnabled: true,
modalPresentationCapturesStatusBarAppearance: true, dismissButtonStyle: flutter_custom_tabs.SafariViewControllerDismissButtonStyle.close,
) ),
); );
} catch (e, stackTrace) {
url_launcher.launchUrl(Uri.parse(url));
Sentry.captureException(e, stackTrace: stackTrace);
}
} }
else { else {
final uri = Uri.parse(url); try {
if (await canLaunchUrl(uri)) { url_launcher.launchUrl(Uri.parse(url));
await launchUrl(uri); } catch (e, stackTrace) {
} else { Sentry.captureException(e, stackTrace: stackTrace);
throw 'Could not launch $url';
} }
} }
} }

View file

@ -5,14 +5,16 @@ import 'package:flutter/material.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/config/globals.dart'; import 'package:adguard_home_manager/config/globals.dart';
void showSnacbkar({ void showSnackbar({
required AppConfigProvider appConfigProvider, required AppConfigProvider appConfigProvider,
required String label, required String label,
required Color color, required Color color,
Color? labelColor Color? labelColor,
GlobalKey<ScaffoldMessengerState>? key,
}) async { }) async {
final GlobalKey<ScaffoldMessengerState> scaffoldKey = key ?? scaffoldMessengerKey;
if (appConfigProvider.showingSnackbar == true) { if (appConfigProvider.showingSnackbar == true) {
scaffoldMessengerKey.currentState?.clearSnackBars(); scaffoldKey.currentState?.clearSnackBars();
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
} }
appConfigProvider.setShowingSnackbar(true); appConfigProvider.setShowingSnackbar(true);
@ -26,7 +28,7 @@ void showSnacbkar({
), ),
backgroundColor: color, backgroundColor: color,
); );
scaffoldMessengerKey.currentState?.showSnackBar(snackBar).closed.then( scaffoldKey.currentState?.showSnackBar(snackBar).closed.then(
(value) => appConfigProvider.setShowingSnackbar(false) (value) => appConfigProvider.setShowingSnackbar(false)
); );
} }

View file

@ -24,7 +24,7 @@
"invalidUsernamePassword": "Invalid username or password", "invalidUsernamePassword": "Invalid username or password",
"tooManyAttempts": "Too many attempts. Try again later.", "tooManyAttempts": "Too many attempts. Try again later.",
"cantReachServer": "Can't reach server. Check connection data.", "cantReachServer": "Can't reach server. Check connection data.",
"sslError": "SSL error. Go to Settings > Advanced settings and enable Override SSL validation.", "sslError": "Handshake exception. Cannot establish a secure connection with the server. This can be a SSL error. Go to Settings > Advanced settings and enable Override SSL validation.",
"unknownError": "Unknown error", "unknownError": "Unknown error",
"connectionNotCreated": "Connection couldn't be created", "connectionNotCreated": "Connection couldn't be created",
"connecting": "Connecting...", "connecting": "Connecting...",
@ -719,11 +719,92 @@
"unblockClient": "Unblock client", "unblockClient": "Unblock client",
"blockingClient": "Blocking client...", "blockingClient": "Blocking client...",
"unblockingClient": "Unblocking client...", "unblockingClient": "Unblocking client...",
"upstreamDnsCacheConfiguration": "Configuración de la caché DNS upstream", "upstreamDnsCacheConfiguration": "DNS upstream cache configuration",
"enableDnsCachingClient": "Enable DNS caching for this client", "enableDnsCachingClient": "Enable DNS caching for this client",
"dnsCacheSize": "DNS cache size", "dnsCacheSize": "DNS cache size",
"nameInvalid": "Name is required", "nameInvalid": "Name is required",
"oneIdentifierRequired": "At least one identifier is required", "oneIdentifierRequired": "At least one identifier is required",
"dnsCacheNumber": "DNS cache size must be a number", "dnsCacheNumber": "DNS cache size must be a number",
"errors": "Errors" "errors": "Errors",
"redirectHttpsWarning": "If you have enabled \"Redirect to HTTPS automatically\" on your AdGuard Home server, you must select an HTTPS connection and use the HTTPS port of your server.",
"logsSettingsDescription": "Configure query logs",
"ignoredDomains": "Ignored domains",
"noIgnoredDomainsAdded": "No domains to ignore added",
"pauseServiceBlocking": "Pause service blocking",
"newSchedule": "New schedule",
"editSchedule": "Edit schedule",
"timezone": "Timezone",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"from": "From",
"to": "To",
"selectStartTime": "Select start time",
"selectEndTime": "Select end time",
"startTimeBeforeEndTime": "Start time must be before end time.",
"noBlockingScheduleThisDevice": "There's no blocking schedule for this device.",
"selectTimezone": "Select a timezone",
"selectClientsFiltersInfo": "Select the clients you want to display. If no clients are selected, all will be displayed.",
"noDataThisSection": "There's no data for this section.",
"statisticsSettings": "Statistics settings",
"statisticsSettingsDescription": "Configure data collection for statistics",
"loadingStatisticsSettings": "Loading statistics settings...",
"statisticsSettingsLoadError": "An error occured when loading statistics settings.",
"statisticsConfigUpdated": "Statistics settings updated successfully",
"statisticsConfigNotUpdated": "Statistics settings couldn't be updated",
"customTimeInHours": "Custom time (in hours)",
"invalidTime": "Invalid time",
"removeDomain": "Remove domain",
"addDomain": "Add domain",
"notLess1Hour": "Time cannot be less than 1 hour",
"rateLimit": "Rate limit",
"subnetPrefixLengthIpv4": "Subnet prefix length for IPv4",
"subnetPrefixLengthIpv6": "Subnet prefix length for IPv6",
"rateLimitAllowlist": "Rate limit allowlist",
"rateLimitAllowlistDescription": "IP addresses excluded from rate limiting",
"dnsOptions": "DNS options",
"editor": "Editor",
"editCustomRules": "Edit custom rules",
"savingCustomRules": "Saving custom rules...",
"customRulesUpdatedSuccessfully": "Custom rules updated successfully",
"customRulesNotUpdated": "Custom rules could not be updated",
"reorder": "Reorder",
"showHide": "Show/hide",
"noElementsReorderMessage": "Enable some elements on the show/hide tab to reorder them here.",
"enablePlainDns": "Enable plain DNS",
"enablePlainDnsDescription": "Plain DNS is enabled by default. You can disable it to force all devices to use encrypted DNS. To do this, you must enable at least one encrypted DNS protocol.",
"date": "Date",
"loadingChangelog": "Loading changelog...",
"invalidIpOrUrl": "Invalid IP address or URL",
"addPersistentClient": "Add as a persistent client",
"blockThisClientOnly": "Block for this client only",
"unblockThisClientOnly": "Unblock for this client only",
"domainBlockedThisClient": "{domain} blocked for this client",
"domainUnblockedThisClient": "{domain} unblocked for this client",
"disallowThisClient": "Disallow this client",
"allowThisClient": "Allow this client",
"clientAllowedSuccessfully": "Client allowed successfully",
"clientDisallowedSuccessfully": "Client disallowed successfully",
"changesNotSaved": "Changes could not be saved",
"allowingClient": "Allowing client...",
"disallowingClient": "Disallowing client...",
"clientIpCopied": "Client IP copied to the clipboard",
"clientNameCopied": "Client name copied to the clipboard",
"dnsServerAddressCopied": "DNS server address copied to the clipboard",
"select": "Select",
"liveLogs": "Live logs",
"hereWillAppearRealtimeLogs": "Here there will appear the logs on realtime.",
"applicationDetails": "Application details",
"applicationDetailsDescription": "App repository, stores where it's available, and more",
"myOtherApps": "My other apps",
"myOtherAppsDescription": "Check my other apps, make a donation, contact support, and more",
"topToBottom": "From top to bottom",
"bottomToTop": "From bottom to top",
"upstreamTimeout": "Upstream timeout",
"upstreamTimeoutHelper": "Specifies the number of seconds to wait for a response from the upstream server",
"fieldCannotBeEmpty": "This field cannot be empty"
} }

View file

@ -24,7 +24,7 @@
"invalidUsernamePassword": "Usuario o contraseña no válidos.", "invalidUsernamePassword": "Usuario o contraseña no válidos.",
"tooManyAttempts": "Demasiados intentos. Prueba de nuevo más tarde.", "tooManyAttempts": "Demasiados intentos. Prueba de nuevo más tarde.",
"cantReachServer": "No se puede alcanzar el servidor. Comprueba los datos de conexión.", "cantReachServer": "No se puede alcanzar el servidor. Comprueba los datos de conexión.",
"sslError": "Error de SSL. Ve a Ajustes > Ajustes avanzados y activa No comprobar SSL.", "sslError": "Handshake exception. No se ha podido establecer una conexión segura con el servidor. Es posible que sea un error de SSL. Ve a Ajustes > Ajustes avanzados y activa No comprobar SSL.",
"unknownError": "Error desconocido", "unknownError": "Error desconocido",
"connectionNotCreated": "No se pudo crear la conexión", "connectionNotCreated": "No se pudo crear la conexión",
"connecting": "Conectando...", "connecting": "Conectando...",
@ -403,7 +403,7 @@
"dnsRewriteRuleDeleted": "Reescritura DNS eliminada correctamente", "dnsRewriteRuleDeleted": "Reescritura DNS eliminada correctamente",
"dnsRewriteRuleNotDeleted": "La reescritura DNS no pudo ser eliminada", "dnsRewriteRuleNotDeleted": "La reescritura DNS no pudo ser eliminada",
"addDnsRewrite": "Añadir reescritura DNS", "addDnsRewrite": "Añadir reescritura DNS",
"addingRewrite": "Añadiend reescritura...", "addingRewrite": "Añadiendo reescritura...",
"dnsRewriteRuleAdded": "Regla de reescritura DNS añadida correctamente", "dnsRewriteRuleAdded": "Regla de reescritura DNS añadida correctamente",
"dnsRewriteRuleNotAdded": "La regla de reescritura DNS no ha podido ser añadida", "dnsRewriteRuleNotAdded": "La regla de reescritura DNS no ha podido ser añadida",
"logsSettings": "Ajustes de registros", "logsSettings": "Ajustes de registros",
@ -725,5 +725,86 @@
"nameInvalid": "Se requiere un nombre", "nameInvalid": "Se requiere un nombre",
"oneIdentifierRequired": "Se require al menos un identificador", "oneIdentifierRequired": "Se require al menos un identificador",
"dnsCacheNumber": "El tamaño de caché de DNS debe ser un número", "dnsCacheNumber": "El tamaño de caché de DNS debe ser un número",
"errors": "Errores" "errors": "Errores",
"redirectHttpsWarning": "Si tienes activado \"Redireccionar a HTTPS automáticamente\" en tu servidor AdGuard Home, debes seleccionar una conexión HTTPS y utilizar el puerto de HTTPS de tu servidor.",
"logsSettingsDescription": "Configura los registros de peticiones",
"ignoredDomains": "Dominios ignorados",
"noIgnoredDomainsAdded": "No hay añadidos dominios para ignorar",
"pauseServiceBlocking": "Pausa del servicio de bloqueo",
"newSchedule": "Nueva programación",
"editSchedule": "Editar programación",
"timezone": "Zona horaria",
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo",
"from": "Desde",
"to": "Hasta",
"selectStartTime": "Seleccionar hora de inicio",
"selectEndTime": "Seleccionar hora de fin",
"startTimeBeforeEndTime": "La hora de inicio debe ser anterior a la hora de fin.",
"noBlockingScheduleThisDevice": "No hay programación de bloqueo para este dispositivo.",
"selectTimezone": "Selecciona una zona horaria",
"selectClientsFiltersInfo": "Selecciona los clientes que quieres mostrar. Si no hay clientes seleccionados, se mostrarán todos.",
"noDataThisSection": "No hay datos para esta sección.",
"statisticsSettings": "Ajustes de estadísticas",
"statisticsSettingsDescription": "Configura la recolección de datos para estadísticas",
"loadingStatisticsSettings": "Cargando ajustes de estadísticas...",
"statisticsSettingsLoadError": "Ocurrió un error al cargar los ajustes de estadísticas.",
"statisticsConfigNotUpdated": "La configuración de estadísticas no pudo ser actualizada.",
"statisticsConfigUpdated": "Configuración de estadísticas actualizada correctamente.",
"customTimeInHours": "Tiempo personalizado (en horas)",
"invalidTime": "Tiempo no válido",
"removeDomain": "Eliminar dominio",
"addDomain": "Añadir dominio",
"notLess1Hour": "El tiempo no puede ser inferior a 1 hora",
"rateLimit": "Limitación de velocidad",
"subnetPrefixLengthIpv4": "Longitud del prefijo de subred para IPv4",
"subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6",
"rateLimitAllowlist": "Lista de permitidos de limitación de velocidad",
"rateLimitAllowlistDescription": "Direcciones IP excluidas de la limitación de velocidad",
"dnsOptions": "Opciones de DNS",
"editor": "Editor",
"editCustomRules": "Editar reglas personalizadas",
"savingCustomRules": "Guardando reglas personalizadas...",
"customRulesUpdatedSuccessfully": "Reglas personalizadas actualizadas correctamente",
"customRulesNotUpdated": "Las reglas personalizadas no pudieron ser actualizadas",
"reorder": "Reordenar",
"showHide": "Mostrar/ocultar",
"noElementsReorderMessage": "Activa algunos elementos en la pestaña de mostrar/ocultar para reordenarlos aquí.",
"enablePlainDns": "Activar DNS simple (sin cifrado)",
"enablePlainDnsDescription": "El DNS simple (sin cifrado) está activado de forma predeterminada. Puedes desactivarlo para obligar a todos los dispositivos a utilizar DNS cifrado. Para ello, debes habilitar al menos un protocolo DNS cifrado.",
"date": "Fecha",
"loadingChangelog": "Cargando registro de cambios...",
"invalidIpOrUrl": "Dirección IP o URL no válida",
"addPersistentClient": "Añadir como cliente persistente",
"blockThisClientOnly": "Bloquear sólo para este cliente",
"unblockThisClientOnly": "Desbloquear sólo para este cliente",
"domainBlockedThisClient": "{domain} bloqueado para este cliente",
"domainUnblockedThisClient": "{domain} desbloqueado para este cliente",
"disallowThisClient": "No permitir este cliente",
"allowThisClient": "Permitir este cliente",
"clientAllowedSuccessfully": "Cliente permitido correctamente",
"clientDisallowedSuccessfully": "Cliente no permitido correctamente",
"changesNotSaved": "Los cambios no han podido ser guardados",
"allowingClient": "Permitiendo cliente...",
"disallowingClient": "No permitiendo cliente...",
"clientIpCopied": "Dirección IP del cliente copiada al portapapeles",
"clientNameCopied": "Nombre del cliente copiado al portapapeles",
"dnsServerAddressCopied": "Dirección del servidor DNS copiada al portapapeles",
"select": "Seleccionar",
"liveLogs": "Registros en directo",
"hereWillAppearRealtimeLogs": "Aquí aparecerán los registros en tiempo real.",
"applicationDetails": "Detalles de la aplicación",
"applicationDetailsDescription": "Repositorio de la app, tiendas donde está disponible, y más",
"myOtherApps": "Mis otras apps",
"myOtherAppsDescription": "Comprueba mis otras apps, hacer una donación, contactar al soporte, y más",
"topToBottom": "Desde arriba hacia abajo",
"bottomToTop": "Desde abajo hacia arriba",
"upstreamTimeout": "Tiempo de espera del upstream",
"upstreamTimeoutHelper": "Especifica el número de segundos que se debe esperar para recibir una respuesta del servidor upstream",
"fieldCannotBeEmpty": "El campo no puede estar vacío"
} }

730
lib/l10n/app_ru.arb Normal file
View file

@ -0,0 +1,730 @@
{
"home": "Главная",
"settings": "Настройки",
"connect": "Подключиться",
"servers": "Серверы",
"createConnection": "Создать подключение",
"editConnection": "Edit connection",
"name": "Имя",
"ipDomain": "IP-адрес или домен",
"path": "Путь",
"port": "Порт",
"username": "Логин",
"password": "Пароль",
"defaultServer": "Сервер по умолчанию",
"general": "Основное",
"connection": "Тип подключения",
"authentication": "Аутентификация",
"other": "Прочее",
"invalidPort": "Неверный порт",
"invalidPath": "Неверный путь",
"invalidIpDomain": "Неверный IP-адрес или домен",
"ipDomainNotEmpty": "IP-адрес или домен не могут быть пустыми",
"nameNotEmpty": "Имя не может быть пустым",
"invalidUsernamePassword": "Неверный логин или пароль",
"tooManyAttempts": "Слишком много попыток. Попробуйте позднее.",
"cantReachServer": "Не удаётся установить соединение с сервером. Проверьте настройки подключения.",
"sslError": "Ошибка SSL. Перейдите в «Настройки» > «Дополнительные настройки» и активируйте «Не проверять SSL-сертификат».",
"unknownError": "Неизвестная ошибка",
"connectionNotCreated": "Не удалось создать подключение",
"connecting": "Подключение...",
"connected": "Подключено",
"selectedDisconnected": "Выбран, но отключён",
"connectionDefaultSuccessfully": "Подключение успешно установлено как «подключение по умолчанию».",
"connectionDefaultFailed": "Не удалось установить «подключением по умолчанию».",
"noSavedConnections": "Нет сохранённых подключений",
"cannotConnect": "Не удается подключиться к серверу",
"connectionRemoved": "Подключение удалено успешно",
"connectionCannotBeRemoved": "Подключение не может быть удалено.",
"remove": "Удалить",
"removeWarning": "Вы уверены, что хотите удалить соединение с этим сервером AdGuard Home?",
"cancel": "Отмена",
"defaultConnection": "Подключение по умолчанию",
"setDefault": "Подключаться по умолчанию",
"edit": "Редактировать",
"delete": "Удалить",
"save": "Сохранить",
"serverStatus": "Статус сервера",
"connectionNotUpdated": "Соединение не было обновлено",
"ruleFilteringWidget": "Правила фильтрации",
"safeBrowsingWidget": "Безопасная навигация",
"parentalFilteringWidget": "Родительский контроль",
"safeSearchWidget": "Безопасный поиск",
"ruleFiltering": "Правила фильтрации",
"safeBrowsing": "Безопасная\nнавигация",
"parentalFiltering": "Родительский\nконтроль",
"safeSearch": "Безопасный поиск",
"serverStatusNotRefreshed": "Не удалось обновить статус сервера",
"loadingStatus": "Загрузка...",
"errorLoadServerStatus": "Не удалось получить статус сервера",
"topQueriedDomains": "Часто запрашиваемые\nдомены",
"viewMore": "Показать больше",
"topClients": "Частые клиенты",
"topBlockedDomains": "Часто блокируемые домены",
"appSettings": "Настройки приложения",
"theme": "Тема",
"light": "Светлая",
"dark": "Тёмная",
"systemDefined": "Системная тема",
"close": "Закрыть",
"connectedTo": "Подключено к:",
"selectedServer": "Выбранный сервер:",
"noServerSelected": "Нет выбранных серверов",
"manageServer": "Управление сервером",
"allProtections": "Защита",
"userNotEmpty": "Логин не может быть пустым",
"passwordNotEmpty": "Пароль не может быть пустым",
"examplePath": "Например: /adguard",
"helperPath": "Если используется реверсивный прокси",
"aboutApp": "О приложении",
"appVersion": "Версия приложения",
"createdBy": "Автор",
"clients": "Клиенты",
"allowed": "Обработан",
"blocked": "Заблокировано",
"noClientsList": "Список клиентов пуст",
"activeClients": "Активные",
"removeClient": "Удалить запись",
"removeClientMessage": "Вы уверены, что хотите удалить данную запись из списка?",
"confirm": "Ок",
"removingClient": "Удаление клиента...",
"clientNotRemoved": "Клиент не может быть удалён из списка",
"addClient": "Добавить клиента",
"list": "Список",
"ipAddress": "IP-адреса",
"ipNotValid": "Недопустимый IP-адрес",
"clientAddedSuccessfully": "Запись успешно добавлена в список",
"addingClient": "Добавление клиента...",
"clientNotAdded": "Клиент не может быть внесён в список",
"clientAnotherList": "Данный клиент уже занесён в один из списков",
"noSavedLogs": "Нет сохранённых журналов",
"logs": "Журнал",
"copyLogsClipboard": "Скопировать журнал в буфер обмена",
"logsCopiedClipboard": "Журнал скопирован в буфер обмена",
"advancedSettings": "Дополнительные настройки",
"dontCheckCertificate": "Не проверять SSL-сертификат",
"dontCheckCertificateDescription": "Переопределяет проверку SSL-сертификата сервера",
"advancedSetupDescription": "Расширенные параметры",
"settingsUpdatedSuccessfully": "Настройки успешно обновлены.",
"cannotUpdateSettings": "Не удалось обновить настройки.",
"restartAppTakeEffect": "Перезапустите приложение",
"loadingLogs": "Загрузка журнала...",
"logsNotLoaded": "Не удалось загрузить журнал",
"processed": "Обработан\nБез списка",
"processedRow": "Обработан",
"blockedBlacklist": "Заблокирован\nЧёрный список",
"blockedBlacklistRow": "Заблокирован Чёрным списком",
"blockedSafeBrowsing": "Заблокирован\nБезопасная навигация",
"blockedSafeBrowsingRow": "Заблокировано Безопасной навигацией",
"blockedParental": "Заблокирован\nРодительский контроль",
"blockedParentalRow": "Заблокировано Родительским контролем",
"blockedInvalid": "Заблокировано\nНеверный",
"blockedInvalidRow": "Заблокирован (Неверный)",
"blockedSafeSearch": "Заблокирован\nБезопасный поиск",
"blockedSafeSearchRow": "Заблокировано Безопасным поиском",
"blockedService": "Заблокирован\nЗаблокированный сервис",
"blockedServiceRow": "Заблокирован (заблокированный сервис)",
"processedWhitelist": "Разрешён\nБелый список",
"processedWhitelistRow": "Разрешён Белым списком",
"processedError": "Обработан\nОшибка",
"processedErrorRow": "Обработан (ошибка)",
"rewrite": "Переписан",
"status": "Статус",
"result": "Результат",
"time": "Время",
"blocklist": "Блок-лист",
"request": "Запрос",
"domain": "Хост",
"type": "Тип",
"clas": "Класс",
"response": "Ответ",
"dnsServer": "DNS-сервер",
"elapsedTime": "Затрачено",
"responseCode": "Код ответа",
"client": "Клиент",
"deviceIp": "IP-адрес",
"deviceName": "Имя устройства",
"logDetails": "Детали запроса",
"blockingRule": "Правило блокировки",
"blockDomain": "Заблокировать домен",
"couldntGetFilteringStatus": "Не удалось получить журнал фильтрации",
"unblockDomain": "Разблокировать домен",
"userFilteringRulesNotUpdated": "Не удалось обновить пользовательские правила фильтрации",
"userFilteringRulesUpdated": "Пользовательские правила фильтрации успешно обновлены",
"savingUserFilters": "Сохранение пользовательских правил фильтрации...",
"filters": "Фильтры",
"logsOlderThan": "Записи журнала старше, чем",
"responseStatus": "Статус ответа",
"selectTime": "Выберите время",
"notSelected": "Не выбрано",
"resetFilters": "Сбросить фильтры",
"noLogsDisplay": "Нет записей в журнале",
"noLogsThatOld": "Возможно, что за это выбранное время записи журнала не сохранены. Попробуйте выбрать более позднее время.",
"apply": "Применить",
"selectAll": "Отметить все",
"unselectAll": "Отменить выбор всех",
"all": "Все",
"filtered": "Отфильтрованные",
"checkAppLogs": "Проверьте журналы приложения",
"refresh": "Обновить",
"search": "Поиск",
"dnsQueries": "DNS-запросы",
"average": "Среднее",
"blockedFilters": "Заблокировано\nфильтрами",
"malwarePhishingBlocked": "Заблокированные\nвредоносные и\nфишинговые сайты",
"blockedAdultWebsites": "Заблокированные\n«взрослые» сайты",
"generalSettings": "Основные настройки",
"generalSettingsDescription": "Различные настройки",
"hideZeroValues": "Скрывать нулевые значения",
"hideZeroValuesDescription": "Скрывать блоки с нулевыми значениями на домашнем экране",
"webAdminPanel": "Веб-панель администрирования",
"visitGooglePlay": "Посетить страницу в Google Play",
"gitHub": "Исходный код приложения доступен на GitHub",
"blockClient": "Заблокировать клиента",
"selectTags": "Выбрать теги клиента",
"noTagsSelected": "Нет выбранных тегов",
"tags": "Теги",
"identifiers": "Идентификаторы",
"identifier": "Идентификатор",
"identifierHelper": "IP-адрес, CIDR, MAC или ClientID",
"noIdentifiers": "Идентификаторы не добавлены",
"useGlobalSettings": "Глобальные настройки",
"enableFiltering": "Включить фильтрацию",
"enableSafeBrowsing": "Безопасная навигация",
"enableParentalControl": "Родительский контроль",
"enableSafeSearch": "Безопасный поиск",
"blockedServices": "Заблокированные сервисы",
"selectBlockedServices": "Заблокированные сервисы",
"noBlockedServicesSelected": "Нет заблокированных сервисов",
"services": "Сервисы",
"servicesBlocked": "в блокировке",
"tagsSelected": "выбрано",
"upstreamServers": "Upstream DNS-серверы",
"serverAddress": "Адрес сервера",
"noUpstreamServers": "Нет upstream DNS-серверов.",
"willBeUsedGeneralServers": "Будут использоваться общие upstream DNS-сервера.",
"added": "Сохранённые",
"clientUpdatedSuccessfully": "Настройки клиента успешно обновлены",
"clientNotUpdated": "Не удалось обновить настройки клиента",
"clientDeletedSuccessfully": "Клиент успешно удалён",
"clientNotDeleted": "Не удалось удалить клиента",
"options": "Параметры",
"loadingFilters": "Загрузка фильтров...",
"filtersNotLoaded": "Не удалось загрузить фильтры.",
"whitelists": "Белые списки DNS",
"blacklists": "Чёрные списки DNS",
"rules": "Количество правил",
"customRules": "Пользовательские правила фильтрации",
"enabledRules": "Активных правил",
"enabled": "Включён",
"disabled": "Отключён",
"rule": "Правило",
"addCustomRule": "Добавить пользовательское правило фильтрации",
"removeCustomRule": "Удалить пользовательское правило фильтрации",
"removeCustomRuleMessage": "Вы уверены, что хотите удалить данное пользовательское правило фильтрации?",
"updatingRules": "Обновление пользовательских правил фильтрации...",
"ruleRemovedSuccessfully": "Правило успешно удалено",
"ruleNotRemoved": "Не удаётся удалить данное правило",
"ruleAddedSuccessfully": "Правило успешно добавлено",
"ruleNotAdded": "Не удаётся добавить правило",
"noCustomFilters": "Нет пользовательских правил фильтрации",
"noBlockedClients": "Нет запрещённых клиентов",
"noBlackLists": "Нет чёрных списков",
"noWhiteLists": "Нет белых списков",
"addWhitelist": "Добавить белый список",
"addBlacklist": "Добавить чёрный список",
"urlNotValid": "Неверный URL или абсолютный путь",
"urlAbsolutePath": "URL-адрес или абсолютный путь",
"addingList": "Добавление списка...",
"listAdded": "Список успешно добавлен. Добавлены:",
"listAlreadyAdded": "Список уже был добавлен",
"listUrlInvalid": "Неверный URL-адрес списка",
"listNotAdded": "Не удаётся добавить список",
"listDetails": "Параметры списка",
"listType": "Тип",
"whitelist": "Белый список",
"blacklist": "Чёрный список",
"latestUpdate": "Последнее обновление",
"disable": "Отключить",
"enable": "Включить",
"currentStatus": "Текущий статус",
"listDataUpdated": "Параметры списка успешно обновлены",
"listDataNotUpdated": "Не удалось обновить параметры списка",
"updatingListData": "Обновление параметров списка...",
"editWhitelist": "Редактировать белый список",
"editBlacklist": "Редактировать чёрный список",
"deletingList": "Удаление списка...",
"listDeleted": "Список успешно удалён",
"listNotDeleted": "Не удалось удалить список",
"deleteList": "Удалить список",
"deleteListMessage": "Вы уверены, что хотите удалить этот список? Это действие нельзя отменить.",
"serverSettings": "Настройки сервера",
"serverInformation": "Информация о сервере",
"serverInformationDescription": "Информация о сервере и текущий статус",
"loadingServerInfo": "Загрузка информации о сервере...",
"serverInfoNotLoaded": "Не удалось загрузить информацию о сервере.",
"dnsAddresses": "DNS-адреса",
"seeDnsAddresses": "Посмотреть DNS-адреса",
"dnsPort": "DNS-порт",
"httpPort": "HTTP-порт",
"protectionEnabled": "Защита активна",
"dhcpAvailable": "DHCP доступен",
"serverRunning": "Сервер запущен",
"serverVersion": "Версия сервера",
"serverLanguage": "Язык сервера",
"yes": "Да",
"no": "Нет",
"allowedClients": "Разрешённые клиенты",
"disallowedClients": "Запрещённые клиенты",
"disallowedDomains": "Неразрешённые домены",
"accessSettings": "Настройки доступа",
"accessSettingsDescription": "Настройка правил доступа к серверу",
"loadingClients": "Загрузка клиентов...",
"clientsNotLoaded": "Не удалось загрузить список клиентов.",
"noAllowedClients": "Нет разрешённых клиентов",
"allowedClientsDescription": "Если в списке есть записи, AdGuard Home будет принимать запросы только от этих клиентов.",
"blockedClientsDescription": "Если в списке есть записи, AdGuard Home будет игнорировать запросы от этих клиентов. Это поле игнорируется, если список разрешённых клиентов содержит записи.",
"disallowedDomainsDescription": "AdGuard Home будет игнорировать DNS-запросы с этими доменами. Такие DNS-запросы не будут отображаться в журнале.",
"addClientFieldDescription": "CIDR, IP-адрес или ClientID",
"clientIdentifier": "Идентификатор клиента",
"allowClient": "Разрешить клиент",
"disallowClient": "Запретить клиента",
"noDisallowedDomains": "Нет запрещенных доменов",
"domainNotAdded": "Не удалось добавить домен",
"statusSelected": "выбран статус",
"updateLists": "Проверить обновления",
"checkHostFiltered": "Проверить хост",
"updatingLists": "Обновление списков...",
"listsUpdated": "списки обновлены",
"listsNotUpdated": "Не удалось обновить списки",
"listsNotLoaded": "Не удалось загрузить списки",
"domainNotValid": "Недействительный домен",
"check": "Проверить",
"checkingHost": "Проверка хоста...",
"errorCheckingHost": "Не удалось проверить хост",
"block": "Запретить",
"unblock": "Разрешить",
"custom": "Своё правило",
"addImportant": "Добавить $important",
"howCreateRules": "Как создать пользовательские правила",
"examples": "Примеры",
"example1": "Заблокировать доступ к домену example.org и всем его поддоменам.",
"example2": "Разблокировать доступ к домену example.org и всем его поддоменам.",
"example3": "Добавлять комментарий.",
"example4": "Блокировать доступ к доменам, соответствующим заданному регулярному выражению.",
"moreInformation": "Больше информации",
"addingRule": "Добавление правила...",
"deletingRule": "Удаление правила...",
"enablingList": "Включение списка...",
"disablingList": "Отключение списка...",
"disableFiltering": "Отключить фильтрацию",
"savingList": "Сохранение списка...",
"enablingFiltering": "Включение фильтрации...",
"disablingFiltering": "Отключение фильтрации...",
"filteringStatusUpdated": "Статус фильтрации успешно обновлен",
"filteringStatusNotUpdated": "Не удалось обновить статус фильтрации",
"updateFrequency": "Частота обновления",
"never": "Никогда",
"hour1": "1 час",
"hours12": "12 часов",
"hours24": "24 часа",
"days3": "3 дня",
"days7": "7 дней",
"changingUpdateFrequency": "Обновление...",
"updateFrequencyChanged": "Частота обновления успешно изменена",
"updateFrequencyNotChanged": "Не удаётся изменить частоту обновления",
"updating": "Обновление значений...",
"blockedServicesUpdated": "Заблокированные сервисы успешно обновлены",
"blockedServicesNotUpdated": "Не удаётся обновить заблокированные сервисы",
"insertDomain": "Проверить фильтрацию имени хоста.",
"dhcpSettings": "Настройки DHCP",
"dhcpSettingsDescription": "Настройка DHCP-сервера",
"dhcpSettingsNotLoaded": "Не удалось загрузить настройки DHCP",
"loadingDhcp": "Загрузка настроек DHCP...",
"enableDhcpServer": "Включить DHCP-сервер",
"selectInterface": "Выбрать интерфейс DHCP",
"hardwareAddress": "MAC-адрес",
"gatewayIp": "IP-адрес шлюза",
"ipv4addresses": "Адрес IPv4",
"ipv6addresses": "Адрес IPv6",
"neededSelectInterface": "Необходимо выбрать интерфейс для настройки DHCP-сервера.",
"ipv4settings": "Настройки IPv4",
"startOfRange": "Начало диапазона",
"endOfRange": "Конец диапазона",
"ipv6settings": "Настройки IPv6",
"subnetMask": "Маска подсети",
"subnetMaskNotValid": "Недопустимая маска подсети",
"gateway": "Шлюз",
"gatewayNotValid": "Недопустимый шлюз",
"leaseTime": "Время аренды",
"seconds": "{time} секунд",
"leaseTimeNotValid": "Недопустимый срок аренды",
"restoreConfiguration": "Сбросить конфигурацию",
"restoreConfigurationMessage": "Вы уверены, что хотите продолжить? Это приведет к сбросу всей конфигурации. Данное действие не может быть отменено.",
"changeInterface": "Изменить интерфейс",
"savingSettings": "Сохранение настроек...",
"settingsSaved": "Настройки успешно сохранены",
"settingsNotSaved": "Не удалось сохранить настройки",
"restoringConfig": "Восстановление конфигурации...",
"configRestored": "Конфигурация успешно сброшена",
"configNotRestored": "Не удалось произвести сброс конфигурации",
"dhcpStatic": "Статические аренды DHCP",
"noDhcpStaticLeases": "Не найдено статических аренд DHCP",
"deleting": "Удаление...",
"staticLeaseDeleted": "Статическая аренда DHCP успешно удалена",
"staticLeaseNotDeleted": "Не удалось удалить статическую аренду DHCP",
"deleteStaticLease": "Удалить статическую аренду",
"deleteStaticLeaseDescription": "Статическая аренда DHCP будет удалена. Данное действие не может быть отменено.",
"addStaticLease": "Добавить статическую аренду",
"macAddress": "MAC-адрес",
"macAddressNotValid": "Недопустимый MAC-адрес",
"hostName": "Имя хоста",
"hostNameError": "Имя хоста не может быть пустым",
"creating": "Создание...",
"staticLeaseCreated": "Статическая аренда DHCP успешно создана",
"staticLeaseNotCreated": "Не удалось создать статическую аренду DHCP",
"staticLeaseExists": "Статическая аренда DHCP уже существует",
"serverNotConfigured": "Сервер не настроен",
"restoreLeases": "Сбросить аренды DHCP",
"restoreLeasesMessage": "Вы уверены, что хотите продолжить? Это приведет к сбросу всех существующих аренд DHCP. Данное действие не может быть отменено.",
"restoringLeases": "Сброс аренд DHCP...",
"leasesRestored": "Аренды DHCP успешно сброшены",
"leasesNotRestored": "Не удалось сбросить аренды DHCP",
"dhcpLeases": "Аренды DHCP",
"noLeases": "Не найдено аренд DHCP",
"dnsRewrites": "Перезапись DNS-запросов",
"dnsRewritesDescription": "Настройка пользовательских правил DNS",
"loadingRewriteRules": "Загрузка правил перезаписи...",
"rewriteRulesNotLoaded": "Не удалось загрузить правила перезаписи DNS.",
"noRewriteRules": "Нет правил перезаписи DNS",
"answer": "Ответ",
"deleteDnsRewrite": "Удалить правило перезаписи DNS-запросов",
"deleteDnsRewriteMessage": "Вы уверены, что хотите удалить это правило перезаписи DNS? Данное действие не может быть отменено.",
"dnsRewriteRuleDeleted": "Правило перезаписи DNS успешно удалено",
"dnsRewriteRuleNotDeleted": "Не удалось удалить правило перезаписи DNS",
"addDnsRewrite": "Добавить правило",
"addingRewrite": "Добавление правила перезаписи DNS-запросов...",
"dnsRewriteRuleAdded": "Правило перезаписи DNS успешно добавлено",
"dnsRewriteRuleNotAdded": "Не удалось добавить правило перезаписи DNS",
"logsSettings": "Настройки журнала",
"enableLog": "Включить журнал",
"clearLogs": "Очистить журнал",
"anonymizeClientIp": "Анонимизировать клиента",
"hours6": "6 часов",
"days30": "30 дней",
"days90": "90 дней",
"retentionTime": "Частота ротации журнала запросов",
"selectOneItem": "Выберите один элемент",
"logSettingsNotLoaded": "Не удалось загрузить настройки журнала.",
"updatingSettings": "Обновление настроек...",
"logsConfigUpdated": "Настройки журнала успешно обновлены",
"logsConfigNotUpdated": "Не удалось обновить настройки журнала",
"deletingLogs": "Очистка журнала...",
"logsCleared": "Журнал успешно очищен",
"logsNotCleared": "Не удалось очистить журнал",
"runningHomeAssistant": "Запускается на Home Assistant",
"serverError": "Ошибка сервера",
"noItems": "Здесь нет предметов для показа",
"dnsSettings": "Настройки DNS",
"dnsSettingsDescription": "Настройка подключения к DNS-серверам",
"upstreamDns": "Upstream DNS-серверы",
"bootstrapDns": "Bootstrap DNS-серверы",
"noUpstreamDns": "Не добавлены upstream DNS-серверы.",
"dnsMode": "Режим DNS",
"noDnsMode": "Не выбран режим DNS",
"loadBalancing": "Распределение нагрузки",
"parallelRequests": "Параллельные запросы",
"fastestIpAddress": "Самый быстрый IP-адрес",
"loadBalancingDescription": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.",
"parallelRequestsDescription": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
"fastestIpAddressDescription": "Опросить все DNS-серверы и вернуть самый быстрый IP-адрес из полученных ответов. Это замедлит DNS-запросы, так как нужно будет дождаться ответов со всех DNS-серверов, но улучшит соединение.",
"noBootstrapDns": "Не добавлены bootstrap DNS-серверы.",
"bootstrapDnsServersInfo": "Bootstrap DNS-сервера используются для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали.",
"privateReverseDnsServers": "Приватные серверы для обратного DNS",
"privateReverseDnsServersDescription": "DNS-серверы, которые AdGuard Home использует для локальных PTR-запросов. Эти серверы используются, чтобы получить доменные имена клиентов с приватными IP-адресами, например «192.168.12.34», с помощью обратного DNS. Если список пуст, AdGuard Home использует DNS-серверы по умолчанию вашей ОС.",
"reverseDnsDefault": "По умолчанию AdGuard Home использует следующие обратные DNS-серверы",
"addItem": "Добавить сервер",
"noServerAddressesAdded": "Адреса серверов не указаны.",
"usePrivateReverseDnsResolvers": "Использовать приватные обратные DNS-резолверы",
"usePrivateReverseDnsResolversDescription": "Посылать обратные DNS-запросы для локально обслуживаемых адресов на указанные серверы. Если отключено, AdGuard Home будет отвечать NXDOMAIN на все подобные PTR-запросы, кроме запросов о клиентах, уже известных по DHCP, /etc/hosts и так далее.",
"enableReverseResolving": "Включить запрашивание доменных имён для IP-адресов клиентов",
"enableReverseResolvingDescription": "Определять доменные имена клиентов через PTR-запросы к соответствующим серверам (приватные DNS-серверы для локальных клиентов, upstream-серверы для клиентов с публичным IP-адресом).",
"dnsServerSettings": "Настройки DNS-сервера",
"limitRequestsSecond": "Лимит запросов в секунду",
"valueNotNumber": "Значение - не число",
"enableEdns": "Включить отправку EDNS Client Subnet",
"enableEdnsDescription": "Добавлять опцию EDNS Client Subnet (ECS) к запросам к upstream-серверам, а также записывать присланные клиентами значения в журнал.",
"enableDnssec": "Включить DNSSEC",
"enableDnssecDescription": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC).",
"disableResolvingIpv6": "Отключить обработку IPv6-адресов",
"disableResolvingIpv6Description": "Игнорировать все DNS-запросы адресов IPv6 (тип AAAA) и удалять IPv6-данные из ответов типа HTTPS.",
"blockingMode": "Режим блокировки",
"defaultMode": "Стандартный",
"defaultDescription": "Отвечает с нулевым IP-адресом, (0.0.0.0 для A; :: для AAAA) когда заблокировано правилом в стиле Adblock; отвечает с IP-адресом, указанным в правиле, когда заблокировано правилом в стиле файлов hosts",
"refusedDescription": "Отвечает с кодом REFUSED",
"nxdomainDescription": "Отвечает с кодом NXDOMAIN",
"nullIp": "Нулевой IP",
"nullIpDescription": "Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)",
"customIp": "Пользовательский IP",
"customIpDescription": "Отвечает с вручную настроенным IP-адресом",
"dnsCacheConfig": "Настройка кеша DNS",
"cacheSize": "Размер кеша",
"inBytes": "В байтах",
"overrideMinimumTtl": "Переопределить минимальный TTL",
"overrideMinimumTtlDescription": "Расширить короткие TTL-значения (в секундах), полученные с upstream-сервера при кешировании DNS-ответов.",
"overrideMaximumTtl": "Переопределить максимальный TTL",
"overrideMaximumTtlDescription": "Установить максимальное TTL-значение (в секундах) для записей в DNS-кеше.",
"optimisticCaching": "Оптимистическое кеширование",
"optimisticCachingDescription": "AdGuard Home будет отвечать из кеша, даже если ответы в нём неактуальны, и попытается обновить их.",
"loadingDnsConfig": "Загрузка конфигурации DNS...",
"dnsConfigNotLoaded": "Не удалось загрузить конфигурацию DNS.",
"blockingIpv4": "Блокируется IPv4",
"blockingIpv4Description": "IP-адрес, который будет возвращен для заблокированного запроса А",
"blockingIpv6": "Блокируется IPv6",
"blockingIpv6Description": "IP-адрес, который будет возвращен для заблокированного запроса AAAA",
"invalidIp": "Недопустимый IP-адрес",
"dnsConfigSaved": "Конфигурация DNS-сервера сохранена успешно",
"dnsConfigNotSaved": "Не удалось сохранить конфигурацию DNS-сервера",
"savingConfig": "Сохранение конфигурации...",
"someValueNotValid": "Некоторое значение недопустимо",
"upstreamDnsDescription": "Настройка upstream DNS-серверов и режима DNS",
"bootstrapDnsDescription": "Настройка bootstrap DNS-серверов",
"privateReverseDnsDescription": "Настройка пользовательских DNS-серверов и приватных серверы для обратного DNS",
"dnsServerSettingsDescription": "Настройка ограничения на количество запросов, режима блокировки и многое другое",
"dnsCacheConfigDescription": "Настройка, как сервер должен управлять кэшем DNS",
"comment": "Комментарий",
"address": "Адрес",
"commentsDescription": "Комментариям всегда предшествует #. Вам не обязательно добавлять его, он будет добавлен автоматически.",
"encryptionSettings": "Настройки шифрования",
"encryptionSettingsDescription": "Поддержка шифрования (HTTPS/QUIC/TLS)",
"loadingEncryptionSettings": "Загрузка настроек шифрования...",
"encryptionSettingsNotLoaded": "Не удалось загрузить настройки шифрования.",
"enableEncryption": "Включить шифрование",
"enableEncryptionTypes": "HTTPS, DNS-over-HTTPS и DNS-over-TLS",
"enableEncryptionDescription": "Если порт HTTPS настроен, веб-интерфейс администрирования AdGuard Home будет доступен через HTTPS, а также DNS-over-HTTPS сервер будет доступен по пути '/dns-query'.",
"serverConfiguration": "Конфигурация сервера",
"domainName": "Доменное имя",
"domainNameDescription": "Если задано, AdGuard Home распознаёт ClientID, отвечает на DDR-запросы, и дополнительно проверяет соединения. Если не задано, этот функционал отключён. Должно соответствовать одному из параметров DNS Names в сертификате.",
"redirectHttps": "Автоматически перенаправлять на HTTPS",
"httpsPort": "Порт HTTPS",
"tlsPort": "Порт DNS-over-TLS",
"dnsOverQuicPort": "Порт DNS-over-QUIC",
"certificates": "Сертификаты",
"certificatesDescription": "Для использования шифрования вам необходимо предоставить корректную цепочку SSL-сертификатов для вашего домена. Вы можете получить бесплатный сертификат на letsencrypt.org или вы можете купить его у одного из доверенных Центров Сертификации.",
"certificateFilePath": "Указать путь к файлу сертификатов",
"pasteCertificateContent": "Вставить содержимое сертификатов",
"certificatePath": "Путь к сертификату",
"certificateContent": "Содержимое сертификата",
"privateKey": "Закрытый ключ",
"privateKeyFile": "Указать файл закрытого ключа",
"pastePrivateKey": "Вставить содержимое закрытого ключа",
"usePreviousKey": "Использовать сохранённый ранее ключ",
"privateKeyPath": "Путь к закрытому ключу",
"invalidCertificate": "Цепочка сертификатов не прошла проверку",
"invalidPrivateKey": "Некорректный приватный ключ",
"validatingData": "Проверка данных",
"dataValid": "Данные действительны",
"dataNotValid": "Недопустимые данные",
"encryptionConfigSaved": "Настройки шифрования успешно сохранены",
"encryptionConfigNotSaved": "Не удаётся сохранить настройки шифрования",
"configError": "Ошибка конфигурации",
"enterOnlyCertificate": "Введите только сертификат. Не вводите строки ---BEGIN--- и ---END---.",
"enterOnlyPrivateKey": "Введите только ключ. Не вводите строки ---BEGIN--- и ---END---.",
"noItemsSearch": "Ничего не найдено по данному запросу.",
"clearSearch": "Очистить поиск",
"exitSearch": "Покинуть поиск",
"searchClients": "Поиск клиентов",
"noClientsSearch": "Не найдено клиентов по данному запросу.",
"customization": "Персонализация",
"customizationDescription": "Настройте внешний вид приложения",
"color": "Цветовая тема",
"useDynamicTheme": "Использовать динамическую тему",
"red": "Красный",
"green": "Зелёный",
"blue": "Синий",
"yellow": "Жёлтый",
"orange": "Оранжевый",
"brown": "Коричневый",
"cyan": "Бирюзовый",
"purple": "Пурпурный",
"pink": "Розовый",
"deepOrange": "Темно-оранжевый",
"indigo": "Индиго",
"useThemeColorStatus": "Использовать цвет темы для обозначения статуса",
"useThemeColorStatusDescription": "Заменяет зеленый и красный цвета статуса цветом темы и серым",
"invalidCertificateChain": "Цепочка сертификатов не прошла проверку",
"validCertificateChain": "Действительная цепочка сертификатов",
"subject": "Субъект",
"issuer": "Издатель",
"expires": "Истекает",
"validPrivateKey": "Действительный закрытый ключ",
"expirationDate": "Истекает",
"keysNotMatch": "Недействительный сертификат или ключ: tls: закрытый ключ не соответствует открытому ключу",
"timeLogs": "Время в записях журнала",
"timeLogsDescription": "Показывать время обработки в журнале",
"hostNames": "Имена хостов",
"keyType": "Тип ключа",
"updateAvailable": "Доступно обновление",
"installedVersion": "Установленная версия",
"newVersion": "Новая версия",
"source": "Источник",
"downloadUpdate": "Загрузить обновление",
"download": "Скачать",
"doNotRememberAgainUpdate": "Не запоминать снова для этой версии",
"downloadingUpdate": "Скачивание",
"completed": "завершено",
"permissionNotGranted": "Разрешение не предоставлено",
"inputSearchTerm": "Введите поисковый запрос.",
"answers": "Ответы",
"copyClipboard": "Скопировать в буфер обмена",
"domainCopiedClipboard": "Домен скопирован в буфер обмена",
"clearDnsCache": "Очистить кэш DNS",
"clearDnsCacheMessage": "Вы уверены, что хотите очистить кэш DNS?",
"dnsCacheCleared": "Кэш DNS очищен успешно",
"clearingDnsCache": "Очистка кэша...",
"dnsCacheNotCleared": "Не удалось очистить кэш DNS",
"clientsSelected": "выбранные клиенты",
"invalidDomain": "Недопустимый домен",
"loadingBlockedServicesList": "Загрузка списка заблокированных сервисов...",
"blockedServicesListNotLoaded": "Не удалось загрузить список заблокированных служб",
"error": "Ошибка",
"updates": "Обновления",
"updatesDescription": "Обновить AdGuard Home server",
"updateNow": "Обновить сейчас",
"currentVersion": "Текущая версия",
"requestStartUpdateFailed": "Не удалось выполнить запрос на запуск обновления",
"requestStartUpdateSuccessful": "Запрос на запуск обновления успешен",
"serverUpdated": "Сервер обновлён",
"unknownStatus": "Неизвестный статус",
"checkingUpdates": "Проверка обновлений..",
"checkUpdates": "Проверить обновления",
"requestingUpdate": "Запрос обновления...",
"autoupdateUnavailable": "Автообновление недоступно",
"autoupdateUnavailableDescription": "Служба автоматического обновления недоступна для этого сервера. Это может быть связано с тем, что сервер запущен в контейнере Docker. Вам необходимо обновить свой сервер вручную.",
"minute": "{time} минута",
"minutes": "{time} минут",
"hour": "{time} час",
"hours": "{time} часов",
"remainingTime": "Оставшееся время",
"safeSearchSettings": "Настройки безопасного поиска",
"loadingSafeSearchSettings": "Загрузка настроек безопасного поиска...",
"safeSearchSettingsNotLoaded": "Ошибка при загрузке настроек безопасного поиска.",
"loadingLogsSettings": "Загрузка настроек журнала...",
"selectOptionLeftColumn": "Выберите опцию в левой колонке",
"selectClientLeftColumn": "Выберите клиента в левой колонке",
"disableList": "Отключить список",
"enableList": "Включить список",
"screens": "Экраны",
"copiedClipboard": "Скопировано в буфер обмена",
"seeDetails": "Смотрите подробности",
"listNotAvailable": "Список недоступен",
"copyListUrl": "Скопировать URL",
"listUrlCopied": "URL списка сохранён в буфер обмена",
"unsupportedVersion": "Неподдерживаемая версия",
"unsupprtedVersionMessage": "Поддержка AdGuard Home версии {version} не гарантируется. Приложение может работать нестабильно с данной версией сервера.\n\nПриложение AdGuard Home Manager предназначено для работы со стабильными версиями AdGuard Home. Приложение может работать с альфа и бета версиями сервера, но совместимость и стабильность не гарантируются.",
"iUnderstand": "Продолжить",
"appUpdates": "Обновления приложений",
"usingLatestVersion": "Вы используете последнюю версию",
"ipLogs": "IP-адреса в записях журнала",
"ipLogsDescription": "Всегда показывать IP-адрес в записях журнала вместо имени клиента",
"application": "Приложение",
"combinedChart": "Объединять графики",
"combinedChartDescription": "Комбинирует все графики в один",
"statistics": "Статистика",
"errorLoadFilters": "Ошибка при загрузке фильтров.",
"clientRemovedSuccessfully": "Запись успешно удалена.",
"editRewriteRule": "Редактировать правило",
"dnsRewriteRuleUpdated": "Правило перезаписи DNS успешно обновлено",
"dnsRewriteRuleNotUpdated": "Не удалось обновить правило перезаписи DNS",
"updatingRule": "Обновление правила...",
"serverUpdateNeeded": "Требуется обновление сервера",
"updateYourServer": "Обновите сервер AdGuard Home до версии {version} или выше, чтобы использовать эту функцию.",
"january": "Январь",
"february": "Февраль",
"march": "Март",
"april": "Апрель",
"may": "Май",
"june": "Июнь",
"july": "Июль",
"august": "Август",
"september": "Сентябрь",
"october": "Октябрь",
"november": "Ноябрь",
"december": "Декабрь",
"malwarePhising": "Вредоносные/фишинговые сайты",
"queries": "Запросы",
"adultSites": "«Взрослые» сайты",
"quickFilters": "Быстрые фильтры",
"searchDomainInternet": "Поиск домена в Интернете",
"hideServerAddress": "Скрывать адрес сервера",
"hideServerAddressDescription": "Скрывает адрес сервера на главном экране",
"topItemsOrder": "Расположение блоков на главном экране",
"topItemsOrderDescription": "Упорядочьте расположение блоков на главном экране",
"topItemsReorderInfo": "Чтобы менять порядок элементов, удерживая элемент, перетащите его на новое место.",
"discardChanges": "Отменить изменения",
"discardChangesDescription": "Вы уверены, что хотите отменить изменения?",
"others": "Прочее",
"showChart": "Показать график",
"hideChart": "Скрыть график",
"showTopItemsChart": "Показывать ТОП-графики на главной странице",
"showTopItemsChartDescription": "По умолчанию на главной странице отображаются круговые диаграммы для часто запрашиваемых доменов, частых клиентов и прочего. Влияет только на просмотр с мобильного устройства",
"openMenu": "Открыть меню",
"closeMenu": "Закрыть меню",
"openListUrl": "Открыть URL списка",
"selectionMode": "Режим выбора",
"enableDisableSelected": "Включить или выключить выбранные элементы",
"deleteSelected": "Удалить выбранные элементы",
"deleteSelectedLists": "Удалить выбранные списки",
"allSelectedListsDeletedSuccessfully": "Все выбранные списки успешно удалены.",
"deletionResult": "Результат удаления",
"deletingLists": "Удаление списков...",
"failedElements": "Неудачные элементы",
"processingLists": "Обработка списков...",
"enableDisableResult": "Включить или выключить результат",
"selectedListsEnabledDisabledSuccessfully": "Все выбранные списки были включены или выключены успешно",
"sslWarning": "Если используется HTTPS-соединение с самоподписанным сертификатом, то должна быть активирована опция «Не проверять SSL-сертификат» в разделе «Настройки» > «Дополнительные настройки».",
"unsupportedServerVersion": "Неподдерживаемая версия сервера",
"unsupportedServerVersionMessage": "Данная версия AdGuard Home устарела и не поддерживается AdGuard Home Manager. Чтобы использовать данное приложение, необходимо выполнить обновление AdGuard Home до актуальной версии.",
"yourVersion": "Ваша версия: {version}",
"minimumRequiredVersion": "Минимальная требуемая версия: {version}",
"topUpstreams": "Часто запрашиваемые\nupstream-серверы",
"averageUpstreamResponseTime": "Среднее время отклика\nupstream-сервера",
"dhcpNotAvailable": "DHCP сервер не доступен.",
"osServerInstalledIncompatible": " Операционная система, в которой установлен сервер, несовместима с этой функцией.",
"resetSettings": "Сбросить настройки",
"resetEncryptionSettingsDescription": "Вы уверены, что хотите сбросить настройки шифрования к значениям по умолчанию?",
"resettingConfig": "Сброс конфигурации...",
"configurationResetSuccessfully": "Конфигурация успешно сброшена",
"configurationResetError": "Не удалось сбросить конфигурацию",
"testUpstreamDnsServers": "Тест upstream DNS-серверов",
"errorTestUpstreamDns": "Ошибка при тестировании upstream DNS-серверов.",
"useCustomIpEdns": "Use custom IP for EDNS",
"useCustomIpEdnsDescription": "Использовать собственный IP-адрес для EDNS",
"sortingOptions": "Параметры сортировки",
"fromHighestToLowest": "От большего к меньшему",
"fromLowestToHighest": "От меньшего к большему",
"queryLogsAndStatistics": "Журналы запросов и статистика",
"ignoreClientQueryLog": "Игнорировать этого клиента в журнале запросов",
"ignoreClientStatistics": "Игнорировать этого клиента в статистике",
"savingChanges": "Сохранение изменений...",
"fallbackDnsServers": "Резервные DNS-серверы",
"fallbackDnsServersDescription": "Настроить резервные DNS-серверы",
"fallbackDnsServersInfo": "Список резервных DNS-серверов, используемых в тех случаях, когда вышестоящие DNS-серверы недоступны. Синтаксис такой же, как и в поле Upstream DNS-серверы выше.",
"noFallbackDnsAdded": "Резервные DNS-серверы не добавлены.",
"blockedResponseTtl": "TTL заблокированного ответа",
"blockedResponseTtlDescription": "Указывает, в течение скольких секунд клиенты должны кешировать отфильтрованный ответ",
"invalidValue": "Недопустимое значение",
"noDataChart": "Нет данных для отображения графика.",
"noData": "Нет данных",
"unblockClient": "Разблокировать клиента",
"blockingClient": "Блокировка клиента...",
"unblockingClient": "Снятие блокироваки с клиента...",
"upstreamDnsCacheConfiguration": "Конфигурация кеша upstream DNS-серверов",
"enableDnsCachingClient": "Включить кеширование для пользовательской конфигурации upstream-серверов этого клиента",
"dnsCacheSize": "Размер DNS-кеша",
"nameInvalid": "Требуется имя",
"oneIdentifierRequired": "Требуется по крайней мере один идентификатор",
"dnsCacheNumber": "Размер кэша DNS должен быть числом",
"errors": "Ошибки",
"redirectHttpsWarning": "Если в AdGuard Home активирована опция «Автоматически перенаправлять на HTTPS», то необходимо использовать HTTPS-соединение и HTTPS-порт."
}

View file

@ -11,7 +11,7 @@
"port": "Bağlantı noktası", "port": "Bağlantı noktası",
"username": "Kullanıcı adı", "username": "Kullanıcı adı",
"password": "Şifre", "password": "Şifre",
"defaultServer": "Varsayılan sunucu", "defaultServer": "Varsayılan sunucu olarak ayarla",
"general": "Genel", "general": "Genel",
"connection": "Bağlantı", "connection": "Bağlantı",
"authentication": "Kimlik doğrulama", "authentication": "Kimlik doğrulama",
@ -40,18 +40,18 @@
"removeWarning": "Bu AdGuard Home sunucusuyla olan bağlantıyı kaldırmak istediğinizden emin misiniz?", "removeWarning": "Bu AdGuard Home sunucusuyla olan bağlantıyı kaldırmak istediğinizden emin misiniz?",
"cancel": "İptal", "cancel": "İptal",
"defaultConnection": "Varsayılan bağlantı", "defaultConnection": "Varsayılan bağlantı",
"setDefault": "Varsayılan ayarla", "setDefault": "Varsayılan sunucu yap",
"edit": "Düzenle", "edit": "Düzenle",
"delete": "Sil", "delete": "Sil",
"save": "Kaydet", "save": "Kaydet",
"serverStatus": "Sunucu durumu", "serverStatus": "Sunucu durumu",
"connectionNotUpdated": "Bağlantı Güncellenmedi", "connectionNotUpdated": "Bağlantı Güncellenmedi",
"ruleFilteringWidget": "Kural filtreleme", "ruleFilteringWidget": "Kural filtreleme",
"safeBrowsingWidget": "Güvenli gezinti", "safeBrowsingWidget": "Güvenli gezinme",
"parentalFilteringWidget": "Ebeveyn filtreleme", "parentalFilteringWidget": "Ebeveyn filtreleme",
"safeSearchWidget": "Güvenli arama", "safeSearchWidget": "Güvenli arama",
"ruleFiltering": "Kural filtreleme", "ruleFiltering": "Kural filtreleme",
"safeBrowsing": "Güvenli gezinti", "safeBrowsing": "Güvenli gezinme",
"parentalFiltering": "Ebeveyn filtreleme", "parentalFiltering": "Ebeveyn filtreleme",
"safeSearch": "Güvenli arama", "safeSearch": "Güvenli arama",
"serverStatusNotRefreshed": "Sunucu durumu yenilenemedi", "serverStatusNotRefreshed": "Sunucu durumu yenilenemedi",
@ -63,9 +63,9 @@
"topBlockedDomains": "En çok engellenenler", "topBlockedDomains": "En çok engellenenler",
"appSettings": "Uygulama ayarları", "appSettings": "Uygulama ayarları",
"theme": "Tema", "theme": "Tema",
"light": "Aydınlık", "light": "Açık",
"dark": "Karanlık", "dark": "Koyu",
"systemDefined": "Sistemle uyumlu hale getir", "systemDefined": "Otomatik (Cihazınızın renk düzenine göre)",
"close": "Kapat", "close": "Kapat",
"connectedTo": "Bağlandı:", "connectedTo": "Bağlandı:",
"selectedServer": "Seçili sunucu:", "selectedServer": "Seçili sunucu:",
@ -75,13 +75,13 @@
"userNotEmpty": "Kullanıcı adı boş bırakılamaz", "userNotEmpty": "Kullanıcı adı boş bırakılamaz",
"passwordNotEmpty": "Şifre boş bırakılamaz", "passwordNotEmpty": "Şifre boş bırakılamaz",
"examplePath": "Örnek: /adguard", "examplePath": "Örnek: /adguard",
"helperPath": "Ters proxy kullanıyorsanız", "helperPath": "Eğer ters proxy kullanıyorsanız",
"aboutApp": "Uygulama hakkında", "aboutApp": "Uygulama hakkında",
"appVersion": "Uygulama sürümü", "appVersion": "Uygulama sürümü",
"createdBy": "Geliştirici", "createdBy": "Geliştirici",
"clients": "İstemciler", "clients": "İstemciler",
"allowed": "İzin verildi", "allowed": "İzin verilen",
"blocked": "Engellendi", "blocked": "Engellenen",
"noClientsList": "Bu listede hiç istemci yok", "noClientsList": "Bu listede hiç istemci yok",
"activeClients": "Etkin", "activeClients": "Etkin",
"removeClient": "İstemciyi kaldır", "removeClient": "İstemciyi kaldır",
@ -102,8 +102,8 @@
"copyLogsClipboard": "Günlükleri panoya kopyala", "copyLogsClipboard": "Günlükleri panoya kopyala",
"logsCopiedClipboard": "Günlükler panoya kopyalandı", "logsCopiedClipboard": "Günlükler panoya kopyalandı",
"advancedSettings": "Gelişmiş ayarlar", "advancedSettings": "Gelişmiş ayarlar",
"dontCheckCertificate": "SSL sertifikasını asla kontrol etme", "dontCheckCertificate": "SSL sertifikasını kontrol etme",
"dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar", "dontCheckCertificateDescription": "Sunucunun SSL sertifikası doğrulamasını geçersiz kılar.",
"advancedSetupDescription": "Gelişmiş seçenekleri yönet", "advancedSetupDescription": "Gelişmiş seçenekleri yönet",
"settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.", "settingsUpdatedSuccessfully": "Ayarlar başarıyla güncellendi.",
"cannotUpdateSettings": "Ayarlar güncellenemiyor.", "cannotUpdateSettings": "Ayarlar güncellenemiyor.",
@ -114,8 +114,8 @@
"processedRow": "İşlendi (Liste yok)", "processedRow": "İşlendi (Liste yok)",
"blockedBlacklist": "Engellendi\nKara Liste", "blockedBlacklist": "Engellendi\nKara Liste",
"blockedBlacklistRow": "Engellendi (Kara liste)", "blockedBlacklistRow": "Engellendi (Kara liste)",
"blockedSafeBrowsing": "Engellendi\nGüvenli gezinti", "blockedSafeBrowsing": "Engellendi\nGüvenli gezinme",
"blockedSafeBrowsingRow": "Engellendi (Güvenli gezinti)", "blockedSafeBrowsingRow": "Engellendi (Güvenli gezinme)",
"blockedParental": "Engellendi\nEbeveyn filtreleme", "blockedParental": "Engellendi\nEbeveyn filtreleme",
"blockedParentalRow": "Engellendi (Ebeveyn filtreleme)", "blockedParentalRow": "Engellendi (Ebeveyn filtreleme)",
"blockedInvalid": "Engellendi\nGeçersiz", "blockedInvalid": "Engellendi\nGeçersiz",
@ -131,11 +131,11 @@
"rewrite": "Yeniden Yaz", "rewrite": "Yeniden Yaz",
"status": "Durum", "status": "Durum",
"result": "Sonuç", "result": "Sonuç",
"time": "Zaman", "time": "Saat",
"blocklist": "Engelleme Listesi", "blocklist": "Engelleme Listesi",
"request": "İstek", "request": "İstek",
"domain": "Alan adı", "domain": "Alan adı",
"type": "Tip", "type": "Tür",
"clas": "Sınıf", "clas": "Sınıf",
"response": "Yanıt", "response": "Yanıt",
"dnsServer": "DNS sunucusu", "dnsServer": "DNS sunucusu",
@ -181,7 +181,7 @@
"visitGooglePlay": "Google Play sayfasını ziyaret et", "visitGooglePlay": "Google Play sayfasını ziyaret et",
"gitHub": "Kaynak kodlarına GitHub'dan ulaşabilirsiniz", "gitHub": "Kaynak kodlarına GitHub'dan ulaşabilirsiniz",
"blockClient": "İstemciyi engelle", "blockClient": "İstemciyi engelle",
"selectTags": "Etiketleri seçin", "selectTags": "Etiketleri seç",
"noTagsSelected": "Seçili etiket yok", "noTagsSelected": "Seçili etiket yok",
"tags": "Etiketler", "tags": "Etiketler",
"identifiers": "Tanımlayıcılar", "identifiers": "Tanımlayıcılar",
@ -190,21 +190,21 @@
"noIdentifiers": "Tanımlayıcı eklenmedi", "noIdentifiers": "Tanımlayıcı eklenmedi",
"useGlobalSettings": "Küresel ayarları kullan", "useGlobalSettings": "Küresel ayarları kullan",
"enableFiltering": "Filtrelemeyi etkinleştir", "enableFiltering": "Filtrelemeyi etkinleştir",
"enableSafeBrowsing": "Güvenli gezintiyi etkinleştir", "enableSafeBrowsing": "Güvenli gezinmeyi etkinleştir",
"enableParentalControl": "Ebeveyn kontrolünü etkinleştir", "enableParentalControl": "Ebeveyn kontrolünü etkinleştir",
"enableSafeSearch": "Güvenli aramayı aktif et", "enableSafeSearch": "Güvenli aramayı etkinleştir",
"blockedServices": "Engellenen hizmetler", "blockedServices": "Engellenen hizmetler",
"selectBlockedServices": "Engellenen hizmetleri seç", "selectBlockedServices": "Engellenen hizmetleri seç",
"noBlockedServicesSelected": "Engellenen hizmetler seçilmedi", "noBlockedServicesSelected": "Engellenen hizmetler seçilmedi",
"services": "Hizmetler", "services": "Hizmetler",
"servicesBlocked": "Hizmetler engellendi", "servicesBlocked": "Hizmetler engellendi",
"tagsSelected": "Seçilen etiketler", "tagsSelected": "Etiket seçildi",
"upstreamServers": "Üst kaynak sunucuları", "upstreamServers": "Üst sunucular",
"serverAddress": "Sunucu adresi", "serverAddress": "Sunucu adresi",
"noUpstreamServers": "Üst kaynak sunucusu yok.", "noUpstreamServers": "Üst sunucu yok.",
"willBeUsedGeneralServers": "Genel üst kaynak sunucuları kullanılacak.", "willBeUsedGeneralServers": "Genel üst sunucular kullanılacak.",
"added": "Eklenenler", "added": "Eklenenler",
"clientUpdatedSuccessfully": "İstemci başarıyla güncellendi", "clientUpdatedSuccessfully": "İstemci ayarları başarıyla güncellendi",
"clientNotUpdated": "İstemci güncellenemedi", "clientNotUpdated": "İstemci güncellenemedi",
"clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı", "clientDeletedSuccessfully": "İstemci başarıyla kaldırıldı",
"clientNotDeleted": "İstemci silinemedi", "clientNotDeleted": "İstemci silinemedi",
@ -215,7 +215,7 @@
"blacklists": "Kara listeler", "blacklists": "Kara listeler",
"rules": "Kurallar", "rules": "Kurallar",
"customRules": "Özel kurallar", "customRules": "Özel kurallar",
"enabledRules": "Etkin kurallar", "enabledRules": "Etkin kural",
"enabled": "Etkin", "enabled": "Etkin",
"disabled": "Devre dışı", "disabled": "Devre dışı",
"rule": "Kural", "rule": "Kural",
@ -234,7 +234,7 @@
"addWhitelist": "Beyaz liste ekle", "addWhitelist": "Beyaz liste ekle",
"addBlacklist": "Kara liste ekle", "addBlacklist": "Kara liste ekle",
"urlNotValid": "Bağlantı adresi geçerli değil", "urlNotValid": "Bağlantı adresi geçerli değil",
"urlAbsolutePath": "Bağlantı adresi veya kesin dosya yolu", "urlAbsolutePath": "Bağlantı adresi veya dosya yolu",
"addingList": "Liste ekleniyor...", "addingList": "Liste ekleniyor...",
"listAdded": "Liste başarıyla eklendi. Eklenen öğeler:", "listAdded": "Liste başarıyla eklendi. Eklenen öğeler:",
"listAlreadyAdded": "Liste zaten eklenmiş", "listAlreadyAdded": "Liste zaten eklenmiş",
@ -267,13 +267,13 @@
"seeDnsAddresses": "DNS adreslerine göz at", "seeDnsAddresses": "DNS adreslerine göz at",
"dnsPort": "DNS bağlantı noktası", "dnsPort": "DNS bağlantı noktası",
"httpPort": "HTTP bağlantı noktası", "httpPort": "HTTP bağlantı noktası",
"protectionEnabled": "Koruma etkin mi?", "protectionEnabled": "Koruma durumu",
"dhcpAvailable": "DHCP mevcut mu?", "dhcpAvailable": "DHCP durumu",
"serverRunning": "Sunucu çalışıyor mu?", "serverRunning": "Sunucu durumu",
"serverVersion": "Sunucu sürümü", "serverVersion": "Sunucu sürümü",
"serverLanguage": "Sunucu dili", "serverLanguage": "Sunucu dili",
"yes": "Evet", "yes": "Etkin",
"no": "Hayır", "no": "Mevcut değil",
"allowedClients": "İzin verilen istemciler", "allowedClients": "İzin verilen istemciler",
"disallowedClients": "İzin verilmeyen istemciler", "disallowedClients": "İzin verilmeyen istemciler",
"disallowedDomains": "İzin verilmeyen alan adları", "disallowedDomains": "İzin verilmeyen alan adları",
@ -288,7 +288,7 @@
"addClientFieldDescription": "CIDR'ler, IP adresi veya ClientID", "addClientFieldDescription": "CIDR'ler, IP adresi veya ClientID",
"clientIdentifier": "İstemci tanımlayıcısı", "clientIdentifier": "İstemci tanımlayıcısı",
"allowClient": "İstemciye izin ver", "allowClient": "İstemciye izin ver",
"disallowClient": "İstemciye izin verme", "disallowClient": "İstemciyi engelle",
"noDisallowedDomains": "İzin verilmeyen alan adı yok", "noDisallowedDomains": "İzin verilmeyen alan adı yok",
"domainNotAdded": "Alan adı eklenemedi", "domainNotAdded": "Alan adı eklenemedi",
"statusSelected": "Durum seçildi.", "statusSelected": "Durum seçildi.",
@ -305,7 +305,7 @@
"block": "Engelle", "block": "Engelle",
"unblock": "Engeli kaldır", "unblock": "Engeli kaldır",
"custom": "Özel", "custom": "Özel",
"addImportant": "Ekle ($important)", "addImportant": "Başına $important ekle",
"howCreateRules": "Özel kurallar nasıl oluşturulur?", "howCreateRules": "Özel kurallar nasıl oluşturulur?",
"examples": "Örnekler", "examples": "Örnekler",
"example1": "example.org ve tüm alt alan adlarına erişimi engeller.", "example1": "example.org ve tüm alt alan adlarına erişimi engeller.",
@ -342,7 +342,7 @@
"dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi", "dhcpSettingsNotLoaded": "DHCP ayarları yüklenemedi",
"loadingDhcp": "DHCP ayarları yükleniyor...", "loadingDhcp": "DHCP ayarları yükleniyor...",
"enableDhcpServer": "DHCP sunucusunu etkinleştir", "enableDhcpServer": "DHCP sunucusunu etkinleştir",
"selectInterface": "Arayüz seçin", "selectInterface": "Arayüz seç",
"hardwareAddress": "Donanım adresi", "hardwareAddress": "Donanım adresi",
"gatewayIp": "Ağ Geçidi IP'si", "gatewayIp": "Ağ Geçidi IP'si",
"ipv4addresses": "IPv4 adresleri", "ipv4addresses": "IPv4 adresleri",
@ -368,37 +368,37 @@
"restoringConfig": "Yapılandırma geri yükleniyor...", "restoringConfig": "Yapılandırma geri yükleniyor...",
"configRestored": "Yapılandırma başarıyla sıfırlandı", "configRestored": "Yapılandırma başarıyla sıfırlandı",
"configNotRestored": "Yapılandırma sıfırlanamadı", "configNotRestored": "Yapılandırma sıfırlanamadı",
"dhcpStatic": "DHCP statik kiralamaları", "dhcpStatic": "DHCP statik kiraları",
"noDhcpStaticLeases": "DHCP statik kiralamaları bulunamadı", "noDhcpStaticLeases": "DHCP statik kirası bulunamadı",
"deleting": "Siliniyor...", "deleting": "Siliniyor...",
"staticLeaseDeleted": "DHCP statik kiralama başarıyla silindi", "staticLeaseDeleted": "DHCP statik kirası başarıyla silindi",
"staticLeaseNotDeleted": "DHCP statik kiralaması silinemedi", "staticLeaseNotDeleted": "DHCP statik kirası silinemedi",
"deleteStaticLease": "Statik kiralamayı sil", "deleteStaticLease": "Statik kirasını sil",
"deleteStaticLeaseDescription": "DHCP statik kirası silinecek. Bu işlem geri alınamaz.", "deleteStaticLeaseDescription": "DHCP statik kirası silinecek. Bu işlem geri alınamaz.",
"addStaticLease": "Statik kiralama ekleyin", "addStaticLease": "Statik kira ekleyin",
"macAddress": "MAC adresi", "macAddress": "MAC adresi",
"macAddressNotValid": "MAC adresi geçersiz", "macAddressNotValid": "MAC adresi geçersiz",
"hostName": "Ana bilgisayar adı", "hostName": "Ana bilgisayar adı",
"hostNameError": "Ana bilgisayar adı boş olamaz", "hostNameError": "Ana bilgisayar adı boş olamaz",
"creating": "Oluşturuluyor...", "creating": "Oluşturuluyor...",
"staticLeaseCreated": "DHCP statik kiralaması başarıyla oluşturuldu", "staticLeaseCreated": "DHCP statik kirası başarıyla oluşturuldu",
"staticLeaseNotCreated": "DHCP statik kiralaması oluşturulamadı", "staticLeaseNotCreated": "DHCP statik kirası oluşturulamadı",
"staticLeaseExists": "DHCP statik kiralaması zaten mevcut", "staticLeaseExists": "DHCP statik kirası zaten mevcut",
"serverNotConfigured": "Sunucu yapılandırılmamış", "serverNotConfigured": "Sunucu yapılandırılmamış",
"restoreLeases": "Kiralamaları sıfırla", "restoreLeases": "Kiraları sıfırla",
"restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiralamaları sıfırlayacaktır. Bu işlem geri alınamaz.", "restoreLeasesMessage": "Devam etmek istediğinizden emin misiniz? Bu, mevcut tüm kiraları sıfırlayacaktır. Bu işlem geri alınamaz.",
"restoringLeases": "Kiralamalar sıfırlanıyor...", "restoringLeases": "Kiralar sıfırlanıyor...",
"leasesRestored": "Kiralamalar başarıyla sıfırlandı", "leasesRestored": "Kiralar başarıyla sıfırlandı",
"leasesNotRestored": "Kiralar sıfırlanamadı", "leasesNotRestored": "Kiralar sıfırlanamadı",
"dhcpLeases": "DHCP kiralamaları", "dhcpLeases": "DHCP kiraları",
"noLeases": "Kullanılabilir DHCP kiralaması yok", "noLeases": "Kullanılabilir DHCP kiraları yok",
"dnsRewrites": "DNS yeniden yazımları", "dnsRewrites": "DNS yeniden yazımları",
"dnsRewritesDescription": "Özel DNS kurallarını yapılandır", "dnsRewritesDescription": "Özel DNS kurallarını yapılandır",
"loadingRewriteRules": "Yeniden yazım kuralları yükleniyor...", "loadingRewriteRules": "Yeniden yazım kuralları yükleniyor...",
"rewriteRulesNotLoaded": "DNS yeniden yazım kuralları yüklenemedi.", "rewriteRulesNotLoaded": "DNS yeniden yazım kuralları yüklenemedi.",
"noRewriteRules": "DNS yeniden yazım kuralları yok", "noRewriteRules": "DNS yeniden yazım kuralları yok",
"answer": "Yanıt", "answer": "Yanıt",
"deleteDnsRewrite": "DNS yeniden yazımı sil", "deleteDnsRewrite": "DNS yeniden yazımını sil",
"deleteDnsRewriteMessage": "Bu DNS yeniden yazımını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "deleteDnsRewriteMessage": "Bu DNS yeniden yazımını silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"dnsRewriteRuleDeleted": "DNS yeniden yazım kuralı başarıyla silindi", "dnsRewriteRuleDeleted": "DNS yeniden yazım kuralı başarıyla silindi",
"dnsRewriteRuleNotDeleted": "DNS yeniden yazım kuralı silinemedi", "dnsRewriteRuleNotDeleted": "DNS yeniden yazım kuralı silinemedi",
@ -409,7 +409,7 @@
"logsSettings": "Günlük ayarları", "logsSettings": "Günlük ayarları",
"enableLog": "Günlüğü etkinleştir", "enableLog": "Günlüğü etkinleştir",
"clearLogs": "Günlükleri temizle", "clearLogs": "Günlükleri temizle",
"anonymizeClientIp": "İstemci IP'sini anonimleştir", "anonymizeClientIp": "İstemci IP'sini gizle",
"hours6": "6 saat", "hours6": "6 saat",
"days30": "30 gün", "days30": "30 gün",
"days90": "90 gün", "days90": "90 gün",
@ -418,7 +418,7 @@
"logSettingsNotLoaded": "Günlük ayarları yüklenemedi.", "logSettingsNotLoaded": "Günlük ayarları yüklenemedi.",
"updatingSettings": "Ayarlar güncelleniyor...", "updatingSettings": "Ayarlar güncelleniyor...",
"logsConfigUpdated": "Günlük ayarları başarıyla güncellendi", "logsConfigUpdated": "Günlük ayarları başarıyla güncellendi",
"logsConfigNotUpdated": "Günlük ayarları başarıyla güncellendi", "logsConfigNotUpdated": "Günlük ayarları güncellenemedi",
"deletingLogs": "Günlükler temizleniyor...", "deletingLogs": "Günlükler temizleniyor...",
"logsCleared": "Günlükler başarıyla temizlendi", "logsCleared": "Günlükler başarıyla temizlendi",
"logsNotCleared": "Günlükler temizlenemedi", "logsNotCleared": "Günlükler temizlenemedi",
@ -427,17 +427,17 @@
"noItems": "Burada gösterilecek öğe yok", "noItems": "Burada gösterilecek öğe yok",
"dnsSettings": "DNS ayarları", "dnsSettings": "DNS ayarları",
"dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır", "dnsSettingsDescription": "DNS sunucuları ile bağlantıyı yapılandır",
"upstreamDns": "Üst kaynak DNS sunucuları", "upstreamDns": "Üst DNS sunucuları",
"bootstrapDns": "Önyükleme DNS sunucuları", "bootstrapDns": "Önyükleme DNS sunucuları",
"noUpstreamDns": "Üst kaynak DNS sunucuları eklenmedi.", "noUpstreamDns": "Üst DNS sunucuları eklenmedi.",
"dnsMode": "DNS modu", "dnsMode": "DNS modu",
"noDnsMode": "DNS modu seçili değil", "noDnsMode": "DNS modu seçili değil",
"loadBalancing": "Yük dengeleme", "loadBalancing": "Yük dengeleme",
"parallelRequests": "Paralel istekler", "parallelRequests": "Paralel istekler",
"fastestIpAddress": "En hızlı IP adresi", "fastestIpAddress": "En hızlı IP adresi",
"loadBalancingDescription": "Her seferinde bir üst kaynak sunucusuna sorgu yap. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.", "loadBalancingDescription": "Her seferinde bir üst sunucuya sorgu yapar. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
"parallelRequestsDescription": "Tüm üst kaynak sunucularını aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", "parallelRequestsDescription": "Tüm üst sunucuları aynı anda sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanılır.",
"fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürün. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.", "fastestIpAddressDescription": "Tüm DNS sunucularına sorgu yapın ve tüm yanıtlar arasında en hızlı IP adresini döndürür. Bu, AdGuard Home'un tüm DNS sunucularından yanıtları beklemesi gerektiği için DNS sorgularını yavaşlatır, ancak genel bağlantıyı iyileştirir.",
"noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.", "noBootstrapDns": "Önyükleme DNS sunucuları eklenmedi.",
"bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.", "bootstrapDnsServersInfo": "Önyükleme DNS sunucuları, üst kaynaklarda belirttiğiniz DoH/DoT çözümleyicilerinin IP adreslerini çözmek için kullanılır.",
"privateReverseDnsServers": "Özel ters DNS sunucuları", "privateReverseDnsServers": "Özel ters DNS sunucuları",
@ -446,36 +446,36 @@
"addItem": "Öğe ekle", "addItem": "Öğe ekle",
"noServerAddressesAdded": "Sunucu adresleri eklenmedi.", "noServerAddressesAdded": "Sunucu adresleri eklenmedi.",
"usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan", "usePrivateReverseDnsResolvers": "Özel ters DNS çözümleyicilerini kullan",
"usePrivateReverseDnsResolversDescription": "Bu üst kaynak sunucularını kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.", "usePrivateReverseDnsResolversDescription": "Bu üst sunucuları kullanarak yerel olarak sunulan adresler için ters DNS sorguları gerçekleştirin. Devre dışı bırakılırsa, AdGuard Home, DHCP, /etc/hosts vb. kaynaklardan bilinen istemciler dışında tüm PTR isteklerine NXDOMAIN yanıtı verir.",
"enableReverseResolving": "İstemcilerin IP adreslerinin ters çözümlemesini etkinleştir", "enableReverseResolving": "İstemcilerin IP adreslerinin ters çözümlemesini etkinleştir",
"enableReverseResolvingDescription": "İstemcilerin IP adreslerini karşılık gelen çözücülere PTR sorguları göndererek IP adreslerini tersine çözümleyerek (yerel istemciler için özel DNS sunucuları, genel IP adresine sahip istemciler için üst kaynak sunucuları) istemcilerin ana bilgisayar adlarını tersine çöz.", "enableReverseResolvingDescription": "Karşılık gelen çözümleyicilere (yerel istemciler için özel DNS sunucuları, genel IP adresleri olan istemciler için üst sunuculara) PTR sorguları göndererek istemcilerin IP adreslerini ana makine adlarının tersine çözer.",
"dnsServerSettings": "AdGuard Home DNS sunucusu ayarları", "dnsServerSettings": "AdGuard Home DNS sunucusu ayarları",
"limitRequestsSecond": "Saniye başına sınırlama isteği", "limitRequestsSecond": "Sıklık limiti",
"valueNotNumber": "Değer bir sayı değil", "valueNotNumber": "Değer bir sayı değil",
"enableEdns": "EDNS istemci alt ağını etkinleştir", "enableEdns": "EDNS istemci alt ağını etkinleştir",
"enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekleyin ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydedin.", "enableEdnsDescription": "Kaynak yönü isteklerine EDNS İstemci Alt Ağı Seçeneği (ECS) ekler ve istemciler tarafından gönderilen değerleri sorgu günlüğüne kaydeder.",
"enableDnssec": "DNSSEC'i etkinleştir", "enableDnssec": "DNSSEC'i etkinleştir",
"enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştir ve sonucu kontrol edin.(DNSSEC etkinleştirilmiş bir çözümleyici gerekli)", "enableDnssecDescription": "Giden DNS sorguları için DNSSEC özelliğini etkinleştirir ve sonucu kontrol eder. (DNSSEC etkinleştirilmiş bir çözümleyici gerekli)",
"disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak", "disableResolvingIpv6": "IPv6 adreslerinin çözümlenmesini devre dışı bırak",
"disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakın (AAAA yazın) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırın.", "disableResolvingIpv6Description": "IPv6 adresleri için tüm DNS sorgularını bırakır (AAAA yazar) ve HTTPS yanıtlarından IPv6 ipuçlarını kaldırır.",
"blockingMode": "Engelleme modu", "blockingMode": "Engelleme modu",
"defaultMode": "Varsayılan", "defaultMode": "Varsayılan",
"defaultDescription": "Reklam engelleme tarzı bir kural tarafından engellendiğinde sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için) /etc/hosts tarzı bir kural tarafından engellendiğinde kuralda belirtilen IP adresi ile yanıt verin.", "defaultDescription": "Reklam engelleme tarzı bir kural tarafından engellendiğinde sıfır IP adresi ile yanıt verir. (A için 0.0.0.0; :: AAAA için) /etc/hosts tarzı bir kural tarafından engellendiğinde kuralda belirtilen IP adresi ile yanıt verir.",
"refusedDescription": "REFUSED kodu ile yanıt verin.", "refusedDescription": "REFUSED kodu ile yanıt verir.",
"nxdomainDescription": "NXDOMAIN kodu ile yanıt verin.", "nxdomainDescription": "NXDOMAIN kodu ile yanıt verir.",
"nullIp": "Boş IP", "nullIp": "Boş IP",
"nullIpDescription": "Sıfır IP adresi ile yanıt verin. (A için 0.0.0.0; :: AAAA için)", "nullIpDescription": "Sıfır IP adresi ile yanıt verir. (A için 0.0.0.0; :: AAAA için)",
"customIp": "Özel IP", "customIp": "Özel IP",
"customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verin.", "customIpDescription": "Manuel olarak ayarlanmış bir IP adresi ile yanıt verir.",
"dnsCacheConfig": "DNS önbellek yapılandırması", "dnsCacheConfig": "DNS önbellek yapılandırması",
"cacheSize": "Önbellek boyutu", "cacheSize": "Önbellek boyutu",
"inBytes": "Bayt olarak", "inBytes": "Alınacak önbelleğin boyutunu ayarla (Bayt olarak)",
"overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl", "overrideMinimumTtl": "Minimum kullanım süresini geçersiz kıl",
"overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini ayarlayın (saniye olarak)", "overrideMinimumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan minimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
"overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl", "overrideMaximumTtl": "Maksimum kullanım süresini geçersiz kıl",
"overrideMaximumTtlDescription": "DNS önbelleğindeki girişler için maksimum kullanım süresi değerini ayarlayın (saniye olarak)", "overrideMaximumTtlDescription": "DNS yanıtlarını önbelleğe alırken üst sunucudan alınan maksimum kullanım süresi değerini (TTL) saniye olarak ayarlayın.",
"optimisticCaching": "İyimser önbelleğe alma", "optimisticCaching": "İyimser önbelleğe alma",
"optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlayın ve aynı zamanda bunları yenilemeye çalışın.", "optimisticCachingDescription": "Girişlerin süresi dolmuş olsa bile Adguard Home'un önbellekten yanıt vermesini sağlar ve aynı zamanda bunları yenilemeye çalışır.",
"loadingDnsConfig": "DNS yapılandırması yükleniyor...", "loadingDnsConfig": "DNS yapılandırması yükleniyor...",
"dnsConfigNotLoaded": "DNS yapılandırması yüklenemedi.", "dnsConfigNotLoaded": "DNS yapılandırması yüklenemedi.",
"blockingIpv4": "IPv4 engelleniyor", "blockingIpv4": "IPv4 engelleniyor",
@ -487,14 +487,14 @@
"dnsConfigNotSaved": "DNS sunucusu yapılandırması kaydedilemedi", "dnsConfigNotSaved": "DNS sunucusu yapılandırması kaydedilemedi",
"savingConfig": "Yapılandırma kaydediliyor...", "savingConfig": "Yapılandırma kaydediliyor...",
"someValueNotValid": "Bazı değerler geçerli değil", "someValueNotValid": "Bazı değerler geçerli değil",
"upstreamDnsDescription": "Üst kaynak sunucularını ve DNS modunu yapılandır", "upstreamDnsDescription": "Üst sunucuları ve DNS modunu yapılandır",
"bootstrapDnsDescription": "Önyükleme DNS sunucularını yapılandır", "bootstrapDnsDescription": "Önyükleme DNS sunucularını yapılandır",
"privateReverseDnsDescription": "Özel DNS çözümleyicileri yapılandır ve özel ters DNS çözümlemeyi etkinleştir", "privateReverseDnsDescription": "Özel DNS çözümleyicileri yapılandır ve özel ters DNS çözümlemeyi etkinleştir",
"dnsServerSettingsDescription": "Hız limiti, engelleme modu ve daha fazlasını yapılandır", "dnsServerSettingsDescription": "Hız limiti, engelleme modu ve daha fazlasını yapılandır",
"dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır", "dnsCacheConfigDescription": "Sunucunun DNS önbelleğini nasıl yöneteceğini yapılandır",
"comment": "Yorum", "comment": "Yorum",
"address": "Adres", "address": "Adres",
"commentsDescription": "Yorumlar her zaman # işareti ile başlar. Onu eklemenize gerek yok, otomatik olarak eklenir.", "commentsDescription": "Yorumlar her zaman # işareti ile başlar. Eklemenize gerek yok, otomatik olarak eklenecektir.",
"encryptionSettings": "Şifreleme ayarları", "encryptionSettings": "Şifreleme ayarları",
"encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği", "encryptionSettingsDescription": "Şifreleme (HTTPS/QUIC/TLS) desteği",
"loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...", "loadingEncryptionSettings": "Şifreleme ayarları yükleniyor...",
@ -513,13 +513,13 @@
"certificatesDescription": "Şifreleme kullanmak için, alan adınız için geçerli bir SSL sertifikası zinciri sağlamanız gereklidir. letsencrypt.org'dan ücretsiz bir sertifika alabilir veya güvenilir sertifika yetkililerinden satın alabilirsiniz.", "certificatesDescription": "Şifreleme kullanmak için, alan adınız için geçerli bir SSL sertifikası zinciri sağlamanız gereklidir. letsencrypt.org'dan ücretsiz bir sertifika alabilir veya güvenilir sertifika yetkililerinden satın alabilirsiniz.",
"certificateFilePath": "Sertifika dosyası belirle", "certificateFilePath": "Sertifika dosyası belirle",
"pasteCertificateContent": "Sertifika içeriğini yapıştır", "pasteCertificateContent": "Sertifika içeriğini yapıştır",
"certificatePath": "Sertifika dosya yolu", "certificatePath": "Sertifikanın dosya yolu",
"certificateContent": "Sertifika içeriği", "certificateContent": "Sertifika içeriği",
"privateKey": "Özel anahtarlar", "privateKey": "Özel anahtarlar",
"privateKeyFile": "Özel anahtar dosyası belirle", "privateKeyFile": "Özel anahtar dosyası belirle",
"pastePrivateKey": "Özel anahtar içeriğini yapıştır", "pastePrivateKey": "Özel anahtar içeriğini yapıştır",
"usePreviousKey": "Önceden kaydedilmiş anahtarı kullan", "usePreviousKey": "Önceden kaydedilmiş olan anahtarı kullan",
"privateKeyPath": "Özel anahtar dosya yolu", "privateKeyPath": "Özel anahtarın dosya yolu",
"invalidCertificate": "Geçersiz sertifika", "invalidCertificate": "Geçersiz sertifika",
"invalidPrivateKey": "Geçersiz özel anahtar", "invalidPrivateKey": "Geçersiz özel anahtar",
"validatingData": "Veri doğrulama", "validatingData": "Veri doğrulama",
@ -560,8 +560,8 @@
"validPrivateKey": "Geçerli özel anahtar", "validPrivateKey": "Geçerli özel anahtar",
"expirationDate": "Son kullanma tarihi", "expirationDate": "Son kullanma tarihi",
"keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.", "keysNotMatch": "Geçersiz bir sertifika veya anahtar: tls: özel anahtar genel anahtarla eşleşmiyor.",
"timeLogs": "Günlüklerdeki işlem süresi", "timeLogs": "Günlüklerde işlem süresini göster",
"timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini göster", "timeLogsDescription": "Günlükler listesinde zaman yerine işlem süresini gösterir.",
"hostNames": "Ana bilgisayar adları", "hostNames": "Ana bilgisayar adları",
"keyType": "Anahtar türü", "keyType": "Anahtar türü",
"updateAvailable": "Güncelleme mevcut", "updateAvailable": "Güncelleme mevcut",
@ -591,7 +591,7 @@
"updates": "Güncellemeler", "updates": "Güncellemeler",
"updatesDescription": "AdGuard Home sunucusunu güncelle", "updatesDescription": "AdGuard Home sunucusunu güncelle",
"updateNow": "Şimdi güncelle", "updateNow": "Şimdi güncelle",
"currentVersion": "Mevcut sürüm", "currentVersion": "Yüklü sürüm",
"requestStartUpdateFailed": "Güncellemeyi başlatma isteği başarısız oldu", "requestStartUpdateFailed": "Güncellemeyi başlatma isteği başarısız oldu",
"requestStartUpdateSuccessful": "Güncellemeyi başlatma isteği başarılı", "requestStartUpdateSuccessful": "Güncellemeyi başlatma isteği başarılı",
"serverUpdated": "Sunucu güncellendi", "serverUpdated": "Sunucu güncellendi",
@ -618,15 +618,15 @@
"copiedClipboard": "Panoya kopyalandı", "copiedClipboard": "Panoya kopyalandı",
"seeDetails": "Detayları gör", "seeDetails": "Detayları gör",
"listNotAvailable": "Liste mevcut değil", "listNotAvailable": "Liste mevcut değil",
"copyListUrl": "Liste bağlantısını kopyala", "copyListUrl": "Bağlantıyı kopyala",
"listUrlCopied": "Panoya kopyalanan bağlantı adresini listeleyin", "listUrlCopied": "Liste bağlantısı panoya kopyalandı",
"unsupportedVersion": "Desteklenmeyen sürüm", "unsupportedVersion": "Desteklenmeyen sürüm",
"unsupprtedVersionMessage": "Sunucu sürümünüz {version} için destek garantisi verilmiyor. Bu uygulamanın bu sunucu sürümüyle çalışmasında bazı sorunlar olabilir. AdGuard Home Yöneticisi, AdGuard Home sunucunun kararlı sürümleriyle çalışacak şekilde tasarlanmıştır. Alfa ve beta sürümleriyle çalışabilir, ancak uyumluluk garanti edilmez ve uygulama bu sürümlerle çalışırken bazı sorunlar yaşayabilir.", "unsupprtedVersionMessage": "Sunucu sürümünüz {version} için destek garantisi verilmiyor. Bu uygulamanın bu sunucu sürümüyle çalışmasında bazı sorunlar olabilir. AdGuard Home Yöneticisi, AdGuard Home sunucunun kararlı sürümleriyle çalışacak şekilde tasarlanmıştır. Alfa ve beta sürümleriyle çalışabilir, ancak uyumluluk garanti edilmez ve uygulama bu sürümlerle çalışırken bazı sorunlar yaşayabilir.",
"iUnderstand": "Anladım", "iUnderstand": "Anladım",
"appUpdates": "Uygulama güncellemeleri", "appUpdates": "Uygulama güncellemeleri",
"usingLatestVersion": "En son sürümü kullanıyorsunuz :)", "usingLatestVersion": "En son sürümü kullanıyorsunuz",
"ipLogs": "Günlüklerdeki IP", "ipLogs": "Günlüklerde IP adresini göster",
"ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini göster", "ipLogsDescription": "Günlükler listesinde istemci adı yerine IP adresini gösterir.",
"application": "Uygulama", "application": "Uygulama",
"combinedChart": "Birleştirilmiş grafik", "combinedChart": "Birleştirilmiş grafik",
"combinedChartDescription": "Tüm grafikleri bir araya getirir.", "combinedChartDescription": "Tüm grafikleri bir araya getirir.",
@ -655,11 +655,11 @@
"queries": "Sorgular", "queries": "Sorgular",
"adultSites": "Yetişkin içerikler", "adultSites": "Yetişkin içerikler",
"quickFilters": "Hızlı filtreler", "quickFilters": "Hızlı filtreler",
"searchDomainInternet": "İnternette alan adı ara", "searchDomainInternet": "Alan adını arat",
"hideServerAddress": "Sunucu adresini gizle", "hideServerAddress": "Sunucu adresini gizle",
"hideServerAddressDescription": "Ana ekranda sunucu adresini gizler.", "hideServerAddressDescription": "Ana ekranda sunucu adresini gizler.",
"topItemsOrder": "Öne çıkan öğeler sıralaması", "topItemsOrder": "Öne çıkan öğeler sıralaması",
"topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sırala", "topItemsOrderDescription": "Ana ekrandaki öne çıkan öğe listelerini sıralayın.",
"topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.", "topItemsReorderInfo": "Yeniden sıralamak için bir öğeyi basılı tutun ve kaydırın.",
"discardChanges": "Değişiklikleri iptal et", "discardChanges": "Değişiklikleri iptal et",
"discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?", "discardChangesDescription": "Değişiklikleri iptal etmek istediğinizden emin misiniz?",
@ -687,6 +687,124 @@
"unsupportedServerVersionMessage": "AdGuard Home sunucu sürümünüz çok eski ve AdGuard Home Manager tarafından desteklenmiyor. Bu uygulamayı kullanmak için AdGuard Home sunucunuzu daha yeni bir sürüme yükseltmeniz gerekecektir.", "unsupportedServerVersionMessage": "AdGuard Home sunucu sürümünüz çok eski ve AdGuard Home Manager tarafından desteklenmiyor. Bu uygulamayı kullanmak için AdGuard Home sunucunuzu daha yeni bir sürüme yükseltmeniz gerekecektir.",
"yourVersion": "Yüklü sürüm: {version}", "yourVersion": "Yüklü sürüm: {version}",
"minimumRequiredVersion": "Gerekli minimum sürüm: {version}", "minimumRequiredVersion": "Gerekli minimum sürüm: {version}",
"topUpstreams": "Öne çıkan üst kaynaklar", "topUpstreams": "Öne çıkan DNS sunucuları",
"averageUpstreamResponseTime": "Üst kaynak ortalama yanıt süresi" "averageUpstreamResponseTime": "DNS sunucuları işlem süresi" ,
"dhcpNotAvailable": "DHCP sunucusu kullanılamıyor.",
"osServerInstalledIncompatible": "AdGuard Home, işletim sisteminizde DHCP sunucusu çalıştıramıyor.",
"resetSettings": "Ayarları sıfırla",
"resetEncryptionSettingsDescription": "Şifreleme ayarlarını sıfırlamak istediğinizden emin misiniz?",
"resettingConfig": "Yapılandırma sıfırlanıyor...",
"configurationResetSuccessfully": "Yapılandırma başarıyla sıfırlandı",
"configurationResetError": "Yapılandırma sıfırlanamadı",
"testUpstreamDnsServers": "DNS sunucusunu test et",
"errorTestUpstreamDns": "DNS sunucularını test ederken hata oluştu.",
"useCustomIpEdns": "EDNS için özel IP kullan",
"useCustomIpEdnsDescription": "EDNS için özel IP kullanımına izin ver",
"sortingOptions": "Sıralama seçenekleri",
"fromHighestToLowest": "Yüksekten düşüğe",
"fromLowestToHighest": "Düşükten yükseğe",
"queryLogsAndStatistics": "Sorgu günlüğü ve istatistikler",
"ignoreClientQueryLog": "Sorgu günlüğünde bu istemciyi yoksay",
"ignoreClientStatistics": "İstatistiklerde bu istemciyi yoksay",
"savingChanges": "Değişiklikler kaydediliyor...",
"fallbackDnsServers": "Yedek DNS sunucuları",
"fallbackDnsServersDescription": "Yedek DNS sunucularını yapılandır",
"fallbackDnsServersInfo": "Üst DNS sunucuları yanıt vermediğinde kullanılan yedek DNS sunucularının listesi. Sözdizimi, yukarıdaki ana üst kaynak alanıyla aynıdır.",
"noFallbackDnsAdded": "Yedek DNS sunucusu eklenmedi.",
"blockedResponseTtl": "Engellenen yanıtın kullanım süresi",
"blockedResponseTtlDescription": "İstemcilerin filtrelenmiş bir yanıtı kaç saniye süreyle önbelleğe alması gerektiğini belirtir",
"invalidValue": "Geçersiz değer",
"noDataChart": "Bu grafiği görüntüleyecek veri yok.",
"noData": "Veri yok",
"unblockClient": "İstemci engelini kaldır",
"blockingClient": "İstemci engelleniyor...",
"unblockingClient": "İstemci engeli kaldırılıyor...",
"upstreamDnsCacheConfiguration": "DNS önbellek yapılandırması",
"enableDnsCachingClient": "Bu istemci için DNS önbelleğe almayı etkinleştir",
"dnsCacheSize": "DNS önbellek boyutu (Bayt cinsinden)",
"nameInvalid": "Ad gereklidir",
"oneIdentifierRequired": "En az bir tanımlayıcı gereklidir",
"dnsCacheNumber": "DNS önbellek boyutu bir rakam içermelidir",
"errors": "Hatalar",
"redirectHttpsWarning": "AdGuard Home sunucunuzda \"Otomatik olarak HTTPS'e yönlendir\" seçeneğini etkinleştirdiyseniz, bir HTTPS bağlantısı seçmeli ve sunucunuzun HTTPS bağlantı noktasını kullanmalısınız.",
"logsSettingsDescription": "Sorgu günlüklerini yapılandır",
"ignoredDomains": "Yok sayılan alan adları",
"noIgnoredDomainsAdded": "Yok sayılacak alan adı eklenmedi",
"pauseServiceBlocking": "Hizmet engellemeyi duraklat",
"newSchedule": "Yeni program",
"editSchedule": "Programı düzenle",
"timezone": "Zaman dilimi",
"monday": "Pazartesi",
"tuesday": "Salı",
"wednesday": "Çarşamba",
"thursday": "Perşembe",
"friday": "Cuma",
"saturday": "Cumartesi",
"sunday": "Pazar",
"from": "Başlangıç",
"to": "Bitiş",
"selectStartTime": "Başlangıç zamanını seç",
"selectEndTime": "Bitiş zamanını seç",
"startTimeBeforeEndTime": "Başlangıç zamanı bitiş zamanından önce olmalıdır.",
"noBlockingScheduleThisDevice": "Bu cihaz için herhangi bir engelleme programı bulunmamaktadır.",
"selectTimezone": "Bir zaman dilimi seç",
"selectClientsFiltersInfo": "Görüntülemek istediğiniz istemcileri seçin. Hiçbir istemci seçilmemişse, hepsi görüntülenecektir.",
"noDataThisSection": "Bu bölüm için veri yok.",
"statisticsSettings": "İstatistik ayarları",
"statisticsSettingsDescription": "İstatistikler için veri toplamayı yapılandır",
"loadingStatisticsSettings": "İstatistik ayarları yükleniyor...",
"statisticsSettingsLoadError": "İstatistik ayarları yüklenirken bir hata oluştu.",
"statisticsConfigUpdated": "İstatistik ayarları başarıyla güncellendi",
"statisticsConfigNotUpdated": "İstatistik ayarları güncellenemedi",
"customTimeInHours": "Özel zaman (Saat olarak)",
"invalidTime": "Geçersiz zaman",
"removeDomain": "Alan adını kaldır",
"addDomain": "Alan adı ekle",
"notLess1Hour": "Zaman 1 saatten az olamaz",
"rateLimit": "Hız sınırı",
"subnetPrefixLengthIpv4": "IPv4 için alt ağ önek uzunluğu",
"subnetPrefixLengthIpv6": "IPv6 için alt ağ önek uzunluğu",
"rateLimitAllowlist": "Hız sınırlama izin listesi",
"rateLimitAllowlistDescription": "Hız sınırlamasından hariç tutulan IP adresleri",
"dnsOptions": "DNS ayarları",
"editor": "Editör",
"editCustomRules": "Özel kuralları düzenle",
"savingCustomRules": "Özel kurallar kaydediliyor...",
"customRulesUpdatedSuccessfully": "Özel kurallar başarıyla güncellendi",
"customRulesNotUpdated": "Özel kurallar güncellenemedi",
"reorder": "Sırala",
"showHide": "Göster/gizle",
"noElementsReorderMessage": "Burada yeniden sıralamak için göster/gizle sekmesindeki bazı öğeleri etkinleştirin.",
"enablePlainDns": "Düz DNS'i etkinleştir",
"enablePlainDnsDescription": "Düz DNS varsayılan olarak etkindir. Tüm aygıtları şifrelenmiş DNS kullanmaya zorlamak için bunu devre dışı bırakabilirsiniz. Bunu yapmak için en az bir şifrelenmiş DNS protokolünü etkinleştirmeniz gerekir.",
"date": "Tarih",
"loadingChangelog": "Değişiklikler yükleniyor...",
"invalidIpOrUrl": "Geçersiz IP adresi veya URL",
"addPersistentClient": "Kalıcı istemci olarak ekle",
"blockThisClientOnly": "Yalnızca bu istemci için engelle",
"unblockThisClientOnly": "Yalnızca bu istemci için engeli kaldır",
"domainBlockedThisClient": "Bu istemci için {domain} engellendi",
"domainUnblockedThisClient": "Bu istemci için {domain} engeli kaldırıldı",
"disallowThisClient": "Bu istemciye izin verme",
"allowThisClient": "Bu istemciye izin ver",
"clientAllowedSuccessfully": "İstemciye başarıyla izin verildi",
"clientDisallowedSuccessfully": "İstemci başarıyla reddedildi",
"changesNotSaved": "Değişiklikler kaydedilemedi",
"allowingClient": "İstemciye izin veriliyor...",
"disallowingClient": "İstemci reddediliyor...",
"clientIpCopied": "İstemci IP'si panoya kopyalandı",
"clientNameCopied": "İstemci adı panoya kopyalandı",
"dnsServerAddressCopied": "DNS sunucu adresi panoya kopyalandı",
"select": "Seç",
"liveLogs": "Canlı günlükler",
"hereWillAppearRealtimeLogs": "Burada gerçek zamanlı günlükler görünecek.",
"applicationDetails": "Uygulama detayları",
"applicationDetailsDescription": "Uygulama deposu, mevcut olduğu mağazalar ve daha fazlası",
"myOtherApps": "Diğer uygulamalarım",
"myOtherAppsDescription": "Diğer uygulamalarımı kontrol et, bağış yap, destekle iletişime geç ve daha fazlası",
"topToBottom": "Yukarıdan aşağıya",
"bottomToTop": "Aşağıdan yukarıya",
"upstreamTimeout": "Üst sunucu zaman aşımı",
"upstreamTimeoutHelper": "Üst DNS sunucusundan yanıt bekleme süresini saniye cinsinden belirtir",
"fieldCannotBeEmpty": "Bu alan boş olamaz"
} }

View file

@ -23,7 +23,7 @@
"invalidUsernamePassword": "用户名或密码错误", "invalidUsernamePassword": "用户名或密码错误",
"tooManyAttempts": "尝试次数过多,请稍后再试", "tooManyAttempts": "尝试次数过多,请稍后再试",
"cantReachServer": "无法连接服务器,请检查连接信息是否正确", "cantReachServer": "无法连接服务器,请检查连接信息是否正确",
"sslError": "SSL 错误转到 设置 > 高级设置 并启用 不检查 SSL 证书", "sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书",
"unknownError": "未知错误", "unknownError": "未知错误",
"connectionNotCreated": "连接无法创建", "connectionNotCreated": "连接无法创建",
"connecting": "正在连接...", "connecting": "正在连接...",
@ -158,7 +158,7 @@
"notSelected": "未选择", "notSelected": "未选择",
"resetFilters": "重置过滤器", "resetFilters": "重置过滤器",
"noLogsDisplay": "无日志可显示", "noLogsDisplay": "无日志可显示",
"noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。", "noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ",
"apply": "应用", "apply": "应用",
"selectAll": "全选", "selectAll": "全选",
"unselectAll": "取消全选", "unselectAll": "取消全选",
@ -235,7 +235,7 @@
"urlNotValid": "URL 无效", "urlNotValid": "URL 无效",
"urlAbsolutePath": "URL 或绝对路径", "urlAbsolutePath": "URL 或绝对路径",
"addingList": "正在添加订阅规则...", "addingList": "正在添加订阅规则...",
"listAdded": "订阅规则添加成功 已添加项目:", "listAdded": "订阅规则添加成功 已添加项目:",
"listAlreadyAdded": "订阅规则已被添加", "listAlreadyAdded": "订阅规则已被添加",
"listUrlInvalid": "订阅规则 URL 无效", "listUrlInvalid": "订阅规则 URL 无效",
"listNotAdded": "无法添加订阅规则", "listNotAdded": "无法添加订阅规则",
@ -282,7 +282,7 @@
"clientsNotLoaded": "无法加载客户端", "clientsNotLoaded": "无法加载客户端",
"noAllowedClients": "没有已允许的客户端", "noAllowedClients": "没有已允许的客户端",
"allowedClientsDescription": "如果此列表中有条目AdGuard Home 将仅接受来自这些客户端的请求", "allowedClientsDescription": "如果此列表中有条目AdGuard Home 将仅接受来自这些客户端的请求",
"blockedClientsDescription": "如果此列表中有条目AdGuard Home 将拒绝来自这些客户端的请求如果已允许客户端中有条目,则会忽略此字段", "blockedClientsDescription": "如果此列表中有条目AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段",
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中", "disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID", "addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
"clientIdentifier": "客户端标识符", "clientIdentifier": "客户端标识符",
@ -358,7 +358,7 @@
"seconds": "{time} 秒", "seconds": "{time} 秒",
"leaseTimeNotValid": "租期无效", "leaseTimeNotValid": "租期无效",
"restoreConfiguration": "重置配置", "restoreConfiguration": "重置配置",
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置此操作无法撤消", "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消",
"changeInterface": "更改接口", "changeInterface": "更改接口",
"savingSettings": "正在保存设置...", "savingSettings": "正在保存设置...",
"settingsSaved": "设置保存成功", "settingsSaved": "设置保存成功",
@ -372,7 +372,7 @@
"staticLeaseDeleted": "DHCP 静态租用删除成功", "staticLeaseDeleted": "DHCP 静态租用删除成功",
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用", "staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
"deleteStaticLease": "删除静态租用", "deleteStaticLease": "删除静态租用",
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除此操作无法撤消", "deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消",
"addStaticLease": "添加静态租用", "addStaticLease": "添加静态租用",
"macAddress": "MAC 地址", "macAddress": "MAC 地址",
"macAddressNotValid": "MAC 地址无效", "macAddressNotValid": "MAC 地址无效",
@ -384,7 +384,7 @@
"staticLeaseExists": "DHCP 静态租用已存在", "staticLeaseExists": "DHCP 静态租用已存在",
"serverNotConfigured": "未配置服务器", "serverNotConfigured": "未配置服务器",
"restoreLeases": "重置租用", "restoreLeases": "重置租用",
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用此操作无法撤消", "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消",
"restoringLeases": "正在重置租用...", "restoringLeases": "正在重置租用...",
"leasesRestored": "租用重置成功", "leasesRestored": "租用重置成功",
"leasesNotRestored": "无法重置租用", "leasesNotRestored": "无法重置租用",
@ -427,24 +427,24 @@
"dnsSettingsDescription": "配置与 DNS 服务器的连接", "dnsSettingsDescription": "配置与 DNS 服务器的连接",
"upstreamDns": "上游 DNS 服务器", "upstreamDns": "上游 DNS 服务器",
"bootstrapDns": "引导 DNS 服务器", "bootstrapDns": "引导 DNS 服务器",
"noUpstreamDns": "未添加上游 DNS 服务器", "noUpstreamDns": "未添加上游 DNS 服务器 ",
"dnsMode": "DNS 模式", "dnsMode": "DNS 模式",
"noDnsMode": "未选择 DNS 模式", "noDnsMode": "未选择 DNS 模式",
"loadBalancing": "负载均衡", "loadBalancing": "负载均衡",
"parallelRequests": "并行请求", "parallelRequests": "并行请求",
"fastestIpAddress": "最快的 IP 地址", "fastestIpAddress": "最快的 IP 地址",
"loadBalancingDescription": "每次查询一个上游服务器AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", "loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器", "parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
"noBootstrapDns": "未添加引导 DNS 服务器", "noBootstrapDns": "未添加引导 DNS 服务器 ",
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址", "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ",
"privateReverseDnsServers": "私有反向 DNS 服务器", "privateReverseDnsServers": "私有反向 DNS 服务器",
"privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\"如果未设置AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址", "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器 这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\" 如果未设置AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址 ",
"reverseDnsDefault": "默认情况下AdGuard Home 使用以下默认反向 DNS 解析器", "reverseDnsDefault": "默认情况下AdGuard Home 使用以下默认反向 DNS 解析器",
"addItem": "添加项目", "addItem": "添加项目",
"noServerAddressesAdded": "未添加服务器地址", "noServerAddressesAdded": "未添加服务器地址",
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器", "usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询如果禁用AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
"enableReverseResolving": "启用客户端 IP 地址的反向解析", "enableReverseResolving": "启用客户端 IP 地址的反向解析",
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)", "enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
"dnsServerSettings": "AdGuard Home DNS 服务器设置", "dnsServerSettings": "AdGuard Home DNS 服务器设置",
@ -492,7 +492,7 @@
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存", "dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
"comment": "注释", "comment": "注释",
"address": "地址", "address": "地址",
"commentsDescription": "注释始终以 # 开头您无需添加它,系统将自动添加", "commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加",
"encryptionSettings": "加密设置", "encryptionSettings": "加密设置",
"encryptionSettingsDescription": "加密HTTPS/QUIC/TLS支持", "encryptionSettingsDescription": "加密HTTPS/QUIC/TLS支持",
"loadingEncryptionSettings": "正在加载加密设置...", "loadingEncryptionSettings": "正在加载加密设置...",
@ -502,13 +502,13 @@
"enableEncryptionDescription": "如果启用加密AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求", "enableEncryptionDescription": "如果启用加密AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
"serverConfiguration": "服务器配置", "serverConfiguration": "服务器配置",
"domainName": "域名", "domainName": "域名",
"domainNameDescription": "如果设置AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配", "domainNameDescription": "如果设置AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配",
"redirectHttps": "自动重定向到 HTTPS", "redirectHttps": "自动重定向到 HTTPS",
"httpsPort": "HTTPS 端口", "httpsPort": "HTTPS 端口",
"tlsPort": "DNS-over-TLS 端口", "tlsPort": "DNS-over-TLS 端口",
"dnsOverQuicPort": "DNS-over-QUIC 端口", "dnsOverQuicPort": "DNS-over-QUIC 端口",
"certificates": "证书", "certificates": "证书",
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
"certificateFilePath": "设置证书文件路径", "certificateFilePath": "设置证书文件路径",
"pasteCertificateContent": "粘贴证书内容", "pasteCertificateContent": "粘贴证书内容",
"certificatePath": "证书路径", "certificatePath": "证书路径",
@ -526,13 +526,13 @@
"encryptionConfigSaved": "加密配置已成功保存", "encryptionConfigSaved": "加密配置已成功保存",
"encryptionConfigNotSaved": "无法保存加密配置", "encryptionConfigNotSaved": "无法保存加密配置",
"configError": "配置错误", "configError": "配置错误",
"enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。", "enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ",
"enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。", "enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ",
"noItemsSearch": "没有匹配的项目", "noItemsSearch": "没有匹配的项目",
"clearSearch": "清除搜索", "clearSearch": "清除搜索",
"exitSearch": "退出搜索", "exitSearch": "退出搜索",
"searchClients": "搜索客户端", "searchClients": "搜索客户端",
"noClientsSearch": "没有匹配的客户端", "noClientsSearch": "没有匹配的客户端 ",
"customization": "定制", "customization": "定制",
"customizationDescription": "自定义此应用程序", "customizationDescription": "自定义此应用程序",
"color": "颜色", "color": "颜色",
@ -598,7 +598,7 @@
"checkUpdates": "检查更新", "checkUpdates": "检查更新",
"requestingUpdate": "正在请求更新...", "requestingUpdate": "正在请求更新...",
"autoupdateUnavailable": "自动更新不可用", "autoupdateUnavailable": "自动更新不可用",
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器", "autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器",
"minute": "{time} 分钟", "minute": "{time} 分钟",
"minutes": "{time} 分钟", "minutes": "{time} 分钟",
"hour": "{time} 小时", "hour": "{time} 小时",
@ -606,7 +606,7 @@
"remainingTime": "剩余时间", "remainingTime": "剩余时间",
"safeSearchSettings": "安全搜索设置", "safeSearchSettings": "安全搜索设置",
"loadingSafeSearchSettings": "正在加载安全搜索设置...", "loadingSafeSearchSettings": "正在加载安全搜索设置...",
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错", "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ",
"loadingLogsSettings": "正在加载日志设置...", "loadingLogsSettings": "正在加载日志设置...",
"selectOptionLeftColumn": "在左侧栏中选择一个选项", "selectOptionLeftColumn": "在左侧栏中选择一个选项",
"selectClientLeftColumn": "在左侧栏中选择一个客户端", "selectClientLeftColumn": "在左侧栏中选择一个客户端",
@ -619,7 +619,7 @@
"copyListUrl": "复制订阅规则 URL", "copyListUrl": "复制订阅规则 URL",
"listUrlCopied": "订阅规则 URL 已复制到剪贴板", "listUrlCopied": "订阅规则 URL 已复制到剪贴板",
"unsupportedVersion": "不支持的版本", "unsupportedVersion": "不支持的版本",
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
"iUnderstand": "我了解", "iUnderstand": "我了解",
"appUpdates": "应用程序更新", "appUpdates": "应用程序更新",
"usingLatestVersion": "您正在使用最新版本", "usingLatestVersion": "您正在使用最新版本",
@ -658,7 +658,136 @@
"hideServerAddressDescription": "在主页上隐藏服务器地址", "hideServerAddressDescription": "在主页上隐藏服务器地址",
"topItemsOrder": "顶部项目顺序", "topItemsOrder": "顶部项目顺序",
"topItemsOrderDescription": "排列主页顶部项目列表", "topItemsOrderDescription": "排列主页顶部项目列表",
"topItemsReorderInfo": "按住并滑动一个项目以重新排序", "topItemsReorderInfo": "按住并滑动一个项目以重新排序 ",
"discardChanges": "放弃更改", "discardChanges": "放弃更改",
"discardChangesDescription": "您确定要放弃更改吗?" "discardChangesDescription": "您确定要放弃更改吗?",
"others": "其他",
"showChart": "显示图表",
"hideChart": "隐藏图表",
"showTopItemsChart": "显示顶部项目图表",
"showTopItemsChartDescription": "默认情况下在移动视图中显示顶部项目部分的环形图 ",
"openMenu": "打开菜单",
"closeMenu": "关闭菜单",
"openListUrl": "打开列表URL",
"selectionMode": "选择模式",
"enableDisableSelected": "启用或禁用选定项目",
"deleteSelected": "删除选定项目",
"deleteSelectedLists": "删除选定列表",
"allSelectedListsDeletedSuccessfully": "所有选定列表已成功删除 ",
"deletionResult": "删除结果",
"deletingLists": "正在删除列表...",
"failedElements": "失败元素",
"processingLists": "正在处理列表...",
"enableDisableResult": "启用或禁用结果",
"selectedListsEnabledDisabledSuccessfully": "所有选定列表已成功启用或禁用",
"sslWarning": "如果您正在使用带有自签名证书的 HTTPS 连接,请确保在设置 > 高级设置中启用 '不检查 SSL 证书' ",
"unsupportedServerVersion": "不支持的服务器版本",
"unsupportedServerVersionMessage": "您的 AdGuard Home 服务器版本过旧,不受 AdGuard Home Manager 支持 您需要将 AdGuard Home 服务器升级到更新的版本才能使用此应用程序 ",
"yourVersion": "您的版本:{version}",
"minimumRequiredVersion": "最低要求版本:{version}",
"topUpstreams": "主要上游",
"averageUpstreamResponseTime": "平均上游响应时间",
"dhcpNotAvailable": "DHCP 服务器不可用 ",
"osServerInstalledIncompatible": "服务器安装的操作系统与此功能不兼容 ",
"resetSettings": "重置设置",
"resetEncryptionSettingsDescription": "您确定要将加密设置重置为默认值吗?",
"resettingConfig": "正在重置配置...",
"configurationResetSuccessfully": "配置已成功重置",
"configurationResetError": "配置无法重置",
"testUpstreamDnsServers": "测试上游 DNS 服务器",
"errorTestUpstreamDns": "测试上游 DNS 服务器时出错 ",
"useCustomIpEdns": "使用 EDNS 的自定义 IP",
"useCustomIpEdnsDescription": "允许使用 EDNS 的自定义 IP",
"sortingOptions": "排序选项",
"fromHighestToLowest": "从高到低",
"fromLowestToHighest": "从低到高",
"queryLogsAndStatistics": "查询日志和统计",
"ignoreClientQueryLog": "在查询日志中忽略此客户端",
"ignoreClientStatistics": "在统计中忽略此客户端",
"savingChanges": "正在保存更改...",
"fallbackDnsServers": "备用 DNS 服务器",
"fallbackDnsServersDescription": "配置备用 DNS 服务器",
"fallbackDnsServersInfo": "当上游 DNS 服务器无响应时使用的备用 DNS 服务器列表 语法与上面的主上游字段相同 ",
"noFallbackDnsAdded": "未添加备用 DNS 服务器 ",
"blockedResponseTtl": "被阻止的响应 TTL",
"blockedResponseTtlDescription": "指定客户端应缓存过滤响应的秒数",
"invalidValue": "无效值",
"noDataChart": "没有数据显示此图表 ",
"noData": "无数据",
"unblockClient": "解除客户端封锁",
"blockingClient": "正在封锁客户端...",
"unblockingClient": "正在解除客户端封锁...",
"upstreamDnsCacheConfiguration": "DNS 上游缓存配置",
"enableDnsCachingClient": "为此客户端启用 DNS 缓存",
"dnsCacheSize": "DNS 缓存大小",
"nameInvalid": "名称是必需的",
"oneIdentifierRequired": "至少需要一个标识符",
"dnsCacheNumber": "DNS 缓存大小必须是一个数字",
"errors": "错误",
"redirectHttpsWarning": "如果您在 AdGuard Home 服务器上启用了 '自动重定向到 HTTPS',则必须选择 HTTPS 连接并使用服务器的 HTTPS 端口 ",
"logsSettingsDescription": "配置查询日志",
"ignoredDomains": "忽略的域名",
"noIgnoredDomainsAdded": "未添加忽略的域名",
"pauseServiceBlocking": "暂停服务阻止",
"newSchedule": "新计划",
"editSchedule": "编辑计划",
"timezone": "时区",
"monday": "星期一",
"tuesday": "星期二",
"wednesday": "星期三",
"thursday": "星期四",
"friday": "星期五",
"saturday": "星期六",
"sunday": "星期日",
"from": "从",
"to": "到",
"selectStartTime": "选择开始时间",
"selectEndTime": "选择结束时间",
"startTimeBeforeEndTime": "开始时间必须在结束时间之前 ",
"noBlockingScheduleThisDevice": "此设备没有阻止计划 ",
"selectTimezone": "选择时区",
"selectClientsFiltersInfo": "选择您想要显示的客户端 如果没有选择任何客户端,将显示所有客户端 ",
"noDataThisSection": "本节没有数据 ",
"statisticsSettings": "统计设置",
"statisticsSettingsDescription": "配置统计数据收集",
"loadingStatisticsSettings": "正在加载统计设置...",
"statisticsSettingsLoadError": "加载统计设置时发生错误 ",
"statisticsConfigUpdated": "统计设置成功更新",
"statisticsConfigNotUpdated": "统计设置无法更新",
"customTimeInHours": "自定义时间(以小时为单位)",
"invalidTime": "无效时间",
"removeDomain": "移除域名",
"addDomain": "添加域名",
"notLess1Hour": "时间不能少于 1 小时",
"rateLimit": "速率限制",
"subnetPrefixLengthIpv4": "IPv4 的子网前缀长度",
"subnetPrefixLengthIpv6": "IPv6 的子网前缀长度",
"rateLimitAllowlist": "速率限制白名单",
"rateLimitAllowlistDescription": "从速率限制中排除的 IP 地址",
"dnsOptions": "DNS 选项",
"editor": "编辑器",
"editCustomRules": "编辑自定义规则",
"savingCustomRules": "正在保存自定义规则...",
"customRulesUpdatedSuccessfully": "自定义规则成功更新",
"customRulesNotUpdated": "自定义规则无法更新",
"reorder": "重新排序",
"showHide": "显示/隐藏",
"noElementsReorderMessage": "在显示/隐藏标签页上启用一些元素,然后在这里重新排序 ",
"enablePlainDns": "启用普通 DNS",
"enablePlainDnsDescription": "默认启用普通 DNS 您可以禁用它,强制所有设备使用加密 DNS 要做到这一点,您必须至少启用一个加密 DNS 协议 ",
"date": "日期",
"loadingChangelog": "正在加载更新日志...",
"invalidIpOrUrl": "无效的 IP 地址或 URL",
"addPersistentClient": "添加为持久客户端",
"blockThisClientOnly": "仅为此客户端封锁",
"unblockThisClientOnly": "仅为此客户端解封",
"domainBlockedThisClient": "{domain} 已为此客户端封锁",
"domainUnblockedThisClient": "{domain} 已为此客户端解封",
"disallowThisClient": "禁止此客户端",
"allowThisClient": "允许此客户端",
"clientAllowedSuccessfully": "客户端成功允许",
"clientDisallowedSuccessfully": "客户端成功禁止",
"changesNotSaved": "更改无法保存",
"allowingClient": "正在允许客户端...",
"disallowingClient": "正在禁止客户端..."
} }

View file

@ -23,7 +23,7 @@
"invalidUsernamePassword": "用户名或密码错误", "invalidUsernamePassword": "用户名或密码错误",
"tooManyAttempts": "尝试次数过多,请稍后再试", "tooManyAttempts": "尝试次数过多,请稍后再试",
"cantReachServer": "无法连接服务器,请检查连接信息是否正确", "cantReachServer": "无法连接服务器,请检查连接信息是否正确",
"sslError": "SSL 错误转到 设置 > 高级设置 并启用 不检查 SSL 证书", "sslError": "SSL 错误 转到 设置 > 高级设置 并启用 不检查 SSL 证书",
"unknownError": "未知错误", "unknownError": "未知错误",
"connectionNotCreated": "连接无法创建", "connectionNotCreated": "连接无法创建",
"connecting": "正在连接...", "connecting": "正在连接...",
@ -158,7 +158,7 @@
"notSelected": "未选择", "notSelected": "未选择",
"resetFilters": "重置过滤器", "resetFilters": "重置过滤器",
"noLogsDisplay": "无日志可显示", "noLogsDisplay": "无日志可显示",
"noLogsThatOld": "选择的时间段可能没有日志。尝试选择近期时间。", "noLogsThatOld": "选择的时间段可能没有日志 请尝试选择近期时间 ",
"apply": "应用", "apply": "应用",
"selectAll": "全选", "selectAll": "全选",
"unselectAll": "取消全选", "unselectAll": "取消全选",
@ -235,7 +235,7 @@
"urlNotValid": "URL 无效", "urlNotValid": "URL 无效",
"urlAbsolutePath": "URL 或绝对路径", "urlAbsolutePath": "URL 或绝对路径",
"addingList": "正在添加订阅规则...", "addingList": "正在添加订阅规则...",
"listAdded": "订阅规则添加成功 已添加项目:", "listAdded": "订阅规则添加成功 已添加项目:",
"listAlreadyAdded": "订阅规则已被添加", "listAlreadyAdded": "订阅规则已被添加",
"listUrlInvalid": "订阅规则 URL 无效", "listUrlInvalid": "订阅规则 URL 无效",
"listNotAdded": "无法添加订阅规则", "listNotAdded": "无法添加订阅规则",
@ -282,7 +282,7 @@
"clientsNotLoaded": "无法加载客户端", "clientsNotLoaded": "无法加载客户端",
"noAllowedClients": "没有已允许的客户端", "noAllowedClients": "没有已允许的客户端",
"allowedClientsDescription": "如果此列表中有条目AdGuard Home 将仅接受来自这些客户端的请求", "allowedClientsDescription": "如果此列表中有条目AdGuard Home 将仅接受来自这些客户端的请求",
"blockedClientsDescription": "如果此列表中有条目AdGuard Home 将拒绝来自这些客户端的请求如果已允许客户端中有条目,则会忽略此字段", "blockedClientsDescription": "如果此列表中有条目AdGuard Home 将拒绝来自这些客户端的请求 如果已允许客户端中有条目,则会忽略此字段",
"disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中", "disallowedDomainsDescription": "AdGuard Home 会丢弃与这些域名匹配的 DNS 查询,这些查询甚至不会出现在查询日志中",
"addClientFieldDescription": "CIDR、IP 地址或客户端 ID", "addClientFieldDescription": "CIDR、IP 地址或客户端 ID",
"clientIdentifier": "客户端标识符", "clientIdentifier": "客户端标识符",
@ -358,7 +358,7 @@
"seconds": "{time} 秒", "seconds": "{time} 秒",
"leaseTimeNotValid": "租期无效", "leaseTimeNotValid": "租期无效",
"restoreConfiguration": "重置配置", "restoreConfiguration": "重置配置",
"restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置此操作无法撤消", "restoreConfigurationMessage": "您确定要继续吗?这将重置所有配置 此操作无法撤消",
"changeInterface": "更改接口", "changeInterface": "更改接口",
"savingSettings": "正在保存设置...", "savingSettings": "正在保存设置...",
"settingsSaved": "设置保存成功", "settingsSaved": "设置保存成功",
@ -372,7 +372,7 @@
"staticLeaseDeleted": "DHCP 静态租用删除成功", "staticLeaseDeleted": "DHCP 静态租用删除成功",
"staticLeaseNotDeleted": "无法删除 DHCP 静态租用", "staticLeaseNotDeleted": "无法删除 DHCP 静态租用",
"deleteStaticLease": "删除静态租用", "deleteStaticLease": "删除静态租用",
"deleteStaticLeaseDescription": "DHCP 静态租用将被删除此操作无法撤消", "deleteStaticLeaseDescription": "DHCP 静态租用将被删除 此操作无法撤消",
"addStaticLease": "添加静态租用", "addStaticLease": "添加静态租用",
"macAddress": "MAC 地址", "macAddress": "MAC 地址",
"macAddressNotValid": "MAC 地址无效", "macAddressNotValid": "MAC 地址无效",
@ -384,7 +384,7 @@
"staticLeaseExists": "DHCP 静态租用已存在", "staticLeaseExists": "DHCP 静态租用已存在",
"serverNotConfigured": "未配置服务器", "serverNotConfigured": "未配置服务器",
"restoreLeases": "重置租用", "restoreLeases": "重置租用",
"restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用此操作无法撤消", "restoreLeasesMessage": "您确定要继续吗?这将重置所有现有租用 此操作无法撤消",
"restoringLeases": "正在重置租用...", "restoringLeases": "正在重置租用...",
"leasesRestored": "租用重置成功", "leasesRestored": "租用重置成功",
"leasesNotRestored": "无法重置租用", "leasesNotRestored": "无法重置租用",
@ -427,24 +427,24 @@
"dnsSettingsDescription": "配置与 DNS 服务器的连接", "dnsSettingsDescription": "配置与 DNS 服务器的连接",
"upstreamDns": "上游 DNS 服务器", "upstreamDns": "上游 DNS 服务器",
"bootstrapDns": "引导 DNS 服务器", "bootstrapDns": "引导 DNS 服务器",
"noUpstreamDns": "未添加上游 DNS 服务器", "noUpstreamDns": "未添加上游 DNS 服务器 ",
"dnsMode": "DNS 模式", "dnsMode": "DNS 模式",
"noDnsMode": "未选择 DNS 模式", "noDnsMode": "未选择 DNS 模式",
"loadBalancing": "负载均衡", "loadBalancing": "负载均衡",
"parallelRequests": "并行请求", "parallelRequests": "并行请求",
"fastestIpAddress": "最快的 IP 地址", "fastestIpAddress": "最快的 IP 地址",
"loadBalancingDescription": "每次查询一个上游服务器AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器", "loadBalancingDescription": "每次查询一个上游服务器 AdGuard Home 使用其加权随机算法选择服务器,以便更频繁地使用最快的服务器",
"parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器", "parallelRequestsDescription": "使用并行查询同时加速解析,同时查询所有上游服务器",
"fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性", "fastestIpAddressDescription": "查询所有 DNS 服务器并返回所有响应中最快的 IP 地址 这会减慢 DNS 查询,因为 AdGuard Home 必须等待所有 DNS 服务器的响应,但可以改善整体连接性",
"noBootstrapDns": "未添加引导 DNS 服务器", "noBootstrapDns": "未添加引导 DNS 服务器 ",
"bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址", "bootstrapDnsServersInfo": "引导 DNS 服务器用于解析您指定的上游 DoH/DoT 解析器的 IP 地址 ",
"privateReverseDnsServers": "私有反向 DNS 服务器", "privateReverseDnsServers": "私有反向 DNS 服务器",
"privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\"如果未设置AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址", "privateReverseDnsServersDescription": "AdGuard Home 用于本地 PTR 查询的 DNS 服务器 这些服务器用于解析私有 IP 范围内的地址的 PTR 请求,例如 \"192.168.12.34\" 如果未设置AdGuard Home 将使用操作系统的默认 DNS 解析器地址,但不包括 AdGuard Home 本身的地址 ",
"reverseDnsDefault": "默认情况下AdGuard Home 使用以下默认反向 DNS 解析器", "reverseDnsDefault": "默认情况下AdGuard Home 使用以下默认反向 DNS 解析器",
"addItem": "添加项目", "addItem": "添加项目",
"noServerAddressesAdded": "未添加服务器地址", "noServerAddressesAdded": "未添加服务器地址",
"usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器", "usePrivateReverseDnsResolvers": "使用私有反向 DNS 解析器",
"usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询如果禁用AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN", "usePrivateReverseDnsResolversDescription": "使用这些上游服务器执行本地提供的地址的反向 DNS 查询 如果禁用AdGuard Home 会对所有此类 PTR 请求(除了来自 DHCP、/etc/hosts 等已知客户端)响应 NXDOMAIN",
"enableReverseResolving": "启用客户端 IP 地址的反向解析", "enableReverseResolving": "启用客户端 IP 地址的反向解析",
"enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)", "enableReverseResolvingDescription": "通过向相应的解析器发送 PTR 查询,将客户端 IP 地址进行反向解析为主机名(对于本地客户端使用私有 DNS 服务器,对于具有公共 IP 地址的客户端使用上游服务器)",
"dnsServerSettings": "AdGuard Home DNS 服务器设置", "dnsServerSettings": "AdGuard Home DNS 服务器设置",
@ -492,7 +492,7 @@
"dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存", "dnsCacheConfigDescription": "配置服务器如何管理 DNS 缓存",
"comment": "注释", "comment": "注释",
"address": "地址", "address": "地址",
"commentsDescription": "注释始终以 # 开头您无需添加它,系统将自动添加", "commentsDescription": "注释始终以 # 开头 您无需添加它,系统将自动添加",
"encryptionSettings": "加密设置", "encryptionSettings": "加密设置",
"encryptionSettingsDescription": "加密HTTPS/QUIC/TLS支持", "encryptionSettingsDescription": "加密HTTPS/QUIC/TLS支持",
"loadingEncryptionSettings": "正在加载加密设置...", "loadingEncryptionSettings": "正在加载加密设置...",
@ -502,13 +502,13 @@
"enableEncryptionDescription": "如果启用加密AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求", "enableEncryptionDescription": "如果启用加密AdGuard Home 管理界面将通过 HTTPS 运行,并且 DNS 服务器将监听 DNS-over-HTTPS 和 DNS-over-TLS 请求",
"serverConfiguration": "服务器配置", "serverConfiguration": "服务器配置",
"domainName": "域名", "domainName": "域名",
"domainNameDescription": "如果设置AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证。如果未设置,这些功能将被禁用。必须与证书中的 DNS 名称之一匹配", "domainNameDescription": "如果设置AdGuard Home 将检测 ClientID、响应 DDR 查询并执行其他连接验证 如果未设置,这些功能将被禁用 必须与证书中的 DNS 名称之一匹配",
"redirectHttps": "自动重定向到 HTTPS", "redirectHttps": "自动重定向到 HTTPS",
"httpsPort": "HTTPS 端口", "httpsPort": "HTTPS 端口",
"tlsPort": "DNS-over-TLS 端口", "tlsPort": "DNS-over-TLS 端口",
"dnsOverQuicPort": "DNS-over-QUIC 端口", "dnsOverQuicPort": "DNS-over-QUIC 端口",
"certificates": "证书", "certificates": "证书",
"certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买", "certificatesDescription": "为了使用加密,您需要为您的域提供有效的 SSL 证书链 您可以在 letsencrypt.org 上获得免费证书,也可以从受信任的证书颁发机构购买",
"certificateFilePath": "设置证书文件路径", "certificateFilePath": "设置证书文件路径",
"pasteCertificateContent": "粘贴证书内容", "pasteCertificateContent": "粘贴证书内容",
"certificatePath": "证书路径", "certificatePath": "证书路径",
@ -526,13 +526,13 @@
"encryptionConfigSaved": "加密配置已成功保存", "encryptionConfigSaved": "加密配置已成功保存",
"encryptionConfigNotSaved": "无法保存加密配置", "encryptionConfigNotSaved": "无法保存加密配置",
"configError": "配置错误", "configError": "配置错误",
"enterOnlyCertificate": "只输入证书。不要输入 ---BEGIN--- 和 ---END--- 行。", "enterOnlyCertificate": "只输入证书 不要输入 ---BEGIN--- 和 ---END--- 行 ",
"enterOnlyPrivateKey": "只输入密钥。不要输入 ---BEGIN--- 和 ---END--- 行。", "enterOnlyPrivateKey": "只输入密钥 不要输入 ---BEGIN--- 和 ---END--- 行 ",
"noItemsSearch": "没有匹配的项目", "noItemsSearch": "没有匹配的项目",
"clearSearch": "清除搜索", "clearSearch": "清除搜索",
"exitSearch": "退出搜索", "exitSearch": "退出搜索",
"searchClients": "搜索客户端", "searchClients": "搜索客户端",
"noClientsSearch": "没有匹配的客户端", "noClientsSearch": "没有匹配的客户端 ",
"customization": "定制", "customization": "定制",
"customizationDescription": "自定义此应用程序", "customizationDescription": "自定义此应用程序",
"color": "颜色", "color": "颜色",
@ -598,7 +598,7 @@
"checkUpdates": "检查更新", "checkUpdates": "检查更新",
"requestingUpdate": "正在请求更新...", "requestingUpdate": "正在请求更新...",
"autoupdateUnavailable": "自动更新不可用", "autoupdateUnavailable": "自动更新不可用",
"autoupdateUnavailableDescription": "此服务器不支持自动更新服务。这可能是因为服务器正在 Docker 容器中运行。您需要手动更新服务器", "autoupdateUnavailableDescription": "此服务器不支持自动更新服务 这可能是因为服务器正在 Docker 容器中运行 您需要手动更新服务器",
"minute": "{time} 分钟", "minute": "{time} 分钟",
"minutes": "{time} 分钟", "minutes": "{time} 分钟",
"hour": "{time} 小时", "hour": "{time} 小时",
@ -606,7 +606,7 @@
"remainingTime": "剩余时间", "remainingTime": "剩余时间",
"safeSearchSettings": "安全搜索设置", "safeSearchSettings": "安全搜索设置",
"loadingSafeSearchSettings": "正在加载安全搜索设置...", "loadingSafeSearchSettings": "正在加载安全搜索设置...",
"safeSearchSettingsNotLoaded": "加载安全搜索设置时出错", "safeSearchSettingsNotLoaded": "加载安全搜索设置时出错 ",
"loadingLogsSettings": "正在加载日志设置...", "loadingLogsSettings": "正在加载日志设置...",
"selectOptionLeftColumn": "在左侧栏中选择一个选项", "selectOptionLeftColumn": "在左侧栏中选择一个选项",
"selectClientLeftColumn": "在左侧栏中选择一个客户端", "selectClientLeftColumn": "在左侧栏中选择一个客户端",
@ -619,7 +619,7 @@
"copyListUrl": "复制订阅规则 URL", "copyListUrl": "复制订阅规则 URL",
"listUrlCopied": "订阅规则 URL 已复制到剪贴板", "listUrlCopied": "订阅规则 URL 已复制到剪贴板",
"unsupportedVersion": "不支持的版本", "unsupportedVersion": "不支持的版本",
"unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题", "unsupprtedVersionMessage": "您的服务器版本 {version} 不在支持范围,配合使用可能会存在问题\n\nAdGuard Home Manager 只适配了 AdGuard Home 服务器的稳定版本 alpha 和 beta 版本也许能用,但不保证兼容性,同时使用时可能会存在问题",
"iUnderstand": "我了解", "iUnderstand": "我了解",
"appUpdates": "应用程序更新", "appUpdates": "应用程序更新",
"usingLatestVersion": "您正在使用最新版本", "usingLatestVersion": "您正在使用最新版本",
@ -658,7 +658,136 @@
"hideServerAddressDescription": "在主页上隐藏服务器地址", "hideServerAddressDescription": "在主页上隐藏服务器地址",
"topItemsOrder": "顶部项目顺序", "topItemsOrder": "顶部项目顺序",
"topItemsOrderDescription": "排列主页顶部项目列表", "topItemsOrderDescription": "排列主页顶部项目列表",
"topItemsReorderInfo": "按住并滑动一个项目以重新排序", "topItemsReorderInfo": "按住并滑动一个项目以重新排序 ",
"discardChanges": "放弃更改", "discardChanges": "放弃更改",
"discardChangesDescription": "您确定要放弃更改吗?" "discardChangesDescription": "您确定要放弃更改吗?",
"others": "其他",
"showChart": "显示图表",
"hideChart": "隐藏图表",
"showTopItemsChart": "显示顶部项目图表",
"showTopItemsChartDescription": "默认情况下在移动视图中显示顶部项目部分的环形图 ",
"openMenu": "打开菜单",
"closeMenu": "关闭菜单",
"openListUrl": "打开列表URL",
"selectionMode": "选择模式",
"enableDisableSelected": "启用或禁用选定项目",
"deleteSelected": "删除选定项目",
"deleteSelectedLists": "删除选定列表",
"allSelectedListsDeletedSuccessfully": "所有选定列表已成功删除 ",
"deletionResult": "删除结果",
"deletingLists": "正在删除列表...",
"failedElements": "失败元素",
"processingLists": "正在处理列表...",
"enableDisableResult": "启用或禁用结果",
"selectedListsEnabledDisabledSuccessfully": "所有选定列表已成功启用或禁用",
"sslWarning": "如果您正在使用带有自签名证书的 HTTPS 连接,请确保在设置 > 高级设置中启用 '不检查 SSL 证书' ",
"unsupportedServerVersion": "不支持的服务器版本",
"unsupportedServerVersionMessage": "您的 AdGuard Home 服务器版本过旧,不受 AdGuard Home Manager 支持 您需要将 AdGuard Home 服务器升级到更新的版本才能使用此应用程序 ",
"yourVersion": "您的版本:{version}",
"minimumRequiredVersion": "最低要求版本:{version}",
"topUpstreams": "主要上游",
"averageUpstreamResponseTime": "平均上游响应时间",
"dhcpNotAvailable": "DHCP 服务器不可用 ",
"osServerInstalledIncompatible": "服务器安装的操作系统与此功能不兼容 ",
"resetSettings": "重置设置",
"resetEncryptionSettingsDescription": "您确定要将加密设置重置为默认值吗?",
"resettingConfig": "正在重置配置...",
"configurationResetSuccessfully": "配置已成功重置",
"configurationResetError": "配置无法重置",
"testUpstreamDnsServers": "测试上游 DNS 服务器",
"errorTestUpstreamDns": "测试上游 DNS 服务器时出错 ",
"useCustomIpEdns": "使用 EDNS 的自定义 IP",
"useCustomIpEdnsDescription": "允许使用 EDNS 的自定义 IP",
"sortingOptions": "排序选项",
"fromHighestToLowest": "从高到低",
"fromLowestToHighest": "从低到高",
"queryLogsAndStatistics": "查询日志和统计",
"ignoreClientQueryLog": "在查询日志中忽略此客户端",
"ignoreClientStatistics": "在统计中忽略此客户端",
"savingChanges": "正在保存更改...",
"fallbackDnsServers": "备用 DNS 服务器",
"fallbackDnsServersDescription": "配置备用 DNS 服务器",
"fallbackDnsServersInfo": "当上游 DNS 服务器无响应时使用的备用 DNS 服务器列表 语法与上面的主上游字段相同 ",
"noFallbackDnsAdded": "未添加备用 DNS 服务器 ",
"blockedResponseTtl": "被阻止的响应 TTL",
"blockedResponseTtlDescription": "指定客户端应缓存过滤响应的秒数",
"invalidValue": "无效值",
"noDataChart": "没有数据显示此图表 ",
"noData": "无数据",
"unblockClient": "解除客户端封锁",
"blockingClient": "正在封锁客户端...",
"unblockingClient": "正在解除客户端封锁...",
"upstreamDnsCacheConfiguration": "DNS 上游缓存配置",
"enableDnsCachingClient": "为此客户端启用 DNS 缓存",
"dnsCacheSize": "DNS 缓存大小",
"nameInvalid": "名称是必需的",
"oneIdentifierRequired": "至少需要一个标识符",
"dnsCacheNumber": "DNS 缓存大小必须是一个数字",
"errors": "错误",
"redirectHttpsWarning": "如果您在 AdGuard Home 服务器上启用了 '自动重定向到 HTTPS',则必须选择 HTTPS 连接并使用服务器的 HTTPS 端口 ",
"logsSettingsDescription": "配置查询日志",
"ignoredDomains": "忽略的域名",
"noIgnoredDomainsAdded": "未添加忽略的域名",
"pauseServiceBlocking": "暂停服务阻止",
"newSchedule": "新计划",
"editSchedule": "编辑计划",
"timezone": "时区",
"monday": "星期一",
"tuesday": "星期二",
"wednesday": "星期三",
"thursday": "星期四",
"friday": "星期五",
"saturday": "星期六",
"sunday": "星期日",
"from": "从",
"to": "到",
"selectStartTime": "选择开始时间",
"selectEndTime": "选择结束时间",
"startTimeBeforeEndTime": "开始时间必须在结束时间之前 ",
"noBlockingScheduleThisDevice": "此设备没有阻止计划 ",
"selectTimezone": "选择时区",
"selectClientsFiltersInfo": "选择您想要显示的客户端 如果没有选择任何客户端,将显示所有客户端 ",
"noDataThisSection": "本节没有数据 ",
"statisticsSettings": "统计设置",
"statisticsSettingsDescription": "配置统计数据收集",
"loadingStatisticsSettings": "正在加载统计设置...",
"statisticsSettingsLoadError": "加载统计设置时发生错误 ",
"statisticsConfigUpdated": "统计设置成功更新",
"statisticsConfigNotUpdated": "统计设置无法更新",
"customTimeInHours": "自定义时间(以小时为单位)",
"invalidTime": "无效时间",
"removeDomain": "移除域名",
"addDomain": "添加域名",
"notLess1Hour": "时间不能少于 1 小时",
"rateLimit": "速率限制",
"subnetPrefixLengthIpv4": "IPv4 的子网前缀长度",
"subnetPrefixLengthIpv6": "IPv6 的子网前缀长度",
"rateLimitAllowlist": "速率限制白名单",
"rateLimitAllowlistDescription": "从速率限制中排除的 IP 地址",
"dnsOptions": "DNS 选项",
"editor": "编辑器",
"editCustomRules": "编辑自定义规则",
"savingCustomRules": "正在保存自定义规则...",
"customRulesUpdatedSuccessfully": "自定义规则成功更新",
"customRulesNotUpdated": "自定义规则无法更新",
"reorder": "重新排序",
"showHide": "显示/隐藏",
"noElementsReorderMessage": "在显示/隐藏标签页上启用一些元素,然后在这里重新排序 ",
"enablePlainDns": "启用普通 DNS",
"enablePlainDnsDescription": "默认启用普通 DNS 您可以禁用它,强制所有设备使用加密 DNS 要做到这一点,您必须至少启用一个加密 DNS 协议 ",
"date": "日期",
"loadingChangelog": "正在加载更新日志...",
"invalidIpOrUrl": "无效的 IP 地址或 URL",
"addPersistentClient": "添加为持久客户端",
"blockThisClientOnly": "仅为此客户端封锁",
"unblockThisClientOnly": "仅为此客户端解封",
"domainBlockedThisClient": "{domain} 已为此客户端封锁",
"domainUnblockedThisClient": "{domain} 已为此客户端解封",
"disallowThisClient": "禁止此客户端",
"allowThisClient": "允许此客户端",
"clientAllowedSuccessfully": "客户端成功允许",
"clientDisallowedSuccessfully": "客户端成功禁止",
"changesNotSaved": "更改无法保存",
"allowingClient": "正在允许客户端...",
"disallowingClient": "正在禁止客户端..."
} }

View file

@ -5,14 +5,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:store_checker/store_checker.dart'; import 'package:window_manager/window_manager.dart';
import 'package:window_size/window_size.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -31,15 +30,16 @@ import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/constants/colors.dart'; import 'package:adguard_home_manager/constants/colors.dart';
import 'package:adguard_home_manager/config/globals.dart'; import 'package:adguard_home_manager/config/globals.dart';
import 'package:adguard_home_manager/config/theme.dart'; import 'package:adguard_home_manager/config/theme.dart';
import 'package:adguard_home_manager/classes/http_override.dart';
import 'package:adguard_home_manager/services/db/database.dart'; import 'package:adguard_home_manager/services/db/database.dart';
import 'package:adguard_home_manager/classes/http_override.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
setWindowMinSize(const Size(500, 500)); await windowManager.ensureInitialized();
WindowManager.instance.setMinimumSize(const Size(500, 700));
} }
if (Platform.isWindows || Platform.isLinux) { if (Platform.isWindows || Platform.isLinux) {
@ -48,8 +48,12 @@ void main() async {
} }
await dotenv.load(fileName: '.env'); await dotenv.load(fileName: '.env');
final sharedPreferences = await SharedPreferences.getInstance();
final AppConfigProvider appConfigProvider = AppConfigProvider(); final AppConfigProvider appConfigProvider = AppConfigProvider(
sharedPreferencesInstance: sharedPreferences
);
final ServersProvider serversProvider = ServersProvider(); final ServersProvider serversProvider = ServersProvider();
final StatusProvider statusProvider = StatusProvider(); final StatusProvider statusProvider = StatusProvider();
final ClientsProvider clientsProvider = ClientsProvider(); final ClientsProvider clientsProvider = ClientsProvider();
@ -69,21 +73,16 @@ void main() async {
appConfigProvider.setIosInfo(iosInfo); appConfigProvider.setIosInfo(iosInfo);
} }
final dbData = await loadDb(appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31); if (sharedPreferences.getBool('overrideSslCheck') == true) {
if (dbData['appConfig']['overrideSslCheck'] == 1) {
HttpOverrides.global = MyHttpOverrides(); HttpOverrides.global = MyHttpOverrides();
} }
if (Platform.isAndroid || Platform.isIOS) { final dbData = await loadDb();
Source installationSource = await StoreChecker.getSource;
appConfigProvider.setInstallationSource(installationSource);
}
serversProvider.setDbInstance(dbData['dbInstance']); serversProvider.setDbInstance(dbData['dbInstance']);
appConfigProvider.saveFromDb(dbData['dbInstance'], dbData['appConfig']);
serversProvider.saveFromDb(dbData['servers']); serversProvider.saveFromDb(dbData['servers']);
appConfigProvider.saveFromSharedPreferences();
PackageInfo appInfo = await PackageInfo.fromPlatform(); PackageInfo appInfo = await PackageInfo.fromPlatform();
appConfigProvider.setAppInfo(appInfo); appConfigProvider.setAppInfo(appInfo);
@ -163,6 +162,31 @@ void main() async {
(options) { (options) {
options.dsn = dotenv.env['SENTRY_DSN']; options.dsn = dotenv.env['SENTRY_DSN'];
options.sendDefaultPii = false; options.sendDefaultPii = false;
options.beforeSend = (event, hint) {
if (event.throwable is HttpException) {
return null;
}
if (event.message?.formatted.contains("HttpException") == true) {
return null;
}
if (
event.message?.formatted.contains("Unexpected character") ?? false ||
(event.throwable != null && event.throwable!.toString().contains("Unexpected character"))
) {
return null;
}
if (
event.message?.formatted.contains("Unexpected end of input") ?? false ||
(event.throwable != null && event.throwable!.toString().contains("Unexpected end of input"))
) {
return null;
}
return event;
};
}, },
appRunner: () => startApp() appRunner: () => startApp()
); );
@ -172,77 +196,53 @@ void main() async {
} }
} }
class Main extends StatefulWidget { class Main extends StatelessWidget {
const Main({super.key}); const Main({super.key});
@override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
List<DisplayMode> modes = <DisplayMode>[];
DisplayMode? active;
DisplayMode? preferred;
Future<void> displayMode() async {
try {
modes = await FlutterDisplayMode.supported;
preferred = await FlutterDisplayMode.preferred;
active = await FlutterDisplayMode.active;
await FlutterDisplayMode.setHighRefreshRate();
setState(() {});
} catch (_) {
// ---- //
}
}
@override
void initState() {
displayMode();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) => MaterialApp( builder: (lightDynamic, darkDynamic) {
title: 'AdGuard Home Manager', appConfigProvider.setSupportsDynamicTheme(lightDynamic != null && darkDynamic != null);
theme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 return MaterialApp(
? appConfigProvider.useDynamicColor == true title: 'AdGuard Home Manager',
? lightTheme(lightDynamic) theme: lightDynamic != null
: lightThemeOldVersions(colors[appConfigProvider.staticColor]) ? appConfigProvider.useDynamicColor == true
: lightThemeOldVersions(colors[appConfigProvider.staticColor]), ? lightTheme(lightDynamic)
darkTheme: appConfigProvider.androidDeviceInfo != null && appConfigProvider.androidDeviceInfo!.version.sdkInt >= 31 : lightThemeOldVersions(colors[appConfigProvider.staticColor])
? appConfigProvider.useDynamicColor == true : lightThemeOldVersions(colors[appConfigProvider.staticColor]),
? darkTheme(darkDynamic) darkTheme: darkDynamic != null
: darkThemeOldVersions(colors[appConfigProvider.staticColor]) ? appConfigProvider.useDynamicColor == true
: darkThemeOldVersions(colors[appConfigProvider.staticColor]), ? darkTheme(darkDynamic)
themeMode: appConfigProvider.selectedTheme, : darkThemeOldVersions(colors[appConfigProvider.staticColor])
debugShowCheckedModeBanner: false, : darkThemeOldVersions(colors[appConfigProvider.staticColor]),
localizationsDelegates: const [ themeMode: appConfigProvider.selectedTheme,
GlobalMaterialLocalizations.delegate, debugShowCheckedModeBanner: false,
GlobalWidgetsLocalizations.delegate, localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate,
AppLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
], GlobalCupertinoLocalizations.delegate,
supportedLocales: const [ AppLocalizations.delegate,
Locale('en', ''), ],
Locale('es', ''), supportedLocales: const [
Locale('zh', ''), Locale('en', ''),
Locale('zh', 'CN'), Locale('es', ''),
Locale('pl', ''), Locale('zh', ''),
Locale('tr', '') Locale('zh', 'CN'),
], Locale('pl', ''),
scaffoldMessengerKey: scaffoldMessengerKey, Locale('tr', ''),
navigatorKey: globalNavigatorKey, Locale('ru', '')
builder: (context, child) => CustomMenuBar( ],
child: child!, scaffoldMessengerKey: scaffoldMessengerKey,
), navigatorKey: globalNavigatorKey,
home: const Layout(), builder: (context, child) => CustomMenuBar(
), child: child!,
),
home: const Layout(),
);
}
); );
} }
} }

View file

@ -1,7 +1,7 @@
class AppliedFiters { class AppliedFiters {
String selectedResultStatus = 'all'; String selectedResultStatus = 'all';
String? searchText; String? searchText;
List<String>? clients; List<String> clients;
AppliedFiters({ AppliedFiters({
required this.selectedResultStatus, required this.selectedResultStatus,

View file

@ -91,6 +91,7 @@ class Client {
final bool? ignoreStatistics; final bool? ignoreStatistics;
final bool? upstreamsCacheEnabled; final bool? upstreamsCacheEnabled;
final int? upstreamsCacheSize; final int? upstreamsCacheSize;
final BlockedServicesSchedule? blockedServicesSchedule;
Client({ Client({
required this.name, required this.name,
@ -108,6 +109,7 @@ class Client {
required this.ignoreStatistics, required this.ignoreStatistics,
required this.upstreamsCacheEnabled, required this.upstreamsCacheEnabled,
required this.upstreamsCacheSize, required this.upstreamsCacheSize,
required this.blockedServicesSchedule,
}); });
factory Client.fromJson(Map<String, dynamic> json) => Client( factory Client.fromJson(Map<String, dynamic> json) => Client(
@ -127,7 +129,10 @@ class Client {
ignoreQuerylog: json["ignore_querylog"], ignoreQuerylog: json["ignore_querylog"],
ignoreStatistics: json["ignore_statistics"], ignoreStatistics: json["ignore_statistics"],
upstreamsCacheEnabled: json["upstreams_cache_enabled"], upstreamsCacheEnabled: json["upstreams_cache_enabled"],
upstreamsCacheSize: json["upstreams_cache_size"] upstreamsCacheSize: json["upstreams_cache_size"],
blockedServicesSchedule: json["blocked_services_schedule"] != null
? BlockedServicesSchedule.fromJson(json["blocked_services_schedule"])
: null
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -145,6 +150,71 @@ class Client {
"ignore_querylog": ignoreQuerylog, "ignore_querylog": ignoreQuerylog,
"ignore_statistics": ignoreStatistics, "ignore_statistics": ignoreStatistics,
"upstreams_cache_enabled": upstreamsCacheEnabled, "upstreams_cache_enabled": upstreamsCacheEnabled,
"upstreams_cache_size": upstreamsCacheSize "upstreams_cache_size": upstreamsCacheSize,
"blocked_services_schedule":blockedServicesSchedule?.toJson()
};
}
class BlockedServicesSchedule {
final String? timeZone;
final BlockedServicesScheduleDay? mon;
final BlockedServicesScheduleDay? tue;
final BlockedServicesScheduleDay? wed;
final BlockedServicesScheduleDay? thu;
final BlockedServicesScheduleDay? fri;
final BlockedServicesScheduleDay? sat;
final BlockedServicesScheduleDay? sun;
BlockedServicesSchedule({
this.timeZone,
this.mon,
this.tue,
this.wed,
this.thu,
this.fri,
this.sat,
this.sun
});
factory BlockedServicesSchedule.fromJson(Map<String, dynamic> json) => BlockedServicesSchedule(
timeZone: json["time_zone"],
mon: json["mon"] == null ? null : BlockedServicesScheduleDay.fromJson(json["mon"]),
tue: json["tue"] == null ? null : BlockedServicesScheduleDay.fromJson(json["tue"]),
wed: json["wed"] == null ? null : BlockedServicesScheduleDay.fromJson(json["wed"]),
thu: json["thu"] == null ? null : BlockedServicesScheduleDay.fromJson(json["thu"]),
fri: json["fri"] == null ? null : BlockedServicesScheduleDay.fromJson(json["fri"]),
sat: json["sat"] == null ? null : BlockedServicesScheduleDay.fromJson(json["sat"]),
sun: json["sun"] == null ? null : BlockedServicesScheduleDay.fromJson(json["sun"]),
);
Map<String, dynamic> toJson() => {
"time_zone": timeZone,
"mon": mon?.toJson(),
"tue": tue?.toJson(),
"wed": wed?.toJson(),
"thu": thu?.toJson(),
"fri": fri?.toJson(),
"sat": sat?.toJson(),
"sun": sun?.toJson(),
};
}
class BlockedServicesScheduleDay {
final int? start;
final int? end;
BlockedServicesScheduleDay({
this.start,
this.end,
});
factory BlockedServicesScheduleDay.fromJson(Map<String, dynamic> json) => BlockedServicesScheduleDay(
start: json["start"],
end: json["end"],
);
Map<String, dynamic> toJson() => {
"start": start,
"end": end,
}; };
} }

View file

@ -1,11 +1,9 @@
import 'dart:convert'; import 'dart:convert';
class DhcpModel { class DhcpModel {
bool dhcpAvailable;
List<NetworkInterface> networkInterfaces; List<NetworkInterface> networkInterfaces;
DhcpStatus? dhcpStatus; DhcpStatus? dhcpStatus;
DhcpModel({ DhcpModel({
required this.dhcpAvailable,
required this.networkInterfaces, required this.networkInterfaces,
required this.dhcpStatus, required this.dhcpStatus,
}); });
@ -83,8 +81,8 @@ class DhcpStatus {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"interface_name": interfaceName, "interface_name": interfaceName,
"v4": v4 != null ? v4!.toJson() : null, "v4": v4?.toJson(),
"v6": v6 != null ? v6!.toJson() : null, "v6": v6?.toJson(),
"leases": List<Lease>.from(leases.map((x) => x)), "leases": List<Lease>.from(leases.map((x) => x)),
"static_leases": List<Lease>.from(staticLeases.map((x) => x)), "static_leases": List<Lease>.from(staticLeases.map((x) => x)),
"enabled": enabled, "enabled": enabled,

View file

@ -23,6 +23,10 @@ class DnsInfo {
String blockingIpv6; String blockingIpv6;
List<String> defaultLocalPtrUpstreams; List<String> defaultLocalPtrUpstreams;
int? blockedResponseTtl; int? blockedResponseTtl;
int? ratelimitSubnetLenIpv4;
int? ratelimitSubnetLenIpv6;
List<String>? ratelimitWhitelist;
int? upstreamTimeout;
DnsInfo({ DnsInfo({
required this.upstreamDns, required this.upstreamDns,
@ -49,6 +53,10 @@ class DnsInfo {
required this.blockingIpv6, required this.blockingIpv6,
required this.defaultLocalPtrUpstreams, required this.defaultLocalPtrUpstreams,
required this.blockedResponseTtl, required this.blockedResponseTtl,
required this.ratelimitSubnetLenIpv4,
required this.ratelimitSubnetLenIpv6,
required this.ratelimitWhitelist,
required this.upstreamTimeout,
}); });
factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo( factory DnsInfo.fromJson(Map<String, dynamic> json) => DnsInfo(
@ -75,7 +83,11 @@ class DnsInfo {
blockingIpv4: json["blocking_ipv4"], blockingIpv4: json["blocking_ipv4"],
blockingIpv6: json["blocking_ipv6"], blockingIpv6: json["blocking_ipv6"],
defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List<String>.from(json["default_local_ptr_upstreams"].map((x) => x)) : [], defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List<String>.from(json["default_local_ptr_upstreams"].map((x) => x)) : [],
blockedResponseTtl: json["blocked_response_ttl"] blockedResponseTtl: json["blocked_response_ttl"],
ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"],
ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"],
ratelimitWhitelist: json["ratelimit_whitelist"] != null ? List<String>.from(json["ratelimit_whitelist"].map((x) => x)) : [],
upstreamTimeout: json["upstream_timeout"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -102,6 +114,10 @@ class DnsInfo {
"blocking_ipv4": blockingIpv4, "blocking_ipv4": blockingIpv4,
"blocking_ipv6": blockingIpv6, "blocking_ipv6": blockingIpv6,
"default_local_ptr_upstreams": List<dynamic>.from(defaultLocalPtrUpstreams.map((x) => x)), "default_local_ptr_upstreams": List<dynamic>.from(defaultLocalPtrUpstreams.map((x) => x)),
"blocked_response_ttl": blockedResponseTtl "blocked_response_ttl": blockedResponseTtl,
"ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4,
"ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6,
"ratelimit_whitelist": ratelimitWhitelist != null ? List<String>.from(ratelimitWhitelist!.map((x) => x)) : null,
"upstream_timeout": upstreamTimeout,
}; };
} }

View file

@ -5,7 +5,7 @@ DnsStatistics dnsStatisticsFromJson(String str) => DnsStatistics.fromJson(json.d
String dnsStatisticsToJson(DnsStatistics data) => json.encode(data.toJson()); String dnsStatisticsToJson(DnsStatistics data) => json.encode(data.toJson());
class DnsStatistics { class DnsStatistics {
final String timeUnits; final String? timeUnits;
final List<Map<String, int>> topQueriedDomains; final List<Map<String, int>> topQueriedDomains;
final List<Map<String, int>> topClients; final List<Map<String, int>> topClients;
final List<Map<String, int>> topBlockedDomains; final List<Map<String, int>> topBlockedDomains;
@ -43,9 +43,9 @@ class DnsStatistics {
factory DnsStatistics.fromJson(Map<String, dynamic> json) => DnsStatistics( factory DnsStatistics.fromJson(Map<String, dynamic> json) => DnsStatistics(
timeUnits: json["time_units"], timeUnits: json["time_units"],
topQueriedDomains: List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))), topQueriedDomains: json["top_queried_domains"] != null ? List<Map<String, int>>.from(json["top_queried_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : [],
topClients: List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))), topClients: json["top_clients"] != null ? List<Map<String, int>>.from(json["top_clients"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : [],
topBlockedDomains: List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))), topBlockedDomains: json["top_blocked_domains"] != null ? List<Map<String, int>>.from(json["top_blocked_domains"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))): [],
topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null, topUpstreamResponses: json["top_upstreams_responses"] != null ? List<Map<String, int>>.from(json["top_upstreams_responses"].map((x) => Map.from(x).map((k, v) => MapEntry<String, int>(k, v)))) : null,
topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null, topUpstreamsAvgTime: json["top_upstreams_avg_time"] != null ? List<Map<String, double>>.from(json["top_upstreams_avg_time"].map((x) => Map.from(x).map((k, v) => MapEntry<String, double>(k, v)))) : null,
dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)), dnsQueries: List<int>.from(json["dns_queries"].map((x) => x)),

View file

@ -39,6 +39,7 @@ class EncryptionData {
final String certificatePath; final String certificatePath;
final String privateKeyPath; final String privateKeyPath;
final bool privateKeySaved; final bool privateKeySaved;
final bool? servePlainDns;
EncryptionData({ EncryptionData({
required this.validCert, required this.validCert,
@ -65,6 +66,7 @@ class EncryptionData {
required this.certificatePath, required this.certificatePath,
required this.privateKeyPath, required this.privateKeyPath,
required this.privateKeySaved, required this.privateKeySaved,
required this.servePlainDns,
}); });
@ -93,6 +95,7 @@ class EncryptionData {
certificatePath: json["certificate_path"], certificatePath: json["certificate_path"],
privateKeyPath: json["private_key_path"], privateKeyPath: json["private_key_path"],
privateKeySaved: json["private_key_saved"], privateKeySaved: json["private_key_saved"],
servePlainDns: json["serve_plain_dns"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -120,6 +123,7 @@ class EncryptionData {
"certificate_path": certificatePath, "certificate_path": certificatePath,
"private_key_path": privateKeyPath, "private_key_path": privateKeyPath,
"private_key_saved": privateKeySaved, "private_key_saved": privateKeySaved,
"serve_plain_dns": servePlainDns,
}; };
} }

View file

@ -63,7 +63,7 @@ class Filter {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"url": url, "url": url,
"name": name, "name": name,
"last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null, "last_updated": lastUpdated?.toIso8601String(),
"id": id, "id": id,
"rules_count": rulesCount, "rules_count": rulesCount,
"enabled": enabled, "enabled": enabled,

View file

@ -65,7 +65,7 @@ class Filter {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"url": url, "url": url,
"name": name, "name": name,
"last_updated": lastUpdated != null ? lastUpdated!.toIso8601String() : null, "last_updated": lastUpdated?.toIso8601String(),
"id": id, "id": id,
"rules_count": rulesCount, "rules_count": rulesCount,
"enabled": enabled, "enabled": enabled,

View file

@ -30,7 +30,7 @@ class LogsData {
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"data": List<dynamic>.from(data.map((x) => x.toJson())), "data": List<dynamic>.from(data.map((x) => x.toJson())),
"oldest": oldest != null ? oldest!.toIso8601String() : null, "oldest": oldest?.toIso8601String(),
}; };
} }

View file

@ -0,0 +1,27 @@
class QueryLogConfig {
final List<String>? ignored;
final int? interval;
final bool? enabled;
final bool? anonymizeClientIp;
QueryLogConfig({
this.ignored,
this.interval,
this.enabled,
this.anonymizeClientIp,
});
factory QueryLogConfig.fromJson(Map<String, dynamic> json) => QueryLogConfig(
ignored: json["ignored"] == null ? [] : List<String>.from(json["ignored"]!.map((x) => x)),
interval: json["interval"],
enabled: json["enabled"],
anonymizeClientIp: json["anonymize_client_ip"],
);
Map<String, dynamic> toJson() => {
"ignored": ignored == null ? [] : List<dynamic>.from(ignored!.map((x) => x)),
"interval": interval,
"enabled": enabled,
"anonymize_client_ip": anonymizeClientIp,
};
}

View file

@ -21,6 +21,7 @@ class ServerStatus {
bool? safeSearchPixabay; bool? safeSearchPixabay;
bool? safeSearchYandex; bool? safeSearchYandex;
bool? safeSearchYoutube; bool? safeSearchYoutube;
bool dhcpAvailable;
ServerStatus({ ServerStatus({
required this.stats, required this.stats,
@ -39,7 +40,8 @@ class ServerStatus {
required this.safeSearchDuckduckgo, required this.safeSearchDuckduckgo,
required this.safeSearchPixabay, required this.safeSearchPixabay,
required this.safeSearchYandex, required this.safeSearchYandex,
required this.safeSearchYoutube required this.safeSearchYoutube,
required this.dhcpAvailable,
}); });
factory ServerStatus.fromJson(Map<String, dynamic> json) => ServerStatus( factory ServerStatus.fromJson(Map<String, dynamic> json) => ServerStatus(
@ -64,5 +66,6 @@ class ServerStatus {
safeSearchPixabay: json['safeSearch']['pixabay'], safeSearchPixabay: json['safeSearch']['pixabay'],
safeSearchYandex: json['safeSearch']['yandex'], safeSearchYandex: json['safeSearch']['yandex'],
safeSearchYoutube: json['safeSearch']['youtube'], safeSearchYoutube: json['safeSearch']['youtube'],
dhcpAvailable: json['status']['dhcp_available'] ?? false
); );
} }

View file

@ -0,0 +1,23 @@
class StatisticsConfig {
final List<dynamic>? ignored;
final int? interval;
final bool? enabled;
StatisticsConfig({
this.ignored,
this.interval,
this.enabled,
});
factory StatisticsConfig.fromJson(Map<String, dynamic> json) => StatisticsConfig(
ignored: json["ignored"] == null ? [] : List<dynamic>.from(json["ignored"]!.map((x) => x)),
interval: json["interval"],
enabled: json["enabled"],
);
Map<String, dynamic> toJson() => {
"ignored": ignored == null ? [] : List<dynamic>.from(ignored!.map((x) => x)),
"interval": interval,
"enabled": enabled,
};
}

View file

@ -1,22 +1,22 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:store_checker/store_checker.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:sqflite/sqlite_api.dart';
import 'package:adguard_home_manager/constants/enums.dart'; import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/config/home_top_items_default_order.dart'; import 'package:adguard_home_manager/config/home_top_items_default_order.dart';
import 'package:adguard_home_manager/models/github_release.dart'; import 'package:adguard_home_manager/models/github_release.dart';
import 'package:adguard_home_manager/services/db/queries.dart';
import 'package:adguard_home_manager/functions/conversions.dart';
import 'package:adguard_home_manager/models/app_log.dart'; import 'package:adguard_home_manager/models/app_log.dart';
class AppConfigProvider with ChangeNotifier { class AppConfigProvider with ChangeNotifier {
Database? _dbInstance; final SharedPreferences sharedPreferencesInstance;
AppConfigProvider({
required this.sharedPreferencesInstance
});
PackageInfo? _appInfo; PackageInfo? _appInfo;
AndroidDeviceInfo? _androidDeviceInfo; AndroidDeviceInfo? _androidDeviceInfo;
@ -28,38 +28,35 @@ class AppConfigProvider with ChangeNotifier {
bool _showingSnackbar = false; bool _showingSnackbar = false;
bool _supportsDynamicTheme = true;
int _selectedTheme = 0; int _selectedTheme = 0;
bool _useDynamicColor = true; bool _useDynamicColor = true;
int _staticColor = 0; int _staticColor = 0;
bool _useThemeColorForStatus = false; final bool _useThemeColorForStatus = false;
int _selectedClientsTab = 0; int _selectedClientsTab = 0;
int _selectedFiltersTab = 0; int _selectedFiltersTab = 0;
List<HomeTopItems> _homeTopItemsOrder = homeTopItemsDefaultOrder; List<HomeTopItems> _homeTopItemsOrder = homeTopItemsDefaultOrder;
int _hideServerAddress = 0; bool _hideServerAddress = false;
final List<AppLog> _logs = []; final List<AppLog> _logs = [];
int _overrideSslCheck = 0; bool _overrideSslCheck = false;
int _hideZeroValues = 0; bool _hideZeroValues = false;
int _showTimeLogs = 0; bool _showTimeLogs = false;
int _showIpLogs = 0; bool _showIpLogs = false;
int _combinedChartHome = 0; bool _combinedChartHome = false;
int _showTopItemsChart = 0;
String? _doNotRememberVersion; String? _doNotRememberVersion;
GitHubRelease? _appUpdatesAvailable; GitHubRelease? _appUpdatesAvailable;
Source _installationSource = Source.UNKNOWN;
PackageInfo? get getAppInfo { PackageInfo? get getAppInfo {
return _appInfo; return _appInfo;
} }
@ -90,6 +87,10 @@ class AppConfigProvider with ChangeNotifier {
} }
} }
bool get supportsDynamicTheme {
return _supportsDynamicTheme;
}
int get selectedThemeNumber { int get selectedThemeNumber {
return _selectedTheme; return _selectedTheme;
} }
@ -107,11 +108,11 @@ class AppConfigProvider with ChangeNotifier {
} }
bool get overrideSslCheck { bool get overrideSslCheck {
return _overrideSslCheck == 1 ? true : false; return _overrideSslCheck;
} }
bool get hideZeroValues { bool get hideZeroValues {
return _hideZeroValues == 1 ? true : false; return _hideZeroValues;
} }
int get selectedScreen { int get selectedScreen {
@ -135,15 +136,15 @@ class AppConfigProvider with ChangeNotifier {
} }
bool get showTimeLogs { bool get showTimeLogs {
return _showTimeLogs == 1 ? true : false; return _showTimeLogs;
} }
bool get showIpLogs { bool get showIpLogs {
return _showIpLogs == 1 ? true : false; return _showIpLogs;
} }
bool get combinedChartHome { bool get combinedChartHome {
return _combinedChartHome == 1 ? true : false; return _combinedChartHome;
} }
String? get doNotRememberVersion { String? get doNotRememberVersion {
@ -158,24 +159,16 @@ class AppConfigProvider with ChangeNotifier {
return _appUpdatesAvailable; return _appUpdatesAvailable;
} }
Source get installationSource {
return _installationSource;
}
List<HomeTopItems> get homeTopItemsOrder { List<HomeTopItems> get homeTopItemsOrder {
return _homeTopItemsOrder; return _homeTopItemsOrder;
} }
bool get hideServerAddress { bool get hideServerAddress {
return _hideServerAddress == 1 ? true : false; return _hideServerAddress;
} }
bool get showTopItemsChart { void setSupportsDynamicTheme(bool value) {
return _showTopItemsChart == 1 ? true : false; _supportsDynamicTheme = value;
}
void setDbInstance(Database db) {
_dbInstance = db;
} }
void setAppInfo(PackageInfo appInfo) { void setAppInfo(PackageInfo appInfo) {
@ -227,230 +220,146 @@ class AppConfigProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void setInstallationSource(Source value) {
_installationSource = value;
notifyListeners();
}
Future<bool> setOverrideSslCheck(bool status) async { Future<bool> setOverrideSslCheck(bool status) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('overrideSslCheck', status);
column: 'overrideSslCheck', _overrideSslCheck = status;
value: status == true ? 1 : 0
);
if (updated == true) {
_overrideSslCheck = status == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setHideZeroValues(bool status) async { Future<bool> setHideZeroValues(bool status) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('hideZeroValues', status);
column: 'hideZeroValues', _hideZeroValues = status;
value: status == true ? 1 : 0
);
if (updated == true) {
_hideZeroValues = status == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setshowTimeLogs(bool status) async { Future<bool> setshowTimeLogs(bool status) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('showTimeLogs', status);
column: 'showTimeLogs', _showTimeLogs = status;
value: status == true ? 1 : 0
);
if (updated == true) {
_showTimeLogs = status == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setShowIpLogs(bool status) async { Future<bool> setShowIpLogs(bool status) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('showIpLogs', status);
column: 'showIpLogs', _showIpLogs = status;
value: status == true ? 1 : 0
);
if (updated == true) {
_showIpLogs = status == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setSelectedTheme(int value) async { Future<bool> setSelectedTheme(int value) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setInt('selectedTheme', value);
column: 'theme',
value: value
);
if (updated == true) {
_selectedTheme = value; _selectedTheme = value;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setUseDynamicColor(bool value) async { Future<bool> setUseDynamicColor(bool value) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('useDynamicColor', value);
column: 'useDynamicColor',
value: value == true ? 1 : 0
);
if (updated == true) {
_useDynamicColor = value; _useDynamicColor = value;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false;
}
}
Future<bool> setUseThemeColorForStatus(bool value) async {
final updated = await updateConfigQuery(
db: _dbInstance!,
column: 'useThemeColorForStatus',
value: value == true ? 1 : 0
);
if (updated == true) {
_useThemeColorForStatus = value;
notifyListeners();
return true;
}
else {
return false; return false;
} }
} }
Future<bool> setCombinedChartHome(bool value) async { Future<bool> setCombinedChartHome(bool value) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('combinedChart', value);
column: 'combinedChart', _combinedChartHome = value;
value: value == true ? 1 : 0
);
if (updated == true) {
_combinedChartHome = value == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setStaticColor(int value) async { Future<bool> setStaticColor(int value) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setInt('staticColor', value);
column: 'staticColor',
value: value
);
if (updated == true) {
_staticColor = value; _staticColor = value;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setHomeTopItemsOrder(List<HomeTopItems> order) async { Future<bool> setHomeTopItemsOrder(List<HomeTopItems> order) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setStringList('homeTopItemsOrder', List<String>.from(order.map((e) => e.name)));
column: 'homeTopItemsOrder',
value: jsonEncode(List<String>.from(order.map((e) => e.name)))
);
if (updated == true) {
_homeTopItemsOrder = order; _homeTopItemsOrder = order;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setHideServerAddress(bool value) async { Future<bool> setHideServerAddress(bool value) async {
final updated = await updateConfigQuery( try {
db: _dbInstance!, sharedPreferencesInstance.setBool('hideServerAddress', value);
column: 'hideServerAddress', _hideServerAddress = value;
value: value == true ? 1 : 0
);
if (updated == true) {
_hideServerAddress = value == true ? 1 : 0;
notifyListeners(); notifyListeners();
return true; return true;
} } catch (e, stackTrace) {
else { Sentry.captureException(e, stackTrace: stackTrace);
return false; return false;
} }
} }
Future<bool> setShowTopItemsChart(bool value) async {
final updated = await updateConfigQuery(
db: _dbInstance!,
column: 'showTopItemsChart',
value: value == true ? 1 : 0
);
if (updated == true) {
_showTopItemsChart = value == true ? 1 : 0;
notifyListeners();
return true;
}
else {
return false;
}
}
Future<bool> setDoNotRememberVersion(String value) async { Future<bool> setDoNotRememberVersion(String value) async {
final updated = await updateConfigQuery( final updated = await sharedPreferencesInstance.setString('hideServerAddress', value);
db: _dbInstance!,
column: 'doNotRememberVersion',
value: value
);
return updated; return updated;
} }
void saveFromDb(Database dbInstance, Map<String, dynamic> dbData) { void saveFromSharedPreferences() {
_selectedTheme = dbData['theme'] ?? 0; _selectedTheme = sharedPreferencesInstance.getInt('selectedTheme') ?? 0;
_overrideSslCheck = dbData['overrideSslCheck'] ?? 0; _overrideSslCheck = sharedPreferencesInstance.getBool('overrideSslCheck') ?? false;
_hideZeroValues = dbData['hideZeroValues']; _hideZeroValues = sharedPreferencesInstance.getBool('hideZeroValues') ?? false;
_useDynamicColor = convertFromIntToBool(dbData['useDynamicColor'])!; _useDynamicColor = sharedPreferencesInstance.getBool('useDynamicColor') ?? true;
_staticColor = dbData['staticColor'] ?? 0; _staticColor = sharedPreferencesInstance.getInt('staticColor') ?? 0;
_useThemeColorForStatus = dbData['useThemeColorForStatus'] != null ? convertFromIntToBool(dbData['useThemeColorForStatus'])! : false; _showTimeLogs = sharedPreferencesInstance.getBool('showTimeLogs') ?? false;
_showTimeLogs = dbData['showTimeLogs'] ?? 0; _doNotRememberVersion = sharedPreferencesInstance.getString('doNotRememberVersion');
_doNotRememberVersion = dbData['doNotRememberVersion']; _showIpLogs = sharedPreferencesInstance.getBool('showIpLogs') ?? false;
_showIpLogs = dbData['showIpLogs'] ?? 0; _combinedChartHome = sharedPreferencesInstance.getBool('combinedChart') ?? false;
_combinedChartHome = dbData['combinedChart'] ?? 0; _hideServerAddress = sharedPreferencesInstance.getBool('hideServerAddress') ?? false;
_hideServerAddress = dbData['hideServerAddress']; if (sharedPreferencesInstance.getStringList('homeTopItemsOrder') != null) {
_showTopItemsChart = dbData['showTopItemsChart'];
if (dbData['homeTopItemsOrder'] != null) {
try { try {
final itemsOrder = List<HomeTopItems>.from( _homeTopItemsOrder = List<HomeTopItems>.from(
List<String>.from(jsonDecode(dbData['homeTopItemsOrder'])).map((e) { List<String>.from(sharedPreferencesInstance.getStringList('homeTopItemsOrder')!).map((e) {
switch (e) { switch (e) {
case 'queriedDomains': case 'queriedDomains':
return HomeTopItems.queriedDomains; return HomeTopItems.queriedDomains;
@ -472,18 +381,10 @@ class AppConfigProvider with ChangeNotifier {
} }
}).where((e) => e != null).toList() }).where((e) => e != null).toList()
); );
final missingItems = homeTopItemsDefaultOrder.where((e) => !itemsOrder.contains(e));
_homeTopItemsOrder = [
...itemsOrder,
...missingItems
];
} catch (e) { } catch (e) {
Sentry.captureException(e); Sentry.captureException(e);
_homeTopItemsOrder = homeTopItemsDefaultOrder; _homeTopItemsOrder = homeTopItemsDefaultOrder;
} }
} }
_dbInstance = dbInstance;
notifyListeners();
} }
} }

View file

@ -119,7 +119,7 @@ class DnsProvider with ChangeNotifier {
if (result.successful == true) { if (result.successful == true) {
DnsInfo data = dnsInfo!; DnsInfo data = dnsInfo!;
data.bootstrapDns = List<String>.from(value['fallback_dns']); data.fallbackDns = List<String>.from(value['fallback_dns']);
setDnsInfoData(data); setDnsInfoData(data);
return result; return result;
} }
@ -151,17 +151,26 @@ class DnsProvider with ChangeNotifier {
final result = await _serversProvider!.apiClient2!.setDnsConfig( final result = await _serversProvider!.apiClient2!.setDnsConfig(
data: value data: value
); );
void updateValue(dynamic parameter, dynamic value) {
if (value != null) {
parameter = value;
}
}
if (result.successful == true) { if (result.successful == true) {
DnsInfo data = dnsInfo!; DnsInfo data = dnsInfo!;
data.ratelimit = value['ratelimit']; updateValue(data.ratelimit, value['ratelimit']);
data.ednsCsEnabled = value['edns_cs_enabled']; updateValue(data.ednsCsEnabled, value['edns_cs_enabled']);
data.dnssecEnabled = value['dnssec_enabled']; updateValue(data.dnssecEnabled, value['dnssec_enabled']);
data.disableIpv6 = value['disable_ipv6']; updateValue(data.disableIpv6, value['disable_ipv6']);
data.blockingMode = value['blocking_mode']; updateValue(data.blockingMode, value['blocking_mode']);
data.blockingIpv4 = value['blocking_ipv4']; updateValue(data.blockingIpv4, value['blocking_ipv4']);
data.blockingIpv6 = value['blocking_ipv6']; updateValue(data.blockingIpv6, value['blocking_ipv6']);
data.blockedResponseTtl = value['blocked_response_ttl']; updateValue(data.blockedResponseTtl, value['blocked_response_ttl']);
updateValue(data.ratelimitSubnetLenIpv4, value['ratelimit_subnet_len_ipv4']);
updateValue(data.ratelimitSubnetLenIpv6, value['ratelimit_subnet_len_ipv6']);
updateValue(data.ratelimitWhitelist, value['ratelimit_whitelist']);
setDnsInfoData(data); setDnsInfoData(data);
return result; return result;
} }

View file

@ -133,7 +133,6 @@ class FilteringProvider with ChangeNotifier {
if (result2.successful == true) { if (result2.successful == true) {
_filtering = result2.content as Filtering; _filtering = result2.content as Filtering;
notifyListeners(); notifyListeners();
print(result.content);
return { return {
"success": true, "success": true,
"data": result.content "data": result.content
@ -289,6 +288,23 @@ class FilteringProvider with ChangeNotifier {
} }
} }
Future<bool> setCustomRules(List<String> rules) async {
final newRules = rules.where((r) => r != " " && r != "").toList();
final result = await _serversProvider!.apiClient2!.setCustomRules(rules: newRules);
if (result.successful == true) {
Filtering filteringData = filtering!;
filteringData.userRules = newRules;
_filtering = filteringData;
notifyListeners();
return true;
}
else {
notifyListeners();
return false;
}
}
Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async { Future<Map<String, dynamic>> addList({required String name, required String url, required String type}) async {
final result1 = await _serversProvider!.apiClient2!.addFilteringList( final result1 = await _serversProvider!.apiClient2!.addFilteringList(
data: { data: {

View file

@ -0,0 +1,51 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
class LiveLogsProvider with ChangeNotifier {
ServersProvider? _serversProvider;
update(ServersProvider? provider) {
_serversProvider = provider;
}
bool _isDisposed = false;
@override
void dispose() {
_isDisposed = true;
super.dispose();
}
List<Log> _logs = [];
List<Log> get logs {
return _logs;
}
DateTime? _lastTime;
void startFetchLogs() {
_lastTime = DateTime.now();
_fetchLogs();
}
void _fetchLogs() async {
if (_lastTime == null) return;
final result = await _serversProvider!.apiClient2!.getLogs(
count: 100
);
if (result.successful == false || result.content == null) return;
final valid = (result.content as LogsData).data.where((e) => e.time.isAfter(_lastTime!));
_logs = [...valid, ..._logs];
_lastTime = DateTime.now();
notifyListeners();
await Future.delayed(const Duration(seconds: 2));
if (_isDisposed == true) return;
_fetchLogs();
}
}

View file

@ -20,7 +20,7 @@ class LogsProvider with ChangeNotifier {
DateTime? _logsOlderThan; DateTime? _logsOlderThan;
String _selectedResultStatus = 'all'; String _selectedResultStatus = 'all';
String? _searchText; String? _searchText;
List<String>? _selectedClients; List<String> _selectedClients = [];
int _logsQuantity = 100; int _logsQuantity = 100;
int _offset = 0; int _offset = 0;
@ -30,7 +30,7 @@ class LogsProvider with ChangeNotifier {
AppliedFiters _appliedFilters = AppliedFiters( AppliedFiters _appliedFilters = AppliedFiters(
selectedResultStatus: 'all', selectedResultStatus: 'all',
searchText: null, searchText: null,
clients: null clients: []
); );
LoadStatus get loadStatus { LoadStatus get loadStatus {
@ -65,7 +65,7 @@ class LogsProvider with ChangeNotifier {
return _offset; return _offset;
} }
List<String>? get selectedClients { List<String> get selectedClients {
return _selectedClients; return _selectedClients;
} }
@ -131,7 +131,7 @@ class LogsProvider with ChangeNotifier {
} }
void setSelectedClients(List<String>? clients) { void setSelectedClients(List<String>? clients) {
_selectedClients = clients; _selectedClients = clients ?? [];
notifyListeners(); notifyListeners();
} }
@ -177,18 +177,18 @@ class LogsProvider with ChangeNotifier {
if (loadingMore != null && loadingMore == true && logsData != null) { if (loadingMore != null && loadingMore == true && logsData != null) {
LogsData newLogsData = result.content; LogsData newLogsData = result.content;
newLogsData.data = [...logsData!.data, ...(result.content as LogsData).data]; newLogsData.data = [...logsData!.data, ...(result.content as LogsData).data];
if (appliedFilters.clients != null) { if (appliedFilters.clients.isNotEmpty) {
newLogsData.data = newLogsData.data.where( newLogsData.data = newLogsData.data.where(
(item) => appliedFilters.clients!.contains(item.client) (item) => appliedFilters.clients.contains(item.client)
).toList(); ).toList();
} }
_logsData = newLogsData; _logsData = newLogsData;
} }
else { else {
LogsData newLogsData = result.content; LogsData newLogsData = result.content;
if (appliedFilters.clients != null) { if (appliedFilters.clients.isNotEmpty) {
newLogsData.data = newLogsData.data.where( newLogsData.data = newLogsData.data.where(
(item) => appliedFilters.clients!.contains(item.client) (item) => appliedFilters.clients.contains(item.client)
).toList(); ).toList();
} }
_logsData = newLogsData; _logsData = newLogsData;
@ -217,7 +217,7 @@ class LogsProvider with ChangeNotifier {
_appliedFilters = AppliedFiters( _appliedFilters = AppliedFiters(
selectedResultStatus: 'all', selectedResultStatus: 'all',
searchText: null, searchText: null,
clients: null clients: []
); );
if (result.successful == true) { if (result.successful == true) {
@ -254,9 +254,9 @@ class LogsProvider with ChangeNotifier {
if (result.successful == true) { if (result.successful == true) {
LogsData newLogsData = result.content as LogsData; LogsData newLogsData = result.content as LogsData;
if (appliedFilters.clients != null) { if (appliedFilters.clients.isNotEmpty) {
newLogsData.data = newLogsData.data.where( newLogsData.data = newLogsData.data.where(
(item) => appliedFilters.clients!.contains(item.client) (item) => appliedFilters.clients.contains(item.client)
).toList(); ).toList();
} }
_logsData = newLogsData; _logsData = newLogsData;

View file

@ -3,10 +3,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/clients.dart';
import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart'; import 'package:adguard_home_manager/screens/clients/client/client_screen_functions.dart';
import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/added_client_tile.dart';
import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart'; import 'package:adguard_home_manager/screens/clients/client/remove_client_modal.dart';
@ -82,14 +82,14 @@ class _AddedListState extends State<AddedList> {
processModal.close(); processModal.close();
if (result == true) { if (result == true) {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully, label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotUpdated, label: AppLocalizations.of(context)!.clientNotUpdated,
color: Colors.red color: Colors.red
@ -107,16 +107,16 @@ class _AddedListState extends State<AddedList> {
if (result == true) { if (result == true) {
if (widget.splitView == true) { if (widget.splitView == true) {
SplitView.of(context).popUntil(0); Navigator.of(clientsNavigatorKey.currentContext!).popUntil((route) => false);
} }
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientDeletedSuccessfully, label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotDeleted, label: AppLocalizations.of(context)!.clientNotDeleted,
color: Colors.red color: Colors.red

View file

@ -81,7 +81,7 @@ class _AddedClientTileState extends State<AddedClientTile> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''), widget.client.name,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -89,6 +89,15 @@ class _AddedClientTileState extends State<AddedClientTile> {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(
widget.client.ids.toString().replaceAll(RegExp(r'^\[|\]$'), ''),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Row( Row(
children: [ children: [
Icon( Icon(

View file

@ -0,0 +1,209 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/client/blocking_schedule_modal.dart';
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
import 'package:adguard_home_manager/widgets/section_label.dart';
import 'package:adguard_home_manager/models/clients.dart';
class EditBlockingSchedule {
final String timezone;
final List<String> weekday;
final int start;
final int end;
const EditBlockingSchedule({
required this.timezone,
required this.weekday,
required this.start,
required this.end,
});
}
class BlockingSchedule extends StatelessWidget {
final BlockedServicesSchedule blockedServicesSchedule;
final void Function(BlockedServicesSchedule) setBlockedServicesSchedule;
const BlockingSchedule({
super.key,
required this.blockedServicesSchedule,
required this.setBlockedServicesSchedule,
});
@override
Widget build(BuildContext context) {
void updateSchedule(EditBlockingSchedule v) {
final scheduleJson = blockedServicesSchedule.toJson();
for (var weekday in v.weekday) {
scheduleJson[weekday] = {
"start": v.start,
"end": v.end
};
}
scheduleJson["time_zone"] = v.timezone;
setBlockedServicesSchedule(BlockedServicesSchedule.fromJson(scheduleJson));
}
void openAddScheduleModal() {
showDialog(
context: context,
builder: (context) => BlockingScheduleModal(
onConfirm: updateSchedule,
),
);
}
void openEditScheduleModal(String weekday) {
showDialog(
context: context,
builder: (context) => BlockingScheduleModal(
schedule: EditBlockingSchedule(
timezone: blockedServicesSchedule.timeZone!,
weekday: [weekday],
start: blockedServicesSchedule.toJson()[weekday]['start'],
end: blockedServicesSchedule.toJson()[weekday]['end'],
),
onConfirm: updateSchedule,
),
);
}
void onDeleteSchedule(String weekday) {
final scheduleJson = blockedServicesSchedule.toJson();
scheduleJson[weekday] = null;
setBlockedServicesSchedule(BlockedServicesSchedule.fromJson(scheduleJson));
}
String formatTime(int time) {
final formatted = Duration(milliseconds: time);
final hours = formatted.inHours;
final minutes = formatted.inMinutes - hours*60;
return "${hours.toString().padLeft(2 , '0')}:${minutes.toString().padLeft(2, '0')}";
}
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SectionLabel(label: AppLocalizations.of(context)!.pauseServiceBlocking),
Padding(
padding: const EdgeInsets.only(right: 12),
child: IconButton(
onPressed: openAddScheduleModal,
icon: const Icon(Icons.add)
),
)
],
),
const SizedBox(height: 2),
if (
blockedServicesSchedule.mon == null &&
blockedServicesSchedule.tue == null &&
blockedServicesSchedule.wed == null &&
blockedServicesSchedule.thu == null &&
blockedServicesSchedule.fri == null &&
blockedServicesSchedule.sat == null &&
blockedServicesSchedule.sun == null
) Padding(
padding: const EdgeInsets.all(16),
child: Text(
AppLocalizations.of(context)!.noBlockingScheduleThisDevice,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
),
if (blockedServicesSchedule.mon != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.monday,
schedule: "${formatTime(blockedServicesSchedule.mon!.start!)} - ${formatTime(blockedServicesSchedule.mon!.end!)}",
onEdit: () => openEditScheduleModal("mon"),
onDelete: () => onDeleteSchedule("mon")
),
if (blockedServicesSchedule.tue != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.tuesday,
schedule: "${formatTime(blockedServicesSchedule.tue!.start!)} - ${formatTime(blockedServicesSchedule.tue!.end!)}",
onEdit: () => openEditScheduleModal("tue"),
onDelete: () => onDeleteSchedule("tue")
),
if (blockedServicesSchedule.wed != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.wednesday,
schedule: "${formatTime(blockedServicesSchedule.wed!.start!)} - ${formatTime(blockedServicesSchedule.wed!.end!)}",
onEdit: () => openEditScheduleModal("wed"),
onDelete: () => onDeleteSchedule("wed")
),
if (blockedServicesSchedule.thu != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.thursday,
schedule: "${formatTime(blockedServicesSchedule.thu!.start!)} - ${formatTime(blockedServicesSchedule.thu!.end!)}",
onEdit: () => openEditScheduleModal("thu"),
onDelete: () => onDeleteSchedule("thu")
),
if (blockedServicesSchedule.fri != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.friday,
schedule: "${formatTime(blockedServicesSchedule.fri!.start!)} - ${formatTime(blockedServicesSchedule.fri!.end!)}",
onEdit: () => openEditScheduleModal("fri"),
onDelete: () => onDeleteSchedule("fri")
),
if (blockedServicesSchedule.sat != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.saturday,
schedule: "${formatTime(blockedServicesSchedule.sat!.start!)} - ${formatTime(blockedServicesSchedule.sat!.end!)}",
onEdit: () => openEditScheduleModal("sat"),
onDelete: () => onDeleteSchedule("sat")
),
if (blockedServicesSchedule.sun != null) _ScheduleTile(
weekday: AppLocalizations.of(context)!.sunday,
schedule: "${formatTime(blockedServicesSchedule.sun!.start!)} - ${formatTime(blockedServicesSchedule.sun!.end!)}",
onEdit: () => openEditScheduleModal("sun"),
onDelete: () => onDeleteSchedule("sun")
),
],
);
}
}
class _ScheduleTile extends StatelessWidget {
final String weekday;
final String schedule;
final void Function() onEdit;
final void Function() onDelete;
const _ScheduleTile({
required this.weekday,
required this.schedule,
required this.onEdit,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return CustomListTile(
title: weekday,
subtitle: schedule,
trailing: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: onEdit,
icon: const Icon(Icons.edit_rounded),
tooltip: AppLocalizations.of(context)!.edit,
),
const SizedBox(width: 4),
IconButton(
onPressed: onDelete,
icon: const Icon(Icons.delete_rounded),
tooltip: AppLocalizations.of(context)!.delete,
),
],
),
padding: const EdgeInsets.only(
left: 16,
top: 6,
right: 12,
bottom: 6
)
);
}
}

View file

@ -0,0 +1,325 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/client/blocking_schedule.dart';
class BlockingScheduleModal extends StatefulWidget {
final EditBlockingSchedule? schedule;
final void Function(EditBlockingSchedule) onConfirm;
const BlockingScheduleModal({
super.key,
this.schedule,
required this.onConfirm,
});
@override
State<BlockingScheduleModal> createState() => _BlockingScheduleModalState();
}
class _BlockingScheduleModalState extends State<BlockingScheduleModal> {
final _weekdaysScrollController = ScrollController();
String? _timezone;
List<String> _weekdays = [];
TimeOfDay? _from;
TimeOfDay? _to;
bool _compareTimes(TimeOfDay startTime, TimeOfDay endTime) {
bool result = false;
int startTimeInt = (startTime.hour * 60 + startTime.minute) * 60;
int endTimeInt = (endTime.hour * 60 + endTime.minute) * 60;
if (endTimeInt > startTimeInt) {
result = true;
} else {
result = false;
}
return result;
}
bool _validate() {
return _timezone != null &&
_weekdays.isNotEmpty &&
_from != null &&
_to != null &&
_compareTimes(_from!, _to!);
}
int _timeOfDayToInt(TimeOfDay timeOfDay) {
return Duration(
days: 0,
hours: timeOfDay.hour,
minutes: timeOfDay.minute,
seconds: 0
).inMilliseconds;
}
TimeOfDay _intToTimeOfDay(int value) {
final duration = Duration(milliseconds: value);
final minutes = duration.inMinutes - duration.inHours*60;
return TimeOfDay(hour: duration.inHours, minute: minutes);
}
@override
void initState() {
tz.initializeTimeZones();
if (widget.schedule != null) {
_timezone = widget.schedule!.timezone;
_weekdays = widget.schedule!.weekday;
_from = _intToTimeOfDay(widget.schedule!.start);
_to = _intToTimeOfDay(widget.schedule!.end);
}
super.initState();
}
@override
Widget build(BuildContext context) {
void onSelectWeekday(bool newStatus, String day) {
if (newStatus == true && !_weekdays.contains(day)) {
setState(() => _weekdays.add(day));
}
else if (newStatus == false) {
setState(() => _weekdays = _weekdays.where((e) => e != day).toList());
}
}
void onConfirm() {
widget.onConfirm(
EditBlockingSchedule(
timezone: _timezone!,
weekday: _weekdays,
start: _timeOfDayToInt(_from!),
end: _timeOfDayToInt(_to!)
)
);
Navigator.pop(context);
}
final valid = _validate();
final validTimes = _from != null && _to != null
? _compareTimes(_from!, _to!)
: null;
return Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 500),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.schedule_rounded,
size: 24,
color: Theme.of(context).listTileTheme.iconColor
),
const SizedBox(height: 16),
Text(
widget.schedule != null
? AppLocalizations.of(context)!.editSchedule
: AppLocalizations.of(context)!.newSchedule,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
),
),
const SizedBox(height: 30),
LayoutBuilder(
builder: (context, constraints) => DropdownButtonFormField(
items: tz.timeZoneDatabase.locations.keys.map((item) => DropdownMenuItem(
value: item,
child: SizedBox(
width: constraints.maxWidth-48,
child: Text(
item,
overflow: TextOverflow.ellipsis,
),
),
)).toList(),
value: _timezone,
onChanged: (v) => setState(() => _timezone = v),
decoration: InputDecoration(
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
label: Text(AppLocalizations.of(context)!.timezone)
),
borderRadius: BorderRadius.circular(20),
),
),
const SizedBox(height: 16),
SizedBox(
height: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 66 : 50,
child: Scrollbar(
controller: _weekdaysScrollController,
thumbVisibility: Platform.isMacOS || Platform.isLinux || Platform.isWindows,
interactive: Platform.isMacOS || Platform.isLinux || Platform.isWindows,
thickness: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 8 : 0,
child: Padding(
padding: EdgeInsets.only(
bottom: Platform.isMacOS || Platform.isLinux || Platform.isWindows ? 16 : 0
),
child: ListView(
controller: _weekdaysScrollController,
scrollDirection: Axis.horizontal,
children: [
FilterChip(
label: Text(AppLocalizations.of(context)!.monday),
selected: _weekdays.contains("mon"),
onSelected: (value) => onSelectWeekday(value, "mon")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.tuesday),
selected: _weekdays.contains("tue"),
onSelected: (value) => onSelectWeekday(value, "tue")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.wednesday),
selected: _weekdays.contains("wed"),
onSelected: (value) => onSelectWeekday(value, "wed")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.thursday),
selected: _weekdays.contains("thu"),
onSelected: (value) => onSelectWeekday(value, "thu")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.friday),
selected: _weekdays.contains("fri"),
onSelected: (value) => onSelectWeekday(value, "fri")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.saturday),
selected: _weekdays.contains("sat"),
onSelected: (value) => onSelectWeekday(value, "sat")
),
const SizedBox(width: 8),
FilterChip(
label: Text(AppLocalizations.of(context)!.sunday),
selected: _weekdays.contains("sun"),
onSelected: (value) => onSelectWeekday(value, "sun")
),
],
),
),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () async {
final selected = await showTimePicker(
context: context,
initialTime: _from ?? const TimeOfDay(hour: 0, minute: 0),
helpText: AppLocalizations.of(context)!.selectStartTime,
confirmText: AppLocalizations.of(context)!.confirm,
);
setState(() => _from = selected);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
children: [
Text(AppLocalizations.of(context)!.from),
const SizedBox(height: 2),
Text(_from != null ? "${_from!.hour.toString().padLeft(2, '0')}:${_from!.minute.toString().padLeft(2, '0')}" : "--:--")
],
),
)
),
ElevatedButton(
onPressed: () async {
final selected = await showTimePicker(
context: context,
initialTime: _to ?? const TimeOfDay(hour: 23, minute: 59),
helpText: AppLocalizations.of(context)!.selectEndTime,
confirmText: AppLocalizations.of(context)!.confirm
);
setState(() => _to = selected);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
children: [
Text(AppLocalizations.of(context)!.to),
const SizedBox(height: 2),
Text(_to != null ? "${_to!.hour.toString().padLeft(2, '0')}:${_to!.minute.toString().padLeft(2, '0')}" : "--:--")
],
),
)
),
],
),
if (validTimes == false) Padding(
padding: const EdgeInsets.only(top: 16),
child: Card(
color: const Color.fromARGB(255, 255, 182, 175),
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(
Icons.error_rounded,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
const SizedBox(width: 16),
Expanded(
child: Text(
AppLocalizations.of(context)!.startTimeBeforeEndTime,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
),
),
],
),
),
)
)
],
),
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close)
),
const SizedBox(width: 8),
TextButton(
onPressed: valid ? () => onConfirm() : null,
child: Text(AppLocalizations.of(context)!.confirm)
),
],
)
],
),
),
),
);
}
}

View file

@ -8,6 +8,7 @@ import 'package:adguard_home_manager/screens/clients/client/identifiers_section.
import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart'; import 'package:adguard_home_manager/screens/clients/client/settings_tile.dart';
import 'package:adguard_home_manager/screens/clients/client/tags_section.dart'; import 'package:adguard_home_manager/screens/clients/client/tags_section.dart';
import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart'; import 'package:adguard_home_manager/screens/clients/client/upstream_servers_section.dart';
import 'package:adguard_home_manager/screens/clients/client/blocking_schedule.dart';
import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_switch_list_tile.dart';
import 'package:adguard_home_manager/widgets/custom_list_tile.dart'; import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
@ -52,6 +53,8 @@ class ClientForm extends StatelessWidget {
final TextEditingController dnsCacheField; final TextEditingController dnsCacheField;
final String? dnsCacheError; final String? dnsCacheError;
final void Function(String?) updateDnsCacheError; final void Function(String?) updateDnsCacheError;
final BlockedServicesSchedule blockedServicesSchedule;
final void Function(BlockedServicesSchedule) setBlockedServicesSchedule;
const ClientForm({ const ClientForm({
super.key, super.key,
@ -90,6 +93,8 @@ class ClientForm extends StatelessWidget {
required this.dnsCacheField, required this.dnsCacheField,
required this.dnsCacheError, required this.dnsCacheError,
required this.updateDnsCacheError, required this.updateDnsCacheError,
required this.blockedServicesSchedule,
required this.setBlockedServicesSchedule,
}); });
@override @override
@ -282,6 +287,11 @@ class ClientForm extends StatelessWidget {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
), ),
BlockingSchedule(
blockedServicesSchedule: blockedServicesSchedule,
setBlockedServicesSchedule: setBlockedServicesSchedule,
),
const SizedBox(height: 16),
], ],
); );
} }

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ClientPlaceholder extends StatelessWidget { class ClientPlaceholder extends StatelessWidget {
const ClientPlaceholder({Key? key}) : super(key: key); const ClientPlaceholder({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return const Center(
child: Text("Select a client"), child: Text("Select a client"),
); );
} }

View file

@ -10,6 +10,16 @@ import 'package:adguard_home_manager/models/safe_search.dart';
import 'package:adguard_home_manager/providers/clients_provider.dart'; import 'package:adguard_home_manager/providers/clients_provider.dart';
import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients.dart';
class ClientInitialData {
final String name;
final String ip;
const ClientInitialData({
required this.name,
required this.ip,
});
}
class ControllerListItem { class ControllerListItem {
final String id; final String id;
final TextEditingController controller; final TextEditingController controller;
@ -25,13 +35,15 @@ class ClientScreen extends StatefulWidget {
final void Function(Client) onConfirm; final void Function(Client) onConfirm;
final void Function(Client)? onDelete; final void Function(Client)? onDelete;
final bool fullScreen; final bool fullScreen;
final ClientInitialData? initialData;
const ClientScreen({ const ClientScreen({
super.key, super.key,
this.client, this.client,
required this.onConfirm, required this.onConfirm,
this.onDelete, this.onDelete,
required this.fullScreen required this.fullScreen,
this.initialData,
}); });
@override @override
@ -81,6 +93,8 @@ class _ClientScreenState extends State<ClientScreen> {
bool _enableDnsCache = false; bool _enableDnsCache = false;
final _dnsCacheField = TextEditingController(); final _dnsCacheField = TextEditingController();
String? _dnsCacheError; String? _dnsCacheError;
BlockedServicesSchedule _blockedServicesSchedule = BlockedServicesSchedule();
// VALIDATIONS // VALIDATIONS
bool _nameValid = true; bool _nameValid = true;
@ -140,6 +154,16 @@ class _ClientScreenState extends State<ClientScreen> {
_dnsCacheField.text = widget.client!.upstreamsCacheSize != null _dnsCacheField.text = widget.client!.upstreamsCacheSize != null
? widget.client!.upstreamsCacheSize.toString() ? widget.client!.upstreamsCacheSize.toString()
: ""; : "";
if (widget.client!.blockedServicesSchedule != null) {
_blockedServicesSchedule = widget.client!.blockedServicesSchedule!;
}
}
if (widget.initialData != null) {
nameController.text = widget.initialData!.name;
identifiersControllers[0] = ControllerListItem(
id: uuid.v4(),
controller: TextEditingController(text: widget.initialData!.ip)
);
} }
super.initState(); super.initState();
} }
@ -166,7 +190,8 @@ class _ClientScreenState extends State<ClientScreen> {
upstreamsCacheEnabled: _enableDnsCache, upstreamsCacheEnabled: _enableDnsCache,
upstreamsCacheSize: _dnsCacheField.text != "" upstreamsCacheSize: _dnsCacheField.text != ""
? int.parse(_dnsCacheField.text) ? int.parse(_dnsCacheField.text)
: null : null,
blockedServicesSchedule: _blockedServicesSchedule
); );
widget.onConfirm(client); widget.onConfirm(client);
} }
@ -210,69 +235,90 @@ class _ClientScreenState extends State<ClientScreen> {
if (widget.fullScreen == true) { if (widget.fullScreen == true) {
return Dialog.fullscreen( return Material(
child: Scaffold( child: NestedScrollView(
appBar: AppBar( headerSliverBuilder: (context, innerBoxIsScrolled) => [
leading: IconButton( SliverOverlapAbsorber(
onPressed: () => Navigator.pop(context), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
icon: const Icon(Icons.close) sliver: SliverAppBar.large(
), pinned: true,
title: Text( floating: true,
widget.client != null centerTitle: false,
? AppLocalizations.of(context)!.client forceElevated: innerBoxIsScrolled,
: AppLocalizations.of(context)!.addClient leading: IconButton(
), onPressed: () => Navigator.pop(context),
actions: actions(), icon: const Icon(Icons.close)
), ),
title: Text(
widget.client != null
? AppLocalizations.of(context)!.client
: AppLocalizations.of(context)!.addClient
),
actions: actions(),
)
)
],
body: SafeArea( body: SafeArea(
child: ListView( top: false,
controller: _scrollController, bottom: false,
children: [ child: Builder(
if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors( builder: (context) => CustomScrollView(
nameValid: _nameValid, slivers: [
identifiersValid: _identifiersValid, SliverOverlapInjector(
dnsCacheValid: _dnsCacheValid handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
), ),
ClientForm( SliverList.list(
isFullScreen: true, children: [
client: widget.client, if (!_nameValid || !_identifiersValid || !_dnsCacheValid) _Errors(
nameController: nameController, nameValid: _nameValid,
identifiersControllers: identifiersControllers, identifiersValid: _identifiersValid,
selectedTags: selectedTags, dnsCacheValid: _dnsCacheValid
useGlobalSettingsFiltering: useGlobalSettingsFiltering, ),
enableFiltering: enableFiltering, ClientForm(
enableParentalControl: enableParentalControl, isFullScreen: true,
enableSafeBrowsing: enableSafeBrowsing, client: widget.client,
enableSafeSearch: enableSafeSearch, nameController: nameController,
safeSearch: safeSearch, identifiersControllers: identifiersControllers,
blockedServices: blockedServices, selectedTags: selectedTags,
updateBlockedServices: (v) => setState(() => blockedServices = v), useGlobalSettingsFiltering: useGlobalSettingsFiltering,
upstreamServers: upstreamServers, enableFiltering: enableFiltering,
updateUpstreamServers: (v) => setState(() => upstreamServers = v), enableParentalControl: enableParentalControl,
defaultSafeSearch: defaultSafeSearch, enableSafeBrowsing: enableSafeBrowsing,
useGlobalSettingsServices: useGlobalSettingsServices, enableSafeSearch: enableSafeSearch,
updateSelectedTags: (v) => setState(() => selectedTags = v), safeSearch: safeSearch,
updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v), blockedServices: blockedServices,
enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering, updateBlockedServices: (v) => setState(() => blockedServices = v),
updateEnableFiltering: (v) => setState(() => enableFiltering = v), upstreamServers: upstreamServers,
updateEnableParentalControl: (v) => setState(() => enableParentalControl = v), updateUpstreamServers: (v) => setState(() => upstreamServers = v),
updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v), defaultSafeSearch: defaultSafeSearch,
updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v), useGlobalSettingsServices: useGlobalSettingsServices,
updateSafeSearch: (v) => setState(() => safeSearch = v), updateSelectedTags: (v) => setState(() => selectedTags = v),
updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v), updateIdentifiersControllers: (v) => setState(() => identifiersControllers = v),
ignoreClientQueryLog: _ignoreClientQueryLog, enableDisableGlobalSettingsFiltering: enableDisableGlobalSettingsFiltering,
ignoreClientStatistics: _ignoreClientStatistics, updateEnableFiltering: (v) => setState(() => enableFiltering = v),
updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v), updateEnableParentalControl: (v) => setState(() => enableParentalControl = v),
updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v), updateEnableSafeBrowsing: (v) => setState(() => enableSafeBrowsing = v),
enableDnsCache: _enableDnsCache, updateEnableSafeSearch: (v) => setState(() => enableSafeSearch = v),
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), updateSafeSearch: (v) => setState(() => safeSearch = v),
dnsCacheField: _dnsCacheField, updateUseGlobalSettingsServices: (v) => setState(() => useGlobalSettingsServices = v),
dnsCacheError: _dnsCacheError, ignoreClientQueryLog: _ignoreClientQueryLog,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v) ignoreClientStatistics: _ignoreClientStatistics,
), updateIgnoreClientQueryLog: (v) => setState(() => _ignoreClientQueryLog = v),
], updateIgnoreClientStatistics: (v) => setState(() => _ignoreClientStatistics = v),
), enableDnsCache: _enableDnsCache,
), updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
dnsCacheField: _dnsCacheField,
dnsCacheError: _dnsCacheError,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v),
blockedServicesSchedule: _blockedServicesSchedule,
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v),
),
],
)
],
),
)
)
), ),
); );
} }
@ -353,7 +399,9 @@ class _ClientScreenState extends State<ClientScreen> {
updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v), updateEnableDnsCache: (v) => setState(() => _enableDnsCache = v),
dnsCacheField: _dnsCacheField, dnsCacheField: _dnsCacheField,
dnsCacheError: _dnsCacheError, dnsCacheError: _dnsCacheError,
updateDnsCacheError: (v) => setState(() => _dnsCacheError = v) updateDnsCacheError: (v) => setState(() => _dnsCacheError = v),
blockedServicesSchedule: _blockedServicesSchedule,
setBlockedServicesSchedule: (v) => setState(() => _blockedServicesSchedule = v),
), ),
], ],
), ),

View file

@ -80,6 +80,7 @@ void openClientFormModal({
Client? client, Client? client,
required void Function(Client) onConfirm, required void Function(Client) onConfirm,
void Function(Client)? onDelete, void Function(Client)? onDelete,
ClientInitialData? initialData,
}) { }) {
showGeneralDialog( showGeneralDialog(
context: context, context: context,
@ -105,6 +106,7 @@ void openClientFormModal({
client: client, client: client,
onConfirm: onConfirm, onConfirm: onConfirm,
onDelete: onDelete, onDelete: onDelete,
initialData: initialData,
), ),
); );
} }

View file

@ -131,146 +131,177 @@ class _LogsListClientState extends State<LogsListClient> {
setState(() => previousIp = widget.ip); setState(() => previousIp = widget.ip);
} }
return Scaffold( return Material(
appBar: AppBar( child: NestedScrollView(
title: Text(widget.name != null && widget.name != '' ? widget.name! : widget.ip), headerSliverBuilder: (context, innerBoxIsScrolled) => [
centerTitle: true, SliverOverlapAbsorber(
surfaceTintColor: isDesktop(MediaQuery.of(context).size.width) handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
? Colors.transparent sliver: SliverAppBar.large(
: null, pinned: true,
actions: [ floating: true,
if (!(Platform.isAndroid || Platform.isIOS)) ...[ centerTitle: false,
IconButton( forceElevated: innerBoxIsScrolled,
onPressed: fetchLogs, title: SafeArea(
icon: const Icon(Icons.refresh_rounded), child: Column(
tooltip: AppLocalizations.of(context)!.refresh, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
const SizedBox(width: 8) Text(
] AppLocalizations.of(context)!.client,
], style: const TextStyle(
), fontSize: 24
body: SafeArea( ),
child: Builder( ),
builder: (context) { const SizedBox(height: 4),
switch (loadStatus) { Text(
case 0: widget.name != null && widget.name != '' ? widget.name! : widget.ip,
return SizedBox( style: TextStyle(
width: double.maxFinite, fontSize: 14,
child: Column( fontWeight: FontWeight.w500,
mainAxisAlignment: MainAxisAlignment.center, color: Theme.of(context).colorScheme.secondary
crossAxisAlignment: CrossAxisAlignment.center, ),
children: [ )
const CircularProgressIndicator(), ],
const SizedBox(height: 30), ),
Text( ),
AppLocalizations.of(context)!.loadingLogs, surfaceTintColor: isDesktop(MediaQuery.of(context).size.width)
textAlign: TextAlign.center, ? Colors.transparent
style: TextStyle( : null,
fontSize: 22, actions: [
color: Theme.of(context).colorScheme.onSurfaceVariant, if (!(Platform.isAndroid || Platform.isIOS)) ...[
), IconButton(
) onPressed: fetchLogs,
], icon: const Icon(Icons.refresh_rounded),
tooltip: AppLocalizations.of(context)!.refresh,
), ),
); const SizedBox(width: 8)
]
case 1: ],
if (logsData!.data.isNotEmpty) { )
return RefreshIndicator( )
onRefresh: fetchLogs, ],
child: ListView.builder( body: SafeArea(
controller: scrollController, top: false,
padding: const EdgeInsets.only(top: 0), bottom: false,
itemCount: isLoadingMore == true child: Builder(
? logsData!.data.length+1 builder: (context) => RefreshIndicator(
: logsData!.data.length, onRefresh: fetchLogs,
itemBuilder: (context, index) { displacement: 95,
if (isLoadingMore == true && index == logsData!.data.length) { child: CustomScrollView(
return const Padding( slivers: [
padding: EdgeInsets.symmetric(vertical: 20), SliverOverlapInjector(
child: Center( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: CircularProgressIndicator(), ),
if (loadStatus == 0) SliverFillRemaining(
child: SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingLogs,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
); )
} ],
else { ),
return LogTile( )
log: logsData!.data[index], ),
index: index, if (loadStatus == 1 && logsData!.data.isNotEmpty) SliverList.builder(
length: logsData!.data.length, itemCount: isLoadingMore == true
useAlwaysNormalTile: true, ? logsData!.data.length+1
onLogTap: (log) => { : logsData!.data.length,
if (width > 700) { itemBuilder: (context, index) {
showDialog( if (isLoadingMore == true && index == logsData!.data.length) {
context: context, return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(
child: CircularProgressIndicator(),
),
);
}
else {
return LogTile(
log: logsData!.data[index],
index: index,
length: logsData!.data.length,
useAlwaysNormalTile: true,
onLogTap: (log) => {
if (width > 700) {
showDialog(
context: context,
builder: (context) => LogDetailsScreen(
log: log,
dialog: true,
twoColumns: widget.splitView,
)
)
}
else {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LogDetailsScreen( builder: (context) => LogDetailsScreen(
log: log, log: log,
dialog: true dialog: false,
twoColumns: widget.splitView,
) )
) )
} )
else { }
Navigator.of(context).push( },
MaterialPageRoute( twoColumns: widget.splitView,
builder: (context) => LogDetailsScreen( );
log: log,
dialog: false
)
)
)
}
},
twoColumns: widget.splitView,
);
}
} }
), }
); ),
} if (loadStatus == 1 && logsData!.data.isEmpty) SliverFillRemaining(
else { child: Center(
return Center( child: Text(
child: Text( AppLocalizations.of(context)!.noLogsDisplay,
AppLocalizations.of(context)!.noLogsDisplay,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
}
case 2:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
) ),
], ),
), ),
); if (loadStatus == 2) SliverFillRemaining(
child: SizedBox(
default: width: double.maxFinite,
return const SizedBox(); child: Column(
} mainAxisAlignment: MainAxisAlignment.center,
}, crossAxisAlignment: CrossAxisAlignment.center,
), children: [
) const Icon(
Icons.error,
color: Colors.red,
size: 50,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.logsNotLoaded,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
],
),
),
)
],
),
),
)
)
),
); );
} }
} }

View file

@ -5,9 +5,9 @@ class RemoveClientModal extends StatelessWidget {
final void Function() onConfirm; final void Function() onConfirm;
const RemoveClientModal({ const RemoveClientModal({
Key? key, super.key,
required this.onConfirm required this.onConfirm
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -11,11 +11,11 @@ class SafeSearchModal extends StatefulWidget {
final void Function(SafeSearch) onConfirm; final void Function(SafeSearch) onConfirm;
const SafeSearchModal({ const SafeSearchModal({
Key? key, super.key,
required this.safeSearch, required this.safeSearch,
required this.disabled, required this.disabled,
required this.onConfirm required this.onConfirm
}) : super(key: key); });
@override @override
State<SafeSearchModal> createState() => _SafeSearchModalState(); State<SafeSearchModal> createState() => _SafeSearchModalState();

View file

@ -10,10 +10,10 @@ class ServicesModal extends StatefulWidget {
final void Function(List<String>) onConfirm; final void Function(List<String>) onConfirm;
const ServicesModal({ const ServicesModal({
Key? key, super.key,
required this.blockedServices, required this.blockedServices,
required this.onConfirm, required this.onConfirm,
}) : super(key: key); });
@override @override
State<ServicesModal> createState() => _ServicesModalStateWidget(); State<ServicesModal> createState() => _ServicesModalStateWidget();

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/clients_lists.dart'; import 'package:adguard_home_manager/screens/clients/clients_lists.dart';
import 'package:adguard_home_manager/models/clients.dart'; import 'package:adguard_home_manager/models/clients.dart';
final clientsNavigatorKey = GlobalKey<NavigatorState>();
class Clients extends StatefulWidget { class Clients extends StatefulWidget {
const Clients({super.key}); const Clients({super.key});
@ -23,27 +23,24 @@ class _ClientsState extends State<Clients> with TickerProviderStateMixin {
return Scaffold( return Scaffold(
body: LayoutBuilder( body: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (constraints.maxWidth > 1000) { if (constraints.maxWidth > 900) {
return SplitView.material( return Row(
hideDivider: true, children: [
flexWidth: const FlexWidth(mainViewFlexWidth: 1, secondaryViewFlexWidth: 2), const Expanded(
placeholder: Center( flex: 1,
child: Padding( child: ClientsLists(
padding: const EdgeInsets.all(24), splitView: true,
child: Text( )
AppLocalizations.of(context)!.selectClientLeftColumn,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant
),
),
),
), ),
child: const ClientsLists( Expanded(
splitView: true, flex: 2,
child: Navigator(
key: clientsNavigatorKey,
onGenerateRoute: (settings) => MaterialPageRoute(builder: (ctx) => const SizedBox()),
),
) )
); ],
);
} }
else { else {
return const ClientsLists( return const ClientsLists(

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/clients/clients.dart';
import 'package:adguard_home_manager/screens/clients/added_list.dart'; import 'package:adguard_home_manager/screens/clients/added_list.dart';
import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart'; import 'package:adguard_home_manager/screens/clients/client/logs_list_client.dart';
import 'package:adguard_home_manager/screens/clients/clients_list.dart'; import 'package:adguard_home_manager/screens/clients/clients_list.dart';
@ -68,7 +68,13 @@ class _ClientsListsState extends State<ClientsLists> with TickerProviderStateMix
splitView: widget.splitView, splitView: widget.splitView,
); );
if (widget.splitView) { if (widget.splitView) {
SplitView.of(context).push(w); Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => w,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
)
);
} }
else { else {
Navigator.of(context).push( Navigator.of(context).push(
@ -88,7 +94,13 @@ class _ClientsListsState extends State<ClientsLists> with TickerProviderStateMix
splitView: widget.splitView, splitView: widget.splitView,
); );
if (widget.splitView) { if (widget.splitView) {
SplitView.of(context).push(w); Navigator.of(clientsNavigatorKey.currentContext!).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => w,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
)
);
} }
else { else {
Navigator.of(context).push( Navigator.of(context).push(

View file

@ -14,7 +14,7 @@ import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
class ClientsFab extends StatelessWidget { class ClientsFab extends StatelessWidget {
const ClientsFab({Key? key}) : super(key: key); const ClientsFab({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -25,22 +25,26 @@ class ClientsFab extends StatelessWidget {
final width = MediaQuery.of(context).size.width; final width = MediaQuery.of(context).size.width;
void confirmAddClient(Client client) async { void confirmAddClient(Client client) async {
if (!context.mounted) return;
ProcessModal processModal = ProcessModal(); ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.addingClient); processModal.open(AppLocalizations.of(context)!.addingClient);
final result = await clientsProvider.addClient(client); final result = await clientsProvider.addClient(client);
if (!context.mounted) return;
processModal.close(); processModal.close();
if (result == true) { if (result == true) {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientAddedSuccessfully, label: AppLocalizations.of(context)!.clientAddedSuccessfully,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotAdded, label: AppLocalizations.of(context)!.clientNotAdded,
color: Colors.red color: Colors.red

View file

@ -95,14 +95,14 @@ class _SearchClientsState extends State<SearchClients> {
processModal.close(); processModal.close();
if (result == true) { if (result == true) {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientDeletedSuccessfully, label: AppLocalizations.of(context)!.clientDeletedSuccessfully,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotDeleted, label: AppLocalizations.of(context)!.clientNotDeleted,
color: Colors.red color: Colors.red
@ -119,14 +119,14 @@ class _SearchClientsState extends State<SearchClients> {
processModal.close(); processModal.close();
if (result == true) { if (result == true) {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientUpdatedSuccessfully, label: AppLocalizations.of(context)!.clientUpdatedSuccessfully,
color: Colors.green color: Colors.green
); );
} }
else { else {
showSnacbkar( showSnackbar(
appConfigProvider: appConfigProvider, appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.clientNotUpdated, label: AppLocalizations.of(context)!.clientNotUpdated,
color: Colors.red color: Colors.red
@ -205,7 +205,7 @@ class _SearchClientsState extends State<SearchClients> {
height: 1, height: 1,
decoration: BoxDecoration( decoration: BoxDecoration(
color: showDivider == true color: showDivider == true
? Theme.of(context).colorScheme.surfaceVariant ? Theme.of(context).colorScheme.surfaceContainerHighest
: Colors.transparent : Colors.transparent
), ),
), ),

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