import { useState, useEffect, useCallback } from 'react';
import { useAuth } from '../components/AuthContext';
import { Shield, Activity, Users, Globe, Lock, ShieldAlert, ArrowLeft, BarChart3, PieChart as PieChartIcon } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell, Legend, LabelList, ComposedChart } from 'recharts';
const SEVERITY_COLORS = ['#EF4444', '#F97316', '#EAB308', '#3B82F6']; // Critical, High, Medium, Low
const SCAN_TYPE_COLORS = ['#3B82F6', '#8B5CF6', '#10B981', '#F59E0B'];
const CustomChartTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
{label &&
{label}
}
{payload.map((item, index) => (
{item.name || item.dataKey} : {item.value}
))}
);
}
return null;
};
export const OrganizationPage = () => {
const { user, token, loading: authLoading } = useAuth();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [summaryData, setSummaryData] = useState(null);
const [scanHistory, setScanHistory] = useState([]);
const [filterOrg, setFilterOrg] = useState('All');
const [filterWebsite, setFilterWebsite] = useState('All');
const getToken = useCallback(() => localStorage.getItem('wss_token') || localStorage.getItem('wss_token') || token, [token]);
const fetchData = useCallback(async () => {
try {
const activeToken = getToken();
if (!activeToken) {
setLoading(false);
return;
}
const [summaryRes, historyRes] = await Promise.all([
fetch('/api/vulnerabilities/summary?global=true', { headers: { 'Authorization': `Bearer ${activeToken}` } }),
fetch('/api/scans/history?global=true&limit=100', { headers: { 'Authorization': `Bearer ${activeToken}` } })
]);
if (!summaryRes.ok || !historyRes.ok) return;
const summaryJson = await summaryRes.json();
const historyJson = await historyRes.json();
setSummaryData(summaryJson.summary);
setScanHistory(historyJson.scans || []);
} catch (err) {
console.error("Failed to fetch organization data:", err);
} finally {
setLoading(false);
}
}, [getToken]);
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 5000); // Polling real-time data every 5s
return () => clearInterval(interval);
}, [fetchData]);
if (loading || authLoading) {
return (
);
}
// Extract Unique Orgs & Websites for filters
const uniqueOrgs = [...new Set(scanHistory.map(s => s.org_name).filter(Boolean))];
const uniqueWebsites = [...new Set(scanHistory.map(s => s.target_url).filter(Boolean))];
// Apply Filters
const filteredScans = scanHistory
.filter(scan => filterOrg === 'All' || scan.org_name === filterOrg)
.filter(scan => filterWebsite === 'All' || scan.target_url === filterWebsite);
// Derive metrics dynamically from filteredScans
const completedScans = filteredScans.filter(s => s.status === 'completed' && s.security_score !== null);
const score = completedScans.length > 0
? Math.round(completedScans.reduce((sum, s) => sum + s.security_score, 0) / completedScans.length)
: null;
const totalVulnerabilities = filteredScans.reduce((sum, s) => {
const vc = s.vulnerabilities_count;
return sum + (vc ? (vc.critical + vc.high + vc.medium + vc.low) : (s.total_vulnerabilities || 0));
}, 0);
// Active Assets (unique target URLs)
const activeAssets = new Set(filteredScans.map(s => s.target_url).filter(Boolean)).size;
// Vulnerability Distribution Pie Chart Data
const vulnCounts = filteredScans.reduce((acc, s) => {
if (s.vulnerabilities_count) {
acc.critical += s.vulnerabilities_count.critical || 0;
acc.high += s.vulnerabilities_count.high || 0;
acc.medium += s.vulnerabilities_count.medium || 0;
acc.low += s.vulnerabilities_count.low || 0;
}
return acc;
}, { critical: 0, high: 0, medium: 0, low: 0 });
const vulnerabilityTypes = [
{ name: 'Critical', value: vulnCounts.critical },
{ name: 'High', value: vulnCounts.high },
{ name: 'Medium', value: vulnCounts.medium },
{ name: 'Low', value: vulnCounts.low },
].filter(v => v.value > 0);
if (vulnerabilityTypes.length === 0) {
vulnerabilityTypes.push({ name: 'Clean / No Risks', value: 1 });
}
// Risk Score Trend (Last 10 Scans)
const riskTrendData = [...filteredScans]
.filter(s => s.started_at)
.sort((a, b) => new Date(a.started_at) - new Date(b.started_at))
.slice(-10)
.map((scan, index) => {
const totalVulns = scan.vulnerabilities_count
? (scan.vulnerabilities_count.critical + scan.vulnerabilities_count.high + scan.vulnerabilities_count.medium + scan.vulnerabilities_count.low)
: (scan.total_vulnerabilities || 0);
return {
name: scan.target_url ? scan.target_url.replace('https://', '').replace('http://', '').replace(/\/$/, '') : `Scan ${index + 1}`,
securityScore: Math.round(scan.security_score ?? 100),
vulnerabilities: totalVulns
};
});
// Group Scans by Month for Bar Chart
const monthsData = {};
filteredScans.forEach(scan => {
const d = new Date(scan.started_at || scan.created_at);
if (isNaN(d.getTime())) return;
const month = d.toLocaleString('en-US', { month: 'short', year: 'numeric' });
if (!monthsData[month]) monthsData[month] = { month, scans: 0, issues: 0 };
monthsData[month].scans += 1;
const totalVulns = scan.vulnerabilities_count
? (scan.vulnerabilities_count.critical + scan.vulnerabilities_count.high + scan.vulnerabilities_count.medium + scan.vulnerabilities_count.low)
: (scan.total_vulnerabilities || 0);
monthsData[month].issues += totalVulns;
});
const scanHistoryChartData = Object.values(monthsData);
// Top Vulnerability Categories Chart Data
const categoriesData = Object.entries(summaryData?.by_category || {})
.map(([cat, count]) => ({ category: cat || 'General Security', count }))
.sort((a, b) => b.count - a.count)
.slice(0, 6);
// Extract Unique Orgs & Websites for filters is moved up.
// Scan Types Breakdown Chart Data (Filtered)
const scanTypeCounts = {};
filteredScans.forEach(scan => {
const type = scan.scan_type ? (scan.scan_type.charAt(0).toUpperCase() + scan.scan_type.slice(1)) + ' Scan' : 'Advanced Scan';
scanTypeCounts[type] = (scanTypeCounts[type] || 0) + 1;
});
const scanTypeChartData = Object.entries(scanTypeCounts).map(([name, value]) => ({ name, value }));
return (
{user?.role === 'support_engineer' ? (
<>Support Engineer Operations >
) : (
<>LarShield Global Management >
)}
{user?.role === 'support_engineer'
? 'Client environment inspection, troubleshooting assistance, and system logs.'
: 'Centralized oversight for all client organizations, scans, and security nodes.'}
Sync Metrics
navigate(user?.role === 'support_engineer' ? '/support' : '/super-admin')} className="flex items-center px-3.5 py-1.5 bg-surface-container border border-outline-variant text-on-surface rounded-lg hover:bg-surface-container-high transition-colors font-bold text-[12.5px] cursor-pointer shadow-2xs">
Global Management
navigate('/super-admin/logs')} className="flex items-center px-3.5 py-1.5 bg-surface-container border border-outline-variant text-on-surface rounded-lg hover:bg-surface-container-high transition-colors font-bold text-[12.5px] cursor-pointer shadow-2xs">
Logs & Threats
navigate(-1)} className="flex items-center px-4 py-1.5 bg-primary text-white rounded-lg hover:brightness-110 transition-all font-bold text-[13px] border-0 cursor-pointer shadow-sm">
Back
{/* Top Metrics Grid */}
{[
{ title: 'SECURITY SCORE', value: score !== null ? `${score}/100` : 'N/A', icon: Shield, color: score === null ? 'text-slate-400' : score > 80 ? 'text-green-500' : score > 50 ? 'text-orange-500' : 'text-error', bg: score === null ? 'bg-slate-500/10 border-slate-500/20' : score > 80 ? 'bg-green-500/10 border-green-500/20' : score > 50 ? 'bg-orange-500/10 border-orange-500/20' : 'bg-error/10 border-error/20' },
{ title: 'ACTIVE ASSETS', value: activeAssets.toString(), icon: Globe, color: 'text-blue-500', bg: 'bg-blue-500/10 border-blue-500/20' },
{ title: 'TOTAL SCANS', value: filteredScans.length.toString(), icon: Activity, color: 'text-purple-500', bg: 'bg-purple-500/10 border-purple-500/20' },
{ title: 'OPEN RISKS', value: totalVulnerabilities.toString(), icon: ShieldAlert, color: totalVulnerabilities > 0 ? 'text-orange-500' : 'text-green-500', bg: totalVulnerabilities > 0 ? 'bg-orange-500/10 border-orange-500/20' : 'bg-green-500/10 border-green-500/20' }
].map((metric, i) => (
{metric.title}
{metric.value}
))}
{/* Scan Engine Distribution */}
{scanTypeChartData.length > 0 && (
Scan Mode Distribution
setFilterOrg(e.target.value)}
className="bg-surface-container border border-outline-variant text-on-surface rounded-lg px-3 py-1.5 text-sm font-bold outline-none"
>
All Organizations
{uniqueOrgs.map(org => {org} )}
setFilterWebsite(e.target.value)}
className="bg-surface-container border border-outline-variant text-on-surface rounded-lg px-3 py-1.5 text-sm font-bold outline-none"
>
All Websites
{uniqueWebsites.map(web => {web.replace(/^https?:\/\//, '')} )}
`${name} ${(percent * 100).toFixed(0)}%`}
>
{scanTypeChartData.map((entry, index) => (
|
))}
} />
)}
{/* Row 1: Charts Grid */}
{/* Risk Trend Line Chart */}
Security Score Trend (Recent {riskTrendData.length} Scans)
{riskTrendData.length > 0 ? (
} />
) : (
No scan history available.
)}
{/* Vulnerability Distribution Bar Chart */}
Severity Level Breakdown
} />
{vulnerabilityTypes.map((entry, index) => (
|
))}
{/* Row 2: Secondary Charts Grid */}
{/* Scan Frequency vs Issues Found */}
Monthly Scans vs Issues Found
{scanHistoryChartData.length > 0 ? (
} />
) : (
No scan history available.
)}
{/* Top Vulnerability Categories */}
Top Vulnerability Categories (OWASP)
{categoriesData.length > 0 ? (
} />
) : (
No category breakdown available.
)}
);
};