Added create and remove static lease and restore leases

This commit is contained in:
Juan Gilsanz Polo 2022-10-15 02:25:15 +02:00
parent b48c2a8d3b
commit 8f8ef05a3f
9 changed files with 724 additions and 20 deletions

View file

@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/dhcp.dart';
class AddStaticLeaseModal extends StatefulWidget {
final void Function(Lease) onConfirm;
const AddStaticLeaseModal({
Key? key,
required this.onConfirm,
}) : super(key: key);
@override
State<AddStaticLeaseModal> createState() => _AddStaticLeaseModalState();
}
class _AddStaticLeaseModalState extends State<AddStaticLeaseModal> {
final TextEditingController macController = TextEditingController();
String? macError;
final TextEditingController ipController = TextEditingController();
String? ipError;
final TextEditingController hostNameController = TextEditingController();
String? hostNameError;
bool validData = false;
void validateMac(String value) {
final RegExp macRegex = RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$');
if (macRegex.hasMatch(value)) {
setState(() => macError = null);
}
else {
setState(() => macError = AppLocalizations.of(context)!.macAddressNotValid);
}
validateData();
}
void validateIp(String value) {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
if (ipAddress.hasMatch(value) == true) {
setState(() => ipError = null);
}
else {
setState(() => ipError = AppLocalizations.of(context)!.ipNotValid);
}
validateData();
}
void validateData() {
if (
macController.text != '' &&
macError == null &&
ipController.text != '' &&
ipError == null &&
hostNameController.text != '' &&
hostNameError == null
) {
setState(() => validData = true);
}
else {
setState(() => validData = false);
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
height: MediaQuery.of(context).viewInsets.bottom > 0
? MediaQuery.of(context).size.height-MediaQuery.of(context).viewInsets.bottom-25
: 550,
decoration: BoxDecoration(
color: Theme.of(context).dialogBackgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28)
)
),
child: Column(
children: [
SizedBox(
height: MediaQuery.of(context).viewInsets.bottom > 0
? MediaQuery.of(context).size.height-425
: MediaQuery.of(context).size.height-400,
child: ListView(
shrinkWrap: true,
primary: false,
children: [
const Padding(
padding: EdgeInsets.only(top: 28),
child: Icon(
Icons.add,
size: 26,
),
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.addStaticLease,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: TextFormField(
controller: macController,
onChanged: validateMac,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.smartphone_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: macError,
labelText: AppLocalizations.of(context)!.macAddress,
),
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: TextFormField(
controller: ipController,
onChanged: validateIp,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: ipError,
labelText: AppLocalizations.of(context)!.ipAddress,
),
),
),
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: TextFormField(
controller: hostNameController,
onChanged: (value) {
if (value != '') {
setState(() => hostNameError = null);
}
else {
setState(() => hostNameError = AppLocalizations.of(context)!.hostNameError);
}
validateData();
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
errorText: hostNameError,
labelText: AppLocalizations.of(context)!.hostName,
),
),
),
],
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel),
),
const SizedBox(width: 20),
TextButton(
onPressed: validData == true
? () {
Navigator.pop(context);
widget.onConfirm(
Lease(
mac: macController.text,
hostname: hostNameController.text,
ip: ipController.text
)
);
}
: null,
child: Text(
AppLocalizations.of(context)!.confirm,
style: TextStyle(
color: validData == true
? Theme.of(context).primaryColor
: Colors.grey
),
),
),
],
),
)
],
),
)
],
),
),
);
}
}

View file

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class DeleteStaticLeaseModal extends StatelessWidget {
final void Function() onConfirm;
const DeleteStaticLeaseModal({
Key? key,
required this.onConfirm
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
children: [
const Icon(
Icons.delete_rounded,
size: 26,
),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.deleteStaticLease,
textAlign: TextAlign.center,
)
],
),
content: Text(AppLocalizations.of(context)!.deleteStaticLeaseDescription),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.cancel)
),
TextButton(
onPressed: () {
onConfirm();
Navigator.pop(context);
},
child: Text(AppLocalizations.of(context)!.confirm)
),
],
);
}
}

View file

@ -6,6 +6,7 @@ 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/dhcp_static.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/select_interface_modal.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
@ -45,8 +46,6 @@ class DhcpWidget extends StatefulWidget {
}
class _DhcpWidgetState extends State<DhcpWidget> {
DhcpModel dhcp = DhcpModel(loadStatus: 0);
NetworkInterface? selectedInterface;
bool enabled = false;
@ -72,14 +71,15 @@ class _DhcpWidgetState extends State<DhcpWidget> {
bool dataValid = false;
void loadDhcpStatus() async {
widget.serversProvider.setDhcpLoadStatus(0, false);
final result = await getDhcpData(server: widget.serversProvider.selectedServer!);
if (mounted) {
if (result['result'] == 'success') {
widget.serversProvider.setDhcpLoadStatus(1, true);
widget.serversProvider.setDhcpData(result['data']);
setState(() {
dhcp.loadStatus = 1;
dhcp.data = result['data'];
if (result['data'].dhcpStatus.interfaceName != '') {
selectedInterface = result['data'].networkInterfaces.firstWhere((interface) => interface.name == result['data'].dhcpStatus.interfaceName);
@ -94,7 +94,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
});
}
else {
setState(() => dhcp.loadStatus = 2);
widget.serversProvider.setDhcpLoadStatus(2, true);
}
}
checkDataValid();
@ -287,6 +287,41 @@ class _DhcpWidgetState extends State<DhcpWidget> {
});
}
void restoreLeases() async {
Future.delayed(const Duration(seconds: 0), () async {
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.restoringLeases);
final result = await restoreAllLeases(server: serversProvider.selectedServer!);
processModal.close();
if (result['result'] == 'success') {
DhcpData data = serversProvider.dhcp.data!;
data.dhcpStatus.staticLeases = [];
data.dhcpStatus.leases = [];
serversProvider.setDhcpData(data);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.leasesRestored,
color: Colors.green
);
}
else {
appConfigProvider.addLog(result['log']);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.leasesNotRestored,
color: Colors.red
);
}
});
}
void selectInterface() {
Future.delayed(const Duration(seconds: 0), () {
showFlexibleBottomSheet(
@ -298,7 +333,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
anchors: [0.95],
context: context,
builder: (ctx, controller, offset) => SelectInterfaceModal(
interfaces: dhcp.data!.networkInterfaces,
interfaces: serversProvider.dhcp.data!.networkInterfaces,
scrollController: controller,
onSelect: (interface) => setState(() {
clearAll();
@ -311,7 +346,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
}
Widget generateBody() {
switch (dhcp.loadStatus) {
switch (serversProvider.dhcp.loadStatus) {
case 0:
return SizedBox(
width: double.maxFinite,
@ -564,7 +599,36 @@ class _DhcpWidgetState extends State<DhcpWidget> {
keyboardType: TextInputType.number,
),
),
]
],
const SizedBox(height: 20),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => DhcpStatic(
items: serversProvider.dhcp.data!.dhcpStatus.staticLeases
)
));
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.dhcpStatic,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16
),
),
const Icon(Icons.arrow_forward_rounded)
],
),
),
),
),
],
);
}
@ -589,7 +653,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
ElevatedButton(
onPressed: selectInterface,
child: Text(AppLocalizations.of(context)!.selectInterface)
)
),
],
);
}
@ -647,6 +711,16 @@ class _DhcpWidgetState extends State<DhcpWidget> {
],
)
),
PopupMenuItem(
onTap: restoreLeases,
child: Row(
children: [
const Icon(Icons.settings_backup_restore_rounded),
const SizedBox(width: 10),
Text(AppLocalizations.of(context)!.restoreLeases)
],
)
),
PopupMenuItem(
onTap: restoreConfig,
child: Row(
@ -656,7 +730,7 @@ class _DhcpWidgetState extends State<DhcpWidget> {
Text(AppLocalizations.of(context)!.restoreConfiguration)
],
)
)
),
]
),
const SizedBox(width: 10)

View file

@ -0,0 +1,168 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/delete_static_lease_modal.dart';
import 'package:adguard_home_manager/screens/settings/dhcp/add_static_lease_modal.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/models/dhcp.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
class DhcpStatic extends StatelessWidget {
final List<Lease> items;
const DhcpStatic({
Key? key,
required this.items,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
void deleteLease(Lease lease) async {
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.deleting);
final result = await deleteStaticLease(server: serversProvider.selectedServer!, data: {
"mac": lease.mac,
"ip": lease.ip,
"hostname": lease.hostname
});
processModal.close();
if (result['result'] == 'success') {
DhcpData data = serversProvider.dhcp.data!;
data.dhcpStatus.staticLeases = data.dhcpStatus.staticLeases.where((l) => l.mac != lease.mac).toList();
serversProvider.setDhcpData(data);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.staticLeaseDeleted,
color: Colors.green
);
}
else {
appConfigProvider.addLog(result['log']);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.staticLeaseNotDeleted,
color: Colors.red
);
}
}
void createLease(Lease lease) async {
ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.creating);
final result = await createStaticLease(server: serversProvider.selectedServer!, data: {
"mac": lease.mac,
"ip": lease.ip,
"hostname": lease.hostname,
});
processModal.close();
if (result['result'] == 'success') {
DhcpData data = serversProvider.dhcp.data!;
data.dhcpStatus.staticLeases.add(lease);
serversProvider.setDhcpData(data);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.staticLeaseCreated,
color: Colors.green
);
}
else if (result['result'] == 'error' && result['message'] == 'already_exists' ) {
appConfigProvider.addLog(result['log']);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.staticLeaseExists,
color: Colors.red
);
}
else {
appConfigProvider.addLog(result['log']);
showSnacbkar(
context: context,
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.staticLeaseNotCreated,
color: Colors.red
);
}
}
void openAddStaticLease() {
showModalBottomSheet(
context: context,
builder: (context) => AddStaticLeaseModal(
onConfirm: createLease
),
backgroundColor: Colors.transparent,
isScrollControlled: true
);
}
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.dhcpStatic),
),
body: items.isNotEmpty
? ListView.builder(
padding: const EdgeInsets.only(top: 0),
itemCount: items.length,
itemBuilder: (context, index) => ListTile(
isThreeLine: true,
title: Text(items[index].ip),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(items[index].mac),
Text(items[index].hostname),
],
),
trailing: IconButton(
onPressed: () {
showModal(
context: context,
builder: (context) => DeleteStaticLeaseModal(
onConfirm: () => deleteLease(items[index])
)
);
},
icon: const Icon(Icons.delete)
),
),
)
: Center(
child: Text(
AppLocalizations.of(context)!.noDhcpStaticLeases,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
fontSize: 22
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: openAddStaticLease,
child: const Icon(Icons.add),
),
);
}
}