diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 11f04c3..1e9271d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -695,5 +695,7 @@ "resetEncryptionSettingsDescription": "Are you sure you want to reset to default values the encryption settings?", "resettingConfig": "Resetting configuration...", "configurationResetSuccessfully": "Configuration resetted successfully", - "configurationResetError": "The configuration couldn't be resetted" + "configurationResetError": "The configuration couldn't be resetted", + "testUpstreamDnsServers": "Test upstream DNS servers", + "errorTestUpstreamDns": "Error when testing upstream DNS servers." } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 94f4299..f649320 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -695,5 +695,7 @@ "resetEncryptionSettingsDescription": "Estás seguro que deseas restaurar a valores por defecto la configuración de encriptación?", "resettingConfig": "Reseteando configuración...", "configurationResetSuccessfully": "Configuración reseteada correctamente", - "configurationResetError": "La configuración no ha podido ser reseteada" + "configurationResetError": "La configuración no ha podido ser reseteada", + "testUpstreamDnsServers": "Probar servidores DNS de subida", + "errorTestUpstreamDns": "Error al probar los servidores DNS de subida." } \ No newline at end of file diff --git a/lib/screens/settings/dns/dns.dart b/lib/screens/settings/dns/dns.dart index 2c711d6..c22572d 100644 --- a/lib/screens/settings/dns/dns.dart +++ b/lib/screens/settings/dns/dns.dart @@ -5,6 +5,7 @@ import 'package:flutter_split_view/flutter_split_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/screens/settings/dns/test_upstream_dns_modal.dart'; import 'package:adguard_home_manager/screens/settings/dns/clear_dns_cache_dialog.dart'; import 'package:adguard_home_manager/screens/settings/dns/cache_config.dart'; import 'package:adguard_home_manager/screens/settings/dns/dns_server_settings.dart'; @@ -84,6 +85,14 @@ class _DnsSettingsState extends State { title: Text(AppLocalizations.of(context)!.dnsSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ + IconButton( + onPressed: () => showDialog( + context: context, + builder: (ctx) => const TestUpstreamDnsModal() + ), + icon: const Icon(Icons.upload_rounded), + tooltip: AppLocalizations.of(context)!.testUpstreamDnsServers, + ), PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( diff --git a/lib/screens/settings/dns/test_upstream_dns_modal.dart b/lib/screens/settings/dns/test_upstream_dns_modal.dart new file mode 100644 index 0000000..8410334 --- /dev/null +++ b/lib/screens/settings/dns/test_upstream_dns_modal.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import 'package:adguard_home_manager/constants/enums.dart'; +import 'package:adguard_home_manager/providers/dns_provider.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class _Item { + final String url; + final bool value; + + const _Item({ + required this.url, + required this.value + }); +} + +class TestUpstreamDnsModal extends StatefulWidget { + const TestUpstreamDnsModal({super.key}); + + @override + State createState() => _TestUpstreamDnsModalState(); +} + +class _TestUpstreamDnsModalState extends State { + LoadStatus loadStatus = LoadStatus.loading; + List<_Item>? values; + + void checkDns() async { + final dnsProvider = Provider.of(context, listen: false); + final result = await Provider.of(context, listen: false).apiClient2!.testUpstreamDns( + body: { + "bootstrap_dns": dnsProvider.dnsInfo!.bootstrapDns, + "fallback_dns": [], + "private_upstream": dnsProvider.dnsInfo!.defaultLocalPtrUpstreams, + "upstream_dns": dnsProvider.dnsInfo!.upstreamDns + } + ); + if (!mounted) return; + if (result.successful == true) { + setState(() { + values = List<_Item>.from( + (result.content as Map).entries.map((e) => _Item( + url: e.key, + value: e.value == "OK" ? true : false + )) + ); + loadStatus = LoadStatus.loaded; + }); + } + else { + setState(() => loadStatus = LoadStatus.error); + } + } + + @override + void initState() { + checkDns(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Column( + children: [ + Icon( + Icons.upload_rounded, + size: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.testUpstreamDnsServers, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface + ), + ) + ], + ), + content: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 500 + ), + child: Builder( + builder: (context) { + switch (loadStatus) { + case LoadStatus.loading: + return const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator() + ], + ), + ); + + case LoadStatus.loaded: + return SingleChildScrollView( + child: Wrap( + children: values!.map((v) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + v.url, + overflow: TextOverflow.ellipsis, + ), + ), + ...[ + const SizedBox(width: 8), + if (v.value == true) const Icon( + Icons.check_circle_rounded, + color: Colors.green, + size: 16, + ), + if (v.value == false) const Icon( + Icons.cancel_rounded, + color: Colors.red, + ) + ] + ], + ), + )).toList(), + ), + ); + + case LoadStatus.error: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_rounded, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 30, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context)!.errorTestUpstreamDns, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ); + + default: + return const SizedBox(); + } + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.close) + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/screens/settings/dns/upstream_dns.dart b/lib/screens/settings/dns/upstream_dns.dart index f952110..e540053 100644 --- a/lib/screens/settings/dns/upstream_dns.dart +++ b/lib/screens/settings/dns/upstream_dns.dart @@ -186,7 +186,7 @@ class _UpstreamDnsScreenState extends State { icon: const Icon(Icons.save_rounded), tooltip: AppLocalizations.of(context)!.save, ), - const SizedBox(width: 10) + const SizedBox(width: 8) ], ), body: SafeArea( diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index f626f75..c2e1fca 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -853,4 +853,18 @@ class ApiClientV2 { ); return ApiResponse(successful: result.successful); } + + Future testUpstreamDns({ + required Map body + }) async { + final result = await HttpRequestClient.post( + urlPath: '/test_upstream_dns', + server: server, + body: body + ); + return ApiResponse( + successful: result.successful, + content: result.body != null ? jsonDecode(result.body!) : null + ); + } } \ No newline at end of file