diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 448810c..6f0d536 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -401,5 +401,12 @@ "days30": "30 days", "days90": "90 days", "retentionTime": "Retention time", - "selectOneItem": "Select one item" + "selectOneItem": "Select one item", + "logSettingsNotLoaded": "Log settings couldn't be loaded.", + "updatingSettings": "Updating settings...", + "logsConfigUpdated": "Logs settings updated successfully", + "logsConfigNotUpdated": "Logs settings couldn't be updated", + "deletingLogs": "Clearing logs...", + "logsCleared": "Logs cleared successfully", + "logsNotCleared": "Logs could not be cleared" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f245d30..c951daf 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -401,5 +401,12 @@ "days30": "30 días", "days90": "90 días", "retentionTime": "Tiempo de retención", - "selectOneItem": "Selecciona un elemento" + "selectOneItem": "Selecciona un elemento", + "logSettingsNotLoaded": "Los ajustes de registros no han podido ser cargados.", + "updatingSettings": "Actualizando ajustes...", + "logsConfigUpdated": "Configuración de registros actualizada correctamente", + "logsConfigNotUpdated": "La configuración de registros no ha podido ser actualizada", + "deletingLogs": "Borrando registros...", + "logsCleared": "Registros borrados correctamente", + "logsNotCleared": "No se han podido borrar los registros" } \ No newline at end of file diff --git a/lib/models/logs.dart b/lib/models/logs.dart index af674b0..8ae4698 100644 --- a/lib/models/logs.dart +++ b/lib/models/logs.dart @@ -38,7 +38,7 @@ class Log { final bool answerDnssec; final bool cached; final String client; - final ClientInfo clientInfo; + final ClientInfo? clientInfo; final String clientProto; final String elapsedMs; final Question question; @@ -56,7 +56,7 @@ class Log { required this.answerDnssec, required this.cached, required this.client, - required this.clientInfo, + this.clientInfo, required this.clientProto, required this.elapsedMs, required this.question, @@ -75,7 +75,7 @@ class Log { answerDnssec: json["answer_dnssec"], cached: json["cached"], client: json["client"], - clientInfo: ClientInfo.fromJson(json["client_info"]), + clientInfo: json["client_info"] != null ? ClientInfo.fromJson(json["client_info"]) : null, clientProto: json["client_proto"], elapsedMs: json["elapsedMs"], question: Question.fromJson(json["question"]), @@ -94,7 +94,7 @@ class Log { "answer_dnssec": answerDnssec, "cached": cached, "client": client, - "client_info": clientInfo.toJson(), + "client_info": clientInfo?.toJson(), "client_proto": clientProto, "elapsedMs": elapsedMs, "question": question.toJson(), diff --git a/lib/screens/logs/log_details_modal.dart b/lib/screens/logs/log_details_modal.dart index d0a55e9..c7acdb1 100644 --- a/lib/screens/logs/log_details_modal.dart +++ b/lib/screens/logs/log_details_modal.dart @@ -173,10 +173,10 @@ class LogDetailsModal extends StatelessWidget { title: AppLocalizations.of(context)!.deviceIp, subtitle: log.client ), - if (log.clientInfo.name != '') LogListTile( + if (log.clientInfo != null && log.clientInfo!.name != '') LogListTile( icon: Icons.abc_rounded, title: AppLocalizations.of(context)!.deviceName, - subtitle: log.clientInfo.name + subtitle: log.clientInfo!.name ), ], ) diff --git a/lib/screens/logs/logs.dart b/lib/screens/logs/logs.dart index f9bd875..d2a8914 100644 --- a/lib/screens/logs/logs.dart +++ b/lib/screens/logs/logs.dart @@ -8,6 +8,8 @@ import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart'; import 'package:adguard_home_manager/screens/logs/logs_config_modal.dart'; import 'package:adguard_home_manager/screens/logs/log_tile.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/classes/process_modal.dart'; import 'package:adguard_home_manager/models/applied_filters.dart'; import 'package:adguard_home_manager/providers/logs_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; @@ -135,8 +137,67 @@ class _LogsWidgetState extends State { @override Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); final logsProvider = Provider.of(context); + void updateConfig(Map data) async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await updateQueryLogParameters(server: serversProvider.selectedServer!, data: data); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigUpdated, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsConfigNotUpdated, + color: Colors.red + ); + } + } + + void clearQueries() async { + ProcessModal processModal = ProcessModal(context: context); + processModal.open(AppLocalizations.of(context)!.updatingSettings); + + final result = await clearLogs(server: serversProvider.selectedServer!); + + processModal.close(); + + if (result['result'] == 'success') { + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsCleared, + color: Colors.green + ); + } + else { + appConfigProvider.addLog(result['log']); + + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.logsNotCleared, + color: Colors.red + ); + } + } + + void openFilersModal() { showModalBottomSheet( context: context, @@ -269,14 +330,6 @@ class _LogsWidgetState extends State { } } - void updateConfig(Map data) async { - - } - - void clearQueries() async { - - } - return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.logs), diff --git a/lib/screens/logs/logs_config_modal.dart b/lib/screens/logs/logs_config_modal.dart index a50f839..ba07297 100644 --- a/lib/screens/logs/logs_config_modal.dart +++ b/lib/screens/logs/logs_config_modal.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -57,10 +58,30 @@ class _LogsConfigModalWidgetState extends State { List> retentionItems = []; + int loadStatus = 0; + + void loadData() async { + final result = await getQueryLogInfo(server: widget.serversProvider.selectedServer!); + + if (mounted) { + if (result['result'] == 'success') { + setState(() { + generalSwitch = result['data']['enabled']; + anonymizeClientIp = result['data']['anonymize_client_ip']; + retentionTime = result['data']['interval'].toString(); + loadStatus = 1; + }); + } + else { + setState(() => loadStatus = 2); + } + } + } + @override void initState() { retentionItems = [ - { + { 'label': AppLocalizations.of(widget.context)!.hours6, 'value': 0.25 }, @@ -82,11 +103,206 @@ class _LogsConfigModalWidgetState extends State { }, ]; + loadData(); super.initState(); } @override Widget build(BuildContext context) { + + Widget generateBody() { + switch (loadStatus) { + case 0: + return const Center( + child: CircularProgressIndicator(), + ); + + case 1: + return Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 28), + child: Icon( + Icons.settings, + size: 26, + ), + ), + const SizedBox(height: 20), + Text( + AppLocalizations.of(context)!.logsSettings, + style: const TextStyle( + fontSize: 24 + ), + ), + const SizedBox(height: 30), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Material( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(28), + child: InkWell( + onTap: () => setState(() => generalSwitch = !generalSwitch), + borderRadius: BorderRadius.circular(28), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.enableLog, + style: const TextStyle( + fontSize: 18, + ), + ), + Switch( + value: generalSwitch, + onChanged: (value) => setState(() => generalSwitch = value), + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + ), + const SizedBox(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.anonymizeClientIp, + style: const TextStyle( + fontSize: 16 + ), + ), + Switch( + value: anonymizeClientIp, + onChanged: (value) => setState(() => anonymizeClientIp = value), + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: DropdownButtonFormField( + items: retentionItems.map>((Map item) { + return DropdownMenuItem( + value: item['value'].toString(), + child: Text(item['label']), + ); + }).toList(), + value: retentionTime, + onChanged: (value) => setState(() => retentionTime = value), + decoration: InputDecoration( + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + label: Text(AppLocalizations.of(context)!.retentionTime) + ), + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + widget.onClear(); + }, + child: Text(AppLocalizations.of(context)!.clearLogs) + ), + Row( + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(AppLocalizations.of(context)!.cancel) + ), + const SizedBox(width: 20), + TextButton( + onPressed: retentionTime != '' + ? () { + Navigator.pop(context); + widget.onConfirm({ + "enabled": generalSwitch, + "interval": double.parse(retentionTime!), + "anonymize_client_ip": anonymizeClientIp + }); + } + : null, + child: Text( + AppLocalizations.of(context)!.confirm, + style: TextStyle( + color: retentionTime != '' + ? Theme.of(context).primaryColor + : Colors.grey + ), + ) + ), + ], + ) + ], + ), + ) + ], + ), + ) + ], + ); + + 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), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + AppLocalizations.of(context)!.logSettingsNotLoaded, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 22, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + + default: + return const SizedBox(); + } + } + return Container( height: 450, decoration: BoxDecoration( @@ -96,150 +312,7 @@ class _LogsConfigModalWidgetState extends State { ), color: Theme.of(context).dialogBackgroundColor ), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 28), - child: Icon( - Icons.settings, - size: 26, - ), - ), - const SizedBox(height: 20), - Text( - AppLocalizations.of(context)!.logsSettings, - style: const TextStyle( - fontSize: 24 - ), - ), - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Material( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(28), - child: InkWell( - onTap: () => setState(() => generalSwitch = !generalSwitch), - borderRadius: BorderRadius.circular(28), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8 - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.enableLog, - style: const TextStyle( - fontSize: 18, - ), - ), - Switch( - value: generalSwitch, - onChanged: (value) => setState(() => generalSwitch = value), - activeColor: Theme.of(context).primaryColor, - ) - ], - ), - ), - ), - ), - ), - const SizedBox(height: 20), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () => setState(() => anonymizeClientIp = !anonymizeClientIp), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - AppLocalizations.of(context)!.anonymizeClientIp, - style: const TextStyle( - fontSize: 16 - ), - ), - Switch( - value: anonymizeClientIp, - onChanged: (value) => setState(() => anonymizeClientIp = value), - activeColor: Theme.of(context).primaryColor, - ) - ], - ), - ), - ), - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: DropdownButtonFormField( - items: retentionItems.map>((Map item) { - return DropdownMenuItem( - value: item['value'].toString(), - child: Text(item['label']), - ); - }).toList(), - onChanged: (value) => setState(() => retentionTime = value), - decoration: InputDecoration( - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(10) - ) - ), - label: Text(AppLocalizations.of(context)!.retentionTime) - ), - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: widget.onClear, - child: Text(AppLocalizations.of(context)!.clearLogs) - ), - Row( - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(AppLocalizations.of(context)!.cancel) - ), - const SizedBox(width: 20), - TextButton( - onPressed: retentionTime != '' - ? () => widget.onConfirm({ - "enabled": generalSwitch, - "interval": double.parse(retentionTime!), - "anonymize_client_ip": anonymizeClientIp - }) - : null, - child: Text( - AppLocalizations.of(context)!.confirm, - style: TextStyle( - color: retentionTime != '' - ? Theme.of(context).primaryColor - : Colors.grey - ), - ) - ), - ], - ) - ], - ), - ) - ], - ), - ) - ], - ), + child: generateBody() ); } } \ No newline at end of file diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 430afb0..71c39bb 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -1462,4 +1462,106 @@ Future addDnsRewriteRule({ else { return result; } +} + +Future getQueryLogInfo({ + required Server server, +}) async { + final result = await apiRequest( + urlPath: '/querylog_info', + method: 'get', + server: server, + type: 'get_query_log_info' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { + 'result': 'success', + 'data': jsonDecode(result['body']) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'get_query_log_info', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'], + ) + }; + } + } + else { + return result; + } +} + +Future updateQueryLogParameters({ + required Server server, + required Map data, +}) async { + final result = await apiRequest( + urlPath: '/querylog_config', + method: 'post', + server: server, + body: data, + type: 'update_query_log_config' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { 'result': 'success' }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'update_query_log_config', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'], + ) + }; + } + } + else { + return result; + } +} + +Future clearLogs({ + required Server server, +}) async { + final result = await apiRequest( + urlPath: '/querylog_clear', + method: 'post', + server: server, + body: {}, + type: 'clear_query_logs' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return { 'result': 'success' }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'clear_query_logs', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'], + ) + }; + } + } + else { + return result; + } } \ No newline at end of file