mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-03 08:06:01 +08:00
feat: admin management panel — web UI for users and usage monitoring (#1963)
* feat(web): add admin management panel * fix(web): address admin panel review findings * fix(web): address remaining admin review feedback * refactor(web): type admin api responses * fix(db): aggregate admin usage summary in sql * Add audit logging for admin privileged state-changes Add structured tracing (warn-level) to suspend, activate, delete, and update handlers so that privileged admin actions are recorded with the acting admin's user_id, the action performed, and the target user. Addresses security assessment item #1 from PR review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix PairingStore::new() call in test after staging merge Use PairingStore::new_noop() since the test doesn't need a real DB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(admin): address PR #1963 review feedback - Fix total_jobs semantics: query agent_jobs directly instead of counting via LEFT JOIN on llm_calls (which missed jobs without LLM calls). Fixed in both libSQL and PostgreSQL backends. - Fix showConfirmModal XSS: escape message parameter internally instead of relying on callers to sanitize. - Add explicit ::numeric cast to PG COALESCE(SUM(cost), 0) to prevent integer type inference. - Use info! instead of warn! for successful admin audit events (update, suspend, activate, delete) — warn implies anomaly. - Add missing index on llm_calls.created_at for both PG (V21 migration) and libSQL (incremental migration 21). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address remaining admin panel review follow-ups * fix: address review comments — query consolidation, CSP, docs, security notes - Collapse 4 redundant llm_calls subqueries into single subquery (libsql + pg) - Add WARNING to V21 migration about table lock risk with CONCURRENTLY note - Add performance doc comments on admin_usage_summary full-table scan - Add CSP and noindex meta tags to admin.html - Add JSDoc for showConfirmModal documenting auto-escaping - Add sessionStorage threat model security comment - Add serde(flatten) collision risk doc on AdminUserDetailResponse - Add TODO(#1968) for inline styles migration to CSS custom properties - Add PG parity test stub for admin_usage_summary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: renumber migration from V21/23 to V24 to avoid conflicts with staging Staging added V21 (backfill_conversation_source_channel), V22 (sandbox_restart_params), and V23 (list_workspace_files_escape_like). Renumber our llm_calls_created_at_index migration to V24 in both PG and libSQL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — CSP, logging, validation, dispatch-exempt, tests - Remove 'unsafe-inline' from script-src CSP; move CSP to HTTP response header - Change audit tracing::info! to tracing::debug! (TUI corruption) - Add dispatch-exempt annotation on usage_summary_handler - Add server-side input validation on users_create_handler (name length, email, role) - Rename detailRowHtml to detailRowRawHtml with XSS safety comment - Add real PG integration test for admin_usage_summary with non-zero data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): skip test-only directories in no-panics check Files under `src/**/tests/*.rs` are Rust test sub-modules included behind `#[cfg(test)]` — they are never compiled in production builds. The no-panics checker was flagging `.unwrap()` and `assert!()` in helper functions at module level in these files as production code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(admin): scope cost aggregates to 30d, drop external fonts, flatten detail response Addresses review feedback on #1963: - Scope all llm_calls aggregates to the 30d `since` window so the admin dashboard query is served by `idx_llm_calls_created_at` rather than a full table scan. Drops the unused all-time `total_cost` subquery from both libsql and postgres backends. - Self-contain the admin SPA — remove `fonts.googleapis.com` / `fonts.gstatic.com` link tags from admin.html and tighten the admin CSP to fully same-origin. Typography degrades to the system-font fallback already listed in `font-family`. - Fold `metadata` into `AdminUserInfo` (optional, skip-if-none) and remove the `#[serde(flatten)]` wrapper, eliminating the documented collision risk. - Add regression test asserting `since` actually bounds the LLM aggregates (future `since` should yield zero LLM counts without affecting non-windowed counts). --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>
This commit is contained in:
@@ -206,12 +206,25 @@ def line_test_contexts(lines: list[str]) -> list[bool]:
|
||||
return contexts
|
||||
|
||||
|
||||
def is_test_only_path(path: str) -> bool:
|
||||
"""Return True for files that live inside a test-only directory.
|
||||
|
||||
Files under ``src/**/tests/*.rs`` are Rust test sub-modules, typically
|
||||
included behind ``#[cfg(test)]`` and never compiled in production.
|
||||
Top-level ``tests/*.rs`` integration test files are already outside
|
||||
``src/`` / ``crates/`` and therefore never checked.
|
||||
"""
|
||||
parts = pathlib.PurePosixPath(path).parts
|
||||
return "tests" in parts
|
||||
|
||||
|
||||
def changed_rust_files(base: str, head: str) -> list[pathlib.Path]:
|
||||
output = run_git("diff", "--name-only", f"{base}...{head}", "--", "src", "crates")
|
||||
files = []
|
||||
for line in output.splitlines():
|
||||
if line.endswith(".rs") and (line.startswith("src/") or line.startswith("crates/")):
|
||||
files.append(pathlib.Path(line))
|
||||
if not is_test_only_path(line):
|
||||
files.append(pathlib.Path(line))
|
||||
return files
|
||||
|
||||
|
||||
@@ -363,6 +376,13 @@ class CheckNoPanicsTests(unittest.TestCase):
|
||||
self.assertFalse(contexts[4])
|
||||
self.assertFalse(contexts[5])
|
||||
|
||||
def test_test_only_path_detection(self) -> None:
|
||||
self.assertTrue(is_test_only_path("src/channels/web/tests/multi_tenant.rs"))
|
||||
self.assertTrue(is_test_only_path("crates/foo/src/tests/helpers.rs"))
|
||||
self.assertFalse(is_test_only_path("src/channels/web/mod.rs"))
|
||||
self.assertFalse(is_test_only_path("src/channels/web/test_helpers.rs"))
|
||||
self.assertFalse(is_test_only_path("crates/foo/src/lib.rs"))
|
||||
|
||||
def test_lifetime_annotations_do_not_desync_braces(self) -> None:
|
||||
"""Lifetime annotations ('a, 'static) must not be parsed as char literals.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user