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>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 19:12:30 +01:00
1 parent fab9add5d5
commit 5b569b6835
108 files changed
+6785 -279

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
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>
</>
);
}