larxius commited on
Commit
68b4f0d
·
verified ·
1 Parent(s): ebdbcb8

Update frontend/src/pages/LogsAndThreats.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/LogsAndThreats.jsx +131 -124
frontend/src/pages/LogsAndThreats.jsx CHANGED
@@ -1,7 +1,7 @@
1
  import React, { useState, useEffect } from 'react';
2
  import { Link } from 'react-router-dom';
3
  import { useAuth } from '../components/AuthContext';
4
- import { ShieldAlert, FileText, AlertTriangle, Search, Download, X, RefreshCw, List } from 'lucide-react';
5
 
6
  const LogsAndThreats = () => {
7
  const { user, loading: authLoading } = useAuth();
@@ -9,8 +9,8 @@ const LogsAndThreats = () => {
9
  const [auditLogs, setAuditLogs] = useState([]);
10
  const [loading, setLoading] = useState(true);
11
 
12
- // Modal State for View All Audit Logs
13
- const [showAllLogsModal, setShowAllLogsModal] = useState(false);
14
  const [allLogs, setAllLogs] = useState([]);
15
  const [loadingAllLogs, setLoadingAllLogs] = useState(false);
16
  const [searchQuery, setSearchQuery] = useState('');
@@ -65,11 +65,15 @@ const LogsAndThreats = () => {
65
  return () => clearInterval(interval);
66
  }, []);
67
 
68
- const openAllLogsModal = () => {
69
- setShowAllLogsModal(true);
70
  fetchAllAuditLogs();
71
  };
72
 
 
 
 
 
73
  const filteredAllLogs = allLogs.filter(log => {
74
  if (!searchQuery) return true;
75
  const q = searchQuery.toLowerCase();
@@ -155,6 +159,121 @@ const LogsAndThreats = () => {
155
  );
156
  }
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  return (
159
  <div className="w-full text-on-surface animate-fade-in">
160
  <div className="flex flex-col md:flex-row md:items-center justify-between mb-xl gap-sm">
@@ -178,7 +297,7 @@ const LogsAndThreats = () => {
178
  <span className="material-symbols-outlined text-error mr-2 text-[20px]">warning</span>
179
  Global Threat Intelligence
180
  </h2>
181
- <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm p-4">
182
  {loading ? (
183
  <div className="text-center text-on-surface-variant text-[13px] py-4">Loading threats...</div>
184
  ) : trends.length === 0 ? (
@@ -188,10 +307,10 @@ const LogsAndThreats = () => {
188
  {trends.map((t, i) => (
189
  <li key={i} className="py-3 flex justify-between items-center">
190
  <span className="font-semibold text-on-surface text-[14px] flex items-center gap-2">
191
- <span className="w-6 h-6 rounded-full bg-error/10 text-error flex items-center justify-center text-[11px]">{i + 1}</span>
192
  {t.title}
193
  </span>
194
- <span className="bg-surface-container-high px-2 py-1 rounded-md text-[12px] font-bold">{t.count} Found</span>
195
  </li>
196
  ))}
197
  </ul>
@@ -207,14 +326,14 @@ const LogsAndThreats = () => {
207
  Admin Audit Logs
208
  </h2>
209
  <button
210
- onClick={openAllLogsModal}
211
  className="flex items-center gap-1.5 px-3 py-1.5 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors font-bold text-[12px] cursor-pointer"
212
  >
213
  <List className="w-4 h-4" />
214
  View All Audit Logs
215
  </button>
216
  </div>
217
- <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm p-4">
218
  {loading ? (
219
  <div className="text-center text-on-surface-variant text-[13px] py-4">Loading logs...</div>
220
  ) : auditLogs.length === 0 ? (
@@ -239,8 +358,8 @@ const LogsAndThreats = () => {
239
  </ul>
240
  <div className="mt-4 pt-3 border-t border-outline-variant text-center">
241
  <button
242
- onClick={openAllLogsModal}
243
- className="text-primary hover:underline text-[13px] font-bold inline-flex items-center gap-1 cursor-pointer"
244
  >
245
  View All Complete Audit Logs &rarr;
246
  </button>
@@ -250,118 +369,6 @@ const LogsAndThreats = () => {
250
  </div>
251
  </div>
252
  </div>
253
-
254
- {/* Modal for View All Audit Logs */}
255
- {showAllLogsModal && (
256
- <div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
257
- <div className="bg-surface border border-outline-variant rounded-2xl w-full max-w-4xl max-h-[85vh] flex flex-col shadow-2xl animate-fade-in">
258
- {/* Modal Header */}
259
- <div className="flex items-center justify-between p-4 border-b border-outline-variant">
260
- <div className="flex items-center gap-2">
261
- <span className="material-symbols-outlined text-primary text-[24px]">policy</span>
262
- <h3 className="text-[18px] font-bold text-on-surface">Complete Admin Audit Logs</h3>
263
- <span className="bg-primary/10 text-primary text-[11px] font-bold px-2 py-0.5 rounded-full ml-2">
264
- {filteredAllLogs.length} Records
265
- </span>
266
- </div>
267
- <button
268
- onClick={() => setShowAllLogsModal(false)}
269
- className="p-1 rounded-lg hover:bg-surface-container-high text-on-surface-variant transition-colors cursor-pointer"
270
- >
271
- <X className="w-5 h-5" />
272
- </button>
273
- </div>
274
-
275
- {/* Modal Actions Bar */}
276
- <div className="p-4 border-b border-outline-variant bg-surface-container-lowest flex flex-col sm:flex-row items-center justify-between gap-3">
277
- <div className="relative w-full sm:w-72">
278
- <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant" />
279
- <input
280
- type="text"
281
- placeholder="Search user, action, or target..."
282
- value={searchQuery}
283
- onChange={(e) => setSearchQuery(e.target.value)}
284
- className="w-full pl-9 pr-3 py-1.5 bg-surface border border-outline-variant rounded-lg text-[13px] text-on-surface focus:outline-none focus:border-primary"
285
- />
286
- </div>
287
-
288
- <div className="flex items-center gap-2 w-full sm:w-auto justify-end">
289
- <button
290
- onClick={fetchAllAuditLogs}
291
- disabled={loadingAllLogs}
292
- className="flex items-center gap-1.5 px-3 py-1.5 bg-surface-container border border-outline-variant text-on-surface hover:bg-surface-container-high rounded-lg text-[12px] font-bold transition-colors cursor-pointer"
293
- >
294
- <RefreshCw className={`w-3.5 h-3.5 ${loadingAllLogs ? 'animate-spin' : ''}`} />
295
- Refresh
296
- </button>
297
- <button
298
- onClick={exportLogsToCSV}
299
- disabled={!filteredAllLogs.length}
300
- className="flex items-center gap-1.5 px-3 py-1.5 bg-primary text-on-primary hover:bg-primary/90 rounded-lg text-[12px] font-bold transition-colors cursor-pointer"
301
- >
302
- <Download className="w-3.5 h-3.5" />
303
- Export CSV
304
- </button>
305
- </div>
306
- </div>
307
-
308
- {/* Modal Body - Audit Table */}
309
- <div className="p-4 overflow-y-auto flex-1">
310
- {loadingAllLogs ? (
311
- <div className="text-center py-12 text-on-surface-variant text-[14px]">Loading full audit history...</div>
312
- ) : filteredAllLogs.length === 0 ? (
313
- <div className="text-center py-12 text-on-surface-variant text-[14px]">No audit logs match your search.</div>
314
- ) : (
315
- <div className="overflow-x-auto">
316
- <table className="w-full text-left text-[13px]">
317
- <thead className="bg-surface-container-high text-on-surface-variant text-[11px] uppercase tracking-wider select-none">
318
- <tr>
319
- {['Timestamp', 'User', 'Action', 'Target'].map((h, i) => (
320
- <th
321
- key={h}
322
- onClick={() => handleLogSort(h)}
323
- className={`p-3 cursor-pointer hover:bg-surface-container-highest transition-colors group ${i === 0 ? 'rounded-l-lg' : i === 3 ? 'rounded-r-lg' : ''}`}
324
- >
325
- <div className="flex items-center gap-xs">
326
- {h}
327
- <span className={`material-symbols-outlined text-[14px] opacity-0 group-hover:opacity-50 transition-opacity ${sortLogCol === h ? 'opacity-100 group-hover:opacity-100 text-primary' : ''}`}>
328
- {sortLogCol === h && sortLogDir === 'desc' ? 'arrow_downward' : 'arrow_upward'}
329
- </span>
330
- </div>
331
- </th>
332
- ))}
333
- </tr>
334
- </thead>
335
- <tbody className="divide-y divide-outline-variant">
336
- {getSortedLogs().map((log, i) => (
337
- <tr key={log.id || i} className="hover:bg-surface-container-lowest/50 transition-colors">
338
- <td className="p-3 text-[11.5px] text-on-surface-variant whitespace-nowrap">
339
- {new Date(log.timestamp).toLocaleString()}
340
- </td>
341
- <td className="p-3 font-bold text-on-surface">
342
- {log.user_email || log.admin_id || 'System'}
343
- </td>
344
- <td className="p-3 text-on-surface">
345
- {log.action}
346
- </td>
347
- <td className="p-3 text-[12px] text-on-surface-variant">
348
- {log.target_name || log.target_id || '-'}
349
- </td>
350
- </tr>
351
- ))}
352
- </tbody>
353
- </table>
354
- </div>
355
- )}
356
- </div>
357
-
358
- {/* Modal Footer */}
359
- <div className="p-3 border-t border-outline-variant bg-surface-container-lowest text-right text-[12px] text-on-surface-variant">
360
- Showing {filteredAllLogs.length} audit event entries.
361
- </div>
362
- </div>
363
- </div>
364
- )}
365
  </div>
366
  );
367
  };
 
1
  import React, { useState, useEffect } from 'react';
2
  import { Link } from 'react-router-dom';
3
  import { useAuth } from '../components/AuthContext';
4
+ import { ShieldAlert, Search, Download, RefreshCw, List, ArrowLeft } from 'lucide-react';
5
 
6
  const LogsAndThreats = () => {
7
  const { user, loading: authLoading } = useAuth();
 
9
  const [auditLogs, setAuditLogs] = useState([]);
10
  const [loading, setLoading] = useState(true);
11
 
12
+ // Dedicated Full Page Audit Logs State
13
+ const [isFullLogsView, setIsFullLogsView] = useState(false);
14
  const [allLogs, setAllLogs] = useState([]);
15
  const [loadingAllLogs, setLoadingAllLogs] = useState(false);
16
  const [searchQuery, setSearchQuery] = useState('');
 
65
  return () => clearInterval(interval);
66
  }, []);
67
 
68
+ const openFullLogsView = () => {
69
+ setIsFullLogsView(true);
70
  fetchAllAuditLogs();
71
  };
72
 
73
+ const closeFullLogsView = () => {
74
+ setIsFullLogsView(false);
75
+ };
76
+
77
  const filteredAllLogs = allLogs.filter(log => {
78
  if (!searchQuery) return true;
79
  const q = searchQuery.toLowerCase();
 
159
  );
160
  }
161
 
162
+ // Full Page View Mode
163
+ if (isFullLogsView) {
164
+ return (
165
+ <div className="w-full text-on-surface animate-fade-in">
166
+ {/* Full Page Header */}
167
+ <div className="flex flex-col md:flex-row md:items-center justify-between mb-lg gap-sm border-b border-outline-variant/60 pb-md">
168
+ <div>
169
+ <button
170
+ onClick={closeFullLogsView}
171
+ className="flex items-center text-primary hover:underline font-bold text-[13px] mb-2 cursor-pointer bg-transparent border-0 p-0"
172
+ >
173
+ <ArrowLeft className="w-4 h-4 mr-1" />
174
+ Back to Summary Dashboard
175
+ </button>
176
+ <h1 className="text-[26px] font-extrabold font-display tracking-tight text-on-surface flex items-center gap-2">
177
+ <span className="material-symbols-outlined text-primary text-[28px]">policy</span>
178
+ Complete Admin Audit Logs
179
+ <span className="bg-primary/10 text-primary text-[12px] font-bold px-2.5 py-0.5 rounded-full ml-2">
180
+ {filteredAllLogs.length} Records
181
+ </span>
182
+ </h1>
183
+ <p className="text-on-surface-variant text-[13.5px] mt-1">Full system audit trail of administrative events and actions.</p>
184
+ </div>
185
+
186
+ <div className="flex items-center gap-2 flex-wrap">
187
+ <button
188
+ onClick={fetchAllAuditLogs}
189
+ disabled={loadingAllLogs}
190
+ className="flex items-center gap-1.5 px-3.5 py-2 bg-surface-container border border-outline-variant text-on-surface hover:bg-surface-container-high rounded-lg text-[13px] font-bold transition-colors cursor-pointer"
191
+ >
192
+ <RefreshCw className={`w-4 h-4 ${loadingAllLogs ? 'animate-spin' : ''}`} />
193
+ Refresh
194
+ </button>
195
+ <button
196
+ onClick={exportLogsToCSV}
197
+ disabled={!filteredAllLogs.length}
198
+ className="flex items-center gap-1.5 px-4 py-2 bg-primary text-on-primary hover:bg-primary/90 rounded-lg text-[13px] font-bold transition-colors cursor-pointer shadow-2xs"
199
+ >
200
+ <Download className="w-4 h-4" />
201
+ Export CSV
202
+ </button>
203
+ </div>
204
+ </div>
205
+
206
+ {/* Filter and Search Bar */}
207
+ <div className="mb-md bg-surface-container-lowest border border-outline-variant/70 rounded-xl p-4 flex flex-col sm:flex-row items-center justify-between gap-3 shadow-2xs">
208
+ <div className="relative w-full sm:w-80">
209
+ <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant" />
210
+ <input
211
+ type="text"
212
+ placeholder="Search user, action, or target..."
213
+ value={searchQuery}
214
+ onChange={(e) => setSearchQuery(e.target.value)}
215
+ className="w-full pl-9 pr-3 py-2 bg-surface border border-outline-variant/60 rounded-lg text-[13.5px] text-on-surface focus:outline-none focus:border-primary"
216
+ />
217
+ </div>
218
+ <div className="text-[12.5px] text-on-surface-variant font-medium">
219
+ Showing <strong className="text-on-surface font-bold">{getSortedLogs().length}</strong> audit event entries
220
+ </div>
221
+ </div>
222
+
223
+ {/* Audit Logs Data Table */}
224
+ <div className="bg-surface-container-lowest border border-outline-variant/70 rounded-xl overflow-hidden shadow-2xs">
225
+ {loadingAllLogs ? (
226
+ <div className="text-center py-16 text-on-surface-variant text-[14px]">Loading full audit history...</div>
227
+ ) : filteredAllLogs.length === 0 ? (
228
+ <div className="text-center py-16 text-on-surface-variant text-[14px]">No audit logs match your search filter.</div>
229
+ ) : (
230
+ <div className="overflow-x-auto">
231
+ <table className="w-full text-left text-[13.5px]">
232
+ <thead className="bg-surface-container-high/60 text-on-surface-variant text-[11px] uppercase tracking-wider select-none border-b border-outline-variant/70">
233
+ <tr>
234
+ {['Timestamp', 'User', 'Action', 'Target'].map((h, i) => (
235
+ <th
236
+ key={h}
237
+ onClick={() => handleLogSort(h)}
238
+ className="p-3.5 cursor-pointer hover:bg-surface-container-highest transition-colors group"
239
+ >
240
+ <div className="flex items-center gap-xs font-bold">
241
+ {h}
242
+ <span className={`material-symbols-outlined text-[14px] opacity-0 group-hover:opacity-50 transition-opacity ${sortLogCol === h ? 'opacity-100 group-hover:opacity-100 text-primary' : ''}`}>
243
+ {sortLogCol === h && sortLogDir === 'desc' ? 'arrow_downward' : 'arrow_upward'}
244
+ </span>
245
+ </div>
246
+ </th>
247
+ ))}
248
+ </tr>
249
+ </thead>
250
+ <tbody className="divide-y divide-outline-variant/60">
251
+ {getSortedLogs().map((log, i) => (
252
+ <tr key={log.id || i} className="hover:bg-surface-container-lowest/80 transition-colors">
253
+ <td className="p-3.5 text-[12px] font-mono text-on-surface-variant whitespace-nowrap">
254
+ {new Date(log.timestamp).toLocaleString()}
255
+ </td>
256
+ <td className="p-3.5 font-bold text-on-surface">
257
+ {log.user_email || log.admin_id || 'System'}
258
+ </td>
259
+ <td className="p-3.5 text-on-surface">
260
+ {log.action}
261
+ </td>
262
+ <td className="p-3.5 text-[12.5px] text-on-surface-variant">
263
+ {log.target_name || log.target_id || '-'}
264
+ </td>
265
+ </tr>
266
+ ))}
267
+ </tbody>
268
+ </table>
269
+ </div>
270
+ )}
271
+ </div>
272
+ </div>
273
+ );
274
+ }
275
+
276
+ // Dashboard Overview Mode
277
  return (
278
  <div className="w-full text-on-surface animate-fade-in">
279
  <div className="flex flex-col md:flex-row md:items-center justify-between mb-xl gap-sm">
 
297
  <span className="material-symbols-outlined text-error mr-2 text-[20px]">warning</span>
298
  Global Threat Intelligence
299
  </h2>
300
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-2xs p-4">
301
  {loading ? (
302
  <div className="text-center text-on-surface-variant text-[13px] py-4">Loading threats...</div>
303
  ) : trends.length === 0 ? (
 
307
  {trends.map((t, i) => (
308
  <li key={i} className="py-3 flex justify-between items-center">
309
  <span className="font-semibold text-on-surface text-[14px] flex items-center gap-2">
310
+ <span className="w-6 h-6 rounded-full bg-error/10 text-error flex items-center justify-center text-[11px] font-bold">{i + 1}</span>
311
  {t.title}
312
  </span>
313
+ <span className="bg-surface-container-high px-2.5 py-1 rounded-md text-[12px] font-bold">{t.count} Found</span>
314
  </li>
315
  ))}
316
  </ul>
 
326
  Admin Audit Logs
327
  </h2>
328
  <button
329
+ onClick={openFullLogsView}
330
  className="flex items-center gap-1.5 px-3 py-1.5 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors font-bold text-[12px] cursor-pointer"
331
  >
332
  <List className="w-4 h-4" />
333
  View All Audit Logs
334
  </button>
335
  </div>
336
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-2xs p-4">
337
  {loading ? (
338
  <div className="text-center text-on-surface-variant text-[13px] py-4">Loading logs...</div>
339
  ) : auditLogs.length === 0 ? (
 
358
  </ul>
359
  <div className="mt-4 pt-3 border-t border-outline-variant text-center">
360
  <button
361
+ onClick={openFullLogsView}
362
+ className="text-primary hover:underline text-[13px] font-bold inline-flex items-center gap-1 cursor-pointer bg-transparent border-0"
363
  >
364
  View All Complete Audit Logs &rarr;
365
  </button>
 
369
  </div>
370
  </div>
371
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  </div>
373
  );
374
  };