Projeto bot pontos
This commit is contained in:
103
web/app/(painel)/auditoria/page.tsx
Normal file
103
web/app/(painel)/auditoria/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user