Skip to content

Commit c1da957

Browse files
feat(console): the Audit Log drawer opens the selected row's record page, which lists the other changes to the same record (#30854)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoVs9GksKU3yRatBp1PXVJMd)_ Fixes #30748 Clause-②: yes (widening) — two new console keys (`console.system.auditLog.openEntryPage`, `console.system.auditLog.openEntryPageHint`) ship in every published `@object-ui/i18n` pack; nothing is narrowed. Metadata-protocol: none ## 维护者速读 - 改了什么:Setup → 审计日志的详情抽屉里加了一个「打开条目页」按钮,点开就是这条审计行自己的记录页;行上带 `record_id` 时,按钮下方一句话说明那一页会列出同一记录的全部变更。十种内置语言都有这两句文案。 - 为什么改:设置写入记下的 `config_change` 行只有摘要,看不到改成了什么值;值在 CRUD 镜像写的同一设置行的 update 行里。服务端一半(#30728)已经让审计记录页按 `record_id` 列出同记录的所有行,但管理员实际看审计的这个页面没有入口通向那一页。 - 风险与回滚:只加一个链接和两条文案,不改任何读取;记录页本身和它的读权限、脱敏都是服务端既有的。回滚即 revert 本 PR。 - 先看:`ui/apps/console/src/pages/system/AuditLogPage.tsx`(抽屉头部新增的链接块与 `entryPageHref`);`ui/apps/console/src/pages/system/AuditLogPage.entryPage-30748.test.tsx`。 - 两分钟验证:`pnpm --dir ui exec vitest run apps/console/src/pages/system/AuditLogPage.entryPage-30748.test.tsx`,预期 5 条全绿;或在 Setup → 审计日志点开一条设置变更行,点「打开条目页」,记录页下方「同一记录的变更」里能看到镜像的 update 行和新旧值。 - 需要你做的:无,席位落地。 ## What changes Setup → Audit Log (`AuditLogPage.tsx`) is a custom page: a row click opens its own drawer, and nothing there reached the row's record page. Since #30728 the platform serves a default record page for `sys_audit_log` (`sys_audit_log_detail`, `plugin-audit`) whose related list holds every ledger row with the same `record_id`. A settings write's `config_change` row carries only a digest; the CRUD mirror's rows about the same settings row hold the value, one row down on that page. - The drawer header gains an **Open entry page** link (`entryPageHref`): the console's record route for `sys_audit_log` and the selected row's id, under the app segment the page is mounted in (the same `listBase` "Open as list" uses, `setup` when none). It is offered on every row with an id: a row about no record still has its own page. - The link carries `state.from` back to this page, labelled by the page title, the shape the Approvals Inbox hands its record links, so the record page's crumb row leads back here in an app whose navigation does not list `sys_audit_log`. - On a row that carries a `record_id`, one muted line under the link says the entry page lists every change to the same record, newest first. - `@object-ui/i18n`: `console.system.auditLog.openEntryPage` and `console.system.auditLog.openEntryPageHint` in all ten built-in packs. The page header's title now reads from one `pageTitle` constant shared with the link state. The card's second option (listing the same-`record_id` rows inside the drawer) was not taken: the record page already makes that read through the data API under the ledger's read gate and redaction, and a second list in the drawer would be a copy of it. ## Tests `ui/apps/console/src/pages/system/AuditLogPage.entryPage-30748.test.tsx` (new, 5 tests), through a real router: the page at `/apps/:appName/system/audit-log` as `systemRoutes` mounts it, and a probe on `/apps/:appName/:objectName/record/:recordId`. - on a `config_change` row the drawer links to `/apps/setup/sys_audit_log/record/ROW_ID` (that row's id) and shows the hint; - clicking it lands on the record route with that id, `state.from` = this page and its title, and the landed row's `record_id` is the one the mirror's update row names; - the app segment follows the mount (`/apps/crm/...`); - a row with no `record_id` still links to its own page, without the hint; - under zh the link and the hint read the zh pack (guarded as real zh rows). What this file does not pin, because it lives elsewhere: the page's same-record list (`plugin-audit` `sys-audit-log-page.test.ts`, "the same-record list is the ledger, keyed on `record_id` on both sides, newest first, with no New") and the related list's `relationshipValueField` read (`plugin-detail`, `record-related-list.relationshipValueFieldUncast-9475.test.tsx`). ## Acceptance notes - The card's pin ("on a `config_change` row the drawer reaches the mirror's update row") is held as a chain: this PR pins drawer → the row's record route; the two named tests above pin route page → same-`record_id` list. No test here mounts the full record page. - The language-pack addition costs the eager `i18n-locale-en` chunk a few dozen bytes; measured below, headroom stays about 2.9 KB of the 60,000-byte ceiling, ceiling untouched. - The first commit on the branch (`b85081cd`, a WIP commit) carries no `Claude-Session` / `Co-authored-by` trailer pair; the branch is not force-pushable under AGENTS §3, so it stays. Later commits carry the pair. ## Verification Readings below were taken on `ee3ec7bb` (base `b6182249`). Head `b0f67dde` changes only the changeset's `Clause-②:` line; on it `check-adr-0087-registration`, `check-changeset-no-major`, `check-empty-changeset` (each `--base origin/main`), `check:changeset-gate-self-tests` and `check-closing-keyword-parity` exit 0. <details><summary>Tests</summary> - `pnpm --dir ui exec vitest run --reporter=verbose apps/console/src/pages/system/AuditLogPage.entryPage-30748.test.tsx`: Test Files 1 passed, Tests 5 passed. - `pnpm --dir ui exec vitest run apps/console/src/pages/system/AuditLogPage apps/console/src/pages/consolePagesLocale-secondPass-13460.test.tsx`: Test Files 10 passed (10), Tests 56 passed (56). - Ablation (`node scripts/ablation-replace.mjs`, the link pointed at `listBase` instead of `entryPageHref(...)`): anchor 1 -> 0, 4 of 5 tests red (the zh leg reads labels only); restored, blob == HEAD, `git diff HEAD` empty. </details> <details><summary>Gates</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 36 families for this diff; every one recorded an exit code, `--ran` reconciliation: "36 derived famil(ies) accounted for — 36 run, 0 NOT-MEASURED". All exit 0, including `node scripts/check-sdui-manifest.mjs` (+ `--self-test`), `pnpm check:cross-package-test-inputs`, `pnpm check:test-source-alias`, `pnpm check:nul-bytes`, `pnpm check:published-files`, the changeset family (`check-adr-0087-registration`, `check-changeset-no-major`, `check-empty-changeset`, `check:changeset-gate-self-tests`). `pnpm --dir ui exec vitest run scripts/` first exited 1: 7 tests in `check-doc-snippet-types.test.ts` and `spec-symbol-ledger-plugin-detail-7265.test.ts` read `@objectstack/spec`'s `.d.ts`, absent after the `OS_SKIP_DTS=1` dependency build ("expected 0 to be greater than 1000"). After `env -u OS_SKIP_DTS pnpm --filter @objectstack/spec build` the two files pass: Test Files 2 passed, Tests 129 passed. The rest of the suite: 168 files passed, 4972 tests passed. Not run locally, CI's: console `type-check` (needs the full `.d.ts` build of the console closure), the full UI test shards, repo-wide lint. `eslint` on the two touched console files: 0 errors (2 pre-existing `react-hooks/set-state-in-effect` warnings on the page's load effect). `pnpm --dir ui check:new-line-citations`: 0 new citations. </details> <details><summary>Eager closure</summary> `CI=true vite build` of `ui/apps/console`, then `node ui/scripts/check-eager-closure-budget.mjs`, exit 0: "Console eager closure is 2156.2 KB gzipped across 103 of 2412 chunks (budget: 2431.6 KB, headroom: 275.4 KB)"; "chunk `i18n-locale-en` 55.7 KB measured / 58.6 KB ceiling (headroom 2.9 KB)". Shared box. </details> <!-- signed: domain:ui#3 · os-tesla · session_01L9qibwL8YVfPjy1sSSSEkH --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9e7fbd3 commit c1da957

13 files changed

Lines changed: 292 additions & 2 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/console': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
feat(console): the Setup → Audit Log drawer opens the selected row's own record page, which lists the other changes to the same record
7+
8+
Clause-②: yes (widening) — two new console keys (`console.system.auditLog.openEntryPage`, `console.system.auditLog.openEntryPageHint`) ship in every published `@object-ui/i18n` pack; nothing is narrowed.
9+
Metadata-protocol: none
10+
11+
The Audit Log drawer gains an "Open entry page" link to the console's record route for `sys_audit_log` and the row's id, under the app the page is mounted in, carrying the way back to the Audit Log. The platform's record page for the ledger lists every row with the same `record_id`, so from a settings write's `config_change` row, which carries only a digest, an administrator now reaches the CRUD mirror's rows that hold the old and new value. On a row that names a record the drawer says so under the link. `@object-ui/i18n` ships the two strings in all ten built-in languages (`console.system.auditLog.openEntryPage`, `console.system.auditLog.openEntryPageHint`).
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* The Audit Log drawer opens the row's own record page.
11+
*
12+
* A settings write records a `config_change` row that carries only a digest.
13+
* The CRUD mirror writes its own rows about the same settings row, and those
14+
* hold the value. Both name the settings row as `record_id`, and the
15+
* platform's record page for `sys_audit_log` (`sys_audit_log_detail`, in
16+
* `plugin-audit`) lists every ledger row with the row's `record_id`. The
17+
* drawer had no way to reach that page: "Open as list" opens the object list,
18+
* not a row.
19+
*
20+
* The router is real: the page mounts at `/apps/:appName/system/audit-log`, as
21+
* `systemRoutes` mounts it, and the console's record route
22+
* `/apps/:appName/:objectName/record/:recordId` holds a probe that reads back
23+
* what the link handed it.
24+
*
25+
* Pinned:
26+
* - on a `config_change` row the drawer links to that row's record page under
27+
* the list's app, says that page lists the record's other changes, and the
28+
* link lands there carrying the way back to this page;
29+
* - the landed row names the settings row the mirror's update row names, so
30+
* the page's same-record list holds the update row;
31+
* - a row about no record still links to its own page, without the hint;
32+
* - under zh the link and the hint read the zh pack.
33+
*
34+
* Not pinned here, and pinned where the code lives: the page's same-record
35+
* list (`plugin-audit`'s `sys-audit-log-page.test.ts`, "the same-record list
36+
* is the ledger, keyed on `record_id` on both sides") and the related list's
37+
* `relationshipValueField` read (`plugin-detail`).
38+
*/
39+
40+
import '@testing-library/jest-dom/vitest';
41+
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
42+
import { render, screen, cleanup, act, fireEvent, within } from '@testing-library/react';
43+
import { MemoryRouter, Route, Routes, useLocation, useParams } from 'react-router-dom';
44+
import { I18nProvider } from '@object-ui/i18n';
45+
import { builtInLocales } from '@object-ui/i18n/locales';
46+
47+
const { ADAPTER } = vi.hoisted(() => ({
48+
ADAPTER: {
49+
find: async () => ({ data: [], total: 0 }),
50+
findOne: async () => null,
51+
getObjectSchema: async () => undefined,
52+
},
53+
}));
54+
55+
vi.mock('@object-ui/app-shell', async (importOriginal) => ({
56+
...(await importOriginal<Record<string, unknown>>()),
57+
useAdapter: () => ADAPTER,
58+
}));
59+
60+
import { AuditLogPage } from './AuditLogPage';
61+
62+
/** The settings row both writers name. */
63+
const SETTINGS_ROW = 'set_mail_from_0001';
64+
65+
/** The `config_change` row: a digest, no value. */
66+
const CONFIG_CHANGE = {
67+
id: 'log_cfg_0001',
68+
created_at: '2026-11-06T10:00:01.000Z',
69+
action: 'config_change',
70+
object_name: 'sys_setting',
71+
record_id: SETTINGS_ROW,
72+
new_value: '{"digest":"sha256:9f2c"}',
73+
};
74+
75+
/** The CRUD mirror's update row about the same settings row: it holds the value. */
76+
const MIRROR_UPDATE = {
77+
id: 'log_upd_0002',
78+
created_at: '2026-11-06T10:00:00.000Z',
79+
action: 'update',
80+
object_name: 'sys_setting',
81+
record_id: SETTINGS_ROW,
82+
old_value: '{"value":"noreply@old.example"}',
83+
new_value: '{"value":"noreply@new.example"}',
84+
};
85+
86+
/** A row about no record (an import run). */
87+
const IMPORT_RUN = {
88+
id: 'log_imp_0003',
89+
created_at: '2026-11-06T09:00:00.000Z',
90+
action: 'import',
91+
object_name: 'account',
92+
record_id: null,
93+
};
94+
95+
const LEDGER = [CONFIG_CHANGE, MIRROR_UPDATE, IMPORT_RUN];
96+
97+
beforeEach(() => {
98+
vi.stubGlobal(
99+
'fetch',
100+
vi.fn(async () =>
101+
new Response(JSON.stringify({ object: 'sys_audit_log', records: LEDGER, total: LEDGER.length }), { status: 200 }),
102+
),
103+
);
104+
});
105+
106+
afterEach(() => {
107+
cleanup();
108+
vi.unstubAllGlobals();
109+
});
110+
111+
async function settle(): Promise<void> {
112+
for (let i = 0; i < 6; i++) {
113+
await act(async () => {
114+
await new Promise((r) => setTimeout(r, 0));
115+
});
116+
}
117+
}
118+
119+
/** The console's record route, answering with what the link handed it. */
120+
function RecordRouteProbe() {
121+
const { appName, objectName, recordId } = useParams();
122+
const from = (useLocation().state as { from?: { pathname?: string; label?: string } } | null)?.from;
123+
return (
124+
<div data-testid="record-route">
125+
<span data-testid="app">{appName}</span>
126+
<span data-testid="object">{objectName}</span>
127+
<span data-testid="record">{recordId}</span>
128+
<span data-testid="from-path">{from?.pathname}</span>
129+
<span data-testid="from-label">{from?.label}</span>
130+
</div>
131+
);
132+
}
133+
134+
function renderAt(path = '/apps/setup/system/audit-log') {
135+
return render(
136+
<MemoryRouter initialEntries={[path]}>
137+
<Routes>
138+
<Route path="/apps/:appName/system/audit-log" element={<AuditLogPage />} />
139+
<Route path="/apps/:appName/:objectName/record/:recordId" element={<RecordRouteProbe />} />
140+
</Routes>
141+
</MemoryRouter>,
142+
);
143+
}
144+
145+
/** Opens the drawer on one ledger row. The table lists the rows in served order under its header row. */
146+
async function openDrawerOn(rowId: string): Promise<HTMLElement> {
147+
await screen.findAllByRole('cell');
148+
await settle();
149+
const index = LEDGER.findIndex((r) => r.id === rowId);
150+
const rows = screen.getAllByRole('row');
151+
expect(rows).toHaveLength(LEDGER.length + 1);
152+
fireEvent.click(rows[index + 1]);
153+
const drawer = await screen.findByRole('dialog');
154+
// The drawer is the chosen row's: it shows that row's id.
155+
expect(within(drawer).getByText(rowId)).toBeInTheDocument();
156+
return drawer;
157+
}
158+
159+
describe('AuditLogPage — the drawer opens the row\'s record page', () => {
160+
it('on a config_change row, links to that row\'s page and says it lists the record\'s other changes', async () => {
161+
renderAt();
162+
const drawer = await openDrawerOn(CONFIG_CHANGE.id);
163+
const link = within(drawer).getByRole('link', { name: /Open entry page/ });
164+
expect(link).toHaveAttribute('href', `/apps/setup/sys_audit_log/record/${CONFIG_CHANGE.id}`);
165+
expect(within(drawer).getByText(builtInLocales.en.console.system.auditLog.openEntryPageHint)).toBeInTheDocument();
166+
});
167+
168+
it('the link lands on the record route with the row id and the way back to this page', async () => {
169+
renderAt();
170+
const drawer = await openDrawerOn(CONFIG_CHANGE.id);
171+
fireEvent.click(within(drawer).getByRole('link', { name: /Open entry page/ }));
172+
expect(await screen.findByTestId('record-route')).toBeInTheDocument();
173+
expect(screen.getByTestId('app')).toHaveTextContent('setup');
174+
expect(screen.getByTestId('object')).toHaveTextContent('sys_audit_log');
175+
expect(screen.getByTestId('record')).toHaveTextContent(CONFIG_CHANGE.id);
176+
expect(screen.getByTestId('from-path')).toHaveTextContent('/apps/setup/system/audit-log');
177+
expect(screen.getByTestId('from-label')).toHaveTextContent('Audit Log');
178+
// The landed row's `record_id` is the one the mirror's update row names,
179+
// so the page's same-record list (keyed on `record_id` on both sides)
180+
// holds the update row and its value.
181+
const landed = LEDGER.find((r) => r.id === screen.getByTestId('record').textContent);
182+
const sameRecord = LEDGER.filter((r) => r.record_id === landed?.record_id).map((r) => r.id);
183+
expect(sameRecord).toContain(MIRROR_UPDATE.id);
184+
});
185+
186+
it('keeps the app segment the page is mounted under', async () => {
187+
renderAt('/apps/crm/system/audit-log');
188+
const drawer = await openDrawerOn(CONFIG_CHANGE.id);
189+
expect(within(drawer).getByRole('link', { name: /Open entry page/ }))
190+
.toHaveAttribute('href', `/apps/crm/sys_audit_log/record/${CONFIG_CHANGE.id}`);
191+
});
192+
193+
it('a row about no record still links to its own page, without the hint', async () => {
194+
renderAt();
195+
const drawer = await openDrawerOn(IMPORT_RUN.id);
196+
expect(within(drawer).getByRole('link', { name: /Open entry page/ }))
197+
.toHaveAttribute('href', `/apps/setup/sys_audit_log/record/${IMPORT_RUN.id}`);
198+
expect(within(drawer).queryByText(builtInLocales.en.console.system.auditLog.openEntryPageHint)).toBeNull();
199+
});
200+
201+
it('under zh, the link and the hint read the zh pack', async () => {
202+
const zhAudit = builtInLocales.zh.console.system.auditLog;
203+
// Guard: real zh rows, not English copied over.
204+
expect(zhAudit.openEntryPage).not.toBe(builtInLocales.en.console.system.auditLog.openEntryPage);
205+
expect(zhAudit.openEntryPageHint).not.toBe(builtInLocales.en.console.system.auditLog.openEntryPageHint);
206+
render(
207+
<I18nProvider config={{ defaultLanguage: 'zh', detectBrowserLanguage: false }} persistLanguage={false}>
208+
<MemoryRouter initialEntries={['/apps/setup/system/audit-log']}>
209+
<Routes>
210+
<Route path="/apps/:appName/system/audit-log" element={<AuditLogPage />} />
211+
</Routes>
212+
</MemoryRouter>
213+
</I18nProvider>,
214+
);
215+
const drawer = await openDrawerOn(CONFIG_CHANGE.id);
216+
expect(within(drawer).getByRole('link', { name: new RegExp(zhAudit.openEntryPage) })).toBeInTheDocument();
217+
expect(within(drawer).getByText(zhAudit.openEntryPageHint)).toBeInTheDocument();
218+
});
219+
});

‎ui/apps/console/src/pages/system/AuditLogPage.tsx‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
* - Paginated table (50/page)
2929
* - Row click opens a side drawer with full event details + JSON
3030
* diff of old_value → new_value when present
31+
* - The drawer opens the row's own record page (`entryPageHref` below),
32+
* which lists the other ledger rows about the same `record_id`
3133
*
3234
* Out of scope (future):
3335
* - Cross-tenant filter (single-tenant view assumed)
@@ -37,7 +39,7 @@
3739
*/
3840

3941
import { Suspense, useCallback, useEffect, useId, useMemo, useState } from 'react';
40-
import { Link, useParams } from 'react-router-dom';
42+
import { Link, useLocation, useParams } from 'react-router-dom';
4143
import { mergeFilterNodes, toFilterNodeSafely } from '@object-ui/core';
4244
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
4345
import {
@@ -261,6 +263,18 @@ function formatDate(s: string | null | undefined, locale: string): string {
261263
try { return new Date(s).toLocaleString(locale); } catch { return s; }
262264
}
263265

266+
/**
267+
* The router path of one audit row's own record page: the console's standard
268+
* record route for `sys_audit_log` under the list's app segment, where
269+
* `RecordDetailView` mounts the page the platform serves for the ledger
270+
* (`sys_audit_log_detail`, `plugin-audit`). That page lists every ledger row
271+
* with the same `record_id`, so a `config_change` row, which carries only a
272+
* digest, leads to the CRUD mirror's rows that hold the value.
273+
*/
274+
function entryPageHref(listBase: string, rowId: string): string {
275+
return `${listBase}/record/${encodeURIComponent(rowId)}`;
276+
}
277+
264278
function truncate(s: string | null | undefined, n = 24): string {
265279
if (!s) return '—';
266280
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
@@ -478,6 +492,14 @@ export function AuditLogPage() {
478492
const listBase = `/apps/${encodeURIComponent(appName || 'setup')}/${AUDIT_OBJECT}`;
479493
const viewHref = (key: string) => `${listBase}/view/${encodeURIComponent(key)}`;
480494
const listHref = activePreset ? viewHref(activePreset.key) : listBase;
495+
// The way back the entry page's crumb row draws (`state.from`), so an app
496+
// whose navigation does not list `sys_audit_log` still leads back here.
497+
const location = useLocation();
498+
const pageTitle = t('console.system.auditLog.title', { defaultValue: 'Audit Log' });
499+
const entryLinkState = useMemo(
500+
() => ({ from: { pathname: `${location.pathname}${location.search}`, label: pageTitle } }),
501+
[location.pathname, location.search, pageTitle],
502+
);
481503

482504
return (
483505
<div>
@@ -487,7 +509,7 @@ export function AuditLogPage() {
487509
one row across the page above the padded body. Below `sm` the header
488510
hides the subtitle and keeps the icon, title and Refresh. */}
489511
<ConsolePageHeader
490-
title={t('console.system.auditLog.title', { defaultValue: 'Audit Log' })}
512+
title={pageTitle}
491513
subtitle={t('console.system.auditLog.subtitle', {
492514
defaultValue: 'Read-only system activity and record changes. Newest first.',
493515
})}
@@ -741,6 +763,24 @@ export function AuditLogPage() {
741763
</SheetDescription>
742764
</SheetHeader>
743765

766+
{selected?.id && (
767+
<div className="mt-4 space-y-1.5">
768+
<Button asChild variant="outline" size="sm">
769+
<Link to={entryPageHref(listBase, selected.id)} state={entryLinkState}>
770+
<ExternalLink className="h-4 w-4 mr-1" />
771+
{t('console.system.auditLog.openEntryPage', { defaultValue: 'Open entry page' })}
772+
</Link>
773+
</Button>
774+
{selected.record_id && (
775+
<p className="text-xs text-muted-foreground">
776+
{t('console.system.auditLog.openEntryPageHint', {
777+
defaultValue: 'The entry page lists every change to the same record, newest first.',
778+
})}
779+
</p>
780+
)}
781+
</div>
782+
)}
783+
744784
{selected && (
745785
<div className="mt-6 space-y-5">
746786
<div className="grid grid-cols-2 gap-3 text-sm">

‎ui/packages/i18n/src/locales/ar.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2925,6 +2925,8 @@ const ar = {
29252925
title: 'سجل التدقيق',
29262926
subtitle: 'نشاط النظام وتغييرات السجلات للقراءة فقط. الأحدث أولاً.',
29272927
openAsList: 'فتح كقائمة',
2928+
openEntryPage: 'فتح صفحة الإدخال',
2929+
openEntryPageHint: 'تعرض صفحة الإدخال كل التغييرات على السجل نفسه، الأحدث أولاً.',
29282930
openPresetAsList: 'فتح {{label}} كقائمة',
29292931
presets: 'إعدادات مسبقة',
29302932
presetOtherSource: 'يقرأ هذا العرض مصدر بيانات آخر.',

‎ui/packages/i18n/src/locales/de.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,6 +2671,8 @@ const de = {
26712671
title: 'Audit-Protokoll',
26722672
subtitle: 'Schreibgeschützte Systemaktivität und Datensatzänderungen. Neueste zuerst.',
26732673
openAsList: 'Als Liste öffnen',
2674+
openEntryPage: 'Eintragsseite öffnen',
2675+
openEntryPageHint: 'Die Eintragsseite listet alle Änderungen am selben Datensatz auf, die neueste zuerst.',
26742676
openPresetAsList: '{{label}} als Liste öffnen',
26752677
presets: 'Voreinstellungen',
26762678
presetOtherSource: 'Diese Ansicht liest eine andere Datenquelle.',

‎ui/packages/i18n/src/locales/en.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3236,6 +3236,8 @@ const en = {
32363236
title: 'Audit Log',
32373237
subtitle: 'Read-only system activity and record changes. Newest first.',
32383238
openAsList: 'Open as list',
3239+
openEntryPage: 'Open entry page',
3240+
openEntryPageHint: 'The entry page lists every change to the same record, newest first.',
32393241
openPresetAsList: 'Open {{label}} as list',
32403242
presets: 'Presets',
32413243
presetOtherSource: 'This view reads another data source.',

‎ui/packages/i18n/src/locales/es.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,6 +2736,8 @@ const es = {
27362736
title: 'Registro de auditoría',
27372737
subtitle: 'Actividad del sistema y cambios de registros, de solo lectura. Lo más reciente primero.',
27382738
openAsList: 'Abrir como lista',
2739+
openEntryPage: 'Abrir la página de la entrada',
2740+
openEntryPageHint: 'La página de la entrada muestra todos los cambios del mismo registro, del más reciente al más antiguo.',
27392741
openPresetAsList: 'Abrir {{label}} como lista',
27402742
presets: 'Ajustes predefinidos',
27412743
presetOtherSource: 'Esta vista lee otra fuente de datos.',

‎ui/packages/i18n/src/locales/fr.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,6 +2732,8 @@ const fr = {
27322732
title: 'Journal d’audit',
27332733
subtitle: 'Activité système et modifications d’enregistrements, en lecture seule. Les plus récentes en premier.',
27342734
openAsList: 'Ouvrir en liste',
2735+
openEntryPage: 'Ouvrir la page de l’entrée',
2736+
openEntryPageHint: 'La page de l’entrée liste toutes les modifications du même enregistrement, de la plus récente à la plus ancienne.',
27352737
openPresetAsList: 'Ouvrir {{label}} en liste',
27362738
presets: 'Préréglages',
27372739
presetOtherSource: 'Cette vue lit une autre source de données.',

‎ui/packages/i18n/src/locales/ja.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,6 +2673,8 @@ const ja = {
26732673
title: '監査ログ',
26742674
subtitle: '読み取り専用のシステムアクティビティとレコード変更。新しい順に表示します。',
26752675
openAsList: 'リストで開く',
2676+
openEntryPage: 'エントリページを開く',
2677+
openEntryPageHint: 'エントリページには、同じレコードへのすべての変更が新しい順に表示されます。',
26762678
openPresetAsList: '{{label}} をリストで開く',
26772679
presets: 'プリセット',
26782680
presetOtherSource: 'このビューは別のデータソースを読み取ります。',

‎ui/packages/i18n/src/locales/ko.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,6 +2671,8 @@ const ko = {
26712671
title: '감사 로그',
26722672
subtitle: '읽기 전용 시스템 활동 및 레코드 변경 내역입니다. 최신 항목이 먼저 표시됩니다.',
26732673
openAsList: '목록으로 열기',
2674+
openEntryPage: '항목 페이지 열기',
2675+
openEntryPageHint: '항목 페이지에는 같은 레코드의 모든 변경 사항이 최신순으로 표시됩니다.',
26742676
openPresetAsList: '{{label}}을(를) 목록으로 열기',
26752677
presets: '프리셋',
26762678
presetOtherSource: '이 뷰는 다른 데이터 소스를 읽습니다.',

0 commit comments

Comments
 (0)