Files
bot-pontos/web/app/(painel)/auditoria/page.tsx
2026-10-23 22:31:02 -03:00

104 lines
3.8 KiB
TypeScript

import Link from "next/link";
import { redirect } from "next/navigation";
import { apiGet } from "@/lib/server-api";
import { fmtDateTime } from "@/lib/format";
import { isRestricted } from "@/lib/access";
import type { AuditLog, Me } from "@/lib/types";
type Search = Promise<Record<string, string | string[] | undefined>>;
const FILTERS = ["from", "to", "source", "event_type", "success", "target_user_id", "actor_account"];
export default async function AuditPage({ searchParams }: { searchParams: Search }) {
const me = await apiGet<Me>("/auth/me");
if (isRestricted(me.account)) redirect("/");
const sp = await searchParams;
const params = new URLSearchParams();
for (const key of [...FILTERS, "before"]) {
const v = sp[key];
if (typeof v === "string" && v) params.set(key, v);
}
const { logs, next_before } = await apiGet<{ logs: AuditLog[]; next_before: number | null }>(`/audit?${params}`);
const value = (key: string) => (typeof sp[key] === "string" ? (sp[key] as string) : "");
const nextParams = new URLSearchParams(params);
if (next_before) nextParams.set("before", String(next_before));
return (
<>
<h1>Auditoria</h1>
<form className="filters" method="get">
<label>
De <input type="date" name="from" defaultValue={value("from")} />
</label>
<label>
Até <input type="date" name="to" defaultValue={value("to")} />
</label>
<select name="source" defaultValue={value("source")}>
<option value="">Todas as origens</option>
<option value="telegram">Telegram</option>
<option value="web">Painel</option>
<option value="worker">Worker</option>
<option value="cli">Terminal</option>
</select>
<input name="event_type" placeholder="Evento (ex.: user_activated)" defaultValue={value("event_type")} />
<select name="success" defaultValue={value("success")}>
<option value="">Qualquer resultado</option>
<option value="true">Sucesso</option>
<option value="false">Falha</option>
</select>
{value("target_user_id") && <input type="hidden" name="target_user_id" value={value("target_user_id")} />}
<button className="btn secondary" type="submit">
Filtrar
</button>
<Link href="/auditoria">Limpar</Link>
</form>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Quando</th>
<th>Origem</th>
<th>Quem</th>
<th>Evento</th>
<th>Alvo</th>
<th>Resultado</th>
<th>Detalhes</th>
</tr>
</thead>
<tbody>
{logs.length === 0 && (
<tr>
<td colSpan={7} className="muted">
Nenhum evento encontrado.
</td>
</tr>
)}
{logs.map((l) => (
<tr key={l.id}>
<td className="nowrap">{fmtDateTime(l.occurred_at)}</td>
<td>{l.source}</td>
<td>{l.actor_username ?? l.actor_user_name ?? "—"}</td>
<td>
<code>{l.event_type}</code>
</td>
<td>{l.target_user_id ? <Link href={`/usuarios/${l.target_user_id}`}>{l.target_user_name}</Link> : "—"}</td>
<td>{l.success ? <span className="badge ok">sucesso</span> : <span className="badge error">falha</span>}</td>
<td className="small">
{Object.keys(l.details).length > 0 && <code className="details">{JSON.stringify(l.details)}</code>}
</td>
</tr>
))}
</tbody>
</table>
</div>
{next_before && (
<p>
<Link href={`/auditoria?${nextParams}`}>Mais antigos →</Link>
</p>
)}
</>
);
}