larxius commited on
Commit
8611e30
·
verified ·
1 Parent(s): d45b4a0

Update frontend/src/pages/LogsAndThreats.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/LogsAndThreats.jsx +369 -344
frontend/src/pages/LogsAndThreats.jsx CHANGED
@@ -1,344 +1,369 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { useAuth } from '../components/AuthContext';
3
- import { ShieldAlert, FileText, AlertTriangle, Search, Download, X, RefreshCw, List } from 'lucide-react';
4
-
5
- const LogsAndThreats = () => {
6
- const { user } = useAuth();
7
- const [trends, setTrends] = useState([]);
8
- const [auditLogs, setAuditLogs] = useState([]);
9
- const [loading, setLoading] = useState(true);
10
-
11
- // Modal State for View All Audit Logs
12
- const [showAllLogsModal, setShowAllLogsModal] = useState(false);
13
- const [allLogs, setAllLogs] = useState([]);
14
- const [loadingAllLogs, setLoadingAllLogs] = useState(false);
15
- const [searchQuery, setSearchQuery] = useState('');
16
-
17
- const [sortLogCol, setSortLogCol] = useState('Timestamp');
18
- const [sortLogDir, setSortLogDir] = useState('desc');
19
-
20
- const fetchStats = async () => {
21
- setLoading(true);
22
- try {
23
- const token = localStorage.getItem('wss_token');
24
- const res = await fetch('/api/auth/global-stats', {
25
- headers: {
26
- 'Authorization': `Bearer ${token}`
27
- }
28
- });
29
- if (res.ok) {
30
- const data = await res.json();
31
- setTrends(data.trends || []);
32
- setAuditLogs(data.audit_logs || []);
33
- }
34
- } catch (err) {
35
- console.error('Failed to fetch stats', err);
36
- } finally {
37
- setLoading(false);
38
- }
39
- };
40
-
41
- const fetchAllAuditLogs = async () => {
42
- setLoadingAllLogs(true);
43
- try {
44
- const token = localStorage.getItem('wss_token');
45
- const res = await fetch('/api/auth/audit-logs?limit=500', {
46
- headers: {
47
- 'Authorization': `Bearer ${token}`
48
- }
49
- });
50
- if (res.ok) {
51
- const data = await res.json();
52
- setAllLogs(data.audit_logs || []);
53
- }
54
- } catch (err) {
55
- console.error('Failed to fetch all audit logs', err);
56
- } finally {
57
- setLoadingAllLogs(false);
58
- }
59
- };
60
-
61
- useEffect(() => {
62
- fetchStats();
63
- const interval = setInterval(fetchStats, 5000);
64
- return () => clearInterval(interval);
65
- }, []);
66
-
67
- const openAllLogsModal = () => {
68
- setShowAllLogsModal(true);
69
- fetchAllAuditLogs();
70
- };
71
-
72
- const filteredAllLogs = allLogs.filter(log => {
73
- if (!searchQuery) return true;
74
- const q = searchQuery.toLowerCase();
75
- return (
76
- (log.action && log.action.toLowerCase().includes(q)) ||
77
- (log.user_email && log.user_email.toLowerCase().includes(q)) ||
78
- (log.target_name && log.target_name.toLowerCase().includes(q)) ||
79
- (log.target_id && log.target_id.toLowerCase().includes(q))
80
- );
81
- });
82
-
83
- const handleLogSort = (column) => {
84
- if (sortLogCol === column) {
85
- setSortLogDir(sortLogDir === 'asc' ? 'desc' : 'asc');
86
- } else {
87
- setSortLogCol(column);
88
- setSortLogDir('desc');
89
- }
90
- };
91
-
92
- const getSortedLogs = () => {
93
- return [...filteredAllLogs].sort((a, b) => {
94
- let aVal, bVal;
95
- switch (sortLogCol) {
96
- case 'Timestamp': aVal = new Date(a.timestamp).getTime(); bVal = new Date(b.timestamp).getTime(); break;
97
- case 'User': aVal = a.user_email || a.admin_id || ''; bVal = b.user_email || b.admin_id || ''; break;
98
- case 'Action': aVal = a.action || ''; bVal = b.action || ''; break;
99
- case 'Target': aVal = a.target_name || a.target_id || ''; bVal = b.target_name || b.target_id || ''; break;
100
- default: return 0;
101
- }
102
- if (aVal < bVal) return sortLogDir === 'asc' ? -1 : 1;
103
- if (aVal > bVal) return sortLogDir === 'asc' ? 1 : -1;
104
- return 0;
105
- });
106
- };
107
-
108
- const exportLogsToCSV = () => {
109
- if (!filteredAllLogs.length) return;
110
- const headers = ["Timestamp", "User Email", "Action", "Target"];
111
- const rows = filteredAllLogs.map(l => [
112
- `"${new Date(l.timestamp).toLocaleString()}"`,
113
- `"${l.user_email || 'System'}"`,
114
- `"${(l.action || '').replace(/"/g, '""')}"`,
115
- `"${(l.target_name || l.target_id || '').replace(/"/g, '""')}"`
116
- ]);
117
- const csvContent = "data:text/csv;charset=utf-8," + [headers.join(","), ...rows.map(e => e.join(","))].join("\n");
118
- const encodedUri = encodeURI(csvContent);
119
- const link = document.createElement("a");
120
- link.setAttribute("href", encodedUri);
121
- link.setAttribute("download", `audit_logs_${new Date().toISOString().slice(0, 10)}.csv`);
122
- document.body.appendChild(link);
123
- link.click();
124
- document.body.removeChild(link);
125
- };
126
-
127
- const isMasterAuthorized = sessionStorage.getItem('superAdminAuth') === 'true';
128
-
129
- if (!isMasterAuthorized && user?.role !== 'super_admin' && user?.role !== 'support_engineer') {
130
- return <div className="text-on-surface text-center mt-20 font-bold">Access Denied. You do not have permissions.</div>;
131
- }
132
-
133
- return (
134
- <div className="w-full text-on-surface animate-fade-in">
135
- <div className="flex flex-col md:flex-row md:items-center justify-between mb-xl gap-sm">
136
- <div>
137
- <h1 className="text-[28px] font-extrabold font-display tracking-tight text-primary flex items-center gap-2">
138
- <ShieldAlert className="w-8 h-8" />
139
- Logs & Global Threats
140
- </h1>
141
- <p className="text-on-surface-variant text-[14px] mt-1">Review system audit trails and global vulnerability trends.</p>
142
- </div>
143
- <button onClick={() => window.history.back()} className="flex items-center px-md py-sm bg-surface-container border border-outline-variant text-on-surface rounded-lg hover:bg-surface-container-high transition-colors font-bold text-[13.5px] cursor-pointer">
144
- <span className="material-symbols-outlined text-[18px] mr-2">arrow_back</span>
145
- Back to Panel
146
- </button>
147
- </div>
148
-
149
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-lg mt-xl">
150
- {/* Threat Intelligence */}
151
- <div>
152
- <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[18px] mb-md">
153
- <span className="material-symbols-outlined text-error mr-2 text-[20px]">warning</span>
154
- Global Threat Intelligence
155
- </h2>
156
- <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm p-4">
157
- {loading ? (
158
- <div className="text-center text-on-surface-variant text-[13px] py-4">Loading threats...</div>
159
- ) : trends.length === 0 ? (
160
- <div className="text-center text-on-surface-variant text-[13px] py-4">No global vulnerabilities recorded yet.</div>
161
- ) : (
162
- <ul className="divide-y divide-outline-variant">
163
- {trends.map((t, i) => (
164
- <li key={i} className="py-3 flex justify-between items-center">
165
- <span className="font-semibold text-on-surface text-[14px] flex items-center gap-2">
166
- <span className="w-6 h-6 rounded-full bg-error/10 text-error flex items-center justify-center text-[11px]">{i + 1}</span>
167
- {t.title}
168
- </span>
169
- <span className="bg-surface-container-high px-2 py-1 rounded-md text-[12px] font-bold">{t.count} Found</span>
170
- </li>
171
- ))}
172
- </ul>
173
- )}
174
- </div>
175
- </div>
176
-
177
- {/* Admin Audit Logs */}
178
- <div>
179
- <div className="flex items-center justify-between mb-md">
180
- <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[18px]">
181
- <span className="material-symbols-outlined text-primary mr-2 text-[20px]">policy</span>
182
- Admin Audit Logs
183
- </h2>
184
- <button
185
- onClick={openAllLogsModal}
186
- 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"
187
- >
188
- <List className="w-4 h-4" />
189
- View All Audit Logs
190
- </button>
191
- </div>
192
- <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm p-4">
193
- {loading ? (
194
- <div className="text-center text-on-surface-variant text-[13px] py-4">Loading logs...</div>
195
- ) : auditLogs.length === 0 ? (
196
- <div className="text-center text-on-surface-variant text-[13px] py-4">No admin actions recorded yet.</div>
197
- ) : (
198
- <div>
199
- <ul className="divide-y divide-outline-variant">
200
- {auditLogs.slice(0, 10).map((log, i) => (
201
- <li key={i} className="py-3">
202
- <div className="flex justify-between items-start mb-1">
203
- <span className="font-semibold text-on-surface text-[13.5px]">{log.action}</span>
204
- <span className="text-[11px] text-on-surface-variant shrink-0 ml-2">{new Date(log.timestamp).toLocaleString()}</span>
205
- </div>
206
- <div className="text-[12px] text-on-surface-variant flex items-center gap-1.5 flex-wrap">
207
- <span>User: <strong className="text-on-surface font-bold">{log.user_email || log.admin_id || 'System'}</strong></span>
208
- {log.target_name && log.target_name !== log.user_email && log.target_name !== log.admin_id && (
209
- <span className="text-outline">| Target: <strong className="text-on-surface font-bold">{log.target_name}</strong></span>
210
- )}
211
- </div>
212
- </li>
213
- ))}
214
- </ul>
215
- <div className="mt-4 pt-3 border-t border-outline-variant text-center">
216
- <button
217
- onClick={openAllLogsModal}
218
- className="text-primary hover:underline text-[13px] font-bold inline-flex items-center gap-1 cursor-pointer"
219
- >
220
- View All Complete Audit Logs &rarr;
221
- </button>
222
- </div>
223
- </div>
224
- )}
225
- </div>
226
- </div>
227
- </div>
228
-
229
- {/* Modal for View All Audit Logs */}
230
- {showAllLogsModal && (
231
- <div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
232
- <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">
233
- {/* Modal Header */}
234
- <div className="flex items-center justify-between p-4 border-b border-outline-variant">
235
- <div className="flex items-center gap-2">
236
- <span className="material-symbols-outlined text-primary text-[24px]">policy</span>
237
- <h3 className="text-[18px] font-bold text-on-surface">Complete Admin Audit Logs</h3>
238
- <span className="bg-primary/10 text-primary text-[11px] font-bold px-2 py-0.5 rounded-full ml-2">
239
- {filteredAllLogs.length} Records
240
- </span>
241
- </div>
242
- <button
243
- onClick={() => setShowAllLogsModal(false)}
244
- className="p-1 rounded-lg hover:bg-surface-container-high text-on-surface-variant transition-colors cursor-pointer"
245
- >
246
- <X className="w-5 h-5" />
247
- </button>
248
- </div>
249
-
250
- {/* Modal Actions Bar */}
251
- <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">
252
- <div className="relative w-full sm:w-72">
253
- <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant" />
254
- <input
255
- type="text"
256
- placeholder="Search user, action, or target..."
257
- value={searchQuery}
258
- onChange={(e) => setSearchQuery(e.target.value)}
259
- 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"
260
- />
261
- </div>
262
-
263
- <div className="flex items-center gap-2 w-full sm:w-auto justify-end">
264
- <button
265
- onClick={fetchAllAuditLogs}
266
- disabled={loadingAllLogs}
267
- 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"
268
- >
269
- <RefreshCw className={`w-3.5 h-3.5 ${loadingAllLogs ? 'animate-spin' : ''}`} />
270
- Refresh
271
- </button>
272
- <button
273
- onClick={exportLogsToCSV}
274
- disabled={!filteredAllLogs.length}
275
- 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"
276
- >
277
- <Download className="w-3.5 h-3.5" />
278
- Export CSV
279
- </button>
280
- </div>
281
- </div>
282
-
283
- {/* Modal Body - Audit Table */}
284
- <div className="p-4 overflow-y-auto flex-1">
285
- {loadingAllLogs ? (
286
- <div className="text-center py-12 text-on-surface-variant text-[14px]">Loading full audit history...</div>
287
- ) : filteredAllLogs.length === 0 ? (
288
- <div className="text-center py-12 text-on-surface-variant text-[14px]">No audit logs match your search.</div>
289
- ) : (
290
- <div className="overflow-x-auto">
291
- <table className="w-full text-left text-[13px]">
292
- <thead className="bg-surface-container-high text-on-surface-variant text-[11px] uppercase tracking-wider select-none">
293
- <tr>
294
- {['Timestamp', 'User', 'Action', 'Target'].map((h, i) => (
295
- <th
296
- key={h}
297
- onClick={() => handleLogSort(h)}
298
- className={`p-3 cursor-pointer hover:bg-surface-container-highest transition-colors group ${i === 0 ? 'rounded-l-lg' : i === 3 ? 'rounded-r-lg' : ''}`}
299
- >
300
- <div className="flex items-center gap-xs">
301
- {h}
302
- <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' : ''}`}>
303
- {sortLogCol === h && sortLogDir === 'desc' ? 'arrow_downward' : 'arrow_upward'}
304
- </span>
305
- </div>
306
- </th>
307
- ))}
308
- </tr>
309
- </thead>
310
- <tbody className="divide-y divide-outline-variant">
311
- {getSortedLogs().map((log, i) => (
312
- <tr key={log.id || i} className="hover:bg-surface-container-lowest/50 transition-colors">
313
- <td className="p-3 text-[11.5px] text-on-surface-variant whitespace-nowrap">
314
- {new Date(log.timestamp).toLocaleString()}
315
- </td>
316
- <td className="p-3 font-bold text-on-surface">
317
- {log.user_email || log.admin_id || 'System'}
318
- </td>
319
- <td className="p-3 text-on-surface">
320
- {log.action}
321
- </td>
322
- <td className="p-3 text-[12px] text-on-surface-variant">
323
- {log.target_name || log.target_id || '-'}
324
- </td>
325
- </tr>
326
- ))}
327
- </tbody>
328
- </table>
329
- </div>
330
- )}
331
- </div>
332
-
333
- {/* Modal Footer */}
334
- <div className="p-3 border-t border-outline-variant bg-surface-container-lowest text-right text-[12px] text-on-surface-variant">
335
- Showing {filteredAllLogs.length} audit event entries.
336
- </div>
337
- </div>
338
- </div>
339
- )}
340
- </div>
341
- );
342
- };
343
-
344
- export default LogsAndThreats;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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();
8
+ const [trends, setTrends] = useState([]);
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('');
17
+
18
+ const [sortLogCol, setSortLogCol] = useState('Timestamp');
19
+ const [sortLogDir, setSortLogDir] = useState('desc');
20
+
21
+ const fetchStats = async () => {
22
+ setLoading(true);
23
+ try {
24
+ const token = localStorage.getItem('wss_token');
25
+ const res = await fetch('/api/auth/global-stats', {
26
+ headers: {
27
+ 'Authorization': `Bearer ${token}`
28
+ }
29
+ });
30
+ if (res.ok) {
31
+ const data = await res.json();
32
+ setTrends(data.trends || []);
33
+ setAuditLogs(data.audit_logs || []);
34
+ }
35
+ } catch (err) {
36
+ console.error('Failed to fetch stats', err);
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ };
41
+
42
+ const fetchAllAuditLogs = async () => {
43
+ setLoadingAllLogs(true);
44
+ try {
45
+ const token = localStorage.getItem('wss_token');
46
+ const res = await fetch('/api/auth/audit-logs?limit=500', {
47
+ headers: {
48
+ 'Authorization': `Bearer ${token}`
49
+ }
50
+ });
51
+ if (res.ok) {
52
+ const data = await res.json();
53
+ setAllLogs(data.audit_logs || []);
54
+ }
55
+ } catch (err) {
56
+ console.error('Failed to fetch all audit logs', err);
57
+ } finally {
58
+ setLoadingAllLogs(false);
59
+ }
60
+ };
61
+
62
+ useEffect(() => {
63
+ fetchStats();
64
+ const interval = setInterval(fetchStats, 5000);
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();
76
+ return (
77
+ (log.action && log.action.toLowerCase().includes(q)) ||
78
+ (log.user_email && log.user_email.toLowerCase().includes(q)) ||
79
+ (log.target_name && log.target_name.toLowerCase().includes(q)) ||
80
+ (log.target_id && log.target_id.toLowerCase().includes(q))
81
+ );
82
+ });
83
+
84
+ const handleLogSort = (column) => {
85
+ if (sortLogCol === column) {
86
+ setSortLogDir(sortLogDir === 'asc' ? 'desc' : 'asc');
87
+ } else {
88
+ setSortLogCol(column);
89
+ setSortLogDir('desc');
90
+ }
91
+ };
92
+
93
+ const getSortedLogs = () => {
94
+ return [...filteredAllLogs].sort((a, b) => {
95
+ let aVal, bVal;
96
+ switch (sortLogCol) {
97
+ case 'Timestamp': aVal = new Date(a.timestamp).getTime(); bVal = new Date(b.timestamp).getTime(); break;
98
+ case 'User': aVal = a.user_email || a.admin_id || ''; bVal = b.user_email || b.admin_id || ''; break;
99
+ case 'Action': aVal = a.action || ''; bVal = b.action || ''; break;
100
+ case 'Target': aVal = a.target_name || a.target_id || ''; bVal = b.target_name || b.target_id || ''; break;
101
+ default: return 0;
102
+ }
103
+ if (aVal < bVal) return sortLogDir === 'asc' ? -1 : 1;
104
+ if (aVal > bVal) return sortLogDir === 'asc' ? 1 : -1;
105
+ return 0;
106
+ });
107
+ };
108
+
109
+ const exportLogsToCSV = () => {
110
+ if (!filteredAllLogs.length) return;
111
+ const headers = ["Timestamp", "User Email", "Action", "Target"];
112
+ const rows = filteredAllLogs.map(l => [
113
+ `"${new Date(l.timestamp).toLocaleString()}"`,
114
+ `"${l.user_email || 'System'}"`,
115
+ `"${(l.action || '').replace(/"/g, '""')}"`,
116
+ `"${(l.target_name || l.target_id || '').replace(/"/g, '""')}"`
117
+ ]);
118
+ const csvContent = "data:text/csv;charset=utf-8," + [headers.join(","), ...rows.map(e => e.join(","))].join("\n");
119
+ const encodedUri = encodeURI(csvContent);
120
+ const link = document.createElement("a");
121
+ link.setAttribute("href", encodedUri);
122
+ link.setAttribute("download", `audit_logs_${new Date().toISOString().slice(0, 10)}.csv`);
123
+ document.body.appendChild(link);
124
+ link.click();
125
+ document.body.removeChild(link);
126
+ };
127
+
128
+ const isMasterAuthorized = sessionStorage.getItem('superAdminAuth') === 'true';
129
+
130
+ if (authLoading) {
131
+ return (
132
+ <div className="flex h-[70vh] items-center justify-center">
133
+ <div className="flex flex-col items-center gap-3">
134
+ <span className="material-symbols-outlined animate-spin text-3xl text-primary">sync</span>
135
+ <span className="font-bold text-on-surface-variant text-sm">Loading Logs & Threats...</span>
136
+ </div>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ if (!isMasterAuthorized && user?.role !== 'super_admin' && user?.role !== 'support_engineer') {
142
+ return (
143
+ <div className="min-h-[60vh] flex flex-col items-center justify-center text-center p-6">
144
+ <div className="w-16 h-16 rounded-full bg-red-500/10 text-error flex items-center justify-center mb-4">
145
+ <span className="material-symbols-outlined text-3xl">lock</span>
146
+ </div>
147
+ <h2 className="text-xl font-bold text-on-surface mb-2">Access Restricted</h2>
148
+ <p className="text-on-surface-variant text-sm max-w-md mb-6 leading-relaxed">
149
+ You do not have permissions to view global logs and threat intelligence.
150
+ </p>
151
+ <Link to="/dashboard" className="px-4 py-2 bg-primary text-white rounded-lg font-bold text-sm no-underline shadow-md">
152
+ Go to Dashboard
153
+ </Link>
154
+ </div>
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">
161
+ <div>
162
+ <h1 className="text-[28px] font-extrabold font-display tracking-tight text-primary flex items-center gap-2">
163
+ <ShieldAlert className="w-8 h-8" />
164
+ Logs & Global Threats
165
+ </h1>
166
+ <p className="text-on-surface-variant text-[14px] mt-1">Review system audit trails and global vulnerability trends.</p>
167
+ </div>
168
+ <button onClick={() => window.history.back()} className="flex items-center px-md py-sm bg-surface-container border border-outline-variant text-on-surface rounded-lg hover:bg-surface-container-high transition-colors font-bold text-[13.5px] cursor-pointer">
169
+ <span className="material-symbols-outlined text-[18px] mr-2">arrow_back</span>
170
+ Back to Panel
171
+ </button>
172
+ </div>
173
+
174
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-lg mt-xl">
175
+ {/* Threat Intelligence */}
176
+ <div>
177
+ <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[18px] mb-md">
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 ? (
185
+ <div className="text-center text-on-surface-variant text-[13px] py-4">No global vulnerabilities recorded yet.</div>
186
+ ) : (
187
+ <ul className="divide-y divide-outline-variant">
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>
198
+ )}
199
+ </div>
200
+ </div>
201
+
202
+ {/* Admin Audit Logs */}
203
+ <div>
204
+ <div className="flex items-center justify-between mb-md">
205
+ <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[18px]">
206
+ <span className="material-symbols-outlined text-primary mr-2 text-[20px]">policy</span>
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 ? (
221
+ <div className="text-center text-on-surface-variant text-[13px] py-4">No admin actions recorded yet.</div>
222
+ ) : (
223
+ <div>
224
+ <ul className="divide-y divide-outline-variant">
225
+ {auditLogs.slice(0, 10).map((log, i) => (
226
+ <li key={i} className="py-3">
227
+ <div className="flex justify-between items-start mb-1">
228
+ <span className="font-semibold text-on-surface text-[13.5px]">{log.action}</span>
229
+ <span className="text-[11px] text-on-surface-variant shrink-0 ml-2">{new Date(log.timestamp).toLocaleString()}</span>
230
+ </div>
231
+ <div className="text-[12px] text-on-surface-variant flex items-center gap-1.5 flex-wrap">
232
+ <span>User: <strong className="text-on-surface font-bold">{log.user_email || log.admin_id || 'System'}</strong></span>
233
+ {log.target_name && log.target_name !== log.user_email && log.target_name !== log.admin_id && (
234
+ <span className="text-outline">| Target: <strong className="text-on-surface font-bold">{log.target_name}</strong></span>
235
+ )}
236
+ </div>
237
+ </li>
238
+ ))}
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>
247
+ </div>
248
+ </div>
249
+ )}
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
+ };
368
+
369
+ export default LogsAndThreats;