Files
I-Fun c649f055d8 feat: initialize Infra Manager with React, Tailwind CSS, and Inertia.js
- Added main application entry point (app.jsx) and bootstrap file for React setup.
- Created Wiki index page (Index.jsx) for documentation and file management.
- Implemented utility functions for formatting and status handling in utils.js.
- Set up Blade template (app.blade.php) for Inertia.js integration.
- Defined API routes for metrics and terminal access in api.php.
- Established console commands for scheduled tasks in console.php.
- Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php.
- Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments.
- Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js.
- Set up Vite for asset bundling and development server in vite.config.js.
2026-04-07 20:29:32 +07:00

139 lines
7.5 KiB
JavaScript

import AppLayout from '@/Layouts/AppLayout';
import { Head, router } from '@inertiajs/react';
import { motion } from 'framer-motion';
import { Shield, AlertTriangle, CheckCircle, TrendingUp, Scan, Lock, Bug } from 'lucide-react';
import { GlassCard, StatCard } from '@/Components/ui/GlassCard';
import { StatusBadge } from '@/Components/ui/StatusBadge';
import { Button } from '@/Components/ui/Button';
import { ProgressBar } from '@/Components/ui/ProgressBar';
import { DataTable } from '@/Components/ui/DataTable';
import { timeAgo, getSecurityGradeColor } from '@/lib/utils';
import { RadarChart, Radar, PolarGrid, PolarAngleAxis, ResponsiveContainer } from 'recharts';
import { cn } from '@/lib/utils';
const radarData = [
{ subject: 'Patching', A: 85 },
{ subject: 'Access Ctrl', A: 72 },
{ subject: 'Encryption', A: 90 },
{ subject: 'Network', A: 65 },
{ subject: 'Monitoring', A: 78 },
{ subject: 'Compliance', A: 60 },
];
function ScoreRing({ score, grade }) {
const color = grade === 'A' ? '#00ff88' : grade === 'B' ? '#00d4ff' : grade === 'C' ? '#ffd700' : grade === 'D' ? '#ff6b35' : '#ff2d55';
const r = 60, circumference = 2 * Math.PI * r;
const dash = (score / 100) * circumference;
return (
<div className="relative inline-flex items-center justify-center">
<svg className="w-40 h-40 -rotate-90">
<circle cx="80" cy="80" r={r} fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="8" />
<circle cx="80" cy="80" r={r} fill="none" stroke={color} strokeWidth="8"
strokeDasharray={`${dash} ${circumference}`}
strokeLinecap="round"
style={{ filter: `drop-shadow(0 0 8px ${color}60)` }}
/>
</svg>
<div className="absolute text-center">
<p className="text-4xl font-bold" style={{ color }}>{score}</p>
<p className="text-xs text-white/40">/ 100</p>
</div>
</div>
);
}
export default function SecurityIndex({ scores, scans, stats }) {
const avgScore = scores?.avg ?? 72;
const grade = avgScore >= 90 ? 'A' : avgScore >= 75 ? 'B' : avgScore >= 60 ? 'C' : avgScore >= 45 ? 'D' : 'F';
const scanColumns = [
{ header: 'Target', accessor: 'target_name', render: row => (
<div>
<p className="text-sm text-white">{row.target_name}</p>
<p className="text-xs text-white/30 capitalize">{row.target_type} · {row.scan_type}</p>
</div>
)},
{ header: 'Score', accessor: 'score', render: row => (
<div className="flex items-center gap-2">
<span className={`text-lg font-bold ${getSecurityGradeColor(row.grade)}`}>{row.grade}</span>
<span className="text-sm text-white/60">{row.score ?? '—'}</span>
</div>
)},
{ header: 'Findings', key: 'findings', render: row => (
<div className="flex items-center gap-1 text-xs">
{row.critical_count > 0 && <span className="px-1.5 py-0.5 rounded bg-red-500/20 text-red-400">{row.critical_count}C</span>}
{row.high_count > 0 && <span className="px-1.5 py-0.5 rounded bg-orange-500/20 text-orange-400">{row.high_count}H</span>}
{row.medium_count > 0 && <span className="px-1.5 py-0.5 rounded bg-yellow-500/20 text-yellow-400">{row.medium_count}M</span>}
{row.low_count > 0 && <span className="px-1.5 py-0.5 rounded bg-blue-500/20 text-blue-400">{row.low_count}L</span>}
</div>
)},
{ header: 'Status', accessor: 'status', render: row => <StatusBadge status={row.status} /> },
{ header: 'Scanned', accessor: 'completed_at', render: row => (
<span className="text-xs text-white/40">{timeAgo(row.completed_at)}</span>
)},
];
return (
<AppLayout title="Security">
<Head title="Security Dashboard" />
<div className="space-y-5">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-white">Cybersecurity Dashboard</h2>
<p className="text-sm text-white/40">Security posture across all infrastructure</p>
</div>
<div className="flex gap-2">
<a href="/security/scans"><Button variant="secondary" icon={Scan}>Scans</Button></a>
<a href="/security/compliance"><Button variant="secondary" icon={CheckCircle}>Compliance</Button></a>
</div>
</div>
{/* Score + Radar */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<GlassCard className="p-6 flex flex-col items-center justify-center gap-3" glow="green">
<h3 className="text-sm font-semibold text-white/60">Overall Security Score</h3>
<ScoreRing score={avgScore} grade={grade} />
<div className="flex items-center gap-2">
<span className={cn('text-3xl font-bold', getSecurityGradeColor(grade))}>Grade {grade}</span>
</div>
<p className="text-xs text-white/30 text-center">Based on {scans?.total ?? 0} scans across infrastructure</p>
</GlassCard>
<GlassCard className="p-5 lg:col-span-2">
<h3 className="text-sm font-semibold text-white mb-1">Security Radar</h3>
<p className="text-xs text-white/30 mb-3">Coverage across security domains</p>
<ResponsiveContainer width="100%" height={220}>
<RadarChart cx="50%" cy="50%" outerRadius={80} data={radarData}>
<PolarGrid stroke="rgba(255,255,255,0.1)" />
<PolarAngleAxis dataKey="subject" tick={{ fill: 'rgba(255,255,255,0.5)', fontSize: 11 }} />
<Radar name="Score" dataKey="A" stroke="#00d4ff" fill="#00d4ff" fillOpacity={0.15} strokeWidth={2} />
</RadarChart>
</ResponsiveContainer>
</GlassCard>
</div>
{/* Stats */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
<StatCard title="Critical" value={stats?.critical ?? 0} color="red" icon={AlertTriangle} />
<StatCard title="High" value={stats?.high ?? 0} color="orange" icon={Bug} />
<StatCard title="Medium" value={stats?.medium ?? 0} color="orange" />
<StatCard title="Low" value={stats?.low ?? 0} color="blue" />
</div>
{/* Scan results table */}
<GlassCard className="p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-semibold text-white">Recent Scans</h3>
<Button icon={Scan} size="sm" onClick={() => router.post('/security/scans')}>Run Scan</Button>
</div>
<DataTable
columns={scanColumns}
data={scans?.data ?? []}
emptyMessage="No scans yet. Run your first security scan."
/>
</GlassCard>
</div>
</AppLayout>
);
}