larxius commited on
Commit
83c6110
·
verified ·
1 Parent(s): 688e619

Update frontend/src/pages/SupportEngineerPanel.jsx

Browse files
frontend/src/pages/SupportEngineerPanel.jsx CHANGED
@@ -17,7 +17,15 @@ import {
17
  RefreshCw,
18
  CheckCircle2,
19
  AlertTriangle,
20
- Info
 
 
 
 
 
 
 
 
21
  } from 'lucide-react';
22
  import { CustomModal } from '../components/CustomModal';
23
 
@@ -130,6 +138,7 @@ export const SupportEngineerPanel = () => {
130
  const [metrics, setMetrics] = useState({});
131
  const [auditLogs, setAuditLogs] = useState([]);
132
  const [users, setUsers] = useState([]);
 
133
  const [demoBookings, setDemoBookings] = useState([]);
134
  const [emailLogs, setEmailLogs] = useState([]);
135
  const [loading, setLoading] = useState(true);
@@ -137,12 +146,16 @@ export const SupportEngineerPanel = () => {
137
  const [activeTab, setActiveTab] = useState(() => {
138
  try {
139
  const params = new URLSearchParams(window.location.search);
140
- return params.get('tab') || 'organizations';
141
  } catch (e) {
142
- return 'organizations';
143
  }
144
  });
145
 
 
 
 
 
146
  // Table sorting states
147
  const [sortOrgCol, setSortOrgCol] = useState('Tenant Name');
148
  const [sortOrgDir, setSortOrgDir] = useState('asc');
@@ -171,13 +184,19 @@ export const SupportEngineerPanel = () => {
171
  const [emailPage, setEmailPage] = useState(1);
172
  const [emailPageSize, setEmailPageSize] = useState(25);
173
 
 
 
 
 
 
 
174
  // Custom Prompt Modal state for editing member roles/orgs (without password)
175
  const [promptModal, setPromptModal] = useState({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null });
176
  const [promptValues, setPromptValues] = useState({});
177
- const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', desc: '', onConfirm: null });
178
 
179
  const closePrompt = () => { setPromptModal({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null }); setPromptValues({}); };
180
- const closeConfirm = () => setConfirmModal({ isOpen: false, title: '', desc: '', onConfirm: null });
181
 
182
  const handlePromptChange = (key, value) => setPromptValues(prev => ({ ...prev, [key]: value }));
183
 
@@ -199,6 +218,7 @@ export const SupportEngineerPanel = () => {
199
  const data = await globalRes.json();
200
  setOrganizations(Array.isArray(data.organizations) ? data.organizations : []);
201
  setMetrics(data.metrics || {});
 
202
  setAuditLogs(Array.isArray(data.audit_logs) ? data.audit_logs : []);
203
  setUsers(Array.isArray(data.users) ? data.users : []);
204
  }
@@ -221,6 +241,103 @@ export const SupportEngineerPanel = () => {
221
  fetchStats();
222
  }, []);
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  const handleOrgSort = (column) => {
225
  if (column === 'Quotas' || column === 'Actions') return;
226
  if (sortOrgCol === column) {
@@ -295,6 +412,7 @@ export const SupportEngineerPanel = () => {
295
  isOpen: true,
296
  title: 'Inspect Tenant Environment',
297
  desc: `Log in as administrator for ${orgName} to troubleshoot customer issue?`,
 
298
  onConfirm: async () => {
299
  try {
300
  const res = await authFetch(`/api/auth/impersonate/${orgId}`, {
@@ -316,7 +434,6 @@ export const SupportEngineerPanel = () => {
316
  });
317
  };
318
 
319
- // Support Engineer can edit role / org assignment, BUT NOT passwords
320
  const handleEditMember = (u) => {
321
  const orgOptions = [
322
  { label: 'None (Global)', value: '' },
@@ -446,62 +563,214 @@ export const SupportEngineerPanel = () => {
446
  <div className="flex flex-col gap-lg animate-fade-in">
447
  {/* Quick Metrics Grid */}
448
  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-md">
449
- <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs">
450
  <div>
451
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Total Tenants</div>
452
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.total_tenants || organizations.length || 0}</div>
453
  </div>
454
- <div className="w-10 h-10 rounded-xl bg-primary/10 text-primary flex items-center justify-center">
455
- <Shield className="w-5 h-5" />
456
  </div>
457
  </div>
458
 
459
- <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs">
460
  <div>
461
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Active Licenses</div>
462
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.active_licenses || 0}</div>
463
  </div>
464
- <div className="w-10 h-10 rounded-xl bg-emerald-500/10 text-emerald-600 flex items-center justify-center">
465
- <CheckCircle2 className="w-5 h-5" />
466
  </div>
467
  </div>
468
 
469
- <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs">
470
  <div>
471
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Global Users</div>
472
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.global_users || users.length || 0}</div>
473
  </div>
474
- <div className="w-10 h-10 rounded-xl bg-purple-500/10 text-purple-600 flex items-center justify-center">
475
  <Users className="w-5 h-5" />
476
  </div>
477
  </div>
478
 
479
- <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs">
480
  <div>
481
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Active Scanners</div>
482
- <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.active_scanners || 0}</div>
483
  </div>
484
- <div className="w-10 h-10 rounded-xl bg-blue-500/10 text-blue-600 flex items-center justify-center">
485
- <Activity className="w-5 h-5" />
486
  </div>
487
  </div>
488
  </div>
489
 
490
- {/* Operational Support Guidance Box */}
491
- <div className="bg-blue-500/5 border border-blue-500/20 rounded-2xl p-5 flex items-start gap-4">
492
- <div className="w-10 h-10 rounded-xl bg-blue-500/10 text-blue-600 flex items-center justify-center shrink-0 mt-0.5">
493
- <Info className="w-5 h-5" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  </div>
495
- <div className="flex-1">
496
- <h3 className="font-bold text-on-surface text-[15px] m-0 mb-1">Support Engineer Scope & Guidelines</h3>
497
- <p className="text-on-surface-variant text-[13px] leading-relaxed m-0">
498
- You are currently in the Support Engineer Operations Console. You have full visibility into organizations, active scan quotas, global members, audit logs, and outbound communication logs to assist client troubleshooting.
499
- </p>
500
- <ul className="text-on-surface-variant text-[12.5px] mt-2 mb-0 pl-4 space-y-1">
501
- <li>• Use the <strong><Key className="w-3.5 h-3.5 inline mr-1 text-primary" /> Inspect</strong> action on any client organization to temporarily log in as their admin and diagnose issue reports.</li>
502
- <li>• You can edit member roles or organization assignments to assist configuration, but password changes are restricted for security.</li>
503
- <li>• Member and Organization creation/deletion are managed exclusively by Super Admins.</li>
504
- </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  </div>
506
  </div>
507
  </div>
@@ -865,6 +1134,89 @@ export const SupportEngineerPanel = () => {
865
  </div>
866
  )}
867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  {/* Prompt Modal for Editing Role/Org */}
869
  <CustomModal
870
  isOpen={promptModal.isOpen}
@@ -918,7 +1270,7 @@ export const SupportEngineerPanel = () => {
918
  footer={
919
  <>
920
  <button onClick={closeConfirm} className="px-4 py-2 text-on-surface-variant hover:bg-surface-container rounded-lg font-bold border-0 bg-transparent cursor-pointer">Cancel</button>
921
- <button onClick={confirmModal.onConfirm} className="px-4 py-2 bg-primary text-white rounded-lg font-bold border-0 cursor-pointer">Confirm</button>
922
  </>
923
  }
924
  />
 
17
  RefreshCw,
18
  CheckCircle2,
19
  AlertTriangle,
20
+ Info,
21
+ Building2,
22
+ CreditCard,
23
+ Server,
24
+ Database,
25
+ HardDrive,
26
+ Eye,
27
+ Download,
28
+ Receipt
29
  } from 'lucide-react';
30
  import { CustomModal } from '../components/CustomModal';
31
 
 
138
  const [metrics, setMetrics] = useState({});
139
  const [auditLogs, setAuditLogs] = useState([]);
140
  const [users, setUsers] = useState([]);
141
+ const [recentPayments, setRecentPayments] = useState([]);
142
  const [demoBookings, setDemoBookings] = useState([]);
143
  const [emailLogs, setEmailLogs] = useState([]);
144
  const [loading, setLoading] = useState(true);
 
146
  const [activeTab, setActiveTab] = useState(() => {
147
  try {
148
  const params = new URLSearchParams(window.location.search);
149
+ return params.get('tab') || 'overview';
150
  } catch (e) {
151
+ return 'overview';
152
  }
153
  });
154
 
155
+ // Billing Sorting State
156
+ const [sortBillCol, setSortBillCol] = useState('Date');
157
+ const [sortBillDir, setSortBillDir] = useState('desc');
158
+
159
  // Table sorting states
160
  const [sortOrgCol, setSortOrgCol] = useState('Tenant Name');
161
  const [sortOrgDir, setSortOrgDir] = useState('asc');
 
184
  const [emailPage, setEmailPage] = useState(1);
185
  const [emailPageSize, setEmailPageSize] = useState(25);
186
 
187
+ // Active Scans & Invoice Modals
188
+ const [activeScansModalOpen, setActiveScansModalOpen] = useState(false);
189
+ const [activeScans, setActiveScans] = useState([]);
190
+ const [fetchingActiveScans, setFetchingActiveScans] = useState(false);
191
+ const [viewInvoice, setViewInvoice] = useState(null);
192
+
193
  // Custom Prompt Modal state for editing member roles/orgs (without password)
194
  const [promptModal, setPromptModal] = useState({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null });
195
  const [promptValues, setPromptValues] = useState({});
196
+ const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
197
 
198
  const closePrompt = () => { setPromptModal({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null }); setPromptValues({}); };
199
+ const closeConfirm = () => setConfirmModal({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
200
 
201
  const handlePromptChange = (key, value) => setPromptValues(prev => ({ ...prev, [key]: value }));
202
 
 
218
  const data = await globalRes.json();
219
  setOrganizations(Array.isArray(data.organizations) ? data.organizations : []);
220
  setMetrics(data.metrics || {});
221
+ setRecentPayments(Array.isArray(data.recent_payments) ? data.recent_payments : []);
222
  setAuditLogs(Array.isArray(data.audit_logs) ? data.audit_logs : []);
223
  setUsers(Array.isArray(data.users) ? data.users : []);
224
  }
 
241
  fetchStats();
242
  }, []);
243
 
244
+ const openActiveScansModal = async () => {
245
+ setActiveScansModalOpen(true);
246
+ setFetchingActiveScans(true);
247
+ try {
248
+ const res = await authFetch('/api/scans/active');
249
+ if (res.ok) {
250
+ const data = await res.json();
251
+ setActiveScans(data.scans || []);
252
+ }
253
+ } catch (err) {
254
+ console.error("Error fetching active scans:", err);
255
+ }
256
+ setFetchingActiveScans(false);
257
+ };
258
+
259
+ const handleKillScan = (scanId) => {
260
+ setConfirmModal({
261
+ isOpen: true,
262
+ title: 'Terminate Scan',
263
+ desc: 'Are you sure you want to forcibly terminate this scan?',
264
+ type: 'error',
265
+ onConfirm: async () => {
266
+ try {
267
+ await authFetch(`/api/scans/${scanId}/terminate`, { method: 'POST' });
268
+ toast.success('Scan terminated successfully');
269
+ openActiveScansModal();
270
+ } catch (err) {
271
+ toast.error('Failed to terminate scan');
272
+ }
273
+ closeConfirm();
274
+ }
275
+ });
276
+ };
277
+
278
+ const handleBillSort = (column) => {
279
+ if (column === 'Invoice') return;
280
+ if (sortBillCol === column) {
281
+ setSortBillDir(sortBillDir === 'asc' ? 'desc' : 'asc');
282
+ } else {
283
+ setSortBillCol(column);
284
+ setSortBillDir('asc');
285
+ }
286
+ };
287
+
288
+ const getSortedBills = () => {
289
+ return [...(recentPayments || [])].sort((a, b) => {
290
+ let aVal, bVal;
291
+ switch (sortBillCol) {
292
+ case 'Date': aVal = new Date(a.created_at).getTime(); bVal = new Date(b.created_at).getTime(); break;
293
+ case 'Organization / User': aVal = a.org_name || ''; bVal = b.org_name || ''; break;
294
+ case 'Tier': aVal = a.tier_id || ''; bVal = b.tier_id || ''; break;
295
+ case 'Amount': aVal = a.amount || 0; bVal = b.amount || 0; break;
296
+ case 'Status': aVal = a.status || ''; bVal = b.status || ''; break;
297
+ default: aVal = new Date(a.created_at).getTime(); bVal = new Date(b.created_at).getTime();
298
+ }
299
+ if (aVal < bVal) return sortBillDir === 'asc' ? -1 : 1;
300
+ if (aVal > bVal) return sortBillDir === 'asc' ? 1 : -1;
301
+ return 0;
302
+ });
303
+ };
304
+
305
+ const handleDownloadInvoice = (payment) => {
306
+ const invoiceHtml = `
307
+ <html>
308
+ <head>
309
+ <title>Invoice - ${payment.id}</title>
310
+ <style>
311
+ body { font-family: sans-serif; padding: 40px; color: #333; }
312
+ h1 { color: #2563eb; margin-bottom: 5px; }
313
+ .header { border-bottom: 2px solid #eee; padding-bottom: 20px; margin-bottom: 20px; }
314
+ .row { display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 14px; }
315
+ .total { font-size: 20px; font-weight: bold; margin-top: 30px; border-top: 2px solid #eee; padding-top: 20px; }
316
+ </style>
317
+ </head>
318
+ <body>
319
+ <div class="header">
320
+ <h1>LarShield</h1>
321
+ <p style="margin: 0; color: #666;">Payment Receipt & Invoice</p>
322
+ </div>
323
+ <div class="row"><strong>Date:</strong> <span>${new Date(payment.created_at).toLocaleString()}</span></div>
324
+ <div class="row"><strong>Organization:</strong> <span>${payment.org_name}</span></div>
325
+ <div class="row"><strong>Email:</strong> <span>${payment.user_email}</span></div>
326
+ <div class="row"><strong>Subscription Tier:</strong> <span><span style="text-transform: capitalize;">${payment.tier_id}</span> Plan</span></div>
327
+ <div class="row"><strong>Status:</strong> <span style="color: ${payment.status === 'successful' ? 'green' : 'red'}">${payment.status.toUpperCase()}</span></div>
328
+ <div class="row total">
329
+ <strong>Total Amount:</strong>
330
+ <span>${payment.currency === 'INR' ? '₹' : '$'}${payment.amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}</span>
331
+ </div>
332
+ <script>window.print();</script>
333
+ </body>
334
+ </html>
335
+ `;
336
+ const blob = new Blob([invoiceHtml], { type: 'text/html' });
337
+ const url = URL.createObjectURL(blob);
338
+ window.open(url, '_blank');
339
+ };
340
+
341
  const handleOrgSort = (column) => {
342
  if (column === 'Quotas' || column === 'Actions') return;
343
  if (sortOrgCol === column) {
 
412
  isOpen: true,
413
  title: 'Inspect Tenant Environment',
414
  desc: `Log in as administrator for ${orgName} to troubleshoot customer issue?`,
415
+ type: 'primary',
416
  onConfirm: async () => {
417
  try {
418
  const res = await authFetch(`/api/auth/impersonate/${orgId}`, {
 
434
  });
435
  };
436
 
 
437
  const handleEditMember = (u) => {
438
  const orgOptions = [
439
  { label: 'None (Global)', value: '' },
 
563
  <div className="flex flex-col gap-lg animate-fade-in">
564
  {/* Quick Metrics Grid */}
565
  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-md">
566
+ <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs hover:shadow-md transition-all group">
567
  <div>
568
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Total Tenants</div>
569
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.total_tenants || organizations.length || 0}</div>
570
  </div>
571
+ <div className="w-10 h-10 rounded-xl bg-primary/10 text-primary flex items-center justify-center group-hover:scale-110 transition-transform">
572
+ <Building2 className="w-5 h-5" />
573
  </div>
574
  </div>
575
 
576
+ <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs hover:shadow-md transition-all group">
577
  <div>
578
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Active Licenses</div>
579
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.active_licenses || 0}</div>
580
  </div>
581
+ <div className="w-10 h-10 rounded-xl bg-emerald-500/10 text-emerald-600 flex items-center justify-center group-hover:scale-110 transition-transform">
582
+ <CreditCard className="w-5 h-5" />
583
  </div>
584
  </div>
585
 
586
+ <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs hover:shadow-md transition-all group">
587
  <div>
588
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Global Users</div>
589
  <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.global_users || users.length || 0}</div>
590
  </div>
591
+ <div className="w-10 h-10 rounded-xl bg-purple-500/10 text-purple-600 flex items-center justify-center group-hover:scale-110 transition-transform">
592
  <Users className="w-5 h-5" />
593
  </div>
594
  </div>
595
 
596
+ <div className="bg-surface-container-lowest border border-outline-variant p-md rounded-2xl flex items-center justify-between shadow-2xs hover:shadow-md transition-all group">
597
  <div>
598
  <div className="text-[12px] font-bold text-on-surface-variant uppercase tracking-wider">Active Scanners</div>
599
+ <div className="text-2xl font-extrabold text-on-surface mt-1">{metrics.active_scanners || 0}<span className="text-sm text-on-surface-variant font-medium">/{metrics.total_scanners || 50}</span></div>
600
  </div>
601
+ <div className="w-10 h-10 rounded-xl bg-blue-500/10 text-blue-600 flex items-center justify-center group-hover:scale-110 transition-transform">
602
+ <Server className="w-5 h-5" />
603
  </div>
604
  </div>
605
  </div>
606
 
607
+ {/* Infrastructure Node & System Health Cards */}
608
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-md">
609
+ <div className="flex flex-col gap-md">
610
+ <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[16.5px]">
611
+ <Activity className="w-4 h-4 text-primary mr-2" /> Node Infrastructure & Health
612
+ </h2>
613
+
614
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl p-md flex flex-col gap-sm shadow-2xs">
615
+ <div className="flex justify-between items-center pb-xs border-b border-outline-variant/50">
616
+ <div className="flex items-center gap-2 text-[13px] font-bold text-on-surface">
617
+ <Database className="w-4 h-4 text-primary" /> Database Cluster Connection Load
618
+ </div>
619
+ <span className="text-[11px] font-bold bg-green-500/10 text-green-600 px-2 py-0.5 rounded border border-green-500/20">
620
+ HEALTHY
621
+ </span>
622
+ </div>
623
+ <div className="flex justify-between items-center text-[12.5px]">
624
+ <span className="text-on-surface-variant font-semibold">Active Connections</span>
625
+ <span className="font-bold text-on-surface">{metrics.db_connections || 14} / 500</span>
626
+ </div>
627
+ </div>
628
+
629
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl p-md flex flex-col gap-sm shadow-2xs">
630
+ <div className="flex justify-between items-center pb-xs border-b border-outline-variant/50">
631
+ <div className="flex items-center gap-2 text-[13px] font-bold text-on-surface">
632
+ <HardDrive className="w-4 h-4 text-primary" /> Celery Workers & Queue Status
633
+ </div>
634
+ <span className={`text-[11px] font-bold px-2 py-0.5 rounded border ${metrics.queue_size > 5 ? 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20' : 'bg-green-500/10 text-green-600 border-green-500/20'}`}>
635
+ {metrics.queue_size > 5 ? 'HEAVY LOAD' : 'NORMAL'}
636
+ </span>
637
+ </div>
638
+ <div className="flex justify-between items-center text-[12.5px]">
639
+ <span className="text-on-surface-variant font-semibold">Queue Size</span>
640
+ <span className="font-bold text-on-surface">{metrics.queue_size || 0} scans pending</span>
641
+ </div>
642
+ </div>
643
+
644
+ <button
645
+ onClick={openActiveScansModal}
646
+ className="w-full bg-surface-container border border-outline-variant text-primary py-2.5 rounded-xl text-[13px] font-bold hover:bg-primary/10 hover:border-primary/30 cursor-pointer transition-colors flex items-center justify-center gap-2 shadow-2xs"
647
+ >
648
+ <span className="material-symbols-outlined text-[18px]">screen_search</span>
649
+ Inspect Active Scans
650
+ </button>
651
  </div>
652
+
653
+ {/* Support Guidance Box */}
654
+ <div className="bg-blue-500/5 border border-blue-500/20 rounded-2xl p-5 flex flex-col justify-between">
655
+ <div>
656
+ <div className="flex items-center gap-2 text-blue-600 mb-2">
657
+ <Info className="w-5 h-5" />
658
+ <h3 className="font-bold text-on-surface text-[15px] m-0">Support Engineer Operating Scope</h3>
659
+ </div>
660
+ <p className="text-on-surface-variant text-[13px] leading-relaxed m-0">
661
+ As a Support Engineer, you have full diagnostic visibility across platform performance metrics, customer environment states, active scans, and transaction logs.
662
+ </p>
663
+ <ul className="text-on-surface-variant text-[12.5px] mt-3 space-y-1.5 pl-4">
664
+ <li>• Use the <strong><Key className="w-3.5 h-3.5 inline text-primary mr-1" /> Inspect Tenant</strong> tool in Organizations tab to troubleshoot tenant issues in real-time.</li>
665
+ <li>• Modify client role assignments or org mappings to resolve onboarding configurations.</li>
666
+ <li>• Member passwords and organization creation/deletion are restricted for administrative security.</li>
667
+ </ul>
668
+ </div>
669
+
670
+ <div className="mt-4 pt-3 border-t border-blue-500/20 flex items-center justify-between text-xs text-on-surface-variant font-medium">
671
+ <span>System Status: Fully Operational</span>
672
+ <span className="text-emerald-600 font-bold flex items-center gap-1">
673
+ <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span> Online
674
+ </span>
675
+ </div>
676
+ </div>
677
+ </div>
678
+
679
+ {/* Global Transaction & Billing History */}
680
+ <div className="flex flex-col gap-md">
681
+ <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[17px] m-0">
682
+ <Receipt className="w-5 h-5 text-primary mr-2" /> Global Transactions & Invoices
683
+ </h2>
684
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm">
685
+ {loading ? (
686
+ <div className="p-10 text-center text-on-surface-variant text-[14px]">Fetching transaction logs...</div>
687
+ ) : (
688
+ <div className="overflow-x-auto max-h-[350px] hide-scrollbar">
689
+ <table className="w-full text-left text-sm border-collapse">
690
+ <thead className="bg-surface-container text-on-surface-variant border-b border-outline-variant sticky top-0 select-none">
691
+ <tr>
692
+ {['Date', 'Organization / User', 'Tier', 'Amount', 'Status', 'Invoice'].map((h, i) => (
693
+ <th
694
+ key={h}
695
+ onClick={() => handleBillSort(h)}
696
+ className={`px-md py-sm font-bold text-[12px] uppercase tracking-wider ${i === 5 ? 'text-right' : ''} ${h !== 'Invoice' ? 'cursor-pointer hover:bg-surface-container-highest transition-colors group' : ''}`}
697
+ >
698
+ <div className={`flex items-center gap-xs ${i === 5 ? 'justify-end' : ''}`}>
699
+ {h}
700
+ </div>
701
+ </th>
702
+ ))}
703
+ </tr>
704
+ </thead>
705
+ <tbody className="divide-y divide-outline-variant">
706
+ {recentPayments.length === 0 ? (
707
+ <tr><td colSpan="6" className="p-10 text-center text-on-surface-variant">No recent transactions.</td></tr>
708
+ ) : getSortedBills().map(p => (
709
+ <tr key={p.id} className="hover:bg-surface-container transition-colors">
710
+ <td className="px-md py-sm text-[13px] text-on-surface-variant">{new Date(p.created_at).toLocaleDateString()}</td>
711
+ <td className="px-md py-sm"><div className="font-bold text-on-surface text-[13px]">{p.org_name}</div></td>
712
+ <td className="px-md py-sm"><span className="text-[12px] font-bold capitalize text-primary bg-primary/10 px-2 py-0.5 rounded border border-primary/20">{p.tier_id}</span></td>
713
+ <td className="px-md py-sm font-bold text-[13.5px] text-on-surface">{p.currency === 'INR' ? '₹' : '$'}{p.amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}</td>
714
+ <td className="px-md py-sm"><div className="flex items-center gap-xs text-[12.5px] font-bold"><span className={`w-2 h-2 rounded-full ${p.status === 'successful' ? 'bg-green-500' : 'bg-error'}`}></span><span className={p.status === 'successful' ? 'text-green-600 dark:text-green-500' : 'text-error'}>{p.status === 'successful' ? 'Success' : 'Failed'}</span></div></td>
715
+ <td className="px-md py-sm text-right">
716
+ <div className="flex items-center justify-end gap-1">
717
+ <button onClick={() => setViewInvoice(p)} className="text-on-surface-variant hover:text-primary transition-colors bg-transparent border-0 cursor-pointer p-1" title="View Invoice">
718
+ <Eye className="w-4 h-4" />
719
+ </button>
720
+ <button onClick={() => handleDownloadInvoice(p)} className="text-on-surface-variant hover:text-primary transition-colors bg-transparent border-0 cursor-pointer p-1" title="Download Invoice">
721
+ <Download className="w-4 h-4" />
722
+ </button>
723
+ </div>
724
+ </td>
725
+ </tr>
726
+ ))}
727
+ </tbody>
728
+ </table>
729
+ </div>
730
+ )}
731
+ </div>
732
+ </div>
733
+
734
+ {/* System Audit Trail Preview */}
735
+ <div className="flex flex-col gap-md">
736
+ <div className="flex justify-between items-center">
737
+ <h2 className="font-headline-sm font-bold text-on-surface flex items-center text-[17px] m-0">
738
+ <Layers className="w-5 h-5 text-primary mr-2" /> Recent System Audit Events
739
+ </h2>
740
+ <button onClick={() => setActiveTab('audit')} className="text-primary font-bold text-[13px] hover:underline bg-transparent border-0 cursor-pointer">
741
+ View All Logs →
742
+ </button>
743
+ </div>
744
+
745
+ <div className="bg-surface-container-lowest border border-outline-variant rounded-2xl overflow-hidden shadow-sm">
746
+ {loading ? (
747
+ <div className="p-10 text-center text-on-surface-variant text-[14px]">Fetching audit logs...</div>
748
+ ) : (
749
+ <div className="overflow-x-auto max-h-[350px] hide-scrollbar">
750
+ <table className="w-full text-left text-sm border-collapse">
751
+ <thead className="bg-surface-container text-on-surface-variant border-b border-outline-variant sticky top-0">
752
+ <tr>
753
+ <th className="px-md py-sm font-bold text-[12px] uppercase tracking-wider">Date & Time</th>
754
+ <th className="px-md py-sm font-bold text-[12px] uppercase tracking-wider">User</th>
755
+ <th className="px-md py-sm font-bold text-[12px] uppercase tracking-wider">Action / Event</th>
756
+ <th className="px-md py-sm font-bold text-[12px] uppercase tracking-wider">Target</th>
757
+ </tr>
758
+ </thead>
759
+ <tbody className="divide-y divide-outline-variant">
760
+ {auditLogs.length === 0 ? (
761
+ <tr><td colSpan="4" className="p-10 text-center text-on-surface-variant">No audit logs found.</td></tr>
762
+ ) : auditLogs.slice(0, 5).map(log => (
763
+ <tr key={log.id} className="hover:bg-surface-container transition-colors">
764
+ <td className="px-md py-sm text-on-surface-variant text-[12.5px]">{new Date(log.timestamp).toLocaleString()}</td>
765
+ <td className="px-md py-sm font-bold text-on-surface text-[13px]">{log.user_email}</td>
766
+ <td className="px-md py-sm font-semibold text-on-surface text-[13px]">{log.action}</td>
767
+ <td className="px-md py-sm font-bold text-[13px] text-on-surface-variant">{log.target_name || log.target_id || '-'}</td>
768
+ </tr>
769
+ ))}
770
+ </tbody>
771
+ </table>
772
+ </div>
773
+ )}
774
  </div>
775
  </div>
776
  </div>
 
1134
  </div>
1135
  )}
1136
 
1137
+ {/* Active Scans Modal */}
1138
+ <CustomModal isOpen={activeScansModalOpen} onClose={() => setActiveScansModalOpen(false)} title="Active Scans Diagnostic">
1139
+ {fetchingActiveScans ? <div className="text-center py-xl font-bold text-on-surface-variant">Loading active scans...</div> : activeScans.length === 0 ? <div className="text-center py-xl font-bold text-on-surface-variant">No active scans running right now.</div> : (
1140
+ <div className="flex flex-col gap-md">
1141
+ {activeScans.map(scan => (
1142
+ <div key={scan.id} className="bg-surface border border-outline-variant rounded-xl p-md flex flex-col sm:flex-row justify-between items-center gap-md">
1143
+ <div>
1144
+ <div className="font-bold text-on-surface">{scan.target_url}</div>
1145
+ <div className="text-xs text-on-surface-variant mt-0.5">Scan ID: {scan.id} | Org ID: {scan.org_id}</div>
1146
+ </div>
1147
+ <button onClick={() => handleKillScan(scan.id)} className="bg-error/10 text-error border border-error/20 py-1.5 px-3 rounded font-bold cursor-pointer hover:bg-error/20 transition-all text-xs">
1148
+ Terminate Scan
1149
+ </button>
1150
+ </div>
1151
+ ))}
1152
+ </div>
1153
+ )}
1154
+ </CustomModal>
1155
+
1156
+ {/* Invoice View Modal */}
1157
+ <CustomModal
1158
+ isOpen={!!viewInvoice}
1159
+ onClose={() => setViewInvoice(null)}
1160
+ title="Invoice Details"
1161
+ footer={
1162
+ <>
1163
+ <button onClick={() => setViewInvoice(null)} className="px-4 py-2 text-on-surface-variant hover:bg-surface-container rounded-lg font-bold border-0 bg-transparent cursor-pointer">Close</button>
1164
+ <button onClick={() => { handleDownloadInvoice(viewInvoice); setViewInvoice(null); }} className="px-4 py-2 bg-primary text-white flex items-center gap-2 rounded-lg font-bold border-0 cursor-pointer">
1165
+ <Download className="w-4 h-4" /> Download PDF
1166
+ </button>
1167
+ </>
1168
+ }
1169
+ >
1170
+ {viewInvoice && (
1171
+ <div className="flex flex-col gap-4 text-on-surface">
1172
+ <div className="flex justify-between items-end border-b border-outline-variant pb-4 mb-2">
1173
+ <div>
1174
+ <h1 className="text-2xl font-bold text-primary m-0">LarShield</h1>
1175
+ <p className="text-on-surface-variant m-0 text-sm mt-1">Payment Receipt & Invoice</p>
1176
+ </div>
1177
+ <div className="text-right">
1178
+ <div className="text-sm font-bold opacity-80 uppercase tracking-wider">Date</div>
1179
+ <div>{new Date(viewInvoice.created_at).toLocaleString()}</div>
1180
+ </div>
1181
+ </div>
1182
+
1183
+ <div className="grid grid-cols-2 gap-4 bg-surface-container-low p-4 rounded-lg">
1184
+ <div>
1185
+ <div className="text-xs text-on-surface-variant font-bold uppercase mb-1">Billed To</div>
1186
+ <div className="font-bold">{viewInvoice.org_name}</div>
1187
+ <div className="text-sm opacity-80">{viewInvoice.user_email}</div>
1188
+ </div>
1189
+ <div className="text-right">
1190
+ <div className="text-xs text-on-surface-variant font-bold uppercase mb-1">Status</div>
1191
+ <div className={`font-bold uppercase ${viewInvoice.status === 'successful' ? 'text-green-600' : 'text-error'}`}>{viewInvoice.status}</div>
1192
+ </div>
1193
+ </div>
1194
+
1195
+ <div className="border border-outline-variant rounded-lg overflow-hidden">
1196
+ <table className="w-full text-left border-collapse text-sm">
1197
+ <thead className="bg-surface-container border-b border-outline-variant">
1198
+ <tr>
1199
+ <th className="p-3 font-bold">Subscription Plan</th>
1200
+ <th className="p-3 font-bold text-right">Amount</th>
1201
+ </tr>
1202
+ </thead>
1203
+ <tbody>
1204
+ <tr>
1205
+ <td className="p-3 font-semibold capitalize">{viewInvoice.tier_id} Plan Subscription</td>
1206
+ <td className="p-3 font-bold text-right">{viewInvoice.currency === 'INR' ? '₹' : '$'}{viewInvoice.amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}</td>
1207
+ </tr>
1208
+ </tbody>
1209
+ </table>
1210
+ </div>
1211
+
1212
+ <div className="flex justify-between items-center bg-primary/10 border border-primary/20 p-4 rounded-lg text-primary font-bold text-lg mt-2">
1213
+ <span>Total Amount Paid</span>
1214
+ <span>{viewInvoice.currency === 'INR' ? '₹' : '$'}{viewInvoice.amount.toLocaleString(undefined, { minimumFractionDigits: 2 })}</span>
1215
+ </div>
1216
+ </div>
1217
+ )}
1218
+ </CustomModal>
1219
+
1220
  {/* Prompt Modal for Editing Role/Org */}
1221
  <CustomModal
1222
  isOpen={promptModal.isOpen}
 
1270
  footer={
1271
  <>
1272
  <button onClick={closeConfirm} className="px-4 py-2 text-on-surface-variant hover:bg-surface-container rounded-lg font-bold border-0 bg-transparent cursor-pointer">Cancel</button>
1273
+ <button onClick={confirmModal.onConfirm} className={`px-4 py-2 rounded-lg font-bold border-0 cursor-pointer text-white ${confirmModal.type === 'error' ? 'bg-error' : 'bg-primary'}`}>Confirm</button>
1274
  </>
1275
  }
1276
  />