/* eslint-disable react-hooks/set-state-in-effect, react-hooks/exhaustive-deps, react-hooks/immutability */ import { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../components/AuthContext'; import { CustomModal } from '../components/CustomModal'; import { useNavigate } from 'react-router-dom'; import { Shield, Users, Building2, Activity, ShieldAlert, Plus, Edit3, Key, Search, RefreshCw, Lock, Unlock, CheckCircle2, AlertCircle, Sliders, FileText, BarChart3 } from 'lucide-react'; import { ErrorBoundary } from '../components/ErrorBoundary'; const AdminPageContent = () => { const { token } = useAuth(); const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('members'); // 'members', 'orgs', 'scan_access', 'audit' const [users, setUsers] = useState([]); const [organizations, setOrganizations] = useState([]); const [scanAccess, setScanAccess] = useState([]); const [auditLogs, setAuditLogs] = useState([]); const [metrics, setMetrics] = useState(null); const [loading, setLoading] = useState(true); const [syncing, setSyncing] = useState(false); const [error, setError] = useState(''); const [message, setMessage] = useState(''); // Search & Filter States const [userSearch, setUserSearch] = useState(''); const [userRoleFilter, setUserRoleFilter] = useState('all'); const [userOrgFilter, setUserOrgFilter] = useState('all'); const [orgSearch, setOrgSearch] = useState(''); // Sorting States const [sortUserCol, setSortUserCol] = useState('Email'); const [sortUserDir, setSortUserDir] = useState('asc'); const [sortOrgCol, setSortOrgCol] = useState('Tenant Name'); const [sortOrgDir, setSortOrgDir] = useState('asc'); // Modal States const [promptModal, setPromptModal] = useState({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null }); const [promptValues, setPromptValues] = useState({}); const closePrompt = () => { setPromptModal({ ...promptModal, isOpen: false }); setPromptValues({}); }; const handlePromptChange = (key, val) => { setPromptValues(prev => ({ ...prev, [key]: val })); }; const fetchAllData = useCallback(async () => { setSyncing(true); try { const activeToken = localStorage.getItem('wss_token') || token; if (!activeToken) { setLoading(false); setSyncing(false); return; } const [statsRes, accessRes] = await Promise.all([ fetch('/api/global-stats', { headers: { 'Authorization': `Bearer ${activeToken}` } }), fetch('/api/admin/scan-access', { headers: { 'Authorization': `Bearer ${activeToken}` } }) ]); if (statsRes.ok) { const statsData = await statsRes.json(); setMetrics(statsData.metrics || null); setOrganizations(statsData.tenants || []); setUsers(statsData.users || []); setAuditLogs(statsData.audit_logs || []); } if (accessRes.ok) { const accessData = await accessRes.json(); setScanAccess(accessData.controls || []); } } catch (err) { console.error('Failed to load admin data:', err); setError('Could not connect to backend server.'); } finally { setLoading(false); setSyncing(false); } }, [token]); useEffect(() => { fetchAllData(); const interval = setInterval(fetchAllData, 5000); return () => clearInterval(interval); }, [fetchAllData]); // User Actions const handleRoleChange = async (userId, newRole) => { setMessage(''); setError(''); try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/auth/users/${userId}/role`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${activeToken}`, }, body: JSON.stringify({ role: newRole }), }); if (res.ok) { setMessage(`User role successfully updated to ${newRole}.`); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to update role.'); } } catch { setError('Could not connect to API.'); } }; const handleUnlock = async (userId) => { setMessage(''); setError(''); try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/auth/users/${userId}/unlock`, { method: 'POST', headers: { 'Authorization': `Bearer ${activeToken}` }, }); if (res.ok) { setMessage('User account unlocked successfully.'); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to unlock user.'); } } catch { setError('Could not connect to API.'); } }; // Organization Actions const handleProvisionTenant = () => { setPromptValues({ tier: 'none', name: '', admin_email: '' }); setPromptModal({ isOpen: true, title: 'Add New Organization', desc: 'Create a new tenant organization in LarShield.', inputs: [ { key: 'name', label: 'Organization Name', placeholder: 'Enter organization name...' }, { key: 'tier', label: 'Subscription Tier', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Quick', value: 'quick' }, { label: 'Advanced', value: 'advanced' }, { label: 'Deep', value: 'deep' }, { label: 'Enterprise (Custom)', value: 'Enterprise(Custom)' } ] }, { key: 'admin_email', label: 'Admin Email (Optional)', placeholder: 'admin@company.com' } ], onConfirm: async (values) => { if (!values.name || !values.name.trim()) { setError('Organization name is required.'); closePrompt(); return; } try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch('/api/auth/organizations', { method: 'POST', headers: { 'Authorization': `Bearer ${activeToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: values.name, tier: values.tier, admin_email: values.admin_email }) }); if (res.ok) { setMessage('Organization created successfully.'); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to create organization.'); } } catch { setError('Could not connect to API to create organization.'); } closePrompt(); } }); }; const handleEditTenant = (org) => { const rawTier = (org.tier || org.subscription_tier || 'none'); const isCustom = rawTier.toLowerCase().includes('custom') || rawTier.toLowerCase().includes('enterprise'); const initialTier = isCustom ? 'Enterprise(Custom)' : ['none', 'quick', 'advanced', 'deep'].includes(rawTier.toLowerCase()) ? rawTier.toLowerCase() : 'none'; setPromptValues({ name: org.name, tier: initialTier }); setPromptModal({ isOpen: true, title: 'Edit Organization', desc: `Modify subscription tier and name for ${org.name}`, inputs: [ { key: 'name', label: 'Organization Name', placeholder: 'Enter organization name...' }, { key: 'tier', label: 'Subscription Tier', type: 'select', options: [ { label: 'None', value: 'none' }, { label: 'Quick', value: 'quick' }, { label: 'Advanced', value: 'advanced' }, { label: 'Deep', value: 'deep' }, { label: 'Enterprise (Custom)', value: 'Enterprise(Custom)' } ] } ], onConfirm: async (values) => { try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/auth/organizations/${org.id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${activeToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: values.name, tier: values.tier }) }); if (res.ok) { setMessage('Organization updated successfully.'); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to update organization.'); } } catch { setError('Could not connect to API to update organization.'); } closePrompt(); } }); }; const handleAssignScans = (org) => { setPromptValues({ scan_type: 'Deep', count: '1' }); setPromptModal({ isOpen: true, title: 'Assign Custom Scans', desc: `Grant specific scan limits for ${org.name}`, inputs: [ { key: 'scan_type', label: 'Scan Type', type: 'select', options: ['Quick', 'Advanced', 'Deep'] }, { key: 'count', label: 'Number of Scans', placeholder: 'e.g., 5' } ], onConfirm: async (values) => { const addedCount = parseInt(values.count); if (isNaN(addedCount) || addedCount <= 0) { setError('Please enter a valid scan count.'); closePrompt(); return; } try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/auth/organizations/${org.id}/quotas`, { method: 'POST', headers: { 'Authorization': `Bearer ${activeToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ scan_type: values.scan_type, count: addedCount }) }); if (res.ok) { setMessage(`${addedCount} ${values.scan_type} scan(s) assigned to ${org.name}.`); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to assign scans.'); } } catch { setError('Could not connect to API for assigning scans.'); } closePrompt(); } }); }; const handleImpersonate = async (orgId, orgName) => { try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/auth/impersonate/${orgId}`, { method: 'POST', headers: { 'Authorization': `Bearer ${activeToken}` } }); if (res.ok) { const data = await res.json(); localStorage.setItem('original_admin_token', activeToken); localStorage.setItem('wss_token', data.access_token); window.location.href = '/dashboard'; } else { const data = await res.json(); setError(data.message || 'Failed to impersonate organization.'); } } catch { setError('Could not connect to API for impersonation.'); } }; const updateScanAccess = async (scanType, requiredTier, isEnabled) => { setMessage(''); setError(''); try { const activeToken = localStorage.getItem('wss_token') || token; const res = await fetch(`/api/admin/scan-access/${scanType}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${activeToken}`, }, body: JSON.stringify({ required_tier: requiredTier, is_enabled: isEnabled }), }); if (res.ok) { setMessage(`${scanType} scan access rules updated.`); fetchAllData(); } else { const data = await res.json(); setError(data.message || 'Failed to update scan access control.'); } } catch { setError('Could not connect to API.'); } }; // User Filter & Sort Logic const getFilteredUsers = () => { return (users || []).filter(u => { const emailMatch = !userSearch || u.email?.toLowerCase().includes(userSearch.toLowerCase()) || u.org_name?.toLowerCase().includes(userSearch.toLowerCase()); const roleMatch = userRoleFilter === 'all' || u.role === userRoleFilter; const orgMatch = userOrgFilter === 'all' || ( userOrgFilter === 'no_org' ? (!u.org_id || u.org_name?.startsWith('No Org')) : String(u.org_id) === String(userOrgFilter) ); return emailMatch && roleMatch && orgMatch; }); }; const getSortedUsers = () => { const list = getFilteredUsers(); return list.sort((a, b) => { let aVal, bVal; switch (sortUserCol) { case 'Email': aVal = a.email || ''; bVal = b.email || ''; break; case 'Role': aVal = a.role || ''; bVal = b.role || ''; break; case 'Organization': aVal = a.org_name || ''; bVal = b.org_name || ''; break; case 'Status': aVal = a.locked_until ? 1 : 0; bVal = b.locked_until ? 1 : 0; break; default: aVal = a.email || ''; bVal = b.email || ''; } if (aVal < bVal) return sortUserDir === 'asc' ? -1 : 1; if (aVal > bVal) return sortUserDir === 'asc' ? 1 : -1; return 0; }); }; const handleUserSort = (col) => { if (col === 'Actions') return; if (sortUserCol === col) { setSortUserDir(sortUserDir === 'asc' ? 'desc' : 'asc'); } else { setSortUserCol(col); setSortUserDir('asc'); } }; // Org Filter & Sort Logic const getFilteredOrgs = () => { return (organizations || []).filter(org => { return !orgSearch || org.name?.toLowerCase().includes(orgSearch.toLowerCase()) || org.tier?.toLowerCase().includes(orgSearch.toLowerCase()); }); }; const getSortedOrgs = () => { const list = getFilteredOrgs(); return list.sort((a, b) => { let aVal, bVal; switch (sortOrgCol) { case 'Tenant Name': aVal = a.name || ''; bVal = b.name || ''; break; case 'Tier': aVal = a.tier || a.subscription_tier || ''; bVal = b.tier || b.subscription_tier || ''; break; case 'Created': aVal = new Date(a.created || a.created_at || 0).getTime(); bVal = new Date(b.created || b.created_at || 0).getTime(); break; default: aVal = a.name || ''; bVal = b.name || ''; } if (aVal < bVal) return sortOrgDir === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrgDir === 'asc' ? 1 : -1; return 0; }); }; const handleOrgSort = (col) => { if (col === 'Actions' || col === 'Quotas') return; if (sortOrgCol === col) { setSortOrgDir(sortOrgDir === 'asc' ? 'desc' : 'asc'); } else { setSortOrgCol(col); setSortOrgDir('asc'); } }; if (loading) { return (
Global client oversight, organization provisioning, user role management, and system logs.
{m.title}
Manage roles, permissions, and account status across all organizations.
| handleUserSort(col)} className={`px-4 py-3 text-[12px] font-bold uppercase tracking-wider text-on-surface-variant ${col === 'Actions' ? 'text-right' : 'cursor-pointer hover:text-primary'}`} > {col} {sortUserCol === col ? (sortUserDir === 'asc' ? '↑' : '↓') : ''} | ))}||||
|---|---|---|---|---|
| {u.email} | {u.org_name || 'No Org'} |
{u.locked_until ? (
|
{u.locked_until && ( )} | |
| No users match the current search filters. | ||||
Provision tenant accounts, update subscription tiers, and allocate scan quotas.
| handleOrgSort(col)} className={`px-4 py-3 text-[12px] font-bold uppercase tracking-wider text-on-surface-variant ${col === 'Actions' ? 'text-right' : 'cursor-pointer hover:text-primary'}`} > {col} {sortOrgCol === col ? (sortOrgDir === 'asc' ? '↑' : '↓') : ''} | ))}||||
|---|---|---|---|---|
| {org.name} | {org.tier || org.subscription_tier || 'Free'} |
{org.quotas?.filter(q => ['Quick', 'Advanced', 'Deep'].includes(q.scan_type)).map((q, idx) => {
const remaining = q.allocated_count === -1 ? '∞' : Math.max(0, q.allocated_count - (q.used_count || 0));
const style = q.scan_type === 'Deep' ? 'bg-orange-500/10 text-orange-400 border-orange-500/30' :
q.scan_type === 'Advanced' ? 'bg-purple-500/10 text-purple-400 border-purple-500/30' :
q.scan_type === 'Quick' ? 'bg-blue-500/10 text-blue-400 border-blue-500/30' :
'bg-emerald-500/10 text-emerald-400 border-emerald-500/30';
return (
{q.scan_type}:
{remaining}
);
})}
|
{org.created ? org.created : (org.created_at ? new Date(org.created_at).toLocaleDateString() : 'N/A')} |
|
| No organizations found. | ||||
Configure global subscription tier requirements and enable/disable specific scan engines platform-wide.
Status: {mode.is_enabled ? 'Globally Enabled' : 'Globally Disabled'}
Security actions and administrative audit logs.
| TIMESTAMP | PERFORMED BY | ACTION & DETAILS |
|---|---|---|
| {log.timestamp || 'N/A'} | {log.user_email || log.admin_id || 'System'} | {log.action} |
| No audit logs available. | ||