Skip to content

Commit 2ddb5b8

Browse files
committed
AGDNS-4329 design review updates
1 parent 090f75d commit 2ddb5b8

78 files changed

Lines changed: 1638 additions & 634 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client_v2/src/__locales/en.json

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"add_tls_certificate": "Add TLS certificate",
77
"add_tls_certificate_private_key": "Add TLS certificate private key",
88
"add_to_allowlist": "Add to allowlist",
9-
"ads_blocked": "Ads blocked",
10-
"ads_blocked_card": "Ads blocked",
9+
"ads_blocked": "Blocked ads",
10+
"ads_blocked_card": "Blocked ads",
1111
"ads_blocked_tooltip": "The number of DNS queries blocked by adblock filters and hosts blocklist",
1212
"adult_websites_blocked": "Adult websites blocked",
1313
"adult_websites_blocked_tooltip": "The number of adult websites blocked",
@@ -228,6 +228,7 @@
228228
"dns_disallowed_clients": "Disallowed clients",
229229
"dns_disallowed_clients_desc": "Drops requests from clients on this list. This setting is ignored when there are entries in Allowed clients",
230230
"dns_disallowed_clients_label": "Disallowed clients, one per line",
231+
"dns_disallowed_clients_notice": "To add disallowed clients, clear the allowed clients list",
231232
"dns_disallowed_domains": "Disallowed domains",
232233
"dns_disallowed_domains_desc": "Drops DNS queries that match these domains, and they don’t appear in Query log",
233234
"dns_disallowed_domains_desc_2": "Enter exact domain names, wildcards, or URL filter rules, such as example.org, *.example.org, or ||example.org^",
@@ -271,7 +272,8 @@
271272
"dns_private_reverse_resolvers": "Private reverse DNS resolvers",
272273
"dns_private_reverse_resolvers_desc": "Resolves PTR, SOA, and NS requests for ARPA domains with private IP addresses through private upstream servers, DHCP, and /etc/hosts",
273274
"dns_private_reverse_resolvers_disabled_desc": "When disabled, responds to all such requests with NXDOMAIN",
274-
"dns_private_reverse_servers_desc": "Sets the DNS servers for private PTR, SOA, and NS requests. These requests come from clients with private IP addresses and ask for ARPA domains containing subnets from private IP address ranges",
275+
"dns_private_reverse_servers_desc": "Sets the DNS servers for private PTR, SOA, and NS requests. These requests come from clients with private IP addresses and ask for ARPA domains containing subnets from private IP address ranges.",
276+
"dns_private_reverse_servers_resolvers": "Default DNS resolvers: %value_1% and %value_2%",
275277
"dns_private_reverse_servers_desc_2": "If this field is empty, your operating system's default DNS resolvers are used, except for AdGuard Home IP addresses. By default, AdGuard Home uses the following reverse DNS resolvers: %value_1% and %value_2%",
276278
"dns_private_reverse_servers_label": "Private reverse DNS servers, one per line",
277279
"dns_private_reverse_servers_placeholder": "IP addresses, sdns://, quic://, https://, h3://, or tls://",
@@ -432,6 +434,8 @@
432434
"ignore_domains_desc_log": "Requests that match these rules are not displayed in Query log",
433435
"ignore_domains_desc_stats": "Requests that match these rules are not included in the statistics",
434436
"ignore_domains_title": "Ignored domains",
437+
"blocked_only": "Blocked only",
438+
"unblocked_only": "Unblocked only",
435439
"inactivity_schedule": "Inactivity schedule",
436440
"inactivity_schedule_add": "Add schedule",
437441
"inactivity_schedule_all_day": "All day",
@@ -482,6 +486,7 @@
482486
"no": "No",
483487
"no_stats_yet": "No stats yet — keep using AdGuard Home to view them",
484488
"none_text": "None",
489+
"not_available": "N/A",
485490
"nothing_found": "Nothing found",
486491
"notify_undo": "Undo",
487492
"notify_updated": "Updated",
@@ -504,7 +509,7 @@
504509
"parental_group_software": "Software development",
505510
"parental_group_streaming": "Streaming",
506511
"password_label": "Password",
507-
"password_login_error": "Your username and password do not match",
512+
"password_login_error": "Invalid username or password",
508513
"password_placeholder": "Enter password",
509514
"password_requirements": "Password requirements",
510515
"password_requirements_characters": "Minimum 8 characters",
@@ -584,6 +589,8 @@
584589
"reset_dns_confirm_text": "All DNS protocol settings will be reset to their default values",
585590
"reset_dns_confirm_title": "Reset DNS protocols?",
586591
"reset_dns_protocols": "Reset DNS protocols",
592+
"delete_tls_certificate": "Delete TLS certificate?",
593+
"delete_tls_certificate_desc": "Clients using encrypted DNS won’t be able to connect until a new TLS certificate is added",
587594
"reset_settings": "Reset settings",
588595
"reset_settings_confirm": "Yes, reset",
589596
"response_details": "Response details",
@@ -769,8 +776,9 @@
769776
"try_again": "Try again",
770777
"tuesday": "Tuesday",
771778
"type_value": "Type: %value%",
772-
"unavailable_dhcp": "DHCP is unavailable",
773-
"unavailable_dhcp_desc": "AdGuard Home cannot run a DHCP server on your OS",
779+
"unavailable_dhcp": "DHCP is unavailable. The operating system isn’t supported or no DHCP ranges are configured",
780+
"setting_not_applied_dhcp": "This setting isn’t applied because DHCP is disabled",
781+
"enable_dhcp_leases_notice": "Enable DHCP leases to add a static lease",
774782
"unblock": "Unblock",
775783
"unblock_client": "Unblock client",
776784
"unknown_filter": "Unknown filter %filterId%",
@@ -884,6 +892,5 @@
884892
"yes_disable": "Yes, disable",
885893
"yes_disallow": "Yes, disallow",
886894
"yes_remove": "Yes, remove",
887-
"yes_reset": "Yes, reset",
888-
"logs": "Logs"
895+
"yes_reset": "Yes, reset"
889896
}

client_v2/src/__tests__/blocked-services.test.ts

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const filterServices = (
1212
search: string,
1313
groupFilter: string[],
1414
serviceGroupMap: Map<string, string>,
15+
blockedIds: Set<string>,
16+
showBlockedOnly: boolean,
17+
showUnblockedOnly: boolean,
1518
): WebService[] => {
1619
let filtered = services;
1720

@@ -23,6 +26,12 @@ const filterServices = (
2326
});
2427
}
2528

29+
if (showBlockedOnly) {
30+
filtered = filtered.filter((s) => blockedIds.has(s.id));
31+
} else if (showUnblockedOnly) {
32+
filtered = filtered.filter((s) => !blockedIds.has(s.id));
33+
}
34+
2635
const term = search.trim().toLowerCase();
2736
if (term) {
2837
filtered = filtered.filter(
@@ -48,36 +57,116 @@ describe('filterServices', () => {
4857
['epic_games', 'gaming'],
4958
]);
5059

60+
const blockedIds = new Set(['telegram', 'steam']);
61+
const noFilters = (showBlockedOnly = false, showUnblockedOnly = false): WebService[] =>
62+
filterServices(
63+
services,
64+
'',
65+
[],
66+
serviceGroupMap,
67+
blockedIds,
68+
showBlockedOnly,
69+
showUnblockedOnly,
70+
);
71+
5172
test('no filters returns all services', () => {
52-
expect(filterServices(services, '', [], serviceGroupMap)).toHaveLength(4);
73+
expect(noFilters()).toHaveLength(4);
5374
});
5475

5576
test('search by name (case-insensitive)', () => {
56-
const result = filterServices(services, 'tel', [], serviceGroupMap);
77+
const result = filterServices(
78+
services,
79+
'tel',
80+
[],
81+
serviceGroupMap,
82+
blockedIds,
83+
false,
84+
false,
85+
);
5786
expect(result).toHaveLength(1);
5887
expect(result[0].id).toBe('telegram');
5988
});
6089

6190
test('search by id', () => {
62-
const result = filterServices(services, 'epic', [], serviceGroupMap);
91+
const result = filterServices(
92+
services,
93+
'epic',
94+
[],
95+
serviceGroupMap,
96+
blockedIds,
97+
false,
98+
false,
99+
);
63100
expect(result).toHaveLength(1);
64101
expect(result[0].id).toBe('epic_games');
65102
});
66103

67104
test('filter by group', () => {
68-
const result = filterServices(services, '', ['gaming'], serviceGroupMap);
105+
const result = filterServices(
106+
services,
107+
'',
108+
['gaming'],
109+
serviceGroupMap,
110+
blockedIds,
111+
false,
112+
false,
113+
);
69114
expect(result).toHaveLength(2);
70115
expect(result.map((s) => s.id)).toEqual(['steam', 'epic_games']);
71116
});
72117

73118
test('combined search + group filter', () => {
74-
const result = filterServices(services, 'steam', ['gaming'], serviceGroupMap);
119+
const result = filterServices(
120+
services,
121+
'steam',
122+
['gaming'],
123+
serviceGroupMap,
124+
blockedIds,
125+
false,
126+
false,
127+
);
75128
expect(result).toHaveLength(1);
76129
expect(result[0].id).toBe('steam');
77130
});
78131

79132
test('no match returns empty array', () => {
80-
const result = filterServices(services, 'xyz', [], serviceGroupMap);
133+
const result = filterServices(
134+
services,
135+
'xyz',
136+
[],
137+
serviceGroupMap,
138+
blockedIds,
139+
false,
140+
false,
141+
);
81142
expect(result).toHaveLength(0);
82143
});
144+
145+
test('blocked only filter returns blocked services', () => {
146+
const result = noFilters(true);
147+
expect(result.map((s) => s.id)).toEqual(['telegram', 'steam']);
148+
});
149+
150+
test('unblocked only filter returns unblocked services', () => {
151+
const result = noFilters(false, true);
152+
expect(result.map((s) => s.id)).toEqual(['whatsapp', 'epic_games']);
153+
});
154+
155+
test('blocked only takes precedence when both state filters are active', () => {
156+
const result = noFilters(true, true);
157+
expect(result.map((s) => s.id)).toEqual(['telegram', 'steam']);
158+
});
159+
160+
test('state filter combines with group filter', () => {
161+
const result = filterServices(
162+
services,
163+
'',
164+
['gaming'],
165+
serviceGroupMap,
166+
blockedIds,
167+
false,
168+
true,
169+
);
170+
expect(result.map((s) => s.id)).toEqual(['epic_games']);
171+
});
83172
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Overrides `window.matchMedia` for a single test.
3+
*
4+
* The global setup mock in `setup.ts` always reports `matches: false`
5+
* (mobile). Components that branch on `useIsDesktop()` need an explicit
6+
* override, e.g. `setMatchMedia(true)` to emulate a desktop viewport.
7+
*/
8+
export const setMatchMedia = (matches: boolean): void => {
9+
Object.defineProperty(window, 'matchMedia', {
10+
writable: true,
11+
value: (query: string): MediaQueryList =>
12+
({
13+
matches,
14+
media: query,
15+
onchange: null,
16+
addListener: () => {},
17+
removeListener: () => {},
18+
addEventListener: () => {},
19+
removeEventListener: () => {},
20+
dispatchEvent: () => false,
21+
}) as MediaQueryList,
22+
});
23+
};

0 commit comments

Comments
 (0)