feat: audit source operations

This commit is contained in:
2026-07-19 05:51:08 +08:00
parent 808deaba97
commit c6079bbb47
10 changed files with 24 additions and 6 deletions
+2
View File
@@ -10,12 +10,14 @@ export default async function AdminPage({ searchParams }: { searchParams: Promis
const reports = db.prepare("SELECT r.id,r.reason,r.created_at,p.id AS post_id,p.content,u.username FROM reports r JOIN posts p ON p.id=r.post_id LEFT JOIN users u ON u.id=r.reporter_id WHERE r.resolved=0 ORDER BY r.created_at LIMIT 100").all() as any[];
const users = db.prepare("SELECT id,username,role,disabled,created_at FROM users ORDER BY created_at DESC LIMIT 100").all() as any[];
const errors = db.prepare("SELECT scope,message,created_at FROM error_events ORDER BY id DESC LIMIT 30").all() as any[];
const audits = db.prepare("SELECT a.action,a.target_type,a.target_id,a.metadata_json,a.created_at,u.username FROM audit_events a LEFT JOIN users u ON u.id=a.actor_user_id ORDER BY a.id DESC LIMIT 50").all() as any[];
const sources = db.prepare("SELECT s.id,s.name,s.base_url,s.sync_status,s.last_synced_at,s.is_enabled,count(sm.user_id) AS member_count FROM sources s LEFT JOIN source_members sm ON sm.source_id=s.id GROUP BY s.id ORDER BY s.id DESC").all() as any[];
return <><h1></h1>{query.updated && <p></p>}{query.error && <p className="error"></p>}
<section className="card"><h2></h2>{reports.length ? <ul className="job-list">{reports.map((report) => <li key={report.id}><strong> #{report.post_id}</strong> · @{report.username || "已刪除使用者"}<br />{report.reason}<br /><span className="meta">{report.content.slice(0, 180)} · {new Date(report.created_at + "Z").toLocaleString("zh-TW")}</span><div className="row"><form action="/api/admin/moderate" method="post"><input type="hidden" name="action" value="hide-post" /><input type="hidden" name="id" value={report.post_id} /><button className="danger"></button></form><form action="/api/admin/moderate" method="post"><input type="hidden" name="action" value="resolve-report" /><input type="hidden" name="id" value={report.id} /><button></button></form></div></li>)}</ul> : <p className="muted"></p>}</section>
<section className="card"><h2>使</h2><ul className="job-list">{users.map((account) => <li key={account.id}><strong>@{account.username}</strong> · <span className="tag">{account.role}</span> · {account.disabled ? "已停權" : "正常"}<div className="row">{account.id !== user.id && <form action="/api/admin/moderate" method="post"><input type="hidden" name="action" value={account.disabled ? "enable-user" : "disable-user"} /><input type="hidden" name="id" value={account.id} /><button className={account.disabled ? "" : "danger"}>{account.disabled ? "解除停權" : "停權"}</button></form>}<form action="/api/admin/moderate" method="post"><input type="hidden" name="action" value="reset-password" /><input type="hidden" name="id" value={account.id} /><input name="password" type="password" minLength={10} required placeholder="管理員重設密碼" /><button></button></form></div></li>)}</ul></section>
<section className="card"><h2></h2>{failures.length ? <ul className="job-list">{failures.map((item) => <li key={`${item.id}-${item.source_id}`}><strong>{item.name}</strong> #{item.source_id} · {item.kind || "source"} · <span className="tag">{item.status || "error"}</span><br /><span className="error">{item.last_error || "來源處於錯誤狀態"}</span></li>)}</ul> : <p className="muted"></p>}</section>
<section className="card"><h2></h2>{errors.length ? <ul className="job-list">{errors.map((error, index) => <li key={index}><strong>{error.scope}</strong> · <span className="error">{error.message}</span><br /><span className="meta">{new Date(error.created_at + "Z").toLocaleString("zh-TW")}</span></li>)}</ul> : <p className="muted"></p>}</section>
<section className="card"><h2></h2>{audits.length ? <ul className="job-list">{audits.map((item, index) => <li key={index}><strong>{item.action}</strong> · @{item.username || "system"} · {item.target_type} #{item.target_id || "—"}<br /><span className="meta">{new Date(item.created_at + "Z").toLocaleString("zh-TW")}</span></li>)}</ul> : <p className="muted"></p>}</section>
<section className="card"><h2></h2><ul className="job-list">{sources.map((source) => <li key={source.id}><strong>{source.name}</strong> · <span className="tag">{source.is_enabled ? source.sync_status : "disabled"}</span> · {source.member_count}<br /><span className="meta">#{source.id} · {source.base_url} · {source.last_synced_at || "尚未完成"}</span></li>)}</ul></section>
</>;
}