Files
Investidor-V2/resources/js/pages/profiles/audit.tsx
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

121 lines
4.0 KiB
TypeScript

import { Head, useForm } from '@inertiajs/react';
import ProfileNavigation from '@/components/profile-navigation';
import {
button,
control,
Pagination,
type Paginated,
} from '@/pages/management/shared';
type Event = {
id: string;
action: string;
actor_user_id: number | null;
entity_type: string;
source_system: string;
occurred_at: string;
};
export default function Audit({
records,
filters,
}: {
records: Paginated<Event>;
filters: Record<string, string>;
}) {
const form = useForm({
actor_id: filters.actor_id ?? '',
action: filters.action ?? '',
record_id: filters.record_id ?? '',
from: filters.from ?? '',
until: filters.until ?? '',
});
const search = new URLSearchParams(
Object.entries(form.data).filter(([, value]) => value !== ''),
);
search.set('format', 'json');
return (
<main className="space-y-6 p-6">
<Head title="Auditoria de acessos" />
<ProfileNavigation />
<h1 className="text-2xl font-semibold">
Auditoria de acessos e decisões
</h1>
<p>
Consulta somente de leitura. A exportação contém metadados; não
inclui credenciais, documentos nem snapshots privados
decifrados. Cada exportação apresenta o limite aplicado.
</p>
<form
className="grid gap-3 sm:grid-cols-3"
onSubmit={(e) => {
e.preventDefault();
form.get('/perfis/auditoria');
}}
>
{(
[
'actor_id',
'action',
'record_id',
'from',
'until',
] as const
).map((field) => (
<label key={field}>
{field}
<input
className={control}
type={
field === 'from' || field === 'until'
? 'datetime-local'
: 'text'
}
value={form.data[field]}
onChange={(e) =>
form.setData(field, e.target.value)
}
/>
</label>
))}
<button className={button}>Filtrar</button>
</form>
{Object.values(form.errors).map((error) => (
<p role="alert" key={error}>
{error}
</p>
))}
<a className="underline" href={`/perfis/auditoria?${search}`}>
Exportar metadados JSON, até 1.000 eventos
</a>
<div className="overflow-auto">
<table className="w-full">
<thead>
<tr>
<th>Data UTC</th>
<th>Conta</th>
<th>Ação</th>
<th>Recurso</th>
<th>Origem</th>
</tr>
</thead>
<tbody>
{records.data.map((e) => (
<tr key={e.id}>
<td>{e.occurred_at}</td>
<td>{e.actor_user_id}</td>
<td>{e.action}</td>
<td>{e.entity_type}</td>
<td>{e.source_system}</td>
</tr>
))}
</tbody>
</table>
</div>
{!records.data.length && (
<p>Nenhum evento corresponde aos filtros.</p>
)}
<Pagination page={records} />
</main>
);
}