Skip to content

Commit 7e72d0c

Browse files
client, server: add delete server log entries functionality
1 parent e86008e commit 7e72d0c

9 files changed

Lines changed: 360 additions & 73 deletions

File tree

school_data_hub_client/lib/src/protocol/client.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ class EndpointAdminLogs extends _i1.EndpointRef {
166166
'getSessionLogs',
167167
{'filter': filter},
168168
);
169+
170+
_i2.Future<void> deleteSessionLog(int sessionLogId) =>
171+
caller.callServerEndpoint<void>(
172+
'adminLogs',
173+
'deleteSessionLog',
174+
{'sessionLogId': sessionLogId},
175+
);
176+
177+
_i2.Future<void> deleteAllSessionLogs() => caller.callServerEndpoint<void>(
178+
'adminLogs',
179+
'deleteAllSessionLogs',
180+
{},
181+
);
169182
}
170183

171184
/// {@category Endpoint}

school_data_hub_flutter/lib/features/server_logs/data/server_logs_api_service.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,12 @@ class ServerLogsApiService {
77
Future<HubSessionLogResult> getSessionLogs(HubSessionLogFilter filter) async {
88
return _client.adminLogs.getSessionLogs(filter);
99
}
10+
11+
Future<void> deleteSessionLog(int sessionLogId) async {
12+
return _client.adminLogs.deleteSessionLog(sessionLogId);
13+
}
14+
15+
Future<void> deleteAllSessionLogs() async {
16+
return _client.adminLogs.deleteAllSessionLogs();
17+
}
1018
}

school_data_hub_flutter/lib/features/server_logs/domain/server_logs_manager.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ class ServerLogsManager extends ChangeNotifier {
4242
restriction: fetchCommand.isRunning,
4343
);
4444

45+
late final deleteCommand = Command.createAsync<int, void>(
46+
_deleteSessionLog,
47+
errorFilter: const GlobalIfNoLocalErrorFilter(),
48+
);
49+
50+
late final deleteAllCommand = Command.createAsyncNoParamNoResult(
51+
_deleteAllSessionLogs,
52+
errorFilter: const GlobalIfNoLocalErrorFilter(),
53+
);
54+
4555
HubSessionLogFilter _buildFilter({int? lastSessionLogId}) {
4656
return HubSessionLogFilter(
4757
endpoint: _endpointFilter.value,
@@ -76,6 +86,27 @@ class ServerLogsManager extends ChangeNotifier {
7686
notifyListeners();
7787
}
7888

89+
Future<void> _deleteSessionLog(int sessionLogId) async {
90+
final api = di<ServerLogsApiService>();
91+
await api.deleteSessionLog(sessionLogId);
92+
93+
// Remove from local list
94+
_sessionLogs.value = _sessionLogs.value
95+
.where((log) => log.sessionLogEntry.sessionId != sessionLogId)
96+
.toList();
97+
notifyListeners();
98+
}
99+
100+
Future<void> _deleteAllSessionLogs() async {
101+
final api = di<ServerLogsApiService>();
102+
await api.deleteAllSessionLogs();
103+
104+
// Clear local list
105+
_sessionLogs.value = [];
106+
_hasMore.value = false;
107+
notifyListeners();
108+
}
109+
79110
void setEndpointFilter(String? value) {
80111
final normalized = value?.trim();
81112
_endpointFilter.value =

school_data_hub_flutter/lib/features/server_logs/presentation/server_logs_page.dart

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ class ServerLogsPage extends WatchingWidget {
3636
},
3737
);
3838

39+
registerHandler(
40+
select: (ServerLogsManager m) => m.deleteCommand.errors,
41+
handler: (context, error, _) {
42+
if (error == null) return;
43+
ScaffoldMessenger.of(context).showSnackBar(
44+
SnackBar(
45+
content: Text('Fehler beim Löschen: ${error.error}'),
46+
backgroundColor: AppColors.dangerButtonColor,
47+
),
48+
);
49+
},
50+
);
51+
52+
registerHandler(
53+
select: (ServerLogsManager m) => m.deleteAllCommand.errors,
54+
handler: (context, error, _) {
55+
if (error == null) return;
56+
ScaffoldMessenger.of(context).showSnackBar(
57+
SnackBar(
58+
content: Text('Fehler beim Löschen aller Einträge: ${error.error}'),
59+
backgroundColor: AppColors.dangerButtonColor,
60+
),
61+
);
62+
},
63+
);
64+
3965
return Scaffold(
4066
backgroundColor: AppColors.canvasColor,
4167
appBar: const GenericAppBar(
@@ -46,6 +72,7 @@ class ServerLogsPage extends WatchingWidget {
4672
filtersActive: filtersActive,
4773
onShowFilters: () => showServerLogsFilterBottomSheet(context),
4874
onResetFilters: manager.resetFilters,
75+
onDeleteAll: () => _showDeleteAllDialog(context, manager),
4976
),
5077
body: Center(
5178
child: ConstrainedBox(
@@ -64,7 +91,12 @@ class ServerLogsPage extends WatchingWidget {
6491
horizontal: 8,
6592
vertical: 2,
6693
),
67-
child: SessionLogCard(info: info),
94+
child: SessionLogCard(
95+
info: info,
96+
onDelete: () => manager.deleteCommand.run(
97+
info.sessionLogEntry.sessionId,
98+
),
99+
),
68100
),
69101
),
70102
if (hasMore && logs.isNotEmpty)
@@ -98,16 +130,46 @@ class ServerLogsPage extends WatchingWidget {
98130
}
99131
}
100132

133+
void _showDeleteAllDialog(BuildContext context, ServerLogsManager manager) {
134+
showDialog(
135+
context: context,
136+
builder: (context) => AlertDialog(
137+
title: const Text('Alle Logs löschen'),
138+
content: const Text(
139+
'Möchten Sie wirklich ALLE Server-Logs löschen? Diese Aktion kann nicht rückgängig gemacht werden.',
140+
),
141+
actions: [
142+
TextButton(
143+
onPressed: () => Navigator.pop(context),
144+
child: const Text('Abbrechen'),
145+
),
146+
TextButton(
147+
onPressed: () {
148+
Navigator.pop(context);
149+
manager.deleteAllCommand.run();
150+
},
151+
style: TextButton.styleFrom(
152+
foregroundColor: AppColors.dangerButtonColor,
153+
),
154+
child: const Text('Alle löschen'),
155+
),
156+
],
157+
),
158+
);
159+
}
160+
101161
class _ServerLogsBottomNavBar extends StatelessWidget {
102162
const _ServerLogsBottomNavBar({
103163
required this.filtersActive,
104164
required this.onShowFilters,
105165
required this.onResetFilters,
166+
required this.onDeleteAll,
106167
});
107168

108169
final bool filtersActive;
109170
final VoidCallback onShowFilters;
110171
final VoidCallback onResetFilters;
172+
final VoidCallback onDeleteAll;
111173

112174
@override
113175
Widget build(BuildContext context) {
@@ -121,6 +183,12 @@ class _ServerLogsBottomNavBar extends StatelessWidget {
121183
constraints: const BoxConstraints(maxWidth: 800),
122184
child: Row(
123185
children: [
186+
IconButton(
187+
tooltip: 'Alle löschen',
188+
icon: const Icon(Icons.delete_sweep, size: 30),
189+
onPressed: onDeleteAll,
190+
color: AppColors.dangerButtonColor,
191+
),
124192
const Spacer(),
125193
IconButton(
126194
tooltip: 'zurück',

school_data_hub_flutter/lib/features/server_logs/presentation/widgets/session_log_card.dart

Lines changed: 108 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import 'package:school_data_hub_flutter/common/theme/app_colors.dart';
66
import 'package:school_data_hub_flutter/common/theme/styles.dart';
77

88
class SessionLogCard extends StatelessWidget {
9-
const SessionLogCard({super.key, required this.info});
9+
const SessionLogCard({
10+
super.key,
11+
required this.info,
12+
this.onDelete,
13+
});
1014

1115
final HubSessionLogInfo info;
16+
final VoidCallback? onDelete;
1217

1318
@override
1419
Widget build(BuildContext context) {
@@ -46,88 +51,120 @@ class SessionLogCard extends StatelessWidget {
4651
);
4752
}
4853

49-
return Card(
50-
color: AppColors.cardInCardColor,
51-
shape: RoundedRectangleBorder(
52-
borderRadius: BorderRadius.circular(16),
53-
side: BorderSide(color: borderColor, width: 2),
54-
),
55-
elevation: 1,
56-
child: ExpansionTile(
57-
tilePadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
58-
childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
59-
shape: const Border(),
60-
title: Column(
61-
crossAxisAlignment: CrossAxisAlignment.start,
62-
children: [
63-
Row(
64-
children: [
65-
_StatusBadge(
66-
hasError: hasError,
67-
isSlow: isSlow,
68-
isOpen: isOpen,
69-
),
70-
const SizedBox(width: 10),
71-
Expanded(
72-
child: Text(
73-
endpointLabel,
74-
style: AppStyles.subtitle.copyWith(
75-
fontWeight: FontWeight.bold,
54+
void handleLongPress() {
55+
if (onDelete == null) return;
56+
57+
showDialog(
58+
context: context,
59+
builder: (context) => AlertDialog(
60+
title: const Text('Eintrag löschen'),
61+
content: const Text('Möchten Sie diesen Log-Eintrag wirklich löschen?'),
62+
actions: [
63+
TextButton(
64+
onPressed: () => Navigator.pop(context),
65+
child: const Text('Abbrechen'),
66+
),
67+
TextButton(
68+
onPressed: () {
69+
Navigator.pop(context);
70+
onDelete?.call();
71+
},
72+
style: TextButton.styleFrom(
73+
foregroundColor: AppColors.dangerButtonColor,
74+
),
75+
child: const Text('Löschen'),
76+
),
77+
],
78+
),
79+
);
80+
}
81+
82+
return InkWell(
83+
onLongPress: onDelete != null ? handleLongPress : null,
84+
borderRadius: BorderRadius.circular(16),
85+
child: Card(
86+
color: AppColors.cardInCardColor,
87+
shape: RoundedRectangleBorder(
88+
borderRadius: BorderRadius.circular(16),
89+
side: BorderSide(color: borderColor, width: 2),
90+
),
91+
elevation: 1,
92+
child: ExpansionTile(
93+
tilePadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
94+
childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
95+
shape: const Border(),
96+
title: Column(
97+
crossAxisAlignment: CrossAxisAlignment.start,
98+
children: [
99+
Row(
100+
children: [
101+
_StatusBadge(
102+
hasError: hasError,
103+
isSlow: isSlow,
104+
isOpen: isOpen,
105+
),
106+
const SizedBox(width: 10),
107+
Expanded(
108+
child: Text(
109+
endpointLabel,
110+
style: AppStyles.subtitle.copyWith(
111+
fontWeight: FontWeight.bold,
112+
),
113+
maxLines: 3,
114+
overflow: TextOverflow.ellipsis,
76115
),
77-
maxLines: 3,
78-
overflow: TextOverflow.ellipsis,
79116
),
80-
),
81-
],
82-
),
83-
const SizedBox(height: 6),
84-
Row(
85-
children: [
86-
Icon(Icons.access_time, size: 14, color: Colors.black54),
87-
const SizedBox(width: 4),
88-
Text(
89-
timestamp,
90-
style: AppStyles.textLabel.copyWith(color: Colors.black54),
91-
),
92-
const Spacer(),
93-
Icon(Icons.timer_outlined, size: 14, color: Colors.black54),
94-
const SizedBox(width: 4),
95-
Text(
96-
durationMs,
97-
style: AppStyles.textLabel.copyWith(color: Colors.black54),
98-
),
99-
if (entry.numQueries != null) ...[
100-
const SizedBox(width: 12),
101-
Icon(Icons.storage_outlined, size: 14, color: Colors.black54),
117+
],
118+
),
119+
const SizedBox(height: 6),
120+
Row(
121+
children: [
122+
Icon(Icons.access_time, size: 14, color: Colors.black54),
102123
const SizedBox(width: 4),
103124
Text(
104-
'${entry.numQueries} Q',
125+
timestamp,
105126
style: AppStyles.textLabel.copyWith(color: Colors.black54),
106127
),
107-
const Gap(20),
108-
IconButton(
109-
icon: const Icon(Icons.copy, size: 20),
110-
padding: EdgeInsets.zero,
111-
constraints: const BoxConstraints(),
112-
onPressed: copyToClipboard,
113-
tooltip: 'Kopieren',
128+
const Spacer(),
129+
Icon(Icons.timer_outlined, size: 14, color: Colors.black54),
130+
const SizedBox(width: 4),
131+
Text(
132+
durationMs,
133+
style: AppStyles.textLabel.copyWith(color: Colors.black54),
114134
),
135+
if (entry.numQueries != null) ...[
136+
const SizedBox(width: 12),
137+
Icon(Icons.storage_outlined, size: 14, color: Colors.black54),
138+
const SizedBox(width: 4),
139+
Text(
140+
'${entry.numQueries} Q',
141+
style: AppStyles.textLabel.copyWith(color: Colors.black54),
142+
),
143+
const Gap(20),
144+
IconButton(
145+
icon: const Icon(Icons.copy, size: 20),
146+
padding: EdgeInsets.zero,
147+
constraints: const BoxConstraints(),
148+
onPressed: copyToClipboard,
149+
tooltip: 'Kopieren',
150+
),
151+
],
115152
],
153+
),
154+
if (hasError) ...[
155+
const SizedBox(height: 6),
156+
_ExpandableErrorText(error: entry.error!),
116157
],
117-
),
118-
if (hasError) ...[
119-
const SizedBox(height: 6),
120-
_ExpandableErrorText(error: entry.error!),
121158
],
159+
),
160+
children: [
161+
if (info.logs.isNotEmpty) _LogEntriesSection(logs: info.logs),
162+
if (info.queries.isNotEmpty)
163+
_QueryEntriesSection(queries: info.queries),
164+
if (entry.stackTrace != null)
165+
_StackTraceSection(stackTrace: entry.stackTrace!),
122166
],
123167
),
124-
children: [
125-
if (info.logs.isNotEmpty) _LogEntriesSection(logs: info.logs),
126-
if (info.queries.isNotEmpty)
127-
_QueryEntriesSection(queries: info.queries),
128-
if (entry.stackTrace != null)
129-
_StackTraceSection(stackTrace: entry.stackTrace!),
130-
],
131168
),
132169
);
133170
}

0 commit comments

Comments
 (0)