mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-18 08:14:06 +00:00
feat(admin): merged activity feed with filters and export
DocsGPT keeps three append-only journals -- auth_events, device_audit_log and guardrail_events -- each readable only on its own terms, and the second of those had an admin endpoint that no UI ever called. An operator reviewing an incident wants one timeline, not three. Adds GET /api/admin/activity: all three journals projected onto a common row shape and merged, ordered newest first, with facets for journal, category, event name, actor, affected user, a time window and free-text search over the detail payload. A category filter that only one journal can satisfy drops the others from the union rather than scanning and discarding them. GET /api/admin/activity/events returns the distinct (event, category) pairs the instance has actually recorded, so the filter UI can offer real choices instead of asking an operator to type "oidc_login_denied" from memory. GET /api/admin/activity/export streams the same filtered feed as CSV or NDJSON. Streamed rather than buffered, and capped, so a compliance export of a busy instance is bounded work. guardrail_events.api_key (a raw agent key) and matched_value (unredacted source text) are excluded from the projection, mirroring the exclusion the per-agent guardrail view already makes. Admin-gating is not a reason to widen what a list response carries.
This commit is contained in:
1 parent
57b9034f2d
commit
92c05089c7
8 files changed
+1096
-55
No files matched your search
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from docsgpt.api.audit import EVENT_CATEGORIES, category_for, record_event
|
||||
from docsgpt.api.audit import ACTIVITY_CATEGORIES, category_for, record_event
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -102,5 +102,22 @@ class TestCategories:
|
||||
def test_unknown_event_is_other(self):
|
||||
assert category_for("something_new") == "other"
|
||||
|
||||
def test_every_category_is_declared(self):
|
||||
assert "other" in EVENT_CATEGORIES
|
||||
@pytest.mark.parametrize(
|
||||
"event,expected",
|
||||
[("device.run_command", "device"), ("guardrail.input", "safety")],
|
||||
)
|
||||
def test_merged_feed_namespaces(self, event, expected):
|
||||
assert category_for(event) == expected
|
||||
|
||||
def test_every_produced_category_is_declared(self):
|
||||
assert "other" in ACTIVITY_CATEGORIES
|
||||
for event in (
|
||||
"oidc_login",
|
||||
"role_granted",
|
||||
"quota_policy_set",
|
||||
"source.deleted",
|
||||
"device.run_command",
|
||||
"guardrail.input",
|
||||
"mystery",
|
||||
):
|
||||
assert category_for(event) in ACTIVITY_CATEGORIES
|
||||
Reference in new issue
Block a user