Added date filtering logs

This commit is contained in:
Juan Gilsanz Polo 2022-10-02 03:58:02 +02:00
parent 84df416011
commit f6b747f729
12 changed files with 432 additions and 72 deletions

View file

@ -6,6 +6,13 @@ String formatTimestamp(DateTime timestamp, String format) {
return f.format(timestamp); return f.format(timestamp);
} }
String formatTimestampUTC(DateTime timestamp, String format) {
final DateFormat dateFormat = DateFormat(format);
final String utcDate = dateFormat.format(DateTime.parse(timestamp.toString()));
final String localDate = dateFormat.parse(utcDate, true).toLocal().toIso8601String();
return dateFormat.format(DateTime.parse(localDate));
}
String formatTimeOfDay(TimeOfDay timestamp, String format) { String formatTimeOfDay(TimeOfDay timestamp, String format) {
DateFormat f = DateFormat(format); DateFormat f = DateFormat(format);
return f.format(DateTime(0, 0, 0, timestamp.hour, timestamp.minute)); return f.format(DateTime(0, 0, 0, timestamp.hour, timestamp.minute));

View file

@ -134,5 +134,13 @@
"unblockDomain": "Unblock domain", "unblockDomain": "Unblock domain",
"userFilteringRulesNotUpdated": "User filtering rules could not be updated", "userFilteringRulesNotUpdated": "User filtering rules could not be updated",
"userFilteringRulesUpdated": "User filtering rules updated successfully", "userFilteringRulesUpdated": "User filtering rules updated successfully",
"savingUserFilters": "Saving user filters..." "savingUserFilters": "Saving user filters...",
"filters": "Filters",
"logsOlderThan": "Logs older than",
"responseStatus": "Response status",
"selectTime": "Select time",
"notSelected": "Not selected",
"resetFilters": "Reset filters",
"noLogsDisplay": "No logs to display",
"noLogsThatOld": "It's possible that there are no logs saved for that selected time. Try selecting a more recent time."
} }

View file

@ -134,5 +134,13 @@
"unblockDomain": "Desbloquear dominio", "unblockDomain": "Desbloquear dominio",
"userFilteringRulesNotUpdated": "No se pudieron actualizar las reglas de filtrado del usuario", "userFilteringRulesNotUpdated": "No se pudieron actualizar las reglas de filtrado del usuario",
"userFilteringRulesUpdated": "Reglas de filtrado del usuario actualizadas correctamente", "userFilteringRulesUpdated": "Reglas de filtrado del usuario actualizadas correctamente",
"savingUserFilters": "Guardando filtros de usuario..." "savingUserFilters": "Guardando filtros de usuario...",
"filters": "Filtros",
"logsOlderThan": "Logs anteriores a",
"responseStatus": "Estado de la respuesta",
"selectTime": "Seleccionar hora",
"notSelected": "No seleccionado",
"resetFilters": "Resetear filtros",
"noLogsDisplay": "No hay registros para mostrar",
"noLogsThatOld": "Es posible que no haya registros guardados para ese tiempo seleccionado. Prueba a seleccionar un tiempo más reciente."
} }

View file

@ -14,6 +14,7 @@ import 'package:adguard_home_manager/base.dart';
import 'package:adguard_home_manager/classes/http_override.dart'; import 'package:adguard_home_manager/classes/http_override.dart';
import 'package:adguard_home_manager/services/database.dart'; import 'package:adguard_home_manager/services/database.dart';
import 'package:adguard_home_manager/providers/logs_provider.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/config/theme.dart'; import 'package:adguard_home_manager/config/theme.dart';
@ -27,6 +28,7 @@ void main() async {
AppConfigProvider appConfigProvider = AppConfigProvider(); AppConfigProvider appConfigProvider = AppConfigProvider();
ServersProvider serversProvider = ServersProvider(); ServersProvider serversProvider = ServersProvider();
LogsProvider logsProvider = LogsProvider();
final dbData = await loadDb(); final dbData = await loadDb();
@ -60,6 +62,9 @@ void main() async {
ChangeNotifierProvider( ChangeNotifierProvider(
create: ((context) => appConfigProvider) create: ((context) => appConfigProvider)
), ),
ChangeNotifierProvider(
create: ((context) => logsProvider)
),
], ],
child: const Main(), child: const Main(),
) )

View file

@ -16,21 +16,21 @@ String logsToJson(LogsData data) => json.encode(data.toJson());
class LogsData { class LogsData {
List<Log> data; List<Log> data;
final DateTime oldest; final DateTime? oldest;
LogsData({ LogsData({
required this.data, required this.data,
required this.oldest, this.oldest,
}); });
factory LogsData.fromJson(Map<String, dynamic> json) => LogsData( factory LogsData.fromJson(Map<String, dynamic> json) => LogsData(
data: List<Log>.from(json["data"].map((x) => Log.fromJson(x))), data: List<Log>.from(json["data"].map((x) => Log.fromJson(x))),
oldest: DateTime.parse(json["oldest"]), oldest: json["oldest"] != '' ? DateTime.parse(json["oldest"]) : null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"data": List<dynamic>.from(data.map((x) => x.toJson())), "data": List<dynamic>.from(data.map((x) => x.toJson())),
"oldest": oldest.toIso8601String(), "oldest": oldest != null ? oldest!.toIso8601String() : null,
}; };
} }

View file

@ -0,0 +1,62 @@
import 'package:adguard_home_manager/models/logs.dart';
import 'package:flutter/material.dart';
class LogsProvider with ChangeNotifier {
int _loadStatus = 0;
LogsData? _logsData;
DateTime? _logsOlderThan;
int _logsQuantity = 100;
int _offset = 0;
int get loadStatus {
return _loadStatus;
}
LogsData? get logsData {
return _logsData;
}
DateTime? get logsOlderThan {
return _logsOlderThan;
}
int get logsQuantity {
return _logsQuantity;
}
int get offset {
return _offset;
}
void setLoadStatus(int value) {
_loadStatus = value;
notifyListeners();
}
void setLogsData(LogsData data) {
_logsData = data;
notifyListeners();
}
void setLogsOlderThan(DateTime? value) {
_logsOlderThan = value;
notifyListeners();
}
void resetFilters() {
_logsOlderThan = null;
_offset = 0;
notifyListeners();
}
void setLogsQuantity(int value) {
_logsQuantity = value;
notifyListeners();
}
void setOffset(int value) {
_offset = value;
}
}

View file

@ -1,14 +1,31 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/logs/logs_filters_modal.dart';
class LogsAppBar extends StatelessWidget with PreferredSizeWidget { class LogsAppBar extends StatelessWidget with PreferredSizeWidget {
const LogsAppBar({Key? key}) : super(key: key); const LogsAppBar({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
void openFilersModal() {
showModalBottomSheet(
context: context,
builder: (context) => const LogsFiltersModal(),
backgroundColor: Colors.transparent,
isScrollControlled: true
);
}
return AppBar( return AppBar(
title: Text(AppLocalizations.of(context)!.logs), title: Text(AppLocalizations.of(context)!.logs),
centerTitle: true, actions: [
IconButton(
onPressed: openFilersModal,
icon: const Icon(Icons.filter_list_rounded)
),
const SizedBox(width: 5),
],
); );
} }

View file

@ -143,7 +143,7 @@ class LogDetailsModal extends StatelessWidget {
LogListTile( LogListTile(
icon: Icons.schedule, icon: Icons.schedule,
title: AppLocalizations.of(context)!.time, title: AppLocalizations.of(context)!.time,
subtitle: formatTimestamp(log.time, 'HH:mm:ss') subtitle: formatTimestampUTC(log.time, 'HH:mm:ss')
), ),
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),

View file

@ -200,7 +200,7 @@ class LogTile extends StatelessWidget {
], ],
), ),
Text( Text(
formatTimestamp(log.time, 'HH:mm:ss') formatTimestampUTC(log.time, 'HH:mm:ss')
), ),
], ],
), ),

View file

@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/screens/logs/log_tile.dart'; import 'package:adguard_home_manager/screens/logs/log_tile.dart';
import 'package:adguard_home_manager/providers/logs_provider.dart';
import 'package:adguard_home_manager/models/filtering_status.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/app_log.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart';
@ -21,25 +22,26 @@ class Logs extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context); final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context); final appConfigProvider = Provider.of<AppConfigProvider>(context);
final logsProvider = Provider.of<LogsProvider>(context);
return LogsWidget( return LogsWidget(
server: serversProvider.selectedServer!, serversProvider: serversProvider,
createLog: appConfigProvider.addLog, appConfigProvider: appConfigProvider,
setFilteringStatus: serversProvider.setFilteringStatus, logsProvider: logsProvider,
); );
} }
} }
class LogsWidget extends StatefulWidget { class LogsWidget extends StatefulWidget {
final Server server; final ServersProvider serversProvider;
final void Function(AppLog) createLog; final AppConfigProvider appConfigProvider;
final void Function(FilteringStatus) setFilteringStatus; final LogsProvider logsProvider;
const LogsWidget({ const LogsWidget({
Key? key, Key? key,
required this.server, required this.serversProvider,
required this.createLog, required this.appConfigProvider,
required this.setFilteringStatus, required this.logsProvider,
}) : super(key: key); }) : super(key: key);
@override @override
@ -47,14 +49,6 @@ class LogsWidget extends StatefulWidget {
} }
class _LogsWidgetState extends State<LogsWidget> { class _LogsWidgetState extends State<LogsWidget> {
LogsList logsList = LogsList(
loadStatus: 0,
logsData: null
);
int itemsPerLoad = 100;
int offset = 0;
late ScrollController scrollController; late ScrollController scrollController;
bool isLoadingMore = false; bool isLoadingMore = false;
@ -63,42 +57,44 @@ class _LogsWidgetState extends State<LogsWidget> {
int? inOffset, int? inOffset,
bool? loadingMore bool? loadingMore
}) async { }) async {
int offst = inOffset ?? offset; int offst = inOffset ?? widget.logsProvider.offset;
if (loadingMore != null && loadingMore == true) { if (loadingMore != null && loadingMore == true) {
setState(() => isLoadingMore = true); setState(() => isLoadingMore = true);
} }
final result = await getLogs(server: widget.server, count: itemsPerLoad, offset: offst); final result = await getLogs(
server: widget.serversProvider.selectedServer!,
count: widget.logsProvider.logsQuantity,
offset: offst
);
if (loadingMore != null && loadingMore == true) { if (loadingMore != null && loadingMore == true) {
setState(() => isLoadingMore = false); setState(() => isLoadingMore = false);
} }
if (result['result'] == 'success') { if (result['result'] == 'success') {
setState(() { widget.logsProvider.setOffset(inOffset != null ? inOffset+widget.logsProvider.logsQuantity : widget.logsProvider.offset+widget.logsProvider.logsQuantity);
offset = inOffset != null ? inOffset+itemsPerLoad : offset+itemsPerLoad; if (loadingMore != null && loadingMore == true && widget.logsProvider.logsData != null) {
if (loadingMore != null && loadingMore == true) { LogsData newLogsData = result['data'];
logsList.logsData!.data = [...logsList.logsData!.data, ...result['data'].data]; newLogsData.data = [...widget.logsProvider.logsData!.data, ...result['data'].data];
widget.logsProvider.setLogsData(newLogsData);
} }
else { else {
logsList.logsData = result['data']; widget.logsProvider.setLogsData(result['data']);
} }
logsList.loadStatus = 1; widget.logsProvider.setLoadStatus(1);
});
} }
else { else {
setState(() { widget.logsProvider.setLoadStatus(2);
logsList.loadStatus = 2; widget.appConfigProvider.addLog(result['log']);
});
widget.createLog(result['log']);
} }
} }
void fetchFilteringRules() async { void fetchFilteringRules() async {
final result = await getFilteringRules(server: widget.server); final result = await getFilteringRules(server: widget.serversProvider.selectedServer!);
if (result['result'] == 'success') { if (result['result'] == 'success') {
widget.setFilteringStatus(result['data']); widget.serversProvider.setFilteringStatus(result['data']);
} }
else { else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -126,7 +122,9 @@ class _LogsWidgetState extends State<LogsWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
switch (logsList.loadStatus) { final logsProvider = Provider.of<LogsProvider>(context);
switch (logsProvider.loadStatus) {
case 0: case 0:
return SizedBox( return SizedBox(
width: double.maxFinite, width: double.maxFinite,
@ -153,14 +151,15 @@ class _LogsWidgetState extends State<LogsWidget> {
onRefresh: () async { onRefresh: () async {
await fetchLogs(inOffset: 0); await fetchLogs(inOffset: 0);
}, },
child: ListView.builder( child: logsProvider.logsData!.data.isNotEmpty
? ListView.builder(
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.only(top: 0), padding: const EdgeInsets.only(top: 0),
itemCount: isLoadingMore == true itemCount: isLoadingMore == true
? logsList.logsData!.data.length+1 ? logsProvider.logsData!.data.length+1
: logsList.logsData!.data.length, : logsProvider.logsData!.data.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (isLoadingMore == true && index == logsList.logsData!.data.length) { if (isLoadingMore == true && index == logsProvider.logsData!.data.length) {
return const Padding( return const Padding(
padding: EdgeInsets.symmetric(vertical: 20), padding: EdgeInsets.symmetric(vertical: 20),
child: Center( child: Center(
@ -170,13 +169,42 @@ class _LogsWidgetState extends State<LogsWidget> {
} }
else { else {
return LogTile( return LogTile(
log: logsList.logsData!.data[index], log: logsProvider.logsData!.data[index],
index: index, index: index,
length: logsList.logsData!.data.length, length: logsProvider.logsData!.data.length,
); );
} }
} }
)
: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context)!.noLogsDisplay,
style: const TextStyle(
fontSize: 24,
color: Colors.grey
), ),
),
if (logsProvider.logsOlderThan != null) Padding(
padding: const EdgeInsets.only(
top: 30,
left: 20,
right: 20
),
child: Text(
AppLocalizations.of(context)!.noLogsThatOld,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16,
color: Colors.grey
),
),
),
]
),
)
); );
case 2: case 2:

View file

@ -0,0 +1,224 @@
// 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/providers/app_config_provider.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/functions/format_time.dart';
import 'package:adguard_home_manager/providers/logs_provider.dart';
class LogsFiltersModal extends StatelessWidget {
const LogsFiltersModal({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final logsProvider = Provider.of<LogsProvider>(context);
final serversProvider = Provider.of<ServersProvider>(context);
final appConfigProvider = Provider.of<AppConfigProvider>(context);
void selectTime() async {
DateTime now = DateTime.now();
DateTime? dateValue = await showDatePicker(
context: context,
initialDate: now,
firstDate: DateTime(now.year, now.month-1, now.day),
lastDate: now
);
if (dateValue != null) {
TimeOfDay? timeValue = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
helpText: AppLocalizations.of(context)!.selectTime,
);
if (timeValue != null) {
DateTime value = DateTime(
dateValue.year,
dateValue.month,
dateValue.day,
timeValue.hour,
timeValue.minute,
dateValue.second
).toUtc();
logsProvider.setLogsOlderThan(value);
logsProvider.setLoadStatus(0);
logsProvider.setOffset(0);
final result = await getLogs(
server: serversProvider.selectedServer!,
count: logsProvider.logsQuantity,
olderThan: logsProvider.logsOlderThan
);
if (result['result'] == 'success') {
logsProvider.setLogsData(result['data']);
logsProvider.setLoadStatus(1);
}
else {
appConfigProvider.addLog(result['log']);
logsProvider.setLoadStatus(2);
}
}
}
}
void resetFilters() async {
logsProvider.setLoadStatus(0);
logsProvider.resetFilters();
final result = await getLogs(
server: serversProvider.selectedServer!,
count: logsProvider.logsQuantity
);
if (result['result'] == 'success') {
logsProvider.setLogsData(result['data']);
logsProvider.setLoadStatus(1);
}
else {
appConfigProvider.addLog(result['log']);
logsProvider.setLoadStatus(2);
}
}
return Container(
height: 350,
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: 24,
bottom: 20,
),
child: Icon(
Icons.filter_list_rounded,
size: 26,
),
),
Text(
AppLocalizations.of(context)!.filters,
style: const TextStyle(
fontSize: 24
),
),
const SizedBox(height: 20),
Expanded(
child: ListView(
physics: const NeverScrollableScrollPhysics(),
children: [
Material(
color: Colors.transparent,
child: InkWell(
onTap: selectTime,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
const Icon(
Icons.schedule,
size: 24,
color: Colors.grey,
),
const SizedBox(width: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.logsOlderThan,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500
),
),
const SizedBox(height: 5),
Text(
logsProvider.logsOlderThan != null
? formatTimestampUTC(logsProvider.logsOlderThan!, 'HH:mm - dd/MM/yyyy')
: AppLocalizations.of(context)!.notSelected,
style: const TextStyle(
fontSize: 14,
color: Colors.grey
),
)
],
)
],
),
),
),
),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => {},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
children: [
const Icon(
Icons.shield_rounded,
size: 24,
color: Colors.grey,
),
const SizedBox(width: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.responseStatus,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500
),
),
const SizedBox(height: 5),
const Text(
"12/12/2000",
style: TextStyle(
fontSize: 14,
color: Colors.grey
),
)
],
)
],
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: resetFilters,
child: Text(AppLocalizations.of(context)!.resetFilters)
),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(AppLocalizations.of(context)!.close)
),
],
),
)
],
),
);
}
}

View file

@ -363,11 +363,11 @@ Future requestAllowedBlockedClientsHosts(Server server, Map<String, List<String>
Future getLogs({ Future getLogs({
required Server server, required Server server,
required int count, required int count,
int? offset int? offset,
DateTime? olderThan,
}) async { }) async {
try {
final result = await getRequest( final result = await getRequest(
urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}', urlPath: '/querylog?limit=$count${offset != null ? '&offset=$offset' : ''}${olderThan != null ? '&older_than=${olderThan.toIso8601String()}' : ''}',
server: server server: server
); );
@ -389,6 +389,7 @@ Future getLogs({
) )
}; };
} }
try {
} on SocketException { } on SocketException {
return { return {
'result': 'no_connection', 'result': 'no_connection',