Created dhcp settings screen

This commit is contained in:
Juan Gilsanz Polo 2022-10-12 03:58:17 +02:00
parent 867822d01e
commit 50685d1ea8
7 changed files with 974 additions and 3 deletions

View file

@ -325,5 +325,29 @@
"updating": "Updating values...",
"blockedServicesUpdated": "Blocked services updated successfully",
"blockedServicesNotUpdated": "Blocked services couldn't be updated",
"insertDomain": "Insert a domain to check it's stauts."
"insertDomain": "Insert a domain to check it's stauts.",
"dhcpSettings": "DHCP settings",
"dhcpSettingsDescription": "Configure the DHCP server",
"dhcpSettingsNotLoaded": "DHCP settings could not be loaded",
"loadingDhcp": "Loading DHCP settings...",
"enableDhcpServer": "Enable DHCP server",
"selectInterface": "Select interface",
"hardwareAddress": "Hardware address",
"gatewayIp": "Gateway IP",
"ipv4addresses": "IPv4 addresses",
"ipv6addresses": "IPv6 addresses",
"neededSelectInterface": "You need to select an interface to configure the DHCP server.",
"ipv4settings": "IPv4 settings",
"startOfRange": "Start of range",
"endOfRange": "End of range",
"ipv6settings": "IPv6 settings",
"subnetMask": "Subnet mask",
"subnetMaskNotValid": "Subnet mask not valid",
"gateway": "Gateway",
"gatewayNotValid": "Gateway not valid",
"leaseTime": "Lease time",
"seconds": "seconds",
"leaseTimeNotValid": "Lease time not valid",
"restoreConfiguration": "Restore configuration",
"changeInterface": "Change interface"
}

View file

@ -325,5 +325,29 @@
"updating": "Actualizando valores...",
"blockedServicesUpdated": "Servicios bloqueados actualizados correctamente",
"blockedServicesNotUpdated": "No se pudieron actualizar los servicios bloqueados",
"insertDomain": "Inserta un dominio para comprobar su estado,"
"insertDomain": "Inserta un dominio para comprobar su estado,",
"dhcpSettings": "Configuración de DHCP",
"dhcpSettingsDescription": "Configura el servidor DHCP",
"dhcpSettingsNotLoaded": "No se ha podido cargar la configuración de DHCP",
"loadingDhcp": "Cargando configuración de DHCP...",
"enableDhcpServer": "Habilitar servidor DHCP",
"selectInterface": "Seleccionar interfaz",
"hardwareAddress": "Dirección física",
"gatewayIp": "Puerta de enlace",
"ipv4addresses": "Direcciones IPv4",
"ipv6addresses": "Direcciones IPv6",
"neededSelectInterface": "Necesitas seleccionar una interfaz para configurar el servidor DHCP.",
"ipv4settings": "Configuración IPv4",
"startOfRange": "Inicio de rango",
"endOfRange": "Final de rango",
"ipv6settings": "Configuración IPv6",
"subnetMask": "Máscara de subred",
"subnetMaskNotValid": "Máscara de subred no válida",
"gateway": "Puerta de enlace",
"gatewayNotValid": "Puerta de enlace no válida",
"leaseTime": "Tiempo de asignación",
"seconds": "segundos",
"leaseTimeNotValid": "Tiempo de asignación no válido",
"restoreConfiguration": "Restaurar configuración",
"changeInterface": "Cambiar interfaz"
}

133
lib/models/dhcp.dart Normal file
View file

@ -0,0 +1,133 @@
import 'dart:convert';
class DhcpModel {
int loadStatus = 0;
DhcpData? data;
DhcpModel({
required this.loadStatus,
this.data,
});
}
class DhcpData {
List<NetworkInterface> networkInterfaces;
DhcpStatus dhcpStatus;
DhcpData({
required this.networkInterfaces,
required this.dhcpStatus,
});
}
NetworkInterface networkInterfaceFromJson(String str) => NetworkInterface.fromJson(json.decode(str));
String networkInterfaceToJson(NetworkInterface data) => json.encode(data.toJson());
class NetworkInterface {
String name;
String hardwareAddress;
List<String> flags;
String gatewayIp;
List<String> ipv4Addresses;
List<String> ipv6Addresses;
NetworkInterface({
required this.name,
required this.hardwareAddress,
required this.flags,
required this.gatewayIp,
required this.ipv4Addresses,
required this.ipv6Addresses,
});
factory NetworkInterface.fromJson(Map<String, dynamic> json) => NetworkInterface(
name: json["name"],
hardwareAddress: json["hardware_address"],
flags: json["flags"] != null ? json["flags"].split("|") : [],
gatewayIp: json["gateway_ip"],
ipv4Addresses: json["ipv4_addresses"] != null ? List<String>.from(json["ipv4_addresses"].map((x) => x)) : [],
ipv6Addresses: json["ipv6_addresses"] != null ? List<String>.from(json["ipv6_addresses"].map((x) => x)) : [],
);
Map<String, dynamic> toJson() => {
"name": name,
"hardware_address": hardwareAddress,
"flags": flags,
"gateway_ip": gatewayIp,
"ipv4_addresses": List<dynamic>.from(ipv4Addresses.map((x) => x)),
"ipv6_addresses": List<dynamic>.from(ipv6Addresses.map((x) => x)),
};
}
DhcpStatus dhcpStatusFromJson(String str) => DhcpStatus.fromJson(json.decode(str));
String dhcpStatusToJson(DhcpStatus data) => json.encode(data.toJson());
class DhcpStatus {
String interfaceName;
IpVersion v4;
IpVersion v6;
List<dynamic> leases;
List<dynamic> staticLeases;
bool enabled;
DhcpStatus({
required this.interfaceName,
required this.v4,
required this.v6,
required this.leases,
required this.staticLeases,
required this.enabled,
});
factory DhcpStatus.fromJson(Map<String, dynamic> json) => DhcpStatus(
interfaceName: json["interface_name"],
v4: IpVersion.fromJson(json["v4"]),
v6: IpVersion.fromJson(json["v6"]),
leases: List<dynamic>.from(json["leases"].map((x) => x)),
staticLeases: List<dynamic>.from(json["static_leases"].map((x) => x)),
enabled: json["enabled"],
);
Map<String, dynamic> toJson() => {
"interface_name": interfaceName,
"v4": v4.toJson(),
"v6": v6.toJson(),
"leases": List<dynamic>.from(leases.map((x) => x)),
"static_leases": List<dynamic>.from(staticLeases.map((x) => x)),
"enabled": enabled,
};
}
class IpVersion {
String ?gatewayIp;
String? subnetMask;
String rangeStart;
String? rangeEnd;
int leaseDuration;
IpVersion({
this.gatewayIp,
this.subnetMask,
required this.rangeStart,
this.rangeEnd,
required this.leaseDuration,
});
factory IpVersion.fromJson(Map<String, dynamic> json) => IpVersion(
gatewayIp: json["gateway_ip"],
subnetMask: json["subnet_mask"],
rangeStart: json["range_start"],
rangeEnd: json["range_end"],
leaseDuration: json["lease_duration"],
);
Map<String, dynamic> toJson() => {
"gateway_ip": gatewayIp,
"subnet_mask": subnetMask,
"range_start": rangeStart,
"range_end": rangeEnd,
"lease_duration": leaseDuration,
};
}

View file

@ -0,0 +1,561 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bottom_sheet/bottom_sheet.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/settings/section_label.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/dhcp.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
class Dhcp extends StatelessWidget {
const Dhcp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
return DhcpWidget(
serversProvider: serversProvider,
appConfigProvider: appConfigProvider
);
}
}
class DhcpWidget extends StatefulWidget {
final ServersProvider serversProvider;
final AppConfigProvider appConfigProvider;
const DhcpWidget({
Key? key,
required this.serversProvider,
required this.appConfigProvider
}) : super(key: key);
@override
State<DhcpWidget> createState() => _DhcpWidgetState();
}
class _DhcpWidgetState extends State<DhcpWidget> {
DhcpModel dhcp = DhcpModel(loadStatus: 0);
NetworkInterface? selectedInterface;
final TextEditingController ipv4StartRangeController = TextEditingController();
String? ipv4StartRangeError;
final TextEditingController ipv4EndRangeController = TextEditingController();
String? ipv4EndRangeError;
final TextEditingController ipv4SubnetMaskController = TextEditingController();
String? ipv4SubnetMaskError;
final TextEditingController ipv4GatewayController = TextEditingController();
String? ipv4GatewayError;
final TextEditingController ipv4LeaseTimeController = TextEditingController();
String? ipv4LeaseTimeError;
final TextEditingController ipv6StartRangeController = TextEditingController();
String? ipv6StartRangeError;
final TextEditingController ipv6EndRangeController = TextEditingController();
String? ipv6EndRangeError;
final TextEditingController ipv6SubnetMaskController = TextEditingController();
String? ipv6SubnetMaskError;
final TextEditingController ipv6GatewayController = TextEditingController();
String? ipv6GatewayError;
final TextEditingController ipv6LeaseTimeController = TextEditingController();
String? ipv6LeaseTimeError;
void loadDhcpStatus() async {
final result = await getDhcpData(server: widget.serversProvider.selectedServer!);
if (mounted) {
if (result['result'] == 'success') {
setState(() {
dhcp.loadStatus = 1;
dhcp.data = result['data'];
});
}
else {
setState(() => dhcp.loadStatus = 2);
}
}
}
void validateIpV4(String value, String errorVar, String errorMessage) {
void setValue(String? error) {
switch (errorVar) {
case 'ipv4StartRangeError':
setState(() => ipv4StartRangeError = error);
break;
case 'ipv4EndRangeError':
setState(() => ipv4EndRangeError = error);
break;
case 'ipv4SubnetMaskError':
setState(() => ipv4SubnetMaskError = error);
break;
case 'ipv4GatewayError':
setState(() => ipv4GatewayError = error);
break;
}
}
final regex = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$');
if (regex.hasMatch(value)) {
setValue(null);
}
else {
setValue(errorMessage);
}
}
void validateIpV6(String value, String errorVar, String errorMessage) {
void setValue(String? error) {
switch (errorVar) {
case 'ipv6StartRangeError':
setState(() => ipv4StartRangeError = error);
break;
case 'ipv6EndRangeError':
setState(() => ipv4EndRangeError = error);
break;
case 'ipv6SubnetMaskError':
setState(() => ipv4SubnetMaskError = error);
break;
case 'ipv6GatewayError':
setState(() => ipv4GatewayError = error);
break;
}
}
final regex = 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]))$');
if (regex.hasMatch(value)) {
setValue(null);
}
else {
setValue(errorMessage);
}
}
@override
void initState() {
loadDhcpStatus();
super.initState();
}
@override
Widget build(BuildContext context) {
void selectInterface() {
Future.delayed(const Duration(seconds: 0), () {
showFlexibleBottomSheet(
minHeight: 0.6,
initHeight: 0.6,
maxHeight: 0.95,
isCollapsible: true,
duration: const Duration(milliseconds: 250),
anchors: [0.95],
context: context,
builder: (ctx, controller, offset) => SelectInterfaceModal(
interfaces: dhcp.data!.networkInterfaces,
scrollController: controller,
onSelect: (interface) => setState(() => selectedInterface = interface)
),
bottomSheetColor: Colors.transparent
);
});
}
Widget generateBody() {
switch (dhcp.loadStatus) {
case 0:
return SizedBox(
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.loadingDhcp,
style: const TextStyle(
fontSize: 22,
color: Colors.grey,
),
)
],
),
);
case 1:
if (selectedInterface != null) {
return ListView(
children: [
Padding(
padding: const EdgeInsets.only(
top: 10,
left: 12,
right: 12
),
child: Material(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(28),
child: InkWell(
onTap: selectedInterface != null
? () => setState(() => dhcp.data!.dhcpStatus.enabled = !dhcp.data!.dhcpStatus.enabled)
: null,
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.enableDhcpServer,
style: const TextStyle(
fontSize: 18,
),
),
if (selectedInterface != null) ...[
Text(
selectedInterface!.name,
style: const TextStyle(
fontSize: 14,
color: Colors.grey
),
)
]
],
),
Switch(
value: dhcp.data!.dhcpStatus.enabled,
onChanged: selectedInterface != null
? (value) => setState(() => dhcp.data!.dhcpStatus.enabled = value)
: null,
activeColor: Theme.of(context).primaryColor,
),
],
),
),
),
),
),
SectionLabel(
label: AppLocalizations.of(context)!.ipv4settings,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv4StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4StartRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_next_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4StartRangeError,
labelText: AppLocalizations.of(context)!.startOfRange,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv4EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv4EndRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_previous_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4EndRangeError,
labelText: AppLocalizations.of(context)!.endOfRange,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv4SubnetMaskController,
onChanged: (value) => validateIpV4(value, 'ipv4SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.hub_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4SubnetMaskError,
labelText: AppLocalizations.of(context)!.subnetMask,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv4GatewayController,
onChanged: (value) => validateIpV4(value, 'ipv4GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.router_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4GatewayError,
labelText: AppLocalizations.of(context)!.gateway,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv4LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv4LeaseTimeError = null);
}
else {
setState(() => ipv4LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.timer),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv4LeaseTimeError,
labelText: AppLocalizations.of(context)!.leaseTime,
),
keyboardType: TextInputType.number,
),
),
if (selectedInterface!.ipv6Addresses.isNotEmpty) ...[
SectionLabel(
label: AppLocalizations.of(context)!.ipv6settings,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv6StartRangeController,
onChanged: (value) => validateIpV4(value, 'ipv6StartRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_next_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6StartRangeError,
labelText: AppLocalizations.of(context)!.startOfRange,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv6EndRangeController,
onChanged: (value) => validateIpV4(value, 'ipv6EndRangeError', AppLocalizations.of(context)!.ipNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.skip_previous_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6EndRangeError,
labelText: AppLocalizations.of(context)!.endOfRange,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv6SubnetMaskController,
onChanged: (value) => validateIpV4(value, 'ipv6SubnetMaskError', AppLocalizations.of(context)!.subnetMaskNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.hub_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6SubnetMaskError,
labelText: AppLocalizations.of(context)!.subnetMask,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv6GatewayController,
onChanged: (value) => validateIpV4(value, 'ipv6GatewayError', AppLocalizations.of(context)!.gatewayNotValid),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.router_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6GatewayError,
labelText: AppLocalizations.of(context)!.gateway,
),
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
controller: ipv6LeaseTimeController,
onChanged: (value) {
if (int.tryParse(value).runtimeType == int) {
setState(() => ipv6LeaseTimeError = null);
}
else {
setState(() => ipv6LeaseTimeError = AppLocalizations.of(context)!.leaseTimeNotValid);
}
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.timer),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipv6LeaseTimeError,
labelText: AppLocalizations.of(context)!.leaseTime,
),
keyboardType: TextInputType.number,
),
),
]
],
);
}
else {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
AppLocalizations.of(context)!.neededSelectInterface,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 22,
color: Colors.grey
),
),
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: selectInterface,
child: Text(AppLocalizations.of(context)!.selectInterface)
)
],
);
}
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)!.dhcpSettingsNotLoaded,
style: const TextStyle(
fontSize: 22,
color: Colors.grey,
),
)
],
),
);
default:
return const SizedBox();
}
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dhcpSettings),
actions: selectedInterface != null ? [
IconButton(
onPressed: () {},
icon: const Icon(Icons.save_rounded),
tooltip: AppLocalizations.of(context)!.save,
),
PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(
onTap: selectInterface,
child: Row(
children: [
const Icon(Icons.swap_horiz_rounded),
const SizedBox(width: 10),
Text(AppLocalizations.of(context)!.changeInterface)
],
)
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.restore),
const SizedBox(width: 10),
Text(AppLocalizations.of(context)!.restoreConfiguration)
],
)
)
]
),
const SizedBox(width: 10)
] : null,
),
body: generateBody(),
);
}
}

View file

@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/dhcp.dart';
class SelectInterfaceModal extends StatelessWidget {
final List<NetworkInterface> interfaces;
final ScrollController scrollController;
final void Function(NetworkInterface) onSelect;
const SelectInterfaceModal({
Key? key,
required this.interfaces,
required this.scrollController,
required this.onSelect,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
)
),
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(top: 28),
child: Icon(
Icons.settings_ethernet_rounded,
size: 26,
),
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.selectInterface,
style: const TextStyle(
fontSize: 24
),
),
const SizedBox(height: 20),
Expanded(
child: ListView.builder(
controller: scrollController,
shrinkWrap: true,
itemCount: interfaces.length,
itemBuilder: (context, index) => Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
onSelect(interfaces[index]);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
interfaces[index].name,
style: const TextStyle(
fontSize: 18
),
),
Row(
children: [
Text(
"${AppLocalizations.of(context)!.hardwareAddress}: ",
style: const TextStyle(
fontWeight: FontWeight.w500
),
),
Text(interfaces[index].hardwareAddress),
],
),
const SizedBox(height: 5),
if (interfaces[index].flags.isNotEmpty) ...[
Row(
children: [
const Text(
"Flags: ",
style: TextStyle(
fontWeight: FontWeight.w500
),
),
Text(interfaces[index].flags.join(', ')),
],
),
const SizedBox(height: 5),
],
if (interfaces[index].gatewayIp != '') ...[
Row(
children: [
Text(
"${AppLocalizations.of(context)!.gatewayIp}: ",
style: const TextStyle(
fontWeight: FontWeight.w500
),
),
Text(interfaces[index].gatewayIp),
],
),
const SizedBox(height: 5),
],
if (interfaces[index].ipv4Addresses.isNotEmpty) ...[
Row(
children: [
Text(
"${AppLocalizations.of(context)!.ipv4addresses}: ",
style: const TextStyle(
fontWeight: FontWeight.w500
),
),
Text(interfaces[index].ipv4Addresses.join(', ')),
],
),
const SizedBox(height: 5),
],
if (interfaces[index].ipv6Addresses.isNotEmpty) ...[
Row(
children: [
Text(
"${AppLocalizations.of(context)!.ipv4addresses}: ",
style: const TextStyle(
fontWeight: FontWeight.w500
),
),
Text(interfaces[index].ipv6Addresses.join(', ')),
],
),
]
],
),
),
),
)
),
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
)
],
),
)
],
),
);
}
}

View file

@ -8,6 +8,7 @@ import 'package:adguard_home_manager/screens/settings/theme_modal.dart';
import 'package:adguard_home_manager/screens/settings/custom_list_tile.dart';
import 'package:adguard_home_manager/screens/settings/server_info/server_info.dart';
import 'package:adguard_home_manager/screens/settings/access_settings/access_settings.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/dhcp.dart';
import 'package:adguard_home_manager/screens/settings/section_label.dart';
import 'package:adguard_home_manager/screens/settings/appbar.dart';
import 'package:adguard_home_manager/screens/servers/servers.dart';
@ -99,6 +100,18 @@ class Settings extends StatelessWidget {
)
},
),
CustomListTile(
leadingIcon: Icons.install_desktop_rounded,
label: AppLocalizations.of(context)!.dhcpSettings,
description: AppLocalizations.of(context)!.dhcpSettingsDescription,
onTap: () => {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const Dhcp()
)
)
},
),
CustomListTile(
leadingIcon: Icons.info_rounded,
label: AppLocalizations.of(context)!.serverInformation,

View file

@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:adguard_home_manager/models/dhcp.dart';
import 'package:adguard_home_manager/models/filtering.dart';
import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
@ -1093,4 +1094,61 @@ Future setBlockedServices({
else {
return result;
}
}
}
Future getDhcpData({
required Server server,
}) async {
final result = await Future.wait([
apiRequest(
urlPath: '/dhcp/interfaces',
method: 'get',
server: server,
type: 'get_dhcp_data'
),
apiRequest(
urlPath: '/dhcp/status',
method: 'get',
server: server,
type: 'get_dhcp_data'
),
]);
if (result[0]['hasResponse'] == true && result[1]['hasResponse'] == true) {
if (result[0]['statusCode'] == 200 && result[1]['statusCode'] == 200) {
List<NetworkInterface> interfaces = List<NetworkInterface>.from(jsonDecode(result[0]['body']).entries.map((entry) => NetworkInterface.fromJson(entry.value)));
return {
'result': 'success',
'data': DhcpData(
networkInterfaces: interfaces,
dhcpStatus: DhcpStatus.fromJson(jsonDecode(result[1]['body']))
)
};
}
else {
return {
'result': 'error',
'log': AppLog(
type: 'get_dhcp_data',
dateTime: DateTime.now(),
message: 'error_code_not_expected',
statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(),
resBody: result.map((res) => res['body'] ?? 'null').toString(),
)
};
}
}
else {
return {
'result': 'error',
'log': AppLog(
type: 'get_dhpc_data',
dateTime: DateTime.now(),
message: [result[0]['log'].message, result[1]['log'].message].toString(),
statusCode: result.map((res) => res['statusCode'] ?? 'null').toString(),
resBody: result.map((res) => res['body'] ?? 'null').toString(),
)
};
}
}