Added fallback DNS servers

This commit is contained in:
Juan Gilsanz Polo 2023-12-11 22:03:36 +01:00
parent ddf9683e88
commit 27e0b5152e
6 changed files with 261 additions and 2 deletions

View file

@ -706,5 +706,9 @@
"queryLogsAndStatistics": "Query logs and statistics",
"ignoreClientQueryLog": "Ignore this client in query log",
"ignoreClientStatistics": "Ignore this client in statistics",
"savingChanges": "Saving changes..."
"savingChanges": "Saving changes...",
"fallbackDnsServers": "Fallback DNS servers",
"fallbackDnsServersDescription": "Configure fallback DNS servers",
"fallbackDnsServersInfo": "List of fallback DNS servers used when upstream DNS servers are not responding. The syntax is the same as in the main upstreams field above.",
"noFallbackDnsAdded": "No fallback DNS servers added."
}

View file

@ -706,5 +706,9 @@
"queryLogsAndStatistics": "Registro de consultas y estadísticas",
"ignoreClientQueryLog": "Ignorar este cliente en el registro de consultas",
"ignoreClientStatistics": "Ignorar este cliente en las estadísticas",
"savingChanges": "Guardando cambios..."
"savingChanges": "Guardando cambios...",
"fallbackDnsServers": "Servidores DNS alternativos",
"fallbackDnsServersDescription": "Configura los servidores DNS alternativos",
"fallbackDnsServersInfo": "Lista de servidores DNS alternativos utilizados cuando los servidores DNS de subida no responden. La sintaxis es la misma que en el campo de los principales DNS de subida anterior.",
"noFallbackDnsAdded": "No hay servidores DNS alternativos añadidos."
}

View file

@ -2,6 +2,7 @@ class DnsInfo {
List<String> upstreamDns;
String? upstreamDnsFile;
List<String> bootstrapDns;
List<String>? fallbackDns;
bool protectionEnabled;
int ratelimit;
String blockingMode;
@ -26,6 +27,7 @@ class DnsInfo {
required this.upstreamDns,
required this.upstreamDnsFile,
required this.bootstrapDns,
required this.fallbackDns,
required this.protectionEnabled,
required this.ratelimit,
required this.blockingMode,
@ -51,6 +53,7 @@ class DnsInfo {
upstreamDns: json["upstream_dns"] != null ? List<String>.from(json["upstream_dns"].map((x) => x)) : [],
upstreamDnsFile: json["upstream_dns_file"],
bootstrapDns: json["bootstrap_dns"] != null ? List<String>.from(json["bootstrap_dns"].map((x) => x)) : [],
fallbackDns: json["fallback_dns"] != null ? List<String>.from(json["fallback_dns"].map((x) => x)) : [],
protectionEnabled: json["protection_enabled"],
ratelimit: json["ratelimit"],
blockingMode: json["blocking_mode"],
@ -76,6 +79,7 @@ class DnsInfo {
"upstream_dns": List<dynamic>.from(upstreamDns.map((x) => x)),
"upstream_dns_file": upstreamDnsFile,
"bootstrap_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
"fallback_dns": List<dynamic>.from(bootstrapDns.map((x) => x)),
"protection_enabled": protectionEnabled,
"ratelimit": ratelimit,
"blocking_mode": blockingMode,

View file

@ -112,6 +112,22 @@ class DnsProvider with ChangeNotifier {
}
}
Future<ApiResponse> saveFallbackDnsConfig(Map<String, dynamic> value) async {
final result = await _serversProvider!.apiClient2!.setDnsConfig(
data: value
);
if (result.successful == true) {
DnsInfo data = dnsInfo!;
data.bootstrapDns = List<String>.from(value['fallback_dns']);
setDnsInfoData(data);
return result;
}
else {
return result;
}
}
Future<ApiResponse> saveCacheCacheConfig(Map<String, dynamic> value) async {
final result = await _serversProvider!.apiClient2!.setDnsConfig(
data: value

View file

@ -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/fallback_dns.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';
@ -167,6 +168,12 @@ class _DnsSettingsState extends State<DnsSettings> {
onTap: () => navigate(const BootstrapDnsScreen()),
icon: Icons.dns_rounded,
),
if (dnsProvider.dnsInfo!.fallbackDns != null) CustomListTile(
title: AppLocalizations.of(context)!.fallbackDnsServers,
subtitle: AppLocalizations.of(context)!.fallbackDnsServersDescription,
onTap: () => navigate(const FallbackDnsScreen()),
icon: Icons.alt_route_rounded,
),
CustomListTile(
title: AppLocalizations.of(context)!.privateReverseDnsServers,
subtitle: AppLocalizations.of(context)!.privateReverseDnsDescription,

View file

@ -0,0 +1,224 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/functions/desktop_mode.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/dns_provider.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
class FallbackDnsScreen extends StatefulWidget {
const FallbackDnsScreen({super.key});
@override
State<FallbackDnsScreen> createState() => _FallbackDnsScreenState();
}
class _FallbackDnsScreenState extends State<FallbackDnsScreen> {
List<Map<String, dynamic>> fallbackControllers = [];
bool validValues = false;
void validateIp(Map<String, dynamic> field, String value) {
RegExp ipAddress = RegExp(r'(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$)');
if (ipAddress.hasMatch(value) == true) {
setState(() => field['error'] = null);
}
else {
setState(() => field['error'] = AppLocalizations.of(context)!.invalidIp);
}
checkValidValues();
}
void checkValidValues() {
if (
fallbackControllers.isNotEmpty &&
fallbackControllers.every((element) => element['controller'].text != '') &&
fallbackControllers.every((element) => element['error'] == null)
) {
setState(() => validValues = true);
}
else {
setState(() => validValues = false);
}
}
@override
void initState() {
final dnsProvider = Provider.of<DnsProvider>(context, listen: false);
for (var item in dnsProvider.dnsInfo!.fallbackDns!) {
final controller = TextEditingController();
controller.text = item;
fallbackControllers.add({
'controller': controller,
'error': null
});
}
validValues = true;
super.initState();
}
@override
Widget build(BuildContext context) {
final dnsProvider = Provider.of<DnsProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
final width = MediaQuery.of(context).size.width;
void saveData() async {
ProcessModal processModal = ProcessModal();
processModal.open(AppLocalizations.of(context)!.savingConfig);
final result = await dnsProvider.saveFallbackDnsConfig({
"fallback_dns": fallbackControllers.map((e) => e['controller'].text).toList(),
});
processModal.close();
if (result.successful == true) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.dnsConfigSaved,
color: Colors.green
);
}
else if (result.successful == false && result.statusCode == 400) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.someValueNotValid,
color: Colors.red
);
}
else {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.dnsConfigNotSaved,
color: Colors.red
);
}
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.fallbackDnsServers),
surfaceTintColor: isDesktop(width) ? Colors.transparent : null,
actions: [
IconButton(
onPressed: validValues == true
? () => saveData()
: null,
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
const SizedBox(width: 10)
],
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.only(top: 10),
children: [
Card(
margin: const EdgeInsets.only(
left: 16, right: 16, bottom: 20
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(
Icons.info_rounded,
color: Theme.of(context).listTileTheme.iconColor,
),
const SizedBox(width: 20),
Flexible(
child: Text(
AppLocalizations.of(context)!.fallbackDnsServersInfo,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface
),
)
)
],
),
),
),
const SizedBox(height: 10),
if (fallbackControllers.isEmpty) Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: Text(
AppLocalizations.of(context)!.noFallbackDnsAdded,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16
),
),
),
),
const SizedBox(height: 20),
],
),
...fallbackControllers.map((c) => Padding(
padding: const EdgeInsets.only(
left: 16, right: 6, bottom: 20
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: c['controller'],
onChanged: (value) => validateIp(c, value),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.dns_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: c['error'],
labelText: AppLocalizations.of(context)!.dnsServer,
)
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
setState(() => fallbackControllers = fallbackControllers.where((con) => con != c).toList());
checkValidValues();
},
icon: const Icon(Icons.remove_circle_outline)
)
],
),
)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => fallbackControllers.add({
'controller': TextEditingController(),
'error': null
}));
checkValidValues();
},
icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.addItem)
),
],
),
const SizedBox(height: 20)
],
),
),
);
}
}