mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(admin): translate native-token audit actions, link recent rows
Two related improvements to the admin recent-activity surface: - New audit actions `auth.token.autoissue.native`, `auth.token.refresh`, and `auth.token.revoke` were leaking through both the dashboard preview and the Login Overview viewer as raw event keys. Added translations and wired the three keys through the action-label map shared by both components. - Recent-activity rows on /admin were a dead-end: a failed login row showed "Unknown" + "Failed" with no link, no IP, no way to drill in. Each row is now a Link into /admin/login-overview, with the IP shown inline at sm+. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
876e074fd8
commit
bc81fd7db2
4 files changed
+45
-23
No files matched your search
@@ -1737,6 +1737,9 @@
|
||||
"authLogout": "Abmeldung",
|
||||
"authPasskeyRegister": "Passkey hinzugefügt",
|
||||
"authPasskeyDelete": "Passkey gelöscht",
|
||||
"authTokenAutoissueNative": "Native-Client-Token ausgestellt",
|
||||
"authTokenRefresh": "Token erneuert",
|
||||
"authTokenRevoke": "Token widerrufen",
|
||||
"medicationReminders": "Medikamentenerinnerungen",
|
||||
"medicationRemindersDescription": "Schwellenwerte für die Farbübergänge der Erinnerungs-Badges auf den Medikamentenkarten.",
|
||||
"reminderLateMinutes": "Spät-Schwellenwert (Minuten)",
|
||||
|
||||
@@ -1737,6 +1737,9 @@
|
||||
"authLogout": "Logout",
|
||||
"authPasskeyRegister": "Passkey added",
|
||||
"authPasskeyDelete": "Passkey deleted",
|
||||
"authTokenAutoissueNative": "Native client token issued",
|
||||
"authTokenRefresh": "Token refreshed",
|
||||
"authTokenRevoke": "Token revoked",
|
||||
"medicationReminders": "Medication Reminders",
|
||||
"medicationRemindersDescription": "Thresholds for reminder badge color transitions on medication cards.",
|
||||
"reminderLateMinutes": "Late threshold (minutes)",
|
||||
|
||||
@@ -76,6 +76,9 @@ export function LoginOverviewSection() {
|
||||
"auth.logout": t("admin.authLogout"),
|
||||
"auth.passkey.register": t("admin.authPasskeyRegister"),
|
||||
"auth.passkey.delete": t("admin.authPasskeyDelete"),
|
||||
"auth.token.autoissue.native": t("admin.authTokenAutoissueNative"),
|
||||
"auth.token.refresh": t("admin.authTokenRefresh"),
|
||||
"auth.token.revoke": t("admin.authTokenRevoke"),
|
||||
};
|
||||
|
||||
// Build the query string once so it's reused by the data-query key, the
|
||||
|
||||
@@ -56,6 +56,9 @@ export function RecentAuditPreview() {
|
||||
"auth.logout": t("admin.authLogout"),
|
||||
"auth.passkey.register": t("admin.authPasskeyRegister"),
|
||||
"auth.passkey.delete": t("admin.authPasskeyDelete"),
|
||||
"auth.token.autoissue.native": t("admin.authTokenAutoissueNative"),
|
||||
"auth.token.refresh": t("admin.authTokenRefresh"),
|
||||
"auth.token.revoke": t("admin.authTokenRevoke"),
|
||||
};
|
||||
|
||||
const entries = data?.entries ?? [];
|
||||
@@ -116,31 +119,41 @@ export function RecentAuditPreview() {
|
||||
<ul className="divide-border divide-y">
|
||||
{entries.map((entry) => {
|
||||
const isFailed = entry.action === "auth.login.failed";
|
||||
// F-31 (v1.4.19): each row links into the full
|
||||
// /admin/login-overview viewer so admins can investigate
|
||||
// failed logins without re-finding the row by hand.
|
||||
return (
|
||||
<li
|
||||
key={entry.id}
|
||||
className="flex items-center gap-3 py-2 text-sm"
|
||||
>
|
||||
<span className="shrink-0" aria-hidden="true">
|
||||
{isFailed ? (
|
||||
<XCircle className="text-destructive h-4 w-4" />
|
||||
) : (
|
||||
<CheckCircle2 className="text-dracula-green h-4 w-4" />
|
||||
<li key={entry.id}>
|
||||
<Link
|
||||
href="/admin/login-overview"
|
||||
className="hover:bg-muted/40 -mx-2 flex items-center gap-3 rounded px-2 py-2 text-sm transition-colors"
|
||||
>
|
||||
<span className="shrink-0" aria-hidden="true">
|
||||
{isFailed ? (
|
||||
<XCircle className="text-destructive h-4 w-4" />
|
||||
) : (
|
||||
<CheckCircle2 className="text-dracula-green h-4 w-4" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate font-medium ${isFailed ? "text-destructive" : ""}`}
|
||||
>
|
||||
{entry.user?.username ?? t("common.unknown")}
|
||||
</span>
|
||||
<span
|
||||
className={`hidden truncate sm:inline ${isFailed ? "text-destructive" : "text-muted-foreground"}`}
|
||||
>
|
||||
{AUTH_ACTION_LABELS[entry.action] ?? entry.action}
|
||||
</span>
|
||||
{entry.ipAddress && (
|
||||
<span className="text-muted-foreground hidden shrink-0 font-mono text-xs sm:inline">
|
||||
{entry.ipAddress}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate font-medium ${isFailed ? "text-destructive" : ""}`}
|
||||
>
|
||||
{entry.user?.username ?? t("common.unknown")}
|
||||
</span>
|
||||
<span
|
||||
className={`hidden truncate sm:inline ${isFailed ? "text-destructive" : "text-muted-foreground"}`}
|
||||
>
|
||||
{AUTH_ACTION_LABELS[entry.action] ?? entry.action}
|
||||
</span>
|
||||
<span className="text-muted-foreground shrink-0 text-xs whitespace-nowrap">
|
||||
{formatDateTime(entry.createdAt)}
|
||||
</span>
|
||||
<span className="text-muted-foreground shrink-0 text-xs whitespace-nowrap">
|
||||
{formatDateTime(entry.createdAt)}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in new issue
Block a user