Show deleted entity name beside target id in admin Audit Log
Task #133: Admin Audit Log rows now display the deleted item's human-readable name next to its `<type> #<id>` target reference without admins having to expand the JSON metadata. Changes - artifacts/tx-os/src/pages/admin.tsx - New helper `forceDeletedEntityName(entry, lang)` that pulls a localized display name from a force-delete row's metadata. Priority: nameEn/nameAr/displayNameEn/displayNameAr (lang-aware) > plain `name` > `@username`. - `AuditLogRow` renders an additional muted "Target: <type> #<id> · <name>" line beneath the summary for force-delete entries that expose a name. When no summary is generated, the existing target fallback line is upgraded to include the name as well. - artifacts/tx-os/src/locales/en.json, ar.json - New `admin.audit.targetWithName` key with English and Arabic translations. Behavior - Names respect the active language (Arabic preferred when lang=ar, with graceful fallback to the other locale, then `name`, then `@username`). - Non-deletion rows and deletion rows whose metadata lacks a name are unchanged — no extra line is rendered, so there is no regression for existing entries. Verification - `pnpm exec tsc --noEmit` in artifacts/tx-os passes after running `pnpm --filter @workspace/api-spec run codegen`. - E2E test (Playwright) confirms the new line renders for both a service.force_delete row (English-only metadata) and an app.delete force row (English + Arabic metadata), and that the Arabic UI surfaces the Arabic name when present and falls back to the English name when not. Follow-ups proposed - #194 Persist user display names in user.delete audit metadata. - #195 Log every service deletion, not only forced ones. Replit-Task-Id: cd312541-1c90-4849-afd6-e6757aedfe06
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@@ -561,6 +561,7 @@
|
||||
"forcedOnlyHint": "عرض إدخالات الحذف القسري للمجموعات والمستخدمين والتطبيقات والخدمات فقط."
|
||||
},
|
||||
"target": "الهدف: {{type}} #{{id}}",
|
||||
"targetWithName": "الهدف: {{type}} #{{id}} · {{name}}",
|
||||
"showing": "عرض {{shown}} من أصل {{total}}",
|
||||
"empty": "لا توجد إدخالات في سجل التدقيق تطابق الفلاتر.",
|
||||
"loadMore": "عرض المزيد",
|
||||
|
||||
@@ -546,6 +546,7 @@
|
||||
"forcedOnlyHint": "Show only force-delete entries for groups, users, apps, and services."
|
||||
},
|
||||
"target": "Target: {{type}} #{{id}}",
|
||||
"targetWithName": "Target: {{type}} #{{id}} · {{name}}",
|
||||
"showing": "Showing {{shown}} of {{total}}",
|
||||
"empty": "No audit log entries match these filters.",
|
||||
"loadMore": "Load more",
|
||||
|
||||
@@ -4837,6 +4837,27 @@ function isForceDeleteEntry(entry: AuditLogEntry): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pulls a human-readable display name for a force-deleted entity from its
|
||||
// audit metadata so the row can show "service #4821 · My Service" inline
|
||||
// without admins having to expand the JSON. Returns null when the row is not
|
||||
// a forced deletion or the metadata lacks any usable name field.
|
||||
function forceDeletedEntityName(
|
||||
entry: AuditLogEntry,
|
||||
lang: string,
|
||||
): string | null {
|
||||
if (!isForceDeleteEntry(entry)) return null;
|
||||
const meta = asRecord(entry.metadata);
|
||||
const en = asString(meta.nameEn) ?? asString(meta.displayNameEn);
|
||||
const ar = asString(meta.nameAr) ?? asString(meta.displayNameAr);
|
||||
const localized = lang === "ar" ? ar ?? en : en ?? ar;
|
||||
if (localized) return localized;
|
||||
const plain = asString(meta.name);
|
||||
if (plain) return plain;
|
||||
const username = asString(meta.username);
|
||||
if (username) return `@${username}`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function dependencyChips(
|
||||
entry: AuditLogEntry,
|
||||
t: AuditTFunction,
|
||||
@@ -4861,6 +4882,8 @@ function AuditLogRow({ entry, lang }: { entry: AuditLogEntry; lang: string }) {
|
||||
const summary = formatAuditSummary(entry, t, lang);
|
||||
const isForced = isForceDeleteEntry(entry);
|
||||
const chips = isForced ? dependencyChips(entry, t) : [];
|
||||
const deletedName = forceDeletedEntityName(entry, lang);
|
||||
const showDeletedTarget = deletedName != null && entry.targetId != null;
|
||||
return (
|
||||
<div
|
||||
className="glass-panel rounded-2xl p-3 space-y-2"
|
||||
@@ -4892,9 +4915,27 @@ function AuditLogRow({ entry, lang }: { entry: AuditLogEntry; lang: string }) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{t("admin.audit.target", {
|
||||
{showDeletedTarget
|
||||
? t("admin.audit.targetWithName", {
|
||||
type: entry.targetType,
|
||||
id: entry.targetId,
|
||||
name: deletedName,
|
||||
})
|
||||
: t("admin.audit.target", {
|
||||
type: entry.targetType,
|
||||
id: entry.targetId ?? "—",
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{summary && showDeletedTarget && (
|
||||
<div
|
||||
className="text-xs text-muted-foreground mt-0.5"
|
||||
data-testid={`audit-deleted-target-${entry.id}`}
|
||||
>
|
||||
{t("admin.audit.targetWithName", {
|
||||
type: entry.targetType,
|
||||
id: entry.targetId ?? "—",
|
||||
id: entry.targetId,
|
||||
name: deletedName,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user