Added option to block and unblock domains from log details

This commit is contained in:
Juan Gilsanz Polo 2022-10-01 03:13:50 +02:00
parent 68d7b34880
commit 84df416011
8 changed files with 364 additions and 15 deletions

View file

@ -125,7 +125,14 @@
"elapsedTime": "Elapsed time",
"responseCode": "Response code",
"client": "Client",
"device": "Device",
"deviceIp": "IP address",
"deviceName": "Name",
"logDetails": "Log details",
"blockingRule": "Blocking rule"
"blockingRule": "Blocking rule",
"blockDomain": "Block domain",
"couldntGetFilteringStatus": "Could not get filtering status",
"unblockDomain": "Unblock domain",
"userFilteringRulesNotUpdated": "User filtering rules could not be updated",
"userFilteringRulesUpdated": "User filtering rules updated successfully",
"savingUserFilters": "Saving user filters..."
}

View file

@ -125,7 +125,14 @@
"elapsedTime": "Tiempo transcurrido",
"responseCode": "Código de respuesta",
"client": "Cliente",
"device": "Dispositivo",
"deviceIp": "Dirección IP",
"deviceName": "Nombre",
"logDetails": "Detalles del registro",
"blockingRule": "Regla de bloqueo"
"blockingRule": "Regla de bloqueo",
"blockDomain": "Bloquear dominio",
"couldntGetFilteringStatus": "No se pudo obtener el estado de filtrado",
"unblockDomain": "Desbloquear dominio",
"userFilteringRulesNotUpdated": "No se pudieron actualizar las reglas de filtrado del usuario",
"userFilteringRulesUpdated": "Reglas de filtrado del usuario actualizadas correctamente",
"savingUserFilters": "Guardando filtros de usuario..."
}

View file

@ -0,0 +1,73 @@
import 'dart:convert';
FilteringStatus filteringStatusFromJson(String str) => FilteringStatus.fromJson(json.decode(str));
String filteringStatusToJson(FilteringStatus data) => json.encode(data.toJson());
class FilteringStatus {
final List<Filter> filters;
final List<Filter> whitelistFilters;
List<String> userRules;
final int interval;
final bool enabled;
FilteringStatus({
required this.filters,
required this.whitelistFilters,
required this.userRules,
required this.interval,
required this.enabled,
});
factory FilteringStatus.fromJson(Map<String, dynamic> json) => FilteringStatus(
filters: List<Filter>.from(json["filters"].map((x) => Filter.fromJson(x))),
whitelistFilters: List<Filter>.from(json["whitelist_filters"].map((x) => Filter.fromJson(x))),
userRules: List<String>.from(json["user_rules"].map((x) => x)),
interval: json["interval"],
enabled: json["enabled"],
);
Map<String, dynamic> toJson() => {
"filters": List<dynamic>.from(filters.map((x) => x.toJson())),
"whitelist_filters": List<dynamic>.from(whitelistFilters.map((x) => x.toJson())),
"user_rules": List<dynamic>.from(userRules.map((x) => x)),
"interval": interval,
"enabled": enabled,
};
}
class Filter {
final String url;
final String name;
final DateTime? lastUpdated;
final int id;
final int rulesCount;
final bool enabled;
Filter({
required this.url,
required this.name,
this.lastUpdated,
required this.id,
required this.rulesCount,
required this.enabled,
});
factory Filter.fromJson(Map<String, dynamic> json) => Filter(
url: json["url"],
name: json["name"],
lastUpdated: json["last_updated"] == null ? null : DateTime.parse(json["last_updated"]),
id: json["id"],
rulesCount: json["rules_count"],
enabled: json["enabled"],
);
Map<String, dynamic> toJson() => {
"url": url,
"name": name,
"last_updated": lastUpdated == null ? null : lastUpdated!.toIso8601String(),
"id": id,
"rules_count": rulesCount,
"enabled": enabled,
};
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
import 'package:adguard_home_manager/models/clients_allowed_blocked.dart';
import 'package:adguard_home_manager/models/clients.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
@ -24,6 +25,8 @@ class ServersProvider with ChangeNotifier {
data: null
);
FilteringStatus? _filteringStatus;
List<Server> get serversList {
return _serversList;
}
@ -44,6 +47,10 @@ class ServersProvider with ChangeNotifier {
return _clients;
}
FilteringStatus? get filteringStatus {
return _filteringStatus;
}
void setDbInstance(Database db) {
_dbInstance = db;
}
@ -84,6 +91,11 @@ class ServersProvider with ChangeNotifier {
_clients.data?.clientsAllowedBlocked = data;
notifyListeners();
}
void setFilteringStatus(FilteringStatus status) {
_filteringStatus = status;
notifyListeners();
}
Future<bool> createServer(Server server) async {
final saved = await saveServerIntoDb(server);

View file

@ -8,14 +8,31 @@ import 'package:adguard_home_manager/models/logs.dart';
class LogDetailsModal extends StatelessWidget {
final Log log;
final void Function(Log, String) blockUnblock;
const LogDetailsModal({
Key? key,
required this.log
required this.log,
required this.blockUnblock
}) : super(key: key);
@override
Widget build(BuildContext context) {
bool isLogBlocked() {
switch (log.reason) {
case 'NotFilteredNotFound':
return false;
case 'NotFilteredWhiteList':
return true;
case 'FilteredBlackList':
return true;
default:
return true;
}
}
Widget getResult() {
switch (log.reason) {
@ -193,21 +210,37 @@ class LogDetailsModal extends StatelessWidget {
),
LogListTile(
icon: Icons.smartphone_rounded,
title: AppLocalizations.of(context)!.device,
title: AppLocalizations.of(context)!.deviceIp,
subtitle: log.client
),
if (log.clientInfo.name != '') LogListTile(
icon: Icons.abc_rounded,
title: AppLocalizations.of(context)!.deviceName,
subtitle: log.clientInfo.name
),
],
)
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
blockUnblock(log, isLogBlocked() == true ? 'unblock' : 'block');
Navigator.pop(context);
},
child: Text(
isLogBlocked() == true
? AppLocalizations.of(context)!.unblockDomain
: AppLocalizations.of(context)!.blockDomain
)
),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close)
)
),
],
),
)

View file

@ -1,8 +1,15 @@
// 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/screens/logs/log_details_modal.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/functions/format_time.dart';
@ -20,6 +27,8 @@ class LogTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context);
final width = MediaQuery.of(context).size.width;
Widget logStatusWidget({
@ -78,12 +87,63 @@ class LogTile extends StatelessWidget {
);
}
}
void blockUnblock(Log log, String newStatus) async {
FilteringStatus oldStatus = serversProvider.filteringStatus!;
List<String> newRules = serversProvider.filteringStatus!.userRules.where((domain) => !domain.contains(log.question.name)).toList();
if (newStatus == 'block') {
newRules.add("||${log.question.name}^");
}
else if (newStatus == 'unblock') {
newRules.add("@@||${log.question.name}^");
}
FilteringStatus newObj = serversProvider.filteringStatus!;
newObj.userRules = newRules;
serversProvider.setFilteringStatus(newObj);
String formattedFilters = "";
for (var rule in newObj.userRules) {
if (formattedFilters == "") {
formattedFilters = "$formattedFilters$rule";
}
else {
formattedFilters = "$formattedFilters\n$rule";
}
}
final ProcessModal processModal = ProcessModal(context: context);
processModal.open(AppLocalizations.of(context)!.savingUserFilters);
final result = await postFilteringRules(server: serversProvider.selectedServer!, data: formattedFilters);
processModal.close();
if (result['result'] == 'success') {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.userFilteringRulesUpdated),
backgroundColor: Colors.green,
)
);
}
else {
serversProvider.setFilteringStatus(oldStatus);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.userFilteringRulesNotUpdated),
backgroundColor: Colors.red,
)
);
}
}
void openLogDetailsModal() {
showModalBottomSheet(
context: context,
builder: (context) => LogDetailsModal(
log: log
builder: (ctx) => LogDetailsModal(
log: log,
blockUnblock: blockUnblock,
),
backgroundColor: Colors.transparent,
isScrollControlled: true

View file

@ -1,9 +1,12 @@
// 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/screens/logs/log_tile.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
import 'package:adguard_home_manager/models/app_log.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
@ -21,7 +24,8 @@ class Logs extends StatelessWidget {
return LogsWidget(
server: serversProvider.selectedServer!,
createLog: appConfigProvider.addLog
createLog: appConfigProvider.addLog,
setFilteringStatus: serversProvider.setFilteringStatus,
);
}
}
@ -29,11 +33,13 @@ class Logs extends StatelessWidget {
class LogsWidget extends StatefulWidget {
final Server server;
final void Function(AppLog) createLog;
final void Function(FilteringStatus) setFilteringStatus;
const LogsWidget({
Key? key,
required this.server,
required this.createLog
required this.createLog,
required this.setFilteringStatus,
}) : super(key: key);
@override
@ -89,6 +95,21 @@ class _LogsWidgetState extends State<LogsWidget> {
}
}
void fetchFilteringRules() async {
final result = await getFilteringRules(server: widget.server);
if (result['result'] == 'success') {
widget.setFilteringStatus(result['data']);
}
else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.couldntGetFilteringStatus),
backgroundColor: Colors.red,
)
);
}
}
void scrollListener() {
if (scrollController.position.extentAfter < 500 && isLoadingMore == false) {
fetchLogs(loadingMore: true);
@ -99,6 +120,7 @@ class _LogsWidgetState extends State<LogsWidget> {
void initState() {
scrollController = ScrollController()..addListener(scrollListener);
fetchLogs(inOffset: 0);
fetchFilteringRules();
super.initState();
}

View file

@ -6,6 +6,7 @@ import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:adguard_home_manager/models/logs.dart';
import 'package:adguard_home_manager/models/filtering_status.dart';
import 'package:adguard_home_manager/models/app_log.dart';
import 'package:adguard_home_manager/models/server_status.dart';
import 'package:adguard_home_manager/models/clients.dart';
@ -27,14 +28,15 @@ Future<http.Response> getRequest({
Future<http.Response> postRequest({
required String urlPath,
required Server server,
Map<String, dynamic>? body
Map<String, dynamic>? body,
String? stringBody
}) {
return http.post(
Uri.parse("${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}/control$urlPath"),
headers: {
'Authorization': 'Basic ${server.authToken}'
},
body: jsonEncode(body)
body: body ?? stringBody
).timeout(const Duration(seconds: 10));
}
@ -363,6 +365,7 @@ Future getLogs({
required int count,
int? offset
}) async {
try {
final result = await getRequest(
urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}',
server: server
@ -386,7 +389,6 @@ Future getLogs({
)
};
}
try {
} on SocketException {
return {
'result': 'no_connection',
@ -425,4 +427,137 @@ Future getLogs({
)
};
}
}
Future getFilteringRules({
required Server server,
}) async {
try {
final result = await getRequest(
urlPath: '/filtering/status',
server: server
);
if (result.statusCode == 200) {
return {
'result': 'success',
'data': FilteringStatus.fromJson(jsonDecode(result.body))
};
}
else {
return {
'result': 'error',
'log': AppLog(
type: 'filtering_status',
dateTime: DateTime.now(),
message: 'error_code_not_expected',
statusCode: result.statusCode,
resBody: result.body
)
};
}
} on SocketException {
return {
'result': 'no_connection',
'log': AppLog(
type: 'filtering_status',
dateTime: DateTime.now(),
message: 'SocketException'
)
};
} on TimeoutException {
return {
'result': 'no_connection',
'log': AppLog(
type: 'filtering_status',
dateTime: DateTime.now(),
message: 'TimeoutException'
)
};
} on HandshakeException {
return {
'result': 'ssl_error',
'message': 'HandshakeException',
'log': AppLog(
type: 'filtering_status',
dateTime: DateTime.now(),
message: 'TimeoutException'
)
};
} catch (e) {
return {
'result': 'error',
'log': AppLog(
type: 'filtering_status',
dateTime: DateTime.now(),
message: e.toString()
)
};
}
}
Future postFilteringRules({
required Server server,
required String data,
}) async {
try {
final result = await postRequest(
urlPath: '/filtering/set_rules',
server: server,
stringBody: data
);
if (result.statusCode == 200) {
return {'result': 'success'};
}
else {
return {
'result': 'error',
'log': AppLog(
type: 'filtering_set_rules',
dateTime: DateTime.now(),
message: 'error_code_not_expected',
statusCode: result.statusCode,
resBody: result.body
)
};
}
} on SocketException {
return {
'result': 'no_connection',
'log': AppLog(
type: 'filtering_set_rules',
dateTime: DateTime.now(),
message: 'SocketException'
)
};
} on TimeoutException {
return {
'result': 'no_connection',
'log': AppLog(
type: 'filtering_set_rules',
dateTime: DateTime.now(),
message: 'TimeoutException'
)
};
} on HandshakeException {
return {
'result': 'ssl_error',
'message': 'HandshakeException',
'log': AppLog(
type: 'filtering_set_rules',
dateTime: DateTime.now(),
message: 'TimeoutException'
)
};
} catch (e) {
return {
'result': 'error',
'log': AppLog(
type: 'filtering_set_rules',
dateTime: DateTime.now(),
message: e.toString()
)
};
}
}