mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-05-25 11:22:23 +00:00
Created dhcp settings screen
This commit is contained in:
parent
867822d01e
commit
50685d1ea8
7 changed files with 974 additions and 3 deletions
|
@ -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"
|
||||
}
|
|
@ -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
133
lib/models/dhcp.dart
Normal 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,
|
||||
};
|
||||
}
|
561
lib/screens/settings/dhcp/dhcp.dart
Normal file
561
lib/screens/settings/dhcp/dhcp.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
158
lib/screens/settings/dhcp/select_interface_modal.dart
Normal file
158
lib/screens/settings/dhcp/select_interface_modal.dart
Normal 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)
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue