From 9a3cf120ce760881925cd9bd4c5ac2d38de2ef11 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Tue, 28 Apr 2026 21:07:19 +0000 Subject: [PATCH] Show readable summaries instead of raw JSON in the audit log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task #94: Each admin audit log row now renders a localized one-line summary derived from the entry's action + metadata, e.g. "Deleted group 'audit-test-group'" or "تم حذف المجموعة 'X'", instead of just showing the raw target type/id. Implementation - Added `formatAuditSummary(entry, t, lang)` in `artifacts/tx-os/src/pages/admin.tsx` covering all known audit actions emitted by the API (group/user/role/app/service/settings/ auth create/update/delete/permission/membership variants, including forced deletions). Returns null for unknown actions so the row gracefully falls back to the existing "Target: type #id" display. - The summary now occupies the prominent text slot in `AuditLogRow`. The raw action code stays in the small badge (it is also the value the action-filter dropdown uses), and the timestamp moved next to it. The expand toggle still reveals the full JSON metadata for power users (data-testid `audit-metadata-` unchanged). - Added `admin.audit.summary.*` keys plus a pluralized `admin.audit.unit.*` helper map in en.json and ar.json. The Arabic unit keys provide all six CLDR plural forms (zero/one/two/few/ many/other) so counts read naturally; English uses one/other. Verification - `tsc --build` clean for the tx-os artifact. - e2e test (admin login → create + delete a uniquely named group → open Audit Log → confirm Arabic and English summaries render, raw JSON is still available behind the toggle, and the action pill still shows the raw action code) passed. Follow-up proposed - #117 "Show readable action names in the audit log filter dropdown" (the filter dropdown still lists raw action codes). Replit-Task-Id: b47aacde-087e-4a2b-8fb2-0e63cb1936e4 --- artifacts/tx-os/public/opengraph.jpg | Bin 25912 -> 24587 bytes artifacts/tx-os/src/locales/ar.json | 110 +++++++++++++ artifacts/tx-os/src/locales/en.json | 66 ++++++++ artifacts/tx-os/src/pages/admin.tsx | 226 ++++++++++++++++++++++++++- 4 files changed, 395 insertions(+), 7 deletions(-) diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index c3ff5c902956b79a5109fa295427c5dc06abb6e4..c353446e73254532976aeeb92f4019a954c212db 100644 GIT binary patch literal 24587 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 BhcN&E literal 25912 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 diff --git a/artifacts/tx-os/src/locales/ar.json b/artifacts/tx-os/src/locales/ar.json index 2f1ae848..3410f2d1 100644 --- a/artifacts/tx-os/src/locales/ar.json +++ b/artifacts/tx-os/src/locales/ar.json @@ -493,6 +493,116 @@ "invalidDate": "يجب أن تكون التواريخ بصيغة YYYY-MM-DD.", "invertedDates": "تاريخ \"من\" يجب أن يكون قبل أو يساوي تاريخ \"إلى\".", "fixFiltersFirst": "عدّل الفلاتر لتحميل الإدخالات." + }, + "unit": { + "member_zero": "بدون أعضاء", + "member_one": "عضو واحد", + "member_two": "عضوان", + "member_few": "{{count}} أعضاء", + "member_many": "{{count}} عضواً", + "member_other": "{{count}} عضو", + "app_zero": "بدون تطبيقات", + "app_one": "تطبيق واحد", + "app_two": "تطبيقان", + "app_few": "{{count}} تطبيقات", + "app_many": "{{count}} تطبيقاً", + "app_other": "{{count}} تطبيق", + "role_zero": "بدون أدوار", + "role_one": "دور واحد", + "role_two": "دوران", + "role_few": "{{count}} أدوار", + "role_many": "{{count}} دوراً", + "role_other": "{{count}} دور", + "group_zero": "بدون مجموعات", + "group_one": "مجموعة واحدة", + "group_two": "مجموعتان", + "group_few": "{{count}} مجموعات", + "group_many": "{{count}} مجموعةً", + "group_other": "{{count}} مجموعة", + "order_zero": "بدون طلبات", + "order_one": "طلب واحد", + "order_two": "طلبان", + "order_few": "{{count}} طلبات", + "order_many": "{{count}} طلباً", + "order_other": "{{count}} طلب", + "change_zero": "بدون تغييرات", + "change_one": "تغيير واحد", + "change_two": "تغييران", + "change_few": "{{count}} تغييرات", + "change_many": "{{count}} تغييراً", + "change_other": "{{count}} تغيير", + "permission_zero": "بدون صلاحيات", + "permission_one": "صلاحية واحدة", + "permission_two": "صلاحيتان", + "permission_few": "{{count}} صلاحيات", + "permission_many": "{{count}} صلاحيةً", + "permission_other": "{{count}} صلاحية", + "restriction_zero": "بدون قيود", + "restriction_one": "قيد واحد", + "restriction_two": "قيدان", + "restriction_few": "{{count}} قيود", + "restriction_many": "{{count}} قيداً", + "restriction_other": "{{count}} قيد", + "note_zero": "بدون ملاحظات", + "note_one": "ملاحظة واحدة", + "note_two": "ملاحظتان", + "note_few": "{{count}} ملاحظات", + "note_many": "{{count}} ملاحظةً", + "note_other": "{{count}} ملاحظة", + "conversation_zero": "بدون محادثات", + "conversation_one": "محادثة واحدة", + "conversation_two": "محادثتان", + "conversation_few": "{{count}} محادثات", + "conversation_many": "{{count}} محادثةً", + "conversation_other": "{{count}} محادثة", + "message_zero": "بدون رسائل", + "message_one": "رسالة واحدة", + "message_two": "رسالتان", + "message_few": "{{count}} رسائل", + "message_many": "{{count}} رسالةً", + "message_other": "{{count}} رسالة" + }, + "summary": { + "app": { + "create": "تم إنشاء التطبيق '{{name}}'", + "update": "تم تعديل التطبيق '{{name}}' ({{changes}})", + "delete": "تم حذف التطبيق '{{name}}'", + "forceDelete": "تم حذف التطبيق '{{name}}' قسراً (تأثرت {{groups}})" + }, + "auth": { + "issueResetLink": "تم إصدار رابط إعادة تعيين كلمة المرور للمستخدم @{{username}}" + }, + "group": { + "create": "تم إنشاء المجموعة '{{name}}' ({{members}}، {{apps}})", + "update": "تم تعديل المجموعة '{{name}}' ({{changes}})", + "delete": "تم حذف المجموعة '{{name}}'", + "forceDelete": "تم حذف المجموعة '{{name}}' قسراً ({{members}})", + "userAdd": "تمت إضافة المستخدم #{{userId}} إلى المجموعة '{{name}}'", + "userRemove": "تمت إزالة المستخدم #{{userId}} من المجموعة '{{name}}'", + "appAdd": "تمت إضافة التطبيق #{{appId}} إلى المجموعة '{{name}}'", + "appRemove": "تمت إزالة التطبيق #{{appId}} من المجموعة '{{name}}'", + "roleAdd": "تمت إضافة الدور #{{roleId}} إلى المجموعة '{{name}}'", + "roleRemove": "تمت إزالة الدور #{{roleId}} من المجموعة '{{name}}'" + }, + "role": { + "create": "تم إنشاء الدور '{{name}}'", + "update": "تم تعديل الدور '{{name}}' ({{changes}})", + "rename": "تمت إعادة تسمية الدور من '{{previousName}}' إلى '{{name}}'", + "delete": "تم حذف الدور '{{name}}'", + "permissionsReplace": "تم تحديث صلاحيات الدور '{{name}}' (أُضيفت {{added}}، حُذفت {{removed}})", + "permissionAdd": "تمت إضافة الصلاحية '{{permission}}' إلى الدور '{{name}}'", + "permissionRemove": "تمت إزالة الصلاحية '{{permission}}' من الدور '{{name}}'" + }, + "service": { + "forceDelete": "تم حذف الخدمة '{{name}}' قسراً (تأثرت {{orders}})" + }, + "settings": { + "update": "تم تحديث إعدادات النظام ({{changes}})" + }, + "user": { + "delete": "تم حذف المستخدم @{{username}}", + "forceDelete": "تم حذف المستخدم @{{username}} قسراً" + } } }, "dashboardSoon": "إحصائيات اللوحة قريباً.", diff --git a/artifacts/tx-os/src/locales/en.json b/artifacts/tx-os/src/locales/en.json index 2e4cc604..ba661044 100644 --- a/artifacts/tx-os/src/locales/en.json +++ b/artifacts/tx-os/src/locales/en.json @@ -490,6 +490,72 @@ "invalidDate": "Dates must be in YYYY-MM-DD format.", "invertedDates": "\"From\" date must be on or before \"To\" date.", "fixFiltersFirst": "Adjust the filters to load entries." + }, + "unit": { + "member_one": "{{count}} member", + "member_other": "{{count}} members", + "app_one": "{{count}} app", + "app_other": "{{count}} apps", + "role_one": "{{count}} role", + "role_other": "{{count}} roles", + "group_one": "{{count}} group", + "group_other": "{{count}} groups", + "order_one": "{{count}} order", + "order_other": "{{count}} orders", + "change_one": "{{count}} change", + "change_other": "{{count}} changes", + "permission_one": "{{count}} permission", + "permission_other": "{{count}} permissions", + "restriction_one": "{{count}} restriction", + "restriction_other": "{{count}} restrictions", + "note_one": "{{count}} note", + "note_other": "{{count}} notes", + "conversation_one": "{{count}} conversation", + "conversation_other": "{{count}} conversations", + "message_one": "{{count}} message", + "message_other": "{{count}} messages" + }, + "summary": { + "app": { + "create": "Created app '{{name}}'", + "update": "Updated app '{{name}}' ({{changes}})", + "delete": "Deleted app '{{name}}'", + "forceDelete": "Force-deleted app '{{name}}' (affected {{groups}})" + }, + "auth": { + "issueResetLink": "Issued password reset link for @{{username}}" + }, + "group": { + "create": "Created group '{{name}}' ({{members}}, {{apps}})", + "update": "Updated group '{{name}}' ({{changes}})", + "delete": "Deleted group '{{name}}'", + "forceDelete": "Force-deleted group '{{name}}' ({{members}})", + "userAdd": "Added user #{{userId}} to group '{{name}}'", + "userRemove": "Removed user #{{userId}} from group '{{name}}'", + "appAdd": "Added app #{{appId}} to group '{{name}}'", + "appRemove": "Removed app #{{appId}} from group '{{name}}'", + "roleAdd": "Added role #{{roleId}} to group '{{name}}'", + "roleRemove": "Removed role #{{roleId}} from group '{{name}}'" + }, + "role": { + "create": "Created role '{{name}}'", + "update": "Updated role '{{name}}' ({{changes}})", + "rename": "Renamed role '{{previousName}}' to '{{name}}'", + "delete": "Deleted role '{{name}}'", + "permissionsReplace": "Updated permissions for role '{{name}}' (added {{added}}, removed {{removed}})", + "permissionAdd": "Added permission '{{permission}}' to role '{{name}}'", + "permissionRemove": "Removed permission '{{permission}}' from role '{{name}}'" + }, + "service": { + "forceDelete": "Force-deleted service '{{name}}' (affected {{orders}})" + }, + "settings": { + "update": "Updated system settings ({{changes}})" + }, + "user": { + "delete": "Deleted user @{{username}}", + "forceDelete": "Force-deleted user @{{username}}" + } } }, "dashboardSoon": "Dashboard widgets coming soon.", diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx index 3364708d..7df48775 100644 --- a/artifacts/tx-os/src/pages/admin.tsx +++ b/artifacts/tx-os/src/pages/admin.tsx @@ -3706,6 +3706,208 @@ function actorLabel(actor: AuditLogEntry["actor"], lang: string): string { return preferred && preferred.trim() ? preferred : actor.username; } +type AuditTFunction = ReturnType["t"]; + +function asRecord(value: unknown): Record { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function asString(v: unknown): string | null { + return typeof v === "string" && v.trim() ? v : null; +} + +function asNumber(v: unknown): number | null { + return typeof v === "number" && Number.isFinite(v) ? v : null; +} + +function unitLabel(t: AuditTFunction, kind: string, count: number): string { + return t(`admin.audit.unit.${kind}`, { count }); +} + +function appName(meta: Record, lang: string, fallbackId: AuditLogEntry["targetId"]): string { + const en = asString(meta.nameEn); + const ar = asString(meta.nameAr); + const slug = asString(meta.slug); + const preferred = lang === "ar" ? ar ?? en : en ?? ar; + return preferred ?? slug ?? `#${fallbackId ?? "?"}`; +} + +function plainName(meta: Record, fallbackId: AuditLogEntry["targetId"]): string { + return asString(meta.name) ?? `#${fallbackId ?? "?"}`; +} + +function changeCount(meta: Record): number { + const changes = meta.changes; + if (changes && typeof changes === "object" && !Array.isArray(changes)) { + return Object.keys(changes as Record).length; + } + if (Array.isArray(changes)) return changes.length; + let count = 0; + for (const key of ["members", "apps", "roles"]) { + const diff = meta[key]; + if (diff && typeof diff === "object" && !Array.isArray(diff)) { + const d = diff as Record; + const added = Array.isArray(d.added) ? d.added.length : 0; + const removed = Array.isArray(d.removed) ? d.removed.length : 0; + if (added + removed > 0) count += 1; + } + } + return count; +} + +function formatAuditSummary( + entry: AuditLogEntry, + t: AuditTFunction, + lang: string, +): string | null { + const action = entry.action; + const meta = asRecord(entry.metadata); + const targetId = entry.targetId; + + switch (action) { + case "app.create": + return t("admin.audit.summary.app.create", { + name: appName(meta, lang, targetId), + }); + case "app.update": + return t("admin.audit.summary.app.update", { + name: appName(meta, lang, targetId), + changes: unitLabel(t, "change", changeCount(meta)), + }); + case "app.delete": { + const name = appName(meta, lang, targetId); + if (meta.force) { + const groupCount = asNumber(meta.groupCount) ?? 0; + return t("admin.audit.summary.app.forceDelete", { + name, + groups: unitLabel(t, "group", groupCount), + }); + } + return t("admin.audit.summary.app.delete", { name }); + } + case "auth.issue_reset_link": + return t("admin.audit.summary.auth.issueResetLink", { + username: asString(meta.username) ?? `#${targetId ?? "?"}`, + }); + case "group.create": + return t("admin.audit.summary.group.create", { + name: plainName(meta, targetId), + members: unitLabel(t, "member", asNumber(meta.memberCount) ?? 0), + apps: unitLabel(t, "app", asNumber(meta.appCount) ?? 0), + }); + case "group.update": + return t("admin.audit.summary.group.update", { + name: plainName(meta, targetId), + changes: unitLabel(t, "change", Math.max(1, changeCount(meta))), + }); + case "group.delete": { + const name = plainName(meta, targetId); + if (meta.force) { + const memberCount = asNumber(meta.memberCount) ?? 0; + return t("admin.audit.summary.group.forceDelete", { + name, + members: unitLabel(t, "member", memberCount), + }); + } + return t("admin.audit.summary.group.delete", { name }); + } + case "group.user.add": + return t("admin.audit.summary.group.userAdd", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + userId: asNumber(meta.userId) ?? "?", + }); + case "group.user.remove": + return t("admin.audit.summary.group.userRemove", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + userId: asNumber(meta.userId) ?? "?", + }); + case "group.app.add": + return t("admin.audit.summary.group.appAdd", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + appId: asNumber(meta.appId) ?? "?", + }); + case "group.app.remove": + return t("admin.audit.summary.group.appRemove", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + appId: asNumber(meta.appId) ?? "?", + }); + case "group.role.add": + return t("admin.audit.summary.group.roleAdd", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + roleId: asNumber(meta.roleId) ?? "?", + }); + case "group.role.remove": + return t("admin.audit.summary.group.roleRemove", { + name: asString(meta.groupName) ?? `#${targetId ?? "?"}`, + roleId: asNumber(meta.roleId) ?? "?", + }); + case "role.create": + return t("admin.audit.summary.role.create", { + name: plainName(meta, targetId), + }); + case "role.update": { + const previousName = asString(meta.previousName); + const name = plainName(meta, targetId); + if (previousName && previousName !== name) { + return t("admin.audit.summary.role.rename", { + previousName, + name, + }); + } + return t("admin.audit.summary.role.update", { + name, + changes: unitLabel(t, "change", Math.max(1, changeCount(meta))), + }); + } + case "role.delete": + return t("admin.audit.summary.role.delete", { + name: plainName(meta, targetId), + }); + case "role.permissions.replace": { + const added = Array.isArray(meta.added) ? meta.added.length : 0; + const removed = Array.isArray(meta.removed) ? meta.removed.length : 0; + return t("admin.audit.summary.role.permissionsReplace", { + name: asString(meta.roleName) ?? `#${targetId ?? "?"}`, + added: unitLabel(t, "permission", added), + removed: unitLabel(t, "permission", removed), + }); + } + case "role.permission.add": + return t("admin.audit.summary.role.permissionAdd", { + name: asString(meta.roleName) ?? `#${targetId ?? "?"}`, + permission: + asString(meta.permissionName) ?? + (asNumber(meta.permissionId) != null ? `#${meta.permissionId}` : "?"), + }); + case "role.permission.remove": + return t("admin.audit.summary.role.permissionRemove", { + name: asString(meta.roleName) ?? `#${targetId ?? "?"}`, + permission: + asString(meta.permissionName) ?? + (asNumber(meta.permissionId) != null ? `#${meta.permissionId}` : "?"), + }); + case "service.force_delete": + return t("admin.audit.summary.service.forceDelete", { + name: asString(meta.nameEn) ?? `#${targetId ?? "?"}`, + orders: unitLabel(t, "order", asNumber(meta.orderCount) ?? 0), + }); + case "settings.update": + return t("admin.audit.summary.settings.update", { + changes: unitLabel(t, "change", changeCount(meta)), + }); + case "user.delete": { + const username = asString(meta.username) ?? `#${targetId ?? "?"}`; + return meta.force + ? t("admin.audit.summary.user.forceDelete", { username }) + : t("admin.audit.summary.user.delete", { username }); + } + default: + return null; + } +} + function AuditLogRow({ entry, lang }: { entry: AuditLogEntry; lang: string }) { const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); @@ -3713,6 +3915,7 @@ function AuditLogRow({ entry, lang }: { entry: AuditLogEntry; lang: string }) { entry.metadata != null && Object.keys(entry.metadata as object).length > 0; const actor = entry.actor; const initial = (actorLabel(actor, lang)[0] ?? "?").toUpperCase(); + const summary = formatAuditSummary(entry, t, lang); return (
)}
+ {summary ? ( +
+ {summary} +
+ ) : ( +
+ {t("admin.audit.target", { + type: entry.targetType, + id: entry.targetId ?? "—", + })} +
+ )}
- {t("admin.audit.target", { - type: entry.targetType, - id: entry.targetId ?? "—", - })} + {formatAuditTimestamp(entry.createdAt, lang)}
-
- {formatAuditTimestamp(entry.createdAt, lang)} -
{hasMetadata && (