Files
codecraft/web/src/components/settings/Audit.tsx
T
Richard ZinkandClaude Opus 5.5 5b569b6835 Add security hardening, Ghidra, recreate skill, checkpoints, shells, web search and themes
- Sandbox (bubblewrap) for non-admin shell/git, MCP per-user rights, hard budgets and quotas,
  login rate limiting, audit log and backups
- Ghidra headless decompilation (ARM64 natives built from source), binary_info tool,
  clean room / deep workflow per session
- Recreate skill and commands for analysing and re-building old software
- File checkpoints with rewind/restore, background shells (bash_output, kill_shell)
- web_search tool (DuckDuckGo, SearXNG, Brave, Tavily)
- In-app dialogs and toasts instead of browser dialogs
- 15 base themes (trilium-themes palettes plus Nord, VS Code, GitHub, Catppuccin, One,
  Dracula, Rose Pine, Everforest, Kanagawa) with light/dark/auto switcher and license notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 19:12:30 +01:00

74 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useFetch } from "../../hooks";
import { fmtDate, useT } from "../../i18n";
interface Entry {
ts: number;
user: string;
action: string;
detail: string;
ip: string;
}
/** Admin: audit log and backup download. */
export function Audit() {
const t = useT();
const [filter, setFilter] = useState("");
const { data, reload } = useFetch<Entry[]>(`/audit?limit=500${filter ? `&action=${encodeURIComponent(filter)}` : ""}`);
return (
<>
<div className="panel">
<h3>{t("audit.backup")}</h3>
<div style={{ fontSize: 13, color: "var(--fg3)", marginBottom: 8 }}>{t("audit.backupHint")}</div>
<a className="button primary" href="/api/backup">
{t("audit.download")}
</a>
</div>
<div className="panel">
<h3>
{t("audit.title")}
<span className="spacer" />
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="">{t("audit.all")}</option>
{["login", "permission", "config", "user", "folder", "session", "git", "backup"].map((a) => (
<option key={a} value={a}>
{a}
</option>
))}
</select>
<button className="small" onClick={reload}>
↻
</button>
</h3>
<div style={{ overflowX: "auto" }}>
<table className="grid">
<thead>
<tr>
<th>{t("audit.time")}</th>
<th>{t("stats.user")}</th>
<th>{t("audit.action")}</th>
<th>{t("audit.detail")}</th>
<th>IP</th>
</tr>
</thead>
<tbody>
{data?.map((e, i) => (
<tr key={i} className={e.action.includes("failed") || e.action.includes("blocked") || e.action.includes("reject") ? "audit-warn" : ""}>
<td style={{ whiteSpace: "nowrap" }}>{fmtDate(e.ts)}</td>
<td>{e.user || "–"}</td>
<td>
<code>{e.action}</code>
</td>
<td style={{ wordBreak: "break-word" }}>{e.detail}</td>
<td>{e.ip}</td>
</tr>
))}
</tbody>
</table>
{data?.length === 0 && <div className="empty">–</div>}
</div>
</div>
</>
);
}