From c5c9941768fe5048cce8990f280bbdd7f1d75e7b Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 13 Sep 2026 19:41:25 +0200 Subject: [PATCH] feat(hk): explain background updates with protected content links and retry details --- e2e/ui/deliveries.spec.ts | 126 ++++++++++++++++++ e2e/ui/fixture-server.mjs | 2 + e2e/ui/fixtures/deliveries-harness.tsx | 119 +++++++++++++++++ e2e/ui/fixtures/entry.tsx | 21 ++- e2e/ui/fixtures/server-actions.ts | 3 + e2e/ui/studio.spec.ts | 99 ++++++++++++++ integration/database.test.ts | 19 ++- src/actions/admin-articles.test.ts | 14 ++ src/actions/admin-articles.ts | 13 +- src/app/admin/devops/deliveries/page.tsx | 75 +++++++---- src/features/operations/README.md | 11 +- src/features/operations/delivery-card.tsx | 105 +++++++++++++++ .../operations/delivery-context.test.ts | 93 +++++++++++++ src/features/operations/delivery-context.ts | 56 ++++++++ src/features/operations/server.ts | 6 +- src/lib/services/news-scheduler.test.ts | 20 ++- src/lib/services/news-scheduler.ts | 5 +- src/messages/ar.json | 36 ++++- src/messages/bg.json | 36 ++++- src/messages/cs.json | 36 ++++- src/messages/da.json | 36 ++++- src/messages/de.json | 36 ++++- src/messages/el.json | 36 ++++- src/messages/en.json | 36 ++++- src/messages/es.json | 36 ++++- src/messages/fi.json | 36 ++++- src/messages/fr.json | 36 ++++- src/messages/hr.json | 36 ++++- src/messages/hu.json | 36 ++++- src/messages/it.json | 36 ++++- src/messages/ja.json | 36 ++++- src/messages/nl.json | 36 ++++- src/messages/no.json | 36 ++++- src/messages/pl.json | 36 ++++- src/messages/pt.json | 36 ++++- src/messages/ro.json | 36 ++++- src/messages/ru.json | 36 ++++- src/messages/sk.json | 36 ++++- src/messages/sr.json | 36 ++++- src/messages/sv.json | 36 ++++- src/messages/tr.json | 36 ++++- src/messages/uk.json | 36 ++++- 42 files changed, 1542 insertions(+), 145 deletions(-) create mode 100644 e2e/ui/deliveries.spec.ts create mode 100644 e2e/ui/fixtures/deliveries-harness.tsx create mode 100644 src/features/operations/delivery-card.tsx create mode 100644 src/features/operations/delivery-context.test.ts create mode 100644 src/features/operations/delivery-context.ts diff --git a/e2e/ui/deliveries.spec.ts b/e2e/ui/deliveries.spec.ts new file mode 100644 index 00000000..282060ce --- /dev/null +++ b/e2e/ui/deliveries.spec.ts @@ -0,0 +1,126 @@ +import { expect, test } from "@playwright/test"; + +test("deliveries explain outcomes without exposing unauthorized content or raw metadata", async ({ + page, +}, testInfo) => { + const errors: string[] = []; + page.on("pageerror", (error) => errors.push(error.message)); + await page.goto("/admin/deliveries-harness"); + const review = page.getByRole("region", { name: "Delivery review" }); + const cards = review.getByRole("article"); + await expect(cards).toHaveCount(6); + const news = cards.nth(0); + await expect(news.getByRole("heading", { level: 2 })).toContainText( + "Community update: SummerFestival", + ); + await expect(news.getByText("Waiting", { exact: true })).toBeVisible(); + await expect( + news.getByText( + "The content is saved. This update runs in the background.", + { exact: true }, + ), + ).toBeVisible(); + await expect(news.getByRole("status")).toContainText( + "The news cache could not be refreshed.", + ); + await expect(news.getByRole("status")).toContainText( + "Next automatic attempt: Jan 1, 2030, 12:05 PM", + ); + await expect( + news.getByRole("link", { name: "Open content", exact: true }), + ).toHaveAttribute("href", "/admin/articles/9007199254740993"); + await expect( + news.getByRole("link", { name: "Service diagnostics", exact: true }), + ).toHaveAttribute("href", "/admin/devops/installation"); + const failed = cards.nth(1); + await expect( + failed.getByRole("heading", { name: "9 catalog offers", exact: true }), + ).toBeVisible(); + await expect( + failed.getByText("Needs attention", { exact: true }), + ).toBeVisible(); + await expect(failed.getByRole("status")).toContainText( + "Check the emulator connection.", + ); + await expect(failed.getByRole("status")).toContainText( + "Automatic attempts are exhausted.", + ); + await expect( + failed.getByRole("link", { name: "Open content", exact: true }), + ).toHaveAttribute("href", "/admin/catalog"); + await expect( + failed.getByRole("button", { name: "Retry delivery", exact: true }), + ).toBeVisible(); + for (const card of [cards.nth(2), cards.nth(3)]) { + await expect( + card.getByRole("heading", { + name: "Content details unavailable", + exact: true, + }), + ).toBeVisible(); + await expect( + card.getByRole("link", { name: "Open content", exact: true }), + ).toHaveCount(0); + await expect( + card.getByRole("button", { name: "Retry delivery", exact: true }), + ).toHaveCount(0); + } + const unknown = cards.nth(4); + await expect( + unknown.getByText("Other background update", { exact: true }), + ).toBeVisible(); + await expect( + unknown.getByText("Unknown status", { exact: true }), + ).toBeVisible(); + await expect(unknown).toContainText("Time unavailable"); + await expect(unknown).toContainText("System"); + await expect(cards.nth(5)).toContainText( + "Check the Git export history for the final result; disabled export does not publish files.", + ); + await expect(review).not.toContainText( + /RESTRICTED_NEWS_TITLE|SERIALIZED_METADATA_SECRET|INTERNAL_FILE_PATH_SECRET|UNKNOWN_OPERATION_SECRET|489 catalog offers|resultJson|articleId/, + ); + await expect(review.locator("details[open]")).toHaveCount(0); + await expect( + news.getByText("Operation: operation-authorized-news-1234567890", { + exact: true, + }), + ).toBeHidden(); + await expect( + news.getByText("Delivery: delivery-authorized-news-1234567890", { + exact: true, + }), + ).toBeHidden(); + await news.locator("summary").click(); + await expect( + news.getByText("Operation: operation-authorized-news-1234567890", { + exact: true, + }), + ).toBeVisible(); + await expect( + news.getByText("Delivery: delivery-authorized-news-1234567890", { + exact: true, + }), + ).toBeVisible(); + await news.locator("summary").click(); + await expect( + news.getByText("Operation: operation-authorized-news-1234567890", { + exact: true, + }), + ).toBeHidden(); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + ).toBe(true); + const title = news.getByRole("heading", { level: 2 }); + expect( + await title.evaluate((node) => node.scrollWidth <= node.clientWidth), + ).toBe(true); + await page.evaluate(() => window.scrollTo(0, 0)); + await testInfo.attach("delivery-cards", { + body: await page.screenshot({ fullPage: true }), + contentType: "image/png", + }); + expect(errors).toEqual([]); +}); diff --git a/e2e/ui/fixture-server.mjs b/e2e/ui/fixture-server.mjs index 972ae263..fa6bdd86 100644 --- a/e2e/ui/fixture-server.mjs +++ b/e2e/ui/fixture-server.mjs @@ -15,6 +15,7 @@ const port = Number(process.env.UI_TEST_PORT || 3107); if (!Number.isInteger(port) || port < 1024 || port > 65535) throw Error("Invalid UI_TEST_PORT"); const actions = new Set([ + "@/actions/catalog", "@/actions/import-furni", "@/actions/events", "@/actions/admin-settings", @@ -199,6 +200,7 @@ const server = createServer(async (req, res) => { [ "/notifications", "/admin/unsaved-harness", + "/admin/deliveries-harness", "/admin/attachment-harness", "/admin/studio-harness", "/admin/jobs-harness", diff --git a/e2e/ui/fixtures/deliveries-harness.tsx b/e2e/ui/fixtures/deliveries-harness.tsx new file mode 100644 index 00000000..75ef7e05 --- /dev/null +++ b/e2e/ui/fixtures/deliveries-harness.tsx @@ -0,0 +1,119 @@ +import { useTranslations } from "next-intl"; +import { Button } from "@/components/ui/button"; +import { + DeliveryCard, + type DeliveryCardItem, +} from "@/features/operations/delivery-card"; +import { deliveryContext } from "@/features/operations/delivery-context"; + +const shared: Omit = { + topic: "news.refresh", + status: "pending", + attempts: 2, + actorId: 7, + createdAt: "2030-01-01T11:00:00Z", + availableAt: "2030-01-01T12:05:00Z", + hasError: true, +}; +const records: DeliveryCardItem[] = [ + { + ...shared, + id: "delivery-authorized-news-1234567890", + operationId: "operation-authorized-news-1234567890", + context: deliveryContext( + "news.update", + JSON.stringify({ + articleId: "9007199254740993", + articleTitle: `Community update: ${"SummerFestival".repeat(14)}`, + internalNotes: "SERIALIZED_METADATA_SECRET", + lastError: "INTERNAL_FILE_PATH_SECRET", + }), + { news: true, catalog: false }, + ), + }, + { + ...shared, + id: "delivery-failed-catalog-1234567890", + operationId: "operation-failed-catalog-1234567890", + topic: "catalog.refresh", + status: "failed", + attempts: 5, + context: deliveryContext("catalog.bulk.apply", '{"changedCount":9}', { + news: false, + catalog: true, + }), + }, + { + ...shared, + id: "delivery-restricted-news", + operationId: "operation-restricted-news", + hasError: false, + context: deliveryContext( + "news.update", + '{"articleTitle":"RESTRICTED_NEWS_TITLE","articleId":"99"}', + { news: false, catalog: true }, + ), + }, + { + ...shared, + id: "delivery-restricted-catalog", + operationId: "operation-restricted-catalog", + topic: "catalog.refresh", + hasError: false, + context: deliveryContext("catalog.bulk.apply", '{"changedCount":489}', { + news: true, + catalog: false, + }), + }, + { + ...shared, + id: "delivery-unknown", + operationId: "operation-unknown", + topic: "future.topic", + status: "future.status", + actorId: 0, + createdAt: "invalid", + context: deliveryContext( + "future.operation", + '{"articleTitle":"UNKNOWN_OPERATION_SECRET"}', + { news: true, catalog: true }, + ), + }, + { + ...shared, + id: "delivery-completed-export", + operationId: "operation-completed-export", + topic: "catalog.export.request", + status: "done", + hasError: false, + context: deliveryContext("catalog.bulk.apply", '{"changedCount":4}', { + news: false, + catalog: true, + }), + }, +]; + +export function DeliveriesHarness() { + const t = useTranslations("pages.admin.deliveries"); + return ( +
+

{t("scope")}

+
    + {records.map((item) => ( +
  • + + {t("retry")} + + ) : undefined + } + /> +
  • + ))} +
+
+ ); +} diff --git a/e2e/ui/fixtures/entry.tsx b/e2e/ui/fixtures/entry.tsx index caffff6c..38033824 100644 --- a/e2e/ui/fixtures/entry.tsx +++ b/e2e/ui/fixtures/entry.tsx @@ -7,6 +7,7 @@ import { CmsSettingsForm } from "@/app/admin/settings/cms-settings-form"; import { TicketsTable } from "@/app/admin/tickets/tickets-table"; import { ArticleForm } from "@/components/admin/article-form"; import { FurnitureSyncQueue } from "@/components/admin/studio/furniture-sync-queue"; +import { OrganizeImportsLauncher } from "@/components/admin/studio/organize-imports-launcher"; import { StudioClient } from "@/components/admin/studio/studio-client"; import { NotificationCenter } from "@/features/notifications/notification-center"; import type { ArticleSaveResult } from "@/lib/article-input"; @@ -15,6 +16,7 @@ import { themePaletteCss } from "@/lib/theme-css"; import { PRESETS } from "@/lib/theme-presets"; import messages from "@/messages/en.json"; import { AttachmentHarness } from "./attachment-harness"; +import { DeliveriesHarness } from "./deliveries-harness"; import { FurnitureJobsHarness } from "./furniture-jobs-harness"; import { fixtureAction } from "./server-actions"; import { UnsavedHarness } from "./unsaved-harness"; @@ -120,18 +122,25 @@ createRoot(root).render( ? "Support desk" : route.includes("articles") ? "News editor" - : "Furniture synchronization"} + : route.includes("deliveries-harness") + ? "Background updates" + : "Furniture synchronization"} {route.includes("notifications") ? ( + ) : route.includes("deliveries-harness") ? ( + ) : route.includes("unsaved-harness") ? ( ) : route.includes("studio-harness") ? ( - + <> + + + ) : route.includes("attachment-harness") ? ( ) : route.includes("events/recovery") ? ( diff --git a/e2e/ui/fixtures/server-actions.ts b/e2e/ui/fixtures/server-actions.ts index 254599f3..70b45571 100644 --- a/e2e/ui/fixtures/server-actions.ts +++ b/e2e/ui/fixtures/server-actions.ts @@ -32,3 +32,6 @@ export const deleteImportedFurni = (input: unknown) => fixtureAction("delete-imported-furni", input); export const setFurnidataTranslateEnabled = (input: unknown) => fixtureAction("furnidata-translation", input); + +export const organizeImportFurni = (input: unknown) => + fixtureAction("organize-import-furni", input); diff --git a/e2e/ui/studio.spec.ts b/e2e/ui/studio.spec.ts index 69f592a1..d396e2b9 100644 --- a/e2e/ui/studio.spec.ts +++ b/e2e/ui/studio.spec.ts @@ -205,3 +205,102 @@ test("Studio opens and dismisses detail, Nitro editor and import review", async expect(mutations).toEqual([]); expect(errors).toEqual([]); }); + +test("Studio loads organize imports on demand and preserves the dialog on reopen", async ({ + page, +}, testInfo) => { + const errors: string[] = []; + const mutations: string[] = []; + let organizeRequests = 0; + page.on("pageerror", (error) => errors.push(error.message)); + await page.route("**/fixture/actions/**", async (route) => { + mutations.push(route.request().url()); + await route.fulfill({ + status: 405, + json: { error: "No mutations expected" }, + }); + }); + await page.route("**/api/**", async (route) => { + const request = route.request(); + const url = new URL(request.url()); + if (request.method() !== "GET") { + mutations.push(`${request.method()} ${url.pathname}`); + return route.fulfill({ + status: 405, + json: { error: "No mutations expected" }, + }); + } + if (url.pathname === "/api/admin/import/organize") { + organizeRequests++; + return route.fulfill({ + json: { + groups: [ + { + name: "Fixture imports", + icon: 1, + layout: "default_3x3", + total: 1, + moved: 0, + added: 1, + thumbs: [], + items: [{ itemId: 1, catalogItemId: null, alreadyPlaced: false }], + }, + ], + importRootPageId: null, + importRootCaption: null, + }, + }); + } + if (url.pathname === "/api/admin/catalog/tree") + return route.fulfill({ json: { pages: [] } }); + if (url.pathname === "/api/admin/import/furni") + return route.fulfill({ + json: + url.searchParams.get("action") === "stats" + ? { totalInDb: 0, inCatalog: 0, notInCatalog: 0, missingNitro: 0 } + : { + items: [], + meta: { currentPage: 1, lastPage: 1, total: 0, perPage: 20 }, + }, + }); + if (url.pathname === "/api/admin/import/clone") + return route.fulfill({ json: { sources: [] } }); + if (url.pathname === "/api/admin/studio/import-jobs") + return route.fulfill({ json: { ok: true, jobs: [], nextCursor: null } }); + return route.fulfill({ + status: 404, + json: { error: "Unknown fixture endpoint" }, + }); + }); + await page.goto("/admin/studio-harness"); + const trigger = page.getByRole("button", { + name: "Organize imports", + exact: true, + }); + await expect(trigger).toBeVisible(); + expect(organizeRequests).toBe(0); + await trigger.click(); + const dialog = page.getByRole("dialog", { name: /^Organize imports/ }); + await expect(dialog).toBeVisible(); + const filter = dialog.getByPlaceholder("Filter import groups..."); + await expect(filter).toBeVisible(); + await expect(dialog.locator('input[value="Fixture imports"]')).toBeVisible(); + await filter.fill("Fixture"); + expect(organizeRequests).toBe(1); + await testInfo.attach("organize-imports", { + body: await page.screenshot(), + contentType: "image/png", + }); + await dialog.getByRole("button", { name: "Cancel", exact: true }).click(); + await expect(dialog).toHaveCount(0); + await expect(trigger).toBeFocused(); + await trigger.click(); + await expect(dialog).toBeVisible(); + await expect(filter).toHaveValue("Fixture"); + await expect.poll(() => organizeRequests).toBe(2); + await page.keyboard.press("Escape"); + await expect(dialog).toHaveCount(0); + await expect(trigger).toBeFocused(); + expect(mutations).toEqual([]); + expect(errors).toEqual([]); +}); diff --git a/integration/database.test.ts b/integration/database.test.ts index beb0f8ff..da91e6c5 100644 --- a/integration/database.test.ts +++ b/integration/database.test.ts @@ -575,6 +575,13 @@ describe("real news publication, scheduling and cache delivery", () => { ); expect(boundaries.notify).not.toHaveBeenCalled(); const existing = await savedArticle(); + const [creation] = await rows( + "SELECT result_json FROM cms_operations WHERE kind='news.create'", + ); + expect(JSON.parse(creation.result_json)).toMatchObject({ + articleId: String(existing.id), + articleTitle: existing.title, + }); expect(existing.status).toBe("draft"); expect(existing.publishedAt).toBeNull(); expect(await publicNews.getPublishedArticle(existing.slug)).toBeNull(); @@ -755,12 +762,20 @@ describe("real news publication, scheduling and cache delivery", () => { { actor: 0, kind: "news.schedule.publish", - result: { articleId: "9007199254740993", published: true }, + result: { + articleId: "9007199254740993", + articleTitle: "due-now", + published: true, + }, }, { actor: 7, kind: "news.schedule.publish", - result: { articleId: "1", published: true }, + result: { + articleId: "1", + articleTitle: "due-earlier", + published: true, + }, }, ]); expect(await rows("SELECT topic,status FROM cms_outbox")).toEqual([ diff --git a/src/actions/admin-articles.test.ts b/src/actions/admin-articles.test.ts index 876f6bc2..fa9ad3b6 100644 --- a/src/actions/admin-articles.test.ts +++ b/src/actions/admin-articles.test.ts @@ -51,6 +51,8 @@ vi.mock("@/lib/db", async () => { requestHash: String(hash), resultJson: null, }); + } else if (sql.startsWith("SELECT CAST(LAST_INSERT_ID() AS CHAR)")) { + return [[{ id: "9007199254740993" }]]; } else if (sql.startsWith("SELECT id,request_hash")) { return [[state.operations.get(params.join(":"))]]; } else if (sql.startsWith("UPDATE cms_operations")) { @@ -121,6 +123,18 @@ describe("news saves", () => { expect(state.notify).toHaveBeenCalledOnce(); expect(state.effects).toHaveLength(1); }); + it("stores safe article context for background deliveries without the body", async () => { + expect((await createArticle(form())).ok).toBe(true); + const result = JSON.parse( + [...state.operations.values()][0].resultJson ?? "null", + ); + expect(result).toEqual({ + ok: true, + articleId: "9007199254740993", + articleTitle: "Test news", + }); + expect(result).not.toHaveProperty("fullStory"); + }); it("returns success when cache refresh fails after the article commits", async () => { state.invalidate.mockRejectedValue(Error("cache unavailable")); await expect(createArticle(form())).resolves.toMatchObject({ ok: true }); diff --git a/src/actions/admin-articles.ts b/src/actions/admin-articles.ts index 628a7e43..024deb1b 100644 --- a/src/actions/admin-articles.ts +++ b/src/actions/admin-articles.ts @@ -1,7 +1,7 @@ "use server"; import { createHash, randomUUID } from "node:crypto"; -import { and, eq, ne } from "drizzle-orm"; +import { and, eq, ne, sql } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; @@ -128,6 +128,13 @@ export async function createArticle( updatedAt: now, publishedAt: fields.status === "published" ? now : null, }); + const [createdRows] = await tx.execute( + sql`SELECT CAST(LAST_INSERT_ID() AS CHAR) AS id`, + ); + const articleId = (createdRows as unknown as Array<{ id: string }>)[0] + ?.id; + if (!articleId || !/^[1-9][0-9]*$/.test(articleId)) + throw new Error("Article identity unavailable"); await enqueueEffect(tx, operationId, "news.refresh"); if (input.status === "published") notification = { @@ -136,7 +143,7 @@ export async function createArticle( target: input.title, details: slug, }; - return { ok: true }; + return { ok: true, articleId, articleTitle: input.title }; }, ); } catch (error) { @@ -219,7 +226,7 @@ export async function updateArticle( .where(eq(WebsiteArticles.id, id)); await recordHistory(tx, "news", historyId, staff.id, historyBefore); await enqueueEffect(tx, operationId, "news.refresh"); - return { ok: true }; + return { ok: true, articleId: String(id), articleTitle: input.title }; }, ); } catch (error) { diff --git a/src/app/admin/devops/deliveries/page.tsx b/src/app/admin/devops/deliveries/page.tsx index 972d76f2..97addde7 100644 --- a/src/app/admin/devops/deliveries/page.tsx +++ b/src/app/admin/devops/deliveries/page.tsx @@ -1,6 +1,8 @@ import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import Link from "@/components/link"; +import { DeliveryCard } from "@/features/operations/delivery-card"; +import { deliveryContext } from "@/features/operations/delivery-context"; import { DeliveryRetry } from "@/features/operations/retry-button"; import { listEffects } from "@/features/operations/server"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; @@ -9,41 +11,62 @@ export default async function DeliveryPage() { if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank)) redirect("/admin"); const editable = canAccess(permissions, PERMS.DEVOPS_EDIT, session.user.rank); + const access = { + news: canAccess(permissions, PERMS.NEWS_VIEW, session.user.rank), + catalog: canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank), + }; const t = await getTranslations("pages.admin.deliveries"); - const items = await listEffects(); + let items: Awaited> | null = null; + try { + items = await listEffects(); + } catch { + /* Preserve the distinction between unavailable and empty history. */ + } + const timestamp = (value: Date | string) => + value instanceof Date ? value.toISOString() : value; return (

{t("title")}

{t("scope")}

- - {t("refresh")} - - {!items.length ? ( +
+ + {t("refresh")} + + {canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank) && ( + + {t("openImports")} + + )} +
+ {items === null ? ( +

+ {t("unavailable")} +

+ ) : !items.length ? (

{t("empty")}

) : (
    {items.map((item) => ( -
  • -
    - - {t( - item.topic === "catalog.refresh" - ? "hotel" - : item.topic === "news.refresh" - ? "news" - : "export", - )} - - {t(`states.${item.status}`)} -
    -

    - {item.operationId} · {t("actor", { id: item.actorId })} ·{" "} - {t("attempts", { count: item.attempts })} -

    - {item.status === "failed" &&

    {t("retryError")}

    } - {editable && item.status === "failed" && ( - - )} +
  • + + ) : undefined + } + />
  • ))}
diff --git a/src/features/operations/README.md b/src/features/operations/README.md index ebcf4a4e..8e1c53f4 100644 --- a/src/features/operations/README.md +++ b/src/features/operations/README.md @@ -1,9 +1,16 @@ -# Durable catalog operations +# Durable operations and background updates Bulk offer apply and undo accept a request UUID. The authenticated actor, operation kind and UUID identify one mutation. A canonical payload hash rejects reuse for different changes. The result and outbox entries commit in the same database transaction as the offers and audit history; replay returns the stored result. The jobs worker claims pending effects under a database lock with a 120-second lease. A claim token protects completion from stale workers. Failed delivery uses exponential retry, stopping after eight attempts. `/admin/devops/deliveries` shows the latest 100 effects to DEVOPS_VIEW; DEVOPS_EDIT can retry failed effects without replaying the catalog mutation. -Delivery is at least once: a crash after sending and before acknowledgement may repeat an effect. Catalog refresh is repeatable; export delivery means a request entered the existing export queue, not that Git publication or client refresh completed. Export disabled by configuration remains a no-op. This first adapter covers bulk offer apply/undo, not every CMS mutation. +Delivery is at least once: a crash after sending and before acknowledgement may repeat an effect. Catalog refresh is repeatable; export delivery means a request entered the existing export queue, not that Git publication or client refresh completed. Export disabled by configuration remains a no-op. Adapters cover bulk offer apply/undo and manual/scheduled news cache refresh, not every CMS mutation. Migration0031 is required before the updated worker starts. Unit tests cover payload identity, dispatch limits, retries and authorization; the separate Docker integration suite covers concurrent SQL requests, transaction rollback and claim ownership. No production database was used for local tests. + + +The delivery screen separates saved content from its subsequent background update. New news operations persist only the article identifier and title alongside the existing result; no article body is stored for display. Article IDs remain strings to preserve bigint precision. Older records retain readable status and technical references even when no content metadata exists. Catalog operations show affected-offer counts; furniture import progress remains in the linked import history. + +Content titles and links require the corresponding NEWS_VIEW or CATALOG_VIEW grant in addition to DEVOPS_VIEW. Raw result JSON and internal error text are never forwarded to the card. Each record shows its recorded time, staff/system actor, attempts, and scheduled retry time when applicable. Failures point to service diagnostics; exhausted attempts expose the existing permission-protected retry without repeating the content mutation. Delivery success does not imply Git publication or client refresh acknowledgement. + +Browser fixtures verify the real cards on desktop/mobile, including long titles, denied metadata, exact links, unknown states and collapsed technical references. The fixture retry slot does not execute a backend action; backend authorization and state changes have separate tests. diff --git a/src/features/operations/delivery-card.tsx b/src/features/operations/delivery-card.tsx new file mode 100644 index 00000000..ae8d1b22 --- /dev/null +++ b/src/features/operations/delivery-card.tsx @@ -0,0 +1,105 @@ +import { useFormatter, useTranslations } from "next-intl"; +import type { ReactNode } from "react"; +import Link from "@/components/link"; +import { deliveryTopic } from "./delivery-context"; + +export interface DeliveryCardItem { + id: string; + operationId: string; + topic: string; + status: string; + attempts: number; + actorId: number; + createdAt: string; + availableAt: string; + hasError: boolean; + context: { + title: string | null; + href: string | null; + changedCount: number | null; + }; +} +export function DeliveryCard({ + item, + retry, +}: { + item: DeliveryCardItem; + retry?: ReactNode; +}) { + const t = useTranslations("pages.admin.deliveries"); + const format = useFormatter(); + const topic = deliveryTopic(item.topic); + const state = ["pending", "running", "done", "failed"].includes(item.status) + ? item.status + : "unknown"; + const date = (value: string) => { + const parsed = new Date(value); + return Number.isNaN(parsed.getTime()) + ? t("unknownTime") + : format.dateTime(parsed, { dateStyle: "medium", timeStyle: "short" }); + }; + return ( +
+
+
+

{t(topic)}

+

+ {item.context.title ?? + (item.context.changedCount !== null + ? t("changedOffers", { count: item.context.changedCount }) + : t("contextUnavailable"))} +

+
+ + {t(`states.${state}`)} + +
+

+ {date(item.createdAt)} ·{" "} + {item.actorId === 0 ? t("system") : t("actor", { id: item.actorId })} ·{" "} + {t("attempts", { count: item.attempts })} +

+ {state === "done" ? ( +

{t(`completed.${topic}`)}

+ ) : ( +

{t("savedAlready")}

+ )} + {item.hasError && state !== "done" && ( +
+

{t(`issues.${topic}`)}

+ {state === "pending" && ( +

+ {t("nextAttempt", { time: date(item.availableAt) })} +

+ )} + {state === "failed" && ( +

{t("attemptsExhausted")}

+ )} +
+ )} +
+ {item.context.href && ( + + {t("openContent")} + + )} + {item.hasError && state !== "done" && ( + + {t("diagnostics")} + + )} + {retry} +
+
+ {t("technicalDetails")} +

+ {t("operationReference", { id: item.operationId })} +

+

{t("deliveryReference", { id: item.id })}

+
+
+ ); +} diff --git a/src/features/operations/delivery-context.test.ts b/src/features/operations/delivery-context.test.ts new file mode 100644 index 00000000..1046c64d --- /dev/null +++ b/src/features/operations/delivery-context.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from "vitest"; +import { deliveryContext, deliveryTopic } from "./delivery-context"; + +describe("delivery context", () => { + it("links a news record without rounding a bigint identifier", () => { + expect( + deliveryContext( + "news.update", + JSON.stringify({ + articleId: "9007199254740993", + articleTitle: "A community update", + }), + { news: true, catalog: false }, + ), + ).toEqual({ + title: "A community update", + href: "/admin/articles/9007199254740993", + changedCount: null, + }); + }); + it("does not expose news metadata without news permission", () => { + expect( + deliveryContext( + "news.create", + JSON.stringify({ + articleTitle: "Private draft", + articleSlug: "private", + }), + { news: false, catalog: true }, + ), + ).toEqual({ title: null, href: null, changedCount: null }); + }); + it("supports searchable new articles and legacy scheduled records", () => { + expect( + deliveryContext( + "news.create", + JSON.stringify({ + articleTitle: "Hello & welcome", + articleSlug: "hello", + }), + { news: true, catalog: false }, + ).href, + ).toBe("/admin/articles?search=Hello+%26+welcome"); + expect( + deliveryContext( + "news.schedule.publish", + '{"articleId":"42","published":true}', + { news: true, catalog: false }, + ).href, + ).toBe("/admin/articles/42"); + }); + it.each([ + null, + "{broken", + "[]", + '{"articleId":"../../settings","articleTitle":42}', + '"value"', + ])("handles missing or invalid historical context: %s", (result) => { + expect( + deliveryContext("news.update", result, { news: true, catalog: false }), + ).toEqual({ title: null, href: "/admin/articles", changedCount: null }); + }); + it("exposes only known bounded catalog counts with catalog permission", () => { + expect( + deliveryContext("catalog.bulk.apply", '{"changedCount":8}', { + news: false, + catalog: true, + }), + ).toEqual({ title: null, href: "/admin/catalog", changedCount: 8 }); + expect( + deliveryContext("catalog.bulk.apply", '{"changedCount":8}', { + news: true, + catalog: false, + }).changedCount, + ).toBeNull(); + expect( + deliveryContext("catalog.bulk.undo", '{"changedCount":-1}', { + news: false, + catalog: true, + }).changedCount, + ).toBeNull(); + }); + it("does not interpret unknown operations or topics as export", () => { + expect( + deliveryContext("future.operation", '{"articleTitle":"secret"}', { + news: true, + catalog: true, + }), + ).toEqual({ title: null, href: null, changedCount: null }); + expect(deliveryTopic("unknown.topic")).toBe("unknown"); + expect(deliveryTopic("news.refresh")).toBe("news"); + }); +}); diff --git a/src/features/operations/delivery-context.ts b/src/features/operations/delivery-context.ts new file mode 100644 index 00000000..8ff0bb3d --- /dev/null +++ b/src/features/operations/delivery-context.ts @@ -0,0 +1,56 @@ +/** Public presentation data only: never forward a stored operation payload to the browser. */ +export function deliveryContext( + kind: string, + serialized: string | null, + access: { news: boolean; catalog: boolean }, +): { title: string | null; href: string | null; changedCount: number | null } { + const context = { + title: null as string | null, + href: null as string | null, + changedCount: null as number | null, + }; + let result: Record = {}; + if (serialized && serialized.length <= 1_048_576) { + try { + const parsed: unknown = JSON.parse(serialized); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) + result = parsed as Record; + } catch { + /* Older records may not contain presentation metadata. */ + } + } + if ( + ["news.create", "news.update", "news.schedule.publish"].includes(kind) && + access.news + ) { + context.href = "/admin/articles"; + if (typeof result.articleTitle === "string" && result.articleTitle.trim()) { + context.title = result.articleTitle.trim().slice(0, 255); + context.href = `/admin/articles?${new URLSearchParams({ search: context.title.slice(0, 191) })}`; + } + if ( + typeof result.articleId === "string" && + /^[1-9][0-9]{0,19}$/.test(result.articleId) + ) + context.href = `/admin/articles/${result.articleId}`; + } else if ( + ["catalog.bulk.apply", "catalog.bulk.undo"].includes(kind) && + access.catalog + ) { + context.href = "/admin/catalog"; + if ( + typeof result.changedCount === "number" && + Number.isSafeInteger(result.changedCount) && + result.changedCount >= 0 && + result.changedCount <= 500 + ) + context.changedCount = result.changedCount; + } + return context; +} +export function deliveryTopic(topic: string) { + if (topic === "catalog.refresh") return "hotel"; + if (topic === "catalog.export.request") return "export"; + if (topic === "news.refresh") return "news"; + return "unknown"; +} diff --git a/src/features/operations/server.ts b/src/features/operations/server.ts index 0abb91f8..f39b9d17 100644 --- a/src/features/operations/server.ts +++ b/src/features/operations/server.ts @@ -90,7 +90,7 @@ export const effectRepository = { }; export async function listEffects() { const [rows] = await db.execute( - sql`SELECT e.id,e.operation_id AS operationId,e.topic,e.status,e.attempts,e.last_error AS lastError,e.created_at AS createdAt,o.actor_id AS actorId,o.kind FROM cms_outbox e JOIN cms_operations o ON o.id=e.operation_id ORDER BY e.created_at DESC,e.id DESC LIMIT 100`, + sql`SELECT e.id,e.operation_id AS operationId,e.topic,e.status,e.attempts,e.last_error AS lastError,e.created_at AS createdAt,e.available_at AS availableAt,o.actor_id AS actorId,o.kind,o.result_json AS resultJson FROM cms_outbox e JOIN cms_operations o ON o.id=e.operation_id ORDER BY e.created_at DESC,e.id DESC LIMIT 100`, ); return rows as unknown as Array<{ id: string; @@ -99,7 +99,9 @@ export async function listEffects() { status: string; attempts: number; lastError: string | null; - createdAt: string; + createdAt: Date | string; + availableAt: Date | string; + resultJson: string | null; actorId: number; kind: string; }>; diff --git a/src/lib/services/news-scheduler.test.ts b/src/lib/services/news-scheduler.test.ts index 231fc961..685e19c5 100644 --- a/src/lib/services/news-scheduler.test.ts +++ b/src/lib/services/news-scheduler.test.ts @@ -7,6 +7,7 @@ const state = vi.hoisted(() => ({ articles: [] as Array<{ id: string; userId: number | null; + title?: string; status: string; publishAt: Date; }>, @@ -146,8 +147,13 @@ it("uses the same UTC cutoff and publication timestamps regardless of the MySQL ); expect(statements).toHaveLength(2); for (const statement of statements) { - expect(format(statement.sql, statement.params, false, "+02:00")).toBe( - format(statement.sql, statement.params, false, "-07:00"), + const values = statement.params.map((value) => { + if (typeof value !== "string") + throw new Error("Expected explicit SQL string parameters"); + return value; + }); + expect(format(statement.sql, values, false, "+02:00")).toBe( + format(statement.sql, values, false, "-07:00"), ); } expect(statements[0].params).toEqual(["2030-06-01 08:09:10.123"]); @@ -158,3 +164,13 @@ it("uses the same UTC cutoff and publication timestamps regardless of the MySQL "2030-06-01 08:09:10.123", ]); }); + +it("records the article title and exact identifier for the operation screen", async () => { + state.articles[0].title = "Scheduled community news"; + await publishDueArticles(now); + expect(JSON.parse(String(state.operations[0][5]))).toEqual({ + articleId: "1", + articleTitle: "Scheduled community news", + published: true, + }); +}); diff --git a/src/lib/services/news-scheduler.ts b/src/lib/services/news-scheduler.ts index b8a1dcd3..554e2ac8 100644 --- a/src/lib/services/news-scheduler.ts +++ b/src/lib/services/news-scheduler.ts @@ -13,12 +13,13 @@ export async function publishDueArticles(now = new Date()): Promise { const utcNow = now.toISOString().slice(0, -1).replace("T", " "); const published = await db.transaction(async (tx) => { const [rows] = await tx.execute( - sql`SELECT CAST(id AS CHAR) AS id,user_id AS userId,publish_at AS publishAt FROM website_articles WHERE status='scheduled' AND publish_at<=${utcNow} ORDER BY publish_at,id LIMIT 100 FOR UPDATE`, + sql`SELECT CAST(id AS CHAR) AS id,user_id AS userId,title,publish_at AS publishAt FROM website_articles WHERE status='scheduled' AND publish_at<=${utcNow} ORDER BY publish_at,id LIMIT 100 FOR UPDATE`, ); let published = 0; for (const article of rows as unknown as Array<{ id: string; userId: number | null; + title: string | null; publishAt: Date | string; }>) { const [result] = await tx.execute( @@ -33,7 +34,7 @@ export async function publishDueArticles(now = new Date()): Promise { }); // Legacy articles without an author remain attributed to system (0). await tx.execute( - sql`INSERT INTO cms_operations (id,actor_id,kind,request_key,request_hash,result_json) VALUES (${operationId},${article.userId ?? 0},${"news.schedule.publish"},${randomUUID()},${hash},${JSON.stringify({ articleId: article.id, published: true })})`, + sql`INSERT INTO cms_operations (id,actor_id,kind,request_key,request_hash,result_json) VALUES (${operationId},${article.userId ?? 0},${"news.schedule.publish"},${randomUUID()},${hash},${JSON.stringify({ articleId: article.id, articleTitle: article.title, published: true })})`, ); await enqueueEffect(tx, operationId, "news.refresh"); published++; diff --git a/src/messages/ar.json b/src/messages/ar.json index 7cb66ccc..331732dc 100644 --- a/src/messages/ar.json +++ b/src/messages/ar.json @@ -3536,8 +3536,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -3551,9 +3551,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "myDashboard": { diff --git a/src/messages/bg.json b/src/messages/bg.json index fabb6a1b..e6a3e0d7 100644 --- a/src/messages/bg.json +++ b/src/messages/bg.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/cs.json b/src/messages/cs.json index 2b255661..6685e2b2 100644 --- a/src/messages/cs.json +++ b/src/messages/cs.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/da.json b/src/messages/da.json index ef5376ee..33e495bc 100644 --- a/src/messages/da.json +++ b/src/messages/da.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/de.json b/src/messages/de.json index 4c1ff49c..0b534168 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -4337,8 +4337,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4352,9 +4352,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "radioRequests": { diff --git a/src/messages/el.json b/src/messages/el.json index a88cd0e7..e6caec40 100644 --- a/src/messages/el.json +++ b/src/messages/el.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/en.json b/src/messages/en.json index a42cb7fe..5f016fbd 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -4540,8 +4540,8 @@ "recoveryHint": "You can continue. If the original files fail, the importer will search enabled sources for a matching classname, type and revision, then validate the Nitro bundle. Unavailable files can still be attached manually." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4555,9 +4555,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "mod": { diff --git a/src/messages/es.json b/src/messages/es.json index 4fc18045..9efd594a 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -4337,8 +4337,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4352,9 +4352,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "radioRequests": { diff --git a/src/messages/fi.json b/src/messages/fi.json index a329b914..f64b0e8d 100644 --- a/src/messages/fi.json +++ b/src/messages/fi.json @@ -3536,8 +3536,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -3551,9 +3551,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "myDashboard": { diff --git a/src/messages/fr.json b/src/messages/fr.json index 569f8690..14f92982 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -4337,8 +4337,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4352,9 +4352,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "radioRequests": { diff --git a/src/messages/hr.json b/src/messages/hr.json index d0fe9293..0e28d725 100644 --- a/src/messages/hr.json +++ b/src/messages/hr.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/hu.json b/src/messages/hu.json index b4c83eb8..69d05fa3 100644 --- a/src/messages/hu.json +++ b/src/messages/hu.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/it.json b/src/messages/it.json index c698cd3c..b3fa4f5b 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -4509,8 +4509,8 @@ "recoveryHint": "Puoi continuare. Se i file originali non sono disponibili, l’importatore cercherà nelle sorgenti attive lo stesso classname, tipo e revisione, verificando poi il bundle Nitro. Puoi comunque allegare manualmente i file mancanti." }, "deliveries": { - "title": "Consegne in sospeso", - "scope": "Ultime 100 consegne per modifiche massive al catalogo. La modifica al database è già salvata. Consegnata significa export accodato o richiesta inviata all’hotel, non applicazione confermata dai client. Il worker ritenta gli errori; qui puoi riavviare i tentativi esauriti.", + "title": "Aggiornamenti in background", + "scope": "Ultimi 100 aggiornamenti per news e modifiche multiple al catalogo. Il salvataggio dei contenuti e gli aggiornamenti successivi sono mostrati separatamente. Gli import si trovano nella cronologia import.", "refresh": "Aggiorna", "empty": "Nessuna consegna registrata.", "retry": "Riprova consegna", @@ -4524,9 +4524,37 @@ "pending": "In attesa", "running": "In consegna", "done": "Consegnata", - "failed": "Da verificare" + "failed": "Da verificare", + "unknown": "Stato sconosciuto" }, - "news": "Aggiornamento news" + "news": "Aggiornamento news", + "unknown": "Altro aggiornamento", + "contextUnavailable": "Dettagli del contenuto non disponibili", + "changedOffers": "{count} offerte del catalogo", + "system": "Sistema", + "unknownTime": "Orario non disponibile", + "savedAlready": "Il contenuto è salvato. Questo aggiornamento viene eseguito in background.", + "nextAttempt": "Prossimo tentativo automatico: {time}", + "attemptsExhausted": "I tentativi automatici sono esauriti. Dopo aver controllato il servizio, puoi riprovare da qui.", + "openContent": "Apri contenuto", + "diagnostics": "Diagnostica servizi", + "technicalDetails": "Riferimenti tecnici", + "operationReference": "Operazione: {id}", + "deliveryReference": "Aggiornamento: {id}", + "openImports": "Cronologia import", + "unavailable": "Impossibile caricare gli aggiornamenti. Prova ad aggiornare la pagina.", + "completed": { + "news": "Cache delle news aggiornata.", + "hotel": "La richiesta di aggiornamento catalogo è stata inviata all’hotel. Non conferma che ogni client si sia aggiornato.", + "export": "La richiesta di esportazione è stata elaborata. Controlla la cronologia Git per il risultato finale; se l’esportazione è disattivata, nessun file viene pubblicato.", + "unknown": "Elaborazione in background completata." + }, + "issues": { + "news": "Impossibile aggiornare la cache delle news. Controlla Redis e il worker.", + "hotel": "Impossibile inviare l’aggiornamento catalogo all’hotel. Controlla la connessione con l’emulatore.", + "export": "Impossibile accodare l’esportazione Git. Controlla la configurazione e il worker.", + "unknown": "L’aggiornamento non è riuscito. Controlla la diagnostica servizi." + } } }, "radioRequests": { diff --git a/src/messages/ja.json b/src/messages/ja.json index 63f049f3..a9d905c8 100644 --- a/src/messages/ja.json +++ b/src/messages/ja.json @@ -3536,8 +3536,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -3551,9 +3551,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "myDashboard": { diff --git a/src/messages/nl.json b/src/messages/nl.json index 28182a45..97606c22 100644 --- a/src/messages/nl.json +++ b/src/messages/nl.json @@ -4540,8 +4540,8 @@ "unavailable": "Status unavailable — open to inspect" }, "deliveries": { - "title": "Openstaande leveringen", - "scope": "De laatste 100 leveringen voor bulkcataloguswijzigingen. De databasewijziging is al opgeslagen. Afgeleverd betekent dat export is ingepland of het hotelverzoek is verstuurd, niet dat clients het hebben toegepast. De worker probeert fouten opnieuw; uitgeputte pogingen kun je hier hervatten.", + "title": "Achtergrondupdates", + "scope": "De laatste 100 updates voor nieuws en gezamenlijke cataloguswijzigingen. Het opslaan van inhoud en de achtergrondupdates worden apart getoond. Importvoortgang staat in de importgeschiedenis.", "refresh": "Vernieuwen", "empty": "Geen leveringen geregistreerd.", "retry": "Opnieuw proberen", @@ -4555,9 +4555,37 @@ "pending": "Wachtend", "running": "Wordt afgeleverd", "done": "Afgeleverd", - "failed": "Aandacht nodig" + "failed": "Aandacht nodig", + "unknown": "Onbekende status" }, - "news": "Nieuws bijwerken" + "news": "Nieuws bijwerken", + "unknown": "Andere achtergrondupdate", + "contextUnavailable": "Inhoudsdetails niet beschikbaar", + "changedOffers": "{count} catalogusaanbiedingen", + "system": "Systeem", + "unknownTime": "Tijd niet beschikbaar", + "savedAlready": "De inhoud is opgeslagen. Deze update wordt op de achtergrond uitgevoerd.", + "nextAttempt": "Volgende automatische poging: {time}", + "attemptsExhausted": "Alle automatische pogingen zijn gebruikt. Controleer de dienst en probeer het hier opnieuw.", + "openContent": "Inhoud openen", + "diagnostics": "Dienstdiagnostiek", + "technicalDetails": "Technische referenties", + "operationReference": "Bewerking: {id}", + "deliveryReference": "Update: {id}", + "openImports": "Importgeschiedenis", + "unavailable": "De achtergrondupdates konden niet worden geladen. Vernieuw de pagina om het opnieuw te proberen.", + "completed": { + "news": "De nieuwscache is vernieuwd.", + "hotel": "Het verzoek om de catalogus te vernieuwen is naar het hotel gestuurd. Dit bevestigt niet dat iedere client is bijgewerkt.", + "export": "Het exportverzoek is verwerkt. Bekijk de Git-exportgeschiedenis voor het eindresultaat; uitgeschakelde export publiceert geen bestanden.", + "unknown": "Achtergrondverwerking voltooid." + }, + "issues": { + "news": "De nieuwscache kon niet worden vernieuwd. Controleer Redis en de achtergrondworker.", + "hotel": "De catalogusupdate kon niet naar het hotel worden gestuurd. Controleer de emulatorverbinding.", + "export": "Het Git-exportverzoek kon niet worden ingepland. Controleer de exportinstellingen en de achtergrondworker.", + "unknown": "De achtergrondupdate is mislukt. Bekijk de dienstdiagnostiek." + } } }, "mod": { diff --git a/src/messages/no.json b/src/messages/no.json index 38981822..e8a560a5 100644 --- a/src/messages/no.json +++ b/src/messages/no.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/pl.json b/src/messages/pl.json index c9ff12d8..9ffc7298 100644 --- a/src/messages/pl.json +++ b/src/messages/pl.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/pt.json b/src/messages/pt.json index cfb90543..d63b0079 100644 --- a/src/messages/pt.json +++ b/src/messages/pt.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/ro.json b/src/messages/ro.json index 42867dbb..7f9cf53a 100644 --- a/src/messages/ro.json +++ b/src/messages/ro.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/ru.json b/src/messages/ru.json index 78ca39a3..ace68262 100644 --- a/src/messages/ru.json +++ b/src/messages/ru.json @@ -4365,8 +4365,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4380,9 +4380,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/sk.json b/src/messages/sk.json index f81f8914..927768ba 100644 --- a/src/messages/sk.json +++ b/src/messages/sk.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/sr.json b/src/messages/sr.json index 9ac79647..50a8d8f3 100644 --- a/src/messages/sr.json +++ b/src/messages/sr.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/sv.json b/src/messages/sv.json index 73e236d9..8b66ebd4 100644 --- a/src/messages/sv.json +++ b/src/messages/sv.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/tr.json b/src/messages/tr.json index d750678a..58f9eddc 100644 --- a/src/messages/tr.json +++ b/src/messages/tr.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": { diff --git a/src/messages/uk.json b/src/messages/uk.json index a83d9391..a0874cc3 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -4367,8 +4367,8 @@ "previewHint": "These links show the current public pages. Use Preview to check unsaved content. The final article slug may receive a suffix if already in use." }, "deliveries": { - "title": "Pending deliveries", - "scope": "Latest 100 delivery records for bulk catalog edits. The database change is already committed. Done means the export was queued or the hotel update request was sent, not that clients applied it. The jobs worker retries failures; exhausted retries can be restarted here.", + "title": "Background updates", + "scope": "Latest 100 background updates for news and bulk catalog changes. Saved content and background delivery are tracked separately. Import progress is available in Import history.", "refresh": "Refresh", "empty": "No delivery records.", "retry": "Retry delivery", @@ -4382,9 +4382,37 @@ "pending": "Waiting", "running": "Delivering", "done": "Delivered", - "failed": "Needs attention" + "failed": "Needs attention", + "unknown": "Unknown status" }, - "news": "News cache refresh" + "news": "News cache refresh", + "unknown": "Other background update", + "contextUnavailable": "Content details unavailable", + "changedOffers": "{count} catalog offers", + "system": "System", + "unknownTime": "Time unavailable", + "savedAlready": "The content is saved. This update runs in the background.", + "nextAttempt": "Next automatic attempt: {time}", + "attemptsExhausted": "Automatic attempts are exhausted. Retry here after checking the service.", + "openContent": "Open content", + "diagnostics": "Service diagnostics", + "technicalDetails": "Technical references", + "operationReference": "Operation: {id}", + "deliveryReference": "Delivery: {id}", + "openImports": "Import history", + "unavailable": "Background updates could not be loaded. Try refreshing this page.", + "completed": { + "news": "News cache refresh completed.", + "hotel": "The catalog update request was sent to the hotel. This does not confirm that every client has refreshed.", + "export": "The export request was processed. Check the Git export history for the final result; disabled export does not publish files.", + "unknown": "Background processing completed." + }, + "issues": { + "news": "The news cache could not be refreshed. Check Redis and the background worker.", + "hotel": "The catalog update could not be delivered to the hotel. Check the emulator connection.", + "export": "The Git export request could not be queued. Check the export configuration and background worker.", + "unknown": "The background update failed. Check the service diagnostics." + } } }, "error": {