Skip to content

feat: add audit logging and web dashboard#1

Merged
Codex-v merged 1 commit into
mainfrom
feature/audit-and-dashboard
Apr 13, 2026
Merged

feat: add audit logging and web dashboard#1
Codex-v merged 1 commit into
mainfrom
feature/audit-and-dashboard

Conversation

@Codex-v
Copy link
Copy Markdown
Owner

@Codex-v Codex-v commented Apr 13, 2026

Summary

Add two major features to Claude Memory:

1. Audit Logging System ✅

  • Complete audit trail of all user actions
  • Track who did what and when
  • Filter by user, action, or date
  • Team statistics and activity summaries
  • New endpoints:
    • GET /audit/logs - Get audit logs with filtering
    • GET /audit/stats - Get team statistics
    • GET /audit/user/ - Get user activity

2. Modern Web Dashboard 🎨

  • React 18 + Vite + Tailwind CSS
  • Pages:
    • Dashboard - Overview with stats
    • Audit Logs - Full audit trail with search
    • Team - View all members
    • Memories - Search team decisions
    • Rules - Team standards
  • Features:
    • Real-time statistics
    • Search and filtering
    • Role-based color coding
    • Professional dark theme
    • Responsive design
    • Lucide icons

Files Added (21 new files)

  • memory_server/audit/audit_logger.py - Audit system
  • web/ - Complete React dashboard with:
    • src/pages/ - Dashboard pages
    • src/components/ - Reusable components
    • src/api/ - HTTP client
    • Vite, Tailwind config
  • FEATURES.md - Comprehensive feature documentation

How to Test

# Start server
python memory_server/server_http.py

# Start dashboard (new terminal)
cd web && npm install && npm run dev

Dashboard: http://localhost:3000
Server: http://localhost:8765

- Add audit logger system to track all user actions
- Implement /audit/* endpoints for audit log retrieval
- Create modern React web dashboard with Tailwind CSS
- Add Dashboard, Audit Logs, Team, Memories, and Rules pages
- Implement real-time statistics and activity feeds
- Add search and filtering capabilities
- Dark theme with professional UI/UX
- Use Vite for fast development and builds
- Add comprehensive feature documentation
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an audit logging subsystem on the Python HTTP server and adds a new React + Vite + Tailwind web dashboard for interacting with Claude Memory.

Changes:

  • Add AuditLogger implementation plus new /audit/* endpoints on the HTTP server.
  • Add a new web/ React dashboard (pages for Dashboard, Audit Logs, Team, Memories, Rules) and shared UI components.
  • Add documentation for the new features and web setup.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
memory_server/server_http.py Adds audit module import/init and exposes /audit/logs, /audit/stats, /audit/user/<username> endpoints.
memory_server/audit/audit_logger.py Implements JSONL audit log writer/reader and simple stats/summary helpers.
web/package.json Adds frontend dependencies/scripts for Vite/React/Tailwind and lint command.
web/vite.config.js Configures Vite dev server and API proxy to the Python server.
web/tailwind.config.js Tailwind content scanning and theme color extension.
web/index.html Vite entry HTML for the dashboard app.
web/src/main.jsx React root render entrypoint.
web/src/index.css Tailwind directives + basic global styles.
web/src/api/client.js Axios client with X-API-Key request interceptor.
web/src/App.jsx App shell with auth fetch, layout, sidebar/navbar, and page switching.
web/src/components/Navbar.jsx Top navigation bar UI.
web/src/components/Sidebar.jsx Left navigation / page selection UI.
web/src/components/Card.jsx Reusable stat card component.
web/src/pages/Dashboard.jsx Dashboard page fetching /brief and /audit/stats and rendering stats/activity.
web/src/pages/AuditLogs.jsx Audit log table with client-side filtering.
web/src/pages/Team.jsx Team list page fetching /list-team and rendering member cards.
web/src/pages/Memories.jsx Placeholder UI for memories/search.
web/src/pages/Rules.jsx Placeholder UI for rules.
web/README.md Setup and usage docs for the dashboard.
FEATURES.md Feature documentation/overview including audit + dashboard.
.claude/settings.local.json Updates local Claude settings allowlist commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 23 to 26
# Add scripts to path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
sys.path.insert(0, str(Path(__file__).parent / "audit"))

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid modifying sys.path at runtime to import the audit module. Consider turning memory_server/audit into a proper package (add __init__.py) and importing via a stable module path so imports work consistently in different execution contexts (gunicorn, tests, etc.).

Copilot uses AI. Check for mistakes.
Comment on lines 43 to 47
# Global store
MEMORY_DIR = Path(__file__).parent.parent / ".memory"
store = MemoryStore(str(MEMORY_DIR))
audit_logger = AuditLogger(str(MEMORY_DIR / "audit"))

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Audit logging is declared/initialized here, but the server never actually writes audit entries (no calls to audit_logger.log(...) anywhere). As a result /audit/* will stay empty, which conflicts with the PR description (“automatic logging of all API actions”). Add logging calls in the relevant endpoints/decorators (ideally in require_auth + per-route handlers with action + status) so actions are recorded.

Copilot uses AI. Check for mistakes.
Comment on lines +926 to +934

@app.route("/audit/logs", methods=["GET"])
@require_auth
def get_audit_logs():
"""Get audit logs with optional filtering."""
user_filter = request.args.get("user")
action_filter = request.args.get("action")
limit = int(request.args.get("limit", 100))

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limit is parsed with int(...) without validation; a non-integer value will raise and return 500, and very large values could be used to force large reads/sorts. Handle invalid values gracefully (400) and clamp limit to a reasonable max.

Suggested change
@app.route("/audit/logs", methods=["GET"])
@require_auth
def get_audit_logs():
"""Get audit logs with optional filtering."""
user_filter = request.args.get("user")
action_filter = request.args.get("action")
limit = int(request.args.get("limit", 100))
MAX_AUDIT_LOG_LIMIT = 1000
@app.route("/audit/logs", methods=["GET"])
@require_auth
def get_audit_logs():
"""Get audit logs with optional filtering."""
user_filter = request.args.get("user")
action_filter = request.args.get("action")
limit_raw = request.args.get("limit", "100")
try:
limit = int(limit_raw)
except (TypeError, ValueError):
return jsonify({"error": "Invalid 'limit' parameter; must be an integer"}), 400
if limit < 1:
return jsonify({"error": "Invalid 'limit' parameter; must be >= 1"}), 400
limit = min(limit, MAX_AUDIT_LOG_LIMIT)

Copilot uses AI. Check for mistakes.
Comment thread web/src/App.jsx
Comment on lines +22 to +30
const fetchCurrentUser = async () => {
try {
const response = await api.get('/whoami')
setCurrentUser(response.data)
setLoading(false)
} catch (error) {
console.error('Failed to fetch user:', error)
setLoading(false)
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /whoami API returns username (and not name), but the app stores the raw response and downstream components render user.name. This will render “undefined” and can break UI assumptions; normalize the response in fetchCurrentUser (e.g., map username to a name field) or update components to use username.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +48
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card icon={Users} label="Team Members" value={stats?.team_count || 0} />
<Card icon={FileText} label="Memories" value={stats?.memories_count || 0} />
<Card icon={Lock} label="Rules" value={stats?.rules_count || 0} />
<Card icon={Activity} label="Sessions" value={stats?.sessions_count || 0} />
</div>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /brief endpoint returns a “brief” payload (user, roles, team array, rules, sessions, etc.), but the dashboard renders team_count, memories_count, rules_count, sessions_count which are not present in that response. Update the dashboard to derive the displayed values from the actual /brief shape (or add a dedicated stats endpoint that returns these counts).

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +149
logs = []
with open(self.log_file, "r") as f:
logs = [json.loads(line) for line in f if line.strip()]

users = set()
actions = {}

for log in logs:
users.add(log["user"])
action = log["action"]
actions[action] = actions.get(action, 0) + 1

return {
"total_actions": len(logs),
"active_users": sorted(list(users)),
"action_breakdown": actions,
"last_action": logs[-1]["timestamp"] if logs else None,
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_team_stats loads the entire audit log file into memory, which won’t scale as the audit trail grows. Consider streaming the file and aggregating counts incrementally (and/or limiting to a time window) to keep memory usage predictable.

Suggested change
logs = []
with open(self.log_file, "r") as f:
logs = [json.loads(line) for line in f if line.strip()]
users = set()
actions = {}
for log in logs:
users.add(log["user"])
action = log["action"]
actions[action] = actions.get(action, 0) + 1
return {
"total_actions": len(logs),
"active_users": sorted(list(users)),
"action_breakdown": actions,
"last_action": logs[-1]["timestamp"] if logs else None,
total_actions = 0
users = set()
actions = {}
last_action = None
with open(self.log_file, "r") as f:
for line in f:
if not line.strip():
continue
log = json.loads(line)
total_actions += 1
users.add(log["user"])
action = log["action"]
actions[action] = actions.get(action, 0) + 1
last_action = log["timestamp"]
return {
"total_actions": total_actions,
"active_users": sorted(list(users)),
"action_breakdown": actions,
"last_action": last_action,

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +12
<button
onClick={onMenuClick}
className="p-2 hover:bg-slate-700 rounded-lg text-slate-400 hover:text-white"
>
<Menu size={20} />
</button>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Icon-only buttons here (menu toggle, notifications, logout) don’t have accessible names for screen readers. Add aria-label (or visible text) so the controls are usable with assistive tech.

Copilot uses AI. Check for mistakes.
"Bash(git add:*)",
"Bash(python -m black --check . && echo \"✅ All files pass Black formatting check!\")",
"Bash(gh repo:*)",
"Bash(find /d/Claude_memeory -name \"*.py\" -type f ! -path \"*/venv/*\" | xargs wc -l | tail -1)"
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command hardcodes a machine-specific absolute path and appears to have a typo (Claude_memeory). This makes the settings non-portable and likely broken for other developers/CI. Consider removing the absolute path, fixing the spelling, or avoiding committing local-only commands into the repo.

Suggested change
"Bash(find /d/Claude_memeory -name \"*.py\" -type f ! -path \"*/venv/*\" | xargs wc -l | tail -1)"
"Bash(find . -name \"*.py\" -type f ! -path \"*/venv/*\" | xargs wc -l | tail -1)"

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +101
logs = []
with open(self.log_file, "r") as f:
for line in f:
if not line.strip():
continue
entry = json.loads(line)

if user and entry["user"] != user:
continue
if action and entry["action"] != action:
continue

logs.append(entry)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_audit_trail assumes every line is valid JSON; a single partial/corrupt line will raise JSONDecodeError and break the endpoint. Add per-line error handling (skip/bucket invalid lines) so reads are resilient, especially given concurrent writes.

Copilot uses AI. Check for mistakes.

logs = []
with open(self.log_file, "r") as f:
logs = [json.loads(line) for line in f if line.strip()]
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_team_stats parses the entire file with json.loads in a list comprehension without error handling. If a single line is malformed, stats will 500. Consider streaming with try/except per line so stats remain available even if the log contains a bad entry.

Suggested change
logs = [json.loads(line) for line in f if line.strip()]
for line in f:
if not line.strip():
continue
try:
logs.append(json.loads(line))
except json.JSONDecodeError:
continue

Copilot uses AI. Check for mistakes.
@Codex-v Codex-v merged commit 499a38f into main Apr 13, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants