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.
This commit is contained in:
138
resources/js/Pages/Security/Index.jsx
Normal file
138
resources/js/Pages/Security/Index.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user