+ {t("msg.dev.audit.load_error", "Error loading logs: {{error}}", {
+ error: errMsg,
+ })}
+
+ );
+ }
+
return (
-
-
-
-
- Audit stream
-
-
- Observe admin actions per tenant
-
-
- ClickHouse-backed feed. Filter by tenant, actor, action, and
- rate-limit status. Enforce admin-only access under /admin.
-
-
-
-
-
-
-
+
+
+
+
+
+ {t("ui.dev.audit.registry.title", "Audit registry")}
+
+
+ {t("ui.dev.audit.title", "Audit Logs")}
+
+
+ {t(
+ "msg.dev.audit.subtitle",
+ "Shows DevFront activity history within current tenant/app scope.",
+ )}
+
+
+
+
+ {t("msg.dev.audit.loaded_count", "Loaded {{count}} rows", {
+ count: logs.length,
+ })}
+
+
+
+
+
+
+
-
-
-
-
-
- Try: tenant:TENANT-12 action:client.*
-
-
-
- {auditFilters.map((filter) => (
-
-
- {filter}
-
- ))}
-
-
- {auditRows.map((row) => (
-
-
{row.action}
-
{row.tenant}
-
{row.actor}
-
-
+
+
+
+ {t("ui.dev.audit.table.time", "Time")}
+
+
+ {t("ui.dev.audit.table.actor", "Actor")}
+
+
+ {t("ui.dev.audit.table.action", "Action")}
+
+
+ {t("ui.dev.audit.table.target", "Target")}
+
+
+ {t("ui.dev.audit.table.status", "Status")}
+
+
+
+
+
+ {logs.length === 0 && (
+
+
- {row.result}
-
-
{row.ts}
-
-
- ))}
-
-
+ {t("msg.dev.audit.empty", "No audit logs found.")}
+
+
+ )}
+ {logs.map((row, index) => {
+ const details = parseDetails(row.details);
+ const actionLabel = details.action || row.event_type;
+ const targetValue = details.target_id || "-";
+ const rowKey = `${row.event_id}-${row.timestamp}-${index}`;
+ const expanded = Boolean(expandedRows[rowKey]);
+ return (
+
+
+
+ {formatDateTime(row.timestamp)}
+
+
+
+ {row.user_id || "-"}
+ {row.user_id ? (
+
+ ) : null}
+
+
+ {actionLabel}
+
+
+ {targetValue}
+ {targetValue !== "-" ? (
+
+ ) : null}
+
+
+
+
+ {row.status}
+
+
+
+
+
+
+ {expanded ? (
+
+
+
+
+
+ Request ID: {formatValue(details.request_id)}
+
+
Method: {formatValue(details.method)}
+
Path: {formatValue(details.path)}
+
+ Tenant: {formatValue(details.tenant_id)}
+
+
+
+
Before: {formatValue(details.before)}
+
After: {formatValue(details.after)}
+
Error: {formatValue(details.error)}
+
+
+
+
+ ) : null}
+
+ );
+ })}
+
+
-
-
-
- Guard rails
-
-
Tenant admin only
-
- Enforce Tenant Admin middleware and admin session TTL before
- surfacing any audit feed. Super Admin role can bypass tenant
- filter when needed.
-
-
-
-
- Export rules
-
-
- Rate-limit sensitive exports
-
-
- Keep export endpoints behind admin-only routes with ClickHouse
- query limits. Log download attempts with IP, role, and tenant
- scope.
-
-
-
-
+ {query.hasNextPage ? (
+
+
+
+ ) : null}
+
+
);
}
diff --git a/devfront/src/features/clients/ClientConsentsPage.tsx b/devfront/src/features/clients/ClientConsentsPage.tsx
index c6f3d108..56cb764b 100644
--- a/devfront/src/features/clients/ClientConsentsPage.tsx
+++ b/devfront/src/features/clients/ClientConsentsPage.tsx
@@ -3,6 +3,7 @@ import {
ArrowLeft,
ChevronLeft,
ChevronRight,
+ Download,
Filter,
Search,
} from "lucide-react";
@@ -275,6 +276,7 @@ function ClientConsentsPage() {
onClick={handleExportCSV}
disabled={filteredRows.length === 0}
>
+
{t("ui.dev.clients.consents.export_csv", "Export CSV")}
diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx
index 91bcd6cc..964d4d55 100644
--- a/devfront/src/features/clients/ClientGeneralPage.tsx
+++ b/devfront/src/features/clients/ClientGeneralPage.tsx
@@ -30,6 +30,7 @@ import {
deleteClient,
fetchClient,
updateClient,
+ updateClientStatus,
} from "../../lib/devApi";
import type {
ClientStatus,
@@ -63,6 +64,7 @@ function ClientGeneralPage() {
const [logoUrl, setLogoUrl] = useState("");
const [clientType, setClientType] = useState