Files
SkinbaseNova/resources/js/Pages/Admin/System/SecurityReportShow.jsx
T

178 lines
8.0 KiB
React

import React from 'react'
import { Head } from '@inertiajs/react'
import AdminLayout from '../../../Layouts/AdminLayout'
function formatTimestamp(value) {
if (!value) {
return 'Not recorded'
}
return new Intl.DateTimeFormat('en-GB', {
dateStyle: 'medium',
timeStyle: 'medium',
}).format(new Date(value))
}
function JsonPanel({ title, value }) {
return (
<div className="rounded-[28px] border border-white/[0.07] bg-white/[0.02] p-5">
<h2 className="text-sm font-bold uppercase tracking-[0.18em] text-slate-400">{title}</h2>
<pre className="nova-scrollbar mt-4 max-h-[28rem] overflow-auto rounded-2xl bg-slate-950/80 p-4 text-[11px] leading-5 text-slate-200">
{JSON.stringify(value ?? {}, null, 2)}
</pre>
</div>
)
}
function FindingTable({ title, rows, columns }) {
return (
<div className="overflow-hidden rounded-[28px] border border-white/[0.07] bg-white/[0.02]">
<div className="border-b border-white/[0.06] px-5 py-4">
<h2 className="text-sm font-bold uppercase tracking-[0.18em] text-slate-400">{title}</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full min-w-[900px] text-sm">
<thead>
<tr className="border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">
{columns.map((column) => (
<th key={column.key} className="px-5 py-4">{column.label}</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-white/[0.05]">
{rows.length === 0 ? (
<tr>
<td colSpan={columns.length} className="px-5 py-12 text-center text-slate-500">No entries in this section.</td>
</tr>
) : rows.map((row, index) => (
<tr key={`${row.package}-${index}`} className="align-top transition hover:bg-white/[0.025]">
{columns.map((column) => (
<td key={column.key} className="px-5 py-4 text-slate-300 break-words">
{row[column.key] || '—'}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default function SecurityReportShow({ report }) {
return (
<AdminLayout title={`Security Report #${report.id}`} subtitle="Private dependency audit details for administrators only.">
<Head title={`Admin · Security Report #${report.id}`} />
<div className="space-y-6">
<div className="rounded-[28px] border border-white/[0.07] bg-white/[0.02] p-6">
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Status</p>
<p className="mt-2 text-sm text-white">{report.status}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Risk</p>
<p className="mt-2 text-sm text-white">{report.risk_label}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Started</p>
<p className="mt-2 text-sm text-white">{formatTimestamp(report.started_at)}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Finished</p>
<p className="mt-2 text-sm text-white">{formatTimestamp(report.finished_at)}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Triggered by</p>
<p className="mt-2 text-sm text-white">{report.triggered_by || 'Unknown'}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Requested by</p>
<p className="mt-2 text-sm text-white">{report.user ? (report.user.username ? `@${report.user.username}` : report.user.name) : 'System'}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Composer outdated</p>
<p className="mt-2 text-sm text-white">{report.composer_outdated_count}</p>
</div>
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">npm outdated</p>
<p className="mt-2 text-sm text-white">{report.npm_outdated_count}</p>
</div>
</div>
{report.error_message ? (
<div className="mt-6 rounded-2xl border border-rose-400/20 bg-rose-500/10 px-4 py-3 text-sm text-rose-100">
{report.error_message}
</div>
) : null}
<div className="mt-6">
<a href={report.index_url} className="inline-flex rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-medium text-white transition hover:bg-white/[0.08]">
Back to Reports
</a>
</div>
</div>
<div className="grid gap-4 md:grid-cols-5">
<div className="rounded-2xl border border-rose-400/15 bg-rose-500/10 p-5 text-rose-100">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-rose-200/70">Critical</p>
<p className="mt-3 text-3xl font-semibold">{report.total_critical}</p>
</div>
<div className="rounded-2xl border border-orange-400/15 bg-orange-500/10 p-5 text-orange-100">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-orange-200/70">High</p>
<p className="mt-3 text-3xl font-semibold">{report.total_high}</p>
</div>
<div className="rounded-2xl border border-amber-400/15 bg-amber-500/10 p-5 text-amber-100">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-amber-200/70">Medium</p>
<p className="mt-3 text-3xl font-semibold">{report.total_medium}</p>
</div>
<div className="rounded-2xl border border-sky-400/15 bg-sky-500/10 p-5 text-sky-100">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-sky-200/70">Low</p>
<p className="mt-3 text-3xl font-semibold">{report.total_low}</p>
</div>
<div className="rounded-2xl border border-white/[0.07] bg-white/[0.02] p-5 text-white">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Unknown</p>
<p className="mt-3 text-3xl font-semibold">{report.total_unknown}</p>
</div>
</div>
<FindingTable
title="Composer Advisories"
rows={report.composer_advisories ?? []}
columns={[
{ key: 'package', label: 'Package' },
{ key: 'severity', label: 'Severity' },
{ key: 'title', label: 'Title' },
{ key: 'cve', label: 'CVE / Ref' },
{ key: 'affected_versions', label: 'Affected versions' },
{ key: 'link', label: 'Source URL' },
]}
/>
<FindingTable
title="npm Vulnerabilities"
rows={report.npm_vulnerabilities ?? []}
columns={[
{ key: 'package', label: 'Package' },
{ key: 'severity', label: 'Severity' },
{ key: 'title', label: 'Title' },
{ key: 'cve', label: 'CVE' },
{ key: 'range', label: 'Affected range' },
{ key: 'fix_available', label: 'Fix available' },
{ key: 'url', label: 'Source URL' },
]}
/>
<div className="grid gap-6 xl:grid-cols-2">
<JsonPanel title="Composer Audit Raw JSON" value={report.composer_audit} />
<JsonPanel title="npm Audit Raw JSON" value={report.npm_audit} />
<JsonPanel title="Composer Outdated Raw JSON" value={report.composer_outdated} />
<JsonPanel title="npm Outdated Raw JSON" value={report.npm_outdated} />
</div>
</div>
</AdminLayout>
)
}