121 lines
4.0 KiB
TypeScript
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>
|
|
);
|
|
}
|