Skip to content

Audit Log — Admin Activity Trail for Compliance

Purpose

This page explains how to use Audit Log to monitor who did what in the HUPH dashboard. Every important admin action (login, FAQ edit, document upload, conversation takeover, role change, etc.) is automatically recorded with full context: user, timestamp, IP, device, browser.

Use audit log for: - Incident investigation — who changed/deleted what - Compliance — regulatory data access logging requirements - Security review — detect suspicious logins or RBAC bypass - Training — understand counselor activity patterns for coaching

Prerequisites

  • Role super_admin or system_admin
  • Access to https://admin.huph.val.id/audit

Concept: event categories

Category Example actions
Authentication user.login, user.logout, api.auth.pass
Content kb.upload, kb.delete, faq.update, faq.create
Conversation conversation.takeover, conversation.return_to_bot, conversation.language.override
Config cluster.create, cluster.update, cluster.delete, rule.update
Admin user.create, user.role_change, user.deactivate
RBAC rbac.deny.dashboard_counselor (logged when unauthorized access attempts happen)

Each row captures: - Admin user (who performed) - Action (what they did) - Resource type + ID (the touched object) - Details (JSONB with before/after payload) - IP address, User-Agent, Device type, OS, Browser - Timestamp

Steps

1. Open audit log

Sidebar: ADMIN → Audit Log (or /audit).

Page shows a filtered table:

Text Only
 ┌─ Audit Log ────────────────────────────────────────┐
 │ Filter: [User ▾] [Action ▾] [Date range]  [Search] │
 │                                                    │
 │ Time       User          Action          Resource  │
 │ 09:42      Siti (C-CBT)  faq.update      faq-abc   │
 │ 09:38      Budi (Admin)  kb.upload       doc-def   │
 │ 09:30      Siti (C-CBT)  conversation.   conv-xyz  │
 │                          takeover                   │
 │ ...                                                │
 └────────────────────────────────────────────────────┘

2. Filter by use case

Filter by user → see all activity of one admin (e.g., onboarding new hire for coaching, or investigating staff)

Filter by action → e.g., kb.delete to review document deletions in KB

Filter by date range → e.g., last 7 days for weekly review

Filter by resource → click a row → side panel shows all actions on that resource (e.g., full edit history of one FAQ)

3. View single event detail

Click a row → side panel:

Text Only
 ┌─ Event Detail ─────────────────────────────────────┐
 │ Time: 2026-04-23 09:42:15 UTC                      │
 │ User: Siti Aminah (marketing_counselor / CBT)     │
 │ Action: faq.update                                 │
 │ Resource: faq-local #abc-123                       │
 │                                                    │
 │ Before:                                            │
 │   answer: "Untuk Informatika, hubungi CIST..."     │
 │ After:                                             │
 │   answer: "Untuk Informatika, hubungi CIST:       +│
 │           *085196105421* (WhatsApp)..."            │
 │                                                    │
 │ IP: 10.0.12.4  •  Device: Mobile (Android)         │
 │ Browser: Chrome 124  •  OS: Android 14             │
 │                                                    │
 │ [Copy to clipboard]  [Export JSON]                 │
 └────────────────────────────────────────────────────┘

3b. Before/After diff — Phase 4 (SHIPPED 2026-04-24)

Rows that mutate config (FAQ, cluster, persona, escalation rule, audience rule, guidance rule, chatbot config) now include two JSON snapshots in the expanded view:

  • Before (rose panel) — state of the resource prior to the action
  • After (emerald panel) — state after the action

Fields that didn't change are identical across both panels; changed fields differ. For create/delete events, one panel shows a placeholder: - — (no prior state) for create - — (deleted) for delete

Click the chevron on the right of a row to expand/collapse.

3c. "Recent Config Changes" widget on Dashboard

The main dashboard (after login) now surfaces the last 5 config actions at the bottom. Each entry shows the action, actor, list of changed field names, and a relative timestamp.

Purpose: operators can notice anomalies without having to open /audit proactively. If super_admin opens the dashboard in the morning and sees "faq.bulk_deactivate by Budi 3 hours ago — 47 rows", that jumps out immediately — no need to remember to investigate.

Clicking an entry navigates to /audit with the resource_id filter pre-applied.

4. Export for reports

Click Download CSV (top right). File includes all active filters. Good for: - Monthly report to compliance officer - Security team investigation - Backup before purge

Automatic retention

Audit log retains at least 90 days via retention_sweep cron job. For longer retention, export CSV periodically.

5. RBAC denies — security signal

Filter action = rbac.deny.* shows every unauthorized access attempt. Normally low (< 10/month). If spiking: - User may have received internal link without proper role - Or RBAC system is being probed (repeated attempts) - Check source IP — if from same IP repeatedly, could be bot/scanner

Example scenarios

Weekly compliance review. Senior admin filters action = kb.delete for last 7 days. Review each deletion — was there a valid reason? If unclear, ask the actor via chat.

"Missing FAQ" investigation. Marketing complains: "The June scholarship FAQ — where did it go?". Admin opens audit log, filters by resource type = faq-local and searches keyword "scholarship" in details. Finds action faq.delete by whom, when, why (details before contains old content — can manually restore).

New admin quality check. Just hired a marketing_admin. After 2 weeks, super_admin filters by user = "Budi Baru". Reviews what he's been doing — good or needs coaching.

Security alert. Notification mentions 20x failed logins from one IP. Admin opens audit log, filters action = user.login_failed AND IP = . Verify pattern — block IP at firewall if suspicious.

Troubleshooting

Event doesn't appear despite confidence it happened. Symptom: perform an action, but audit log doesn't record it. Cause 1: the action isn't yet instrumented (not ALL actions are audited, only sensitive ones). Cause 2: async logger failed in background. Fix: check huph-api logs (docker logs huph-api | grep audit) — escalate to developer if error.

CSV export "too many rows" error. Symptom: export > 10k rows fails. Fix: tighten filters (date range, user) then export in batches.

Detail JSON unparseable. Symptom: event detail panel empty or "Invalid JSON". Cause: resource model changed since event was logged, old format. Fix: view raw JSON from database directly (check with developer).

See also

Feature Coverage widget (super_admin only)

The Dashboard for super_admin now shows a Feature Coverage card at the bottom. This card aggregates audit_log data, showing how actively the 15 main admin features were used in the last 7 / 30 / 90 days.

How to read it

  • Green status dot — feature was used in the selected time window
  • Rose (red) status dot — feature has zero activity. Think:
    • Was this feature genuinely unused (e.g., Campaigns while there are no active campaigns)?
    • Or is the feature undiscoverable and the team doesn't know it exists?
    • For the second case, update the K1 PageHelp banner on that page or add an operator nudge
  • "Other (unmapped)" — a new action that hasn't been grouped into any domain. Hover to see the action list → developer needs to update DOMAIN_MAP in apps/api/src/routes/adminUsageStats.ts

Time range

  • 7d — this week's activity (good for weekly check-in)
  • 30d — default; monthly activity (good for monthly review)
  • 90d — quarterly view (good for "was this feature used at least once in 3 months?")

Per-user detail

This widget aggregates ONLY per feature. For per-user detail (who did what when), use the regular /audit page with user + action filters.

Why super_admin only

Feature Coverage shows usage patterns across the entire admin team. This data is sensitive (indirectly implicates feature owners who aren't active). The widget is scoped to super_admin only to prevent it becoming pressure tooling across roles.