@purposeinplay/payload-ai-translate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +714 -0
- package/dist/alerts-collection.d.ts +21 -0
- package/dist/alerts-collection.js +159 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +918 -0
- package/dist/bulk-translate-batches-collection.d.ts +29 -0
- package/dist/bulk-translate-batches-collection.js +404 -0
- package/dist/bulk-translate-units-collection.d.ts +35 -0
- package/dist/bulk-translate-units-collection.js +310 -0
- package/dist/client/estimated-cost-cell.d.ts +6 -0
- package/dist/client/estimated-cost-cell.js +12 -0
- package/dist/client/excluded-fields-field.d.ts +45 -0
- package/dist/client/excluded-fields-field.js +553 -0
- package/dist/client/field-translate-button.d.ts +6 -0
- package/dist/client/field-translate-button.js +199 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +6 -0
- package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
- package/dist/client/lib/use-global-kill-switches.js +58 -0
- package/dist/client/translate-button.d.ts +2 -0
- package/dist/client/translate-button.js +228 -0
- package/dist/client/translate-modal.d.ts +16 -0
- package/dist/client/translate-modal.js +549 -0
- package/dist/client/translation-progress.d.ts +10 -0
- package/dist/client/translation-progress.js +297 -0
- package/dist/components/TranslationNavGroup.d.ts +45 -0
- package/dist/components/TranslationNavGroup.js +104 -0
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +16 -0
- package/dist/endpoints/client-config.d.ts +44 -0
- package/dist/endpoints/client-config.js +145 -0
- package/dist/endpoints/estimate.d.ts +5 -0
- package/dist/endpoints/estimate.js +237 -0
- package/dist/endpoints/progress.d.ts +2 -0
- package/dist/endpoints/progress.js +314 -0
- package/dist/endpoints/translate.d.ts +11 -0
- package/dist/endpoints/translate.js +376 -0
- package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
- package/dist/endpoints/translation-hub/_helpers.js +297 -0
- package/dist/endpoints/translation-hub/active.d.ts +21 -0
- package/dist/endpoints/translation-hub/active.js +220 -0
- package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
- package/dist/endpoints/translation-hub/cancel.js +233 -0
- package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
- package/dist/endpoints/translation-hub/enqueue.js +529 -0
- package/dist/endpoints/translation-hub/failures.d.ts +12 -0
- package/dist/endpoints/translation-hub/failures.js +67 -0
- package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
- package/dist/endpoints/translation-hub/force-reset.js +144 -0
- package/dist/endpoints/translation-hub/index.d.ts +21 -0
- package/dist/endpoints/translation-hub/index.js +20 -0
- package/dist/endpoints/translation-hub/list.d.ts +40 -0
- package/dist/endpoints/translation-hub/list.js +182 -0
- package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
- package/dist/endpoints/translation-hub/preflight.js +141 -0
- package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
- package/dist/endpoints/translation-hub/retry-failed.js +235 -0
- package/dist/endpoints/translation-hub/revert.d.ts +88 -0
- package/dist/endpoints/translation-hub/revert.js +405 -0
- package/dist/endpoints/translation-hub/status.d.ts +45 -0
- package/dist/endpoints/translation-hub/status.js +391 -0
- package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
- package/dist/endpoints/translation-hub/usage-summary.js +481 -0
- package/dist/exports/client.d.ts +6 -0
- package/dist/exports/client.js +6 -0
- package/dist/exports/components.d.ts +6 -0
- package/dist/exports/components.js +5 -0
- package/dist/exports/index.d.ts +8 -0
- package/dist/exports/index.js +7 -0
- package/dist/exports/providers.d.ts +9 -0
- package/dist/exports/providers.js +5 -0
- package/dist/exports/views-client.d.ts +23 -0
- package/dist/exports/views-client.js +22 -0
- package/dist/exports/views.d.ts +30 -0
- package/dist/exports/views.js +29 -0
- package/dist/hooks/after-change-global.d.ts +4 -0
- package/dist/hooks/after-change-global.js +109 -0
- package/dist/hooks/after-change.d.ts +16 -0
- package/dist/hooks/after-change.js +205 -0
- package/dist/hooks/after-delete.d.ts +30 -0
- package/dist/hooks/after-delete.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs-collection.d.ts +17 -0
- package/dist/jobs-collection.js +139 -0
- package/dist/lexical/classifier.d.ts +3 -0
- package/dist/lexical/classifier.js +108 -0
- package/dist/lexical/deserializer.d.ts +4 -0
- package/dist/lexical/deserializer.js +263 -0
- package/dist/lexical/placeholder-integrity.d.ts +6 -0
- package/dist/lexical/placeholder-integrity.js +21 -0
- package/dist/lexical/placeholders.d.ts +21 -0
- package/dist/lexical/placeholders.js +117 -0
- package/dist/lexical/serializer.d.ts +21 -0
- package/dist/lexical/serializer.js +233 -0
- package/dist/lexical/types.d.ts +32 -0
- package/dist/lexical/types.js +1 -0
- package/dist/lib/auth-diagnostics.d.ts +14 -0
- package/dist/lib/auth-diagnostics.js +19 -0
- package/dist/lib/batch-counts.d.ts +58 -0
- package/dist/lib/batch-counts.js +105 -0
- package/dist/lib/bulk-translate-migrations.d.ts +92 -0
- package/dist/lib/bulk-translate-migrations.js +153 -0
- package/dist/lib/coalescing-queue.d.ts +38 -0
- package/dist/lib/coalescing-queue.js +69 -0
- package/dist/lib/content-extractor.d.ts +16 -0
- package/dist/lib/content-extractor.js +410 -0
- package/dist/lib/content-hash.d.ts +1 -0
- package/dist/lib/content-hash.js +19 -0
- package/dist/lib/content-patcher.d.ts +15 -0
- package/dist/lib/content-patcher.js +293 -0
- package/dist/lib/cost-guards.d.ts +2 -0
- package/dist/lib/cost-guards.js +18 -0
- package/dist/lib/daily-spend-cap.d.ts +58 -0
- package/dist/lib/daily-spend-cap.js +233 -0
- package/dist/lib/effective-locales.d.ts +181 -0
- package/dist/lib/effective-locales.js +302 -0
- package/dist/lib/error-messages.d.ts +245 -0
- package/dist/lib/error-messages.js +626 -0
- package/dist/lib/events.d.ts +39 -0
- package/dist/lib/events.js +146 -0
- package/dist/lib/exclude-fields.d.ts +3 -0
- package/dist/lib/exclude-fields.js +64 -0
- package/dist/lib/field-breadcrumb.d.ts +31 -0
- package/dist/lib/field-breadcrumb.js +227 -0
- package/dist/lib/field-diff.d.ts +1 -0
- package/dist/lib/field-diff.js +25 -0
- package/dist/lib/field-empty.d.ts +2 -0
- package/dist/lib/field-empty.js +68 -0
- package/dist/lib/field-resolver.d.ts +3 -0
- package/dist/lib/field-resolver.js +164 -0
- package/dist/lib/group-soft-skips.d.ts +39 -0
- package/dist/lib/group-soft-skips.js +45 -0
- package/dist/lib/locale-merge.d.ts +44 -0
- package/dist/lib/locale-merge.js +357 -0
- package/dist/lib/locale-row-check.d.ts +30 -0
- package/dist/lib/locale-row-check.js +64 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +97 -0
- package/dist/lib/manual-edit-guard.d.ts +128 -0
- package/dist/lib/manual-edit-guard.js +393 -0
- package/dist/lib/output-validation.d.ts +48 -0
- package/dist/lib/output-validation.js +148 -0
- package/dist/lib/payload-read.d.ts +16 -0
- package/dist/lib/payload-read.js +51 -0
- package/dist/lib/per-doc-claim.d.ts +90 -0
- package/dist/lib/per-doc-claim.js +140 -0
- package/dist/lib/per-doc-lock.d.ts +94 -0
- package/dist/lib/per-doc-lock.js +119 -0
- package/dist/lib/persist-usage.d.ts +91 -0
- package/dist/lib/persist-usage.js +116 -0
- package/dist/lib/progress-store.d.ts +103 -0
- package/dist/lib/progress-store.js +314 -0
- package/dist/lib/rate-limiter.d.ts +3 -0
- package/dist/lib/rate-limiter.js +53 -0
- package/dist/lib/snapshot-select.d.ts +43 -0
- package/dist/lib/snapshot-select.js +108 -0
- package/dist/lib/translate-prompt.d.ts +31 -0
- package/dist/lib/translate-prompt.js +66 -0
- package/dist/lib/translation-token-bucket.d.ts +57 -0
- package/dist/lib/translation-token-bucket.js +365 -0
- package/dist/lib/truncate-source-value.d.ts +1 -0
- package/dist/lib/truncate-source-value.js +27 -0
- package/dist/manual-edit-collection.d.ts +22 -0
- package/dist/manual-edit-collection.js +124 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +934 -0
- package/dist/providers/ai-sdk-adapter.d.ts +35 -0
- package/dist/providers/ai-sdk-adapter.js +100 -0
- package/dist/providers/anthropic.d.ts +31 -0
- package/dist/providers/anthropic.js +66 -0
- package/dist/providers/custom.d.ts +36 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/gemini.d.ts +20 -0
- package/dist/providers/gemini.js +48 -0
- package/dist/providers/mock.d.ts +2 -0
- package/dist/providers/mock.js +29 -0
- package/dist/providers/openai.d.ts +28 -0
- package/dist/providers/openai.js +69 -0
- package/dist/settings-global.d.ts +74 -0
- package/dist/settings-global.js +216 -0
- package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
- package/dist/tasks/bulk-translate-coordinator.js +708 -0
- package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
- package/dist/tasks/bulk-translate-doc-task.js +1000 -0
- package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
- package/dist/tasks/bulk-translate-janitor.js +311 -0
- package/dist/tasks/translate-job-task.d.ts +51 -0
- package/dist/tasks/translate-job-task.js +154 -0
- package/dist/translate.d.ts +113 -0
- package/dist/translate.js +911 -0
- package/dist/translation-daily-spend-collection.d.ts +24 -0
- package/dist/translation-daily-spend-collection.js +133 -0
- package/dist/translation-rate-limits-collection.d.ts +30 -0
- package/dist/translation-rate-limits-collection.js +144 -0
- package/dist/types.d.ts +672 -0
- package/dist/types.js +1 -0
- package/dist/usage-collection.d.ts +14 -0
- package/dist/usage-collection.js +377 -0
- package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
- package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
- package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
- package/dist/views/BulkRunsHub/BucketRow.js +982 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
- package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
- package/dist/views/BulkRunsHub/EmptyState.js +64 -0
- package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
- package/dist/views/BulkRunsHub/FilterBar.js +284 -0
- package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
- package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
- package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
- package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
- package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
- package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
- package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
- package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
- package/dist/views/BulkRunsHub/index.d.ts +17 -0
- package/dist/views/BulkRunsHub/index.js +80 -0
- package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
- package/dist/views/BulkRunsHub/urlFilters.js +50 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
- package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
- package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
- package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
- package/dist/views/TranslationHub/ActiveJobs.js +320 -0
- package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
- package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
- package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
- package/dist/views/TranslationHub/AlertBanner.js +568 -0
- package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
- package/dist/views/TranslationHub/AuditPanel.js +1367 -0
- package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
- package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
- package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
- package/dist/views/TranslationHub/Hub.client.js +357 -0
- package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
- package/dist/views/TranslationHub/ModelCombobox.js +415 -0
- package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
- package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
- package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
- package/dist/views/TranslationHub/SettingsRail.js +382 -0
- package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
- package/dist/views/TranslationHub/StatusStrip.js +451 -0
- package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
- package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
- package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
- package/dist/views/TranslationHub/UsageTable.js +1240 -0
- package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
- package/dist/views/TranslationHub/alertGrouping.js +99 -0
- package/dist/views/TranslationHub/index.d.ts +20 -0
- package/dist/views/TranslationHub/index.js +109 -0
- package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
- package/dist/views/TranslationHub/tabNavigation.js +74 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
- package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
- package/dist/views/TranslationHub/useFocusTrap.js +81 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
- package/dist/views/shared/EditorError.d.ts +97 -0
- package/dist/views/shared/EditorError.js +205 -0
- package/dist/views/shared/ModelCell.d.ts +18 -0
- package/dist/views/shared/ModelCell.js +31 -0
- package/dist/views/shared/docHref.d.ts +16 -0
- package/dist/views/shared/docHref.js +26 -0
- package/dist/views/shared/fetch-error-body.d.ts +25 -0
- package/dist/views/shared/fetch-error-body.js +42 -0
- package/dist/views/shared/filterPillStyle.d.ts +35 -0
- package/dist/views/shared/filterPillStyle.js +40 -0
- package/dist/views/shared/format.d.ts +75 -0
- package/dist/views/shared/format.js +131 -0
- package/package.json +141 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Polling hook for the bulk-translate runs list endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the structure of `useBulkTranslateActive` — 5s visibility-aware
|
|
6
|
+
* poll loop, offline threshold, refetch escape hatch.
|
|
7
|
+
*
|
|
8
|
+
* Polling is active only when:
|
|
9
|
+
* 1. `document.visibilityState === 'visible'`
|
|
10
|
+
* 2. Any batch in `data.batches` is in an active status.
|
|
11
|
+
*
|
|
12
|
+
* When those conditions are false the poll is suspended. The hook still
|
|
13
|
+
* fires once on mount and once when visibility returns.
|
|
14
|
+
*/ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
15
|
+
import { readResponseError } from '../shared/fetch-error-body.js';
|
|
16
|
+
import { isActiveStatus } from '../TranslationHub/BulkTranslate.types.js';
|
|
17
|
+
const POLL_INTERVAL_MS = 5000;
|
|
18
|
+
const OFFLINE_THRESHOLD = 3;
|
|
19
|
+
const DEFAULT_LIMIT = 20;
|
|
20
|
+
function buildUrl(basePath, filters, cursor = null, limit = DEFAULT_LIMIT) {
|
|
21
|
+
const params = new URLSearchParams();
|
|
22
|
+
if (filters.status) params.set('status', filters.status);
|
|
23
|
+
if (filters.mode) params.set('mode', filters.mode);
|
|
24
|
+
if (filters.triggeredBy) params.set('triggeredBy', filters.triggeredBy);
|
|
25
|
+
if (filters.since) params.set('since', filters.since);
|
|
26
|
+
if (filters.until) params.set('until', filters.until);
|
|
27
|
+
if (filters.hasFailures) params.set('hasFailures', 'true');
|
|
28
|
+
if (cursor) params.set('cursor', cursor);
|
|
29
|
+
params.set('limit', String(limit));
|
|
30
|
+
// Payload's collection-list router intercepts requests whose only query
|
|
31
|
+
// string is `?limit=N`, redirecting to a locale-prefixed canonical URL
|
|
32
|
+
// and 404ing on our custom endpoint. Adding a second param sidesteps
|
|
33
|
+
// the heuristic. `depth=0` is a no-op for our handler.
|
|
34
|
+
params.set('depth', '0');
|
|
35
|
+
const qs = params.toString();
|
|
36
|
+
return `${basePath}/api/translation-hub/bulk-translate${qs ? `?${qs}` : ''}`;
|
|
37
|
+
}
|
|
38
|
+
export function useBulkRunsList(basePath, filters) {
|
|
39
|
+
const [data, setData] = useState(null);
|
|
40
|
+
const [loading, setLoading] = useState(true);
|
|
41
|
+
const [error, setError] = useState(null);
|
|
42
|
+
const [failedStreak, setFailedStreak] = useState(0);
|
|
43
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
44
|
+
const cancelledRef = useRef(false);
|
|
45
|
+
const timerRef = useRef(undefined);
|
|
46
|
+
// Stable ref so refetch() can be called from event handlers without
|
|
47
|
+
// re-subscribing to the effect.
|
|
48
|
+
const refetchRef = useRef(()=>undefined);
|
|
49
|
+
useEffect(()=>{
|
|
50
|
+
cancelledRef.current = false;
|
|
51
|
+
let isFirstFetch = true;
|
|
52
|
+
async function fetchOnce() {
|
|
53
|
+
const url = buildUrl(basePath, filters);
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
credentials: 'include'
|
|
57
|
+
});
|
|
58
|
+
if (cancelledRef.current) return;
|
|
59
|
+
if (res.status === 404) {
|
|
60
|
+
setData({
|
|
61
|
+
batches: [],
|
|
62
|
+
nextCursor: null,
|
|
63
|
+
total: 0
|
|
64
|
+
});
|
|
65
|
+
setError(null);
|
|
66
|
+
setFailedStreak(0);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) throw new Error(await readResponseError(res));
|
|
70
|
+
const json = await res.json();
|
|
71
|
+
if (cancelledRef.current) return;
|
|
72
|
+
// v1.2.5: same fix as BatchRow's polling — preserve
|
|
73
|
+
// load-more-appended batches across polling ticks. Pre-1.2.5
|
|
74
|
+
// the polling tick called setData(json) which fully replaced
|
|
75
|
+
// the batches array; if the user had clicked "Load more" to
|
|
76
|
+
// load page 2, the next polling tick wiped page 2 (the
|
|
77
|
+
// polling fetch returns only the first page). Now we merge:
|
|
78
|
+
// new batches override matching ids, prev batches not in the
|
|
79
|
+
// new response (i.e. loaded-more pages) survive. First fetch
|
|
80
|
+
// (filter change / mount) still replaces fully.
|
|
81
|
+
if (isFirstFetch) {
|
|
82
|
+
setData(json);
|
|
83
|
+
isFirstFetch = false;
|
|
84
|
+
} else {
|
|
85
|
+
setData((prev)=>{
|
|
86
|
+
if (!prev) return json;
|
|
87
|
+
const newById = new Map(json.batches.map((b)=>[
|
|
88
|
+
String(b.id),
|
|
89
|
+
b
|
|
90
|
+
]));
|
|
91
|
+
const preserved = prev.batches.filter((b)=>!newById.has(String(b.id)));
|
|
92
|
+
return {
|
|
93
|
+
...json,
|
|
94
|
+
batches: [
|
|
95
|
+
...json.batches,
|
|
96
|
+
...preserved
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
setError(null);
|
|
102
|
+
setFailedStreak(0);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (cancelledRef.current) return;
|
|
105
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
106
|
+
setFailedStreak((n)=>n + 1);
|
|
107
|
+
} finally{
|
|
108
|
+
if (!cancelledRef.current) {
|
|
109
|
+
setLoading(false);
|
|
110
|
+
// Only keep polling when page is visible AND a batch is active.
|
|
111
|
+
if (document.visibilityState === 'visible') {
|
|
112
|
+
// We can't read `data` here (stale closure) so we schedule
|
|
113
|
+
// unconditionally; the interval handler checks activity itself.
|
|
114
|
+
timerRef.current = setTimeout(maybePoll, POLL_INTERVAL_MS);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function maybePoll() {
|
|
120
|
+
if (cancelledRef.current) return;
|
|
121
|
+
// Read latest data from the outer state via the ref we refresh below.
|
|
122
|
+
// If none of the current batches are active, suspend polling.
|
|
123
|
+
const shouldPoll = dataRef.current?.batches.some((b)=>isActiveStatus(b.status));
|
|
124
|
+
if (shouldPoll) {
|
|
125
|
+
fetchOnce();
|
|
126
|
+
} else {
|
|
127
|
+
// Reschedule — a new run might start while the tab is open.
|
|
128
|
+
timerRef.current = setTimeout(maybePoll, POLL_INTERVAL_MS);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function onVisibility() {
|
|
132
|
+
if (document.visibilityState === 'visible' && !cancelledRef.current) {
|
|
133
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
134
|
+
fetchOnce();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
refetchRef.current = ()=>{
|
|
138
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
139
|
+
fetchOnce();
|
|
140
|
+
};
|
|
141
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
142
|
+
fetchOnce();
|
|
143
|
+
return ()=>{
|
|
144
|
+
cancelledRef.current = true;
|
|
145
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
146
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
147
|
+
};
|
|
148
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
149
|
+
}, [
|
|
150
|
+
basePath,
|
|
151
|
+
filters.status,
|
|
152
|
+
filters.mode,
|
|
153
|
+
filters.triggeredBy,
|
|
154
|
+
filters.since,
|
|
155
|
+
filters.until,
|
|
156
|
+
filters.hasFailures
|
|
157
|
+
]);
|
|
158
|
+
// Keep a ref to latest data so `maybePoll` doesn't stale-close.
|
|
159
|
+
const dataRef = useRef(data);
|
|
160
|
+
useEffect(()=>{
|
|
161
|
+
dataRef.current = data;
|
|
162
|
+
}, [
|
|
163
|
+
data
|
|
164
|
+
]);
|
|
165
|
+
const loadMore = useCallback(async ()=>{
|
|
166
|
+
const cursor = dataRef.current?.nextCursor;
|
|
167
|
+
if (!cursor || loadingMore) return;
|
|
168
|
+
setLoadingMore(true);
|
|
169
|
+
try {
|
|
170
|
+
const url = buildUrl(basePath, filters, cursor);
|
|
171
|
+
const res = await fetch(url, {
|
|
172
|
+
credentials: 'include'
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) throw new Error(await readResponseError(res));
|
|
175
|
+
const json = await res.json();
|
|
176
|
+
setData((prev)=>prev ? {
|
|
177
|
+
...json,
|
|
178
|
+
batches: [
|
|
179
|
+
...prev.batches,
|
|
180
|
+
...json.batches
|
|
181
|
+
]
|
|
182
|
+
} : json);
|
|
183
|
+
} catch {
|
|
184
|
+
// Non-fatal — the Load more button just stays visible.
|
|
185
|
+
} finally{
|
|
186
|
+
setLoadingMore(false);
|
|
187
|
+
}
|
|
188
|
+
}, [
|
|
189
|
+
basePath,
|
|
190
|
+
filters,
|
|
191
|
+
loadingMore
|
|
192
|
+
]);
|
|
193
|
+
return {
|
|
194
|
+
data,
|
|
195
|
+
loading,
|
|
196
|
+
error,
|
|
197
|
+
isOffline: failedStreak >= OFFLINE_THRESHOLD,
|
|
198
|
+
refetch: ()=>refetchRef.current(),
|
|
199
|
+
loadMore,
|
|
200
|
+
loadingMore
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export const BULK_RUNS_OFFLINE_THRESHOLD = OFFLINE_THRESHOLD;
|
|
204
|
+
export const BULK_RUNS_POLL_INTERVAL_MS = POLL_INTERVAL_MS;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BulkRunsFilterState, BulkRunsTimeRange } from '../TranslationHub/BulkTranslate.types.js';
|
|
2
|
+
export { DEFAULT_FILTERS } from './urlFilters.js';
|
|
3
|
+
export type UrlFiltersResult = {
|
|
4
|
+
filters: BulkRunsFilterState;
|
|
5
|
+
timeRange: BulkRunsTimeRange;
|
|
6
|
+
setFilters: (patch: Partial<BulkRunsFilterState>) => void;
|
|
7
|
+
setTimeRange: (range: BulkRunsTimeRange) => void;
|
|
8
|
+
clearAll: () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function useUrlFilters(): UrlFiltersResult;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* Sync the BulkRunsHub filter + timeRange state with the URL search
|
|
4
|
+
* params so the filtered view is refresh-stable and shareable.
|
|
5
|
+
*
|
|
6
|
+
* BR-7 (v1.2.6): before the fix, every filter (status pill, mode pill,
|
|
7
|
+
* since, until, hasFailures, triggeredBy) lived in React state only.
|
|
8
|
+
* Refresh or share-the-link dropped the filter; editors couldn't send
|
|
9
|
+
* "failed runs in May" as a URL.
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - The hook owns READ + WRITE of the filter / timeRange tuple. The
|
|
13
|
+
* component holds NO local React state for these — it reads from
|
|
14
|
+
* the hook and writes back via the returned setters.
|
|
15
|
+
* - URL writes use `router.replace(..., { scroll: false })` so the
|
|
16
|
+
* browser doesn't push a history entry per pill click and so the
|
|
17
|
+
* page doesn't jump to the top on every state update.
|
|
18
|
+
* - Empty / default values are omitted from the URL to keep it readable
|
|
19
|
+
* (`?status=failed&since=2026-05-01` is friendlier than the same
|
|
20
|
+
* plus six empty params).
|
|
21
|
+
* - SSR-safe: when `usePathname()` / `useSearchParams()` return null
|
|
22
|
+
* (first render in some App Router edge cases), we fall back to the
|
|
23
|
+
* defaults so the table doesn't render with `undefined` filter state.
|
|
24
|
+
*
|
|
25
|
+
* The pure serializers live in `./urlFilters.ts` so they can be unit
|
|
26
|
+
* tested without dragging `next/navigation` into the node test
|
|
27
|
+
* environment.
|
|
28
|
+
*/ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
29
|
+
import { useCallback, useMemo } from 'react';
|
|
30
|
+
import { buildSearchString, DEFAULT_FILTERS, readFilters, readTimeRange } from './urlFilters.js';
|
|
31
|
+
export { DEFAULT_FILTERS } from './urlFilters.js';
|
|
32
|
+
export function useUrlFilters() {
|
|
33
|
+
const router = useRouter();
|
|
34
|
+
const pathname = usePathname();
|
|
35
|
+
const searchParams = useSearchParams();
|
|
36
|
+
const filters = useMemo(()=>{
|
|
37
|
+
return readFilters(searchParams ? new URLSearchParams(searchParams.toString()) : new URLSearchParams());
|
|
38
|
+
}, [
|
|
39
|
+
searchParams
|
|
40
|
+
]);
|
|
41
|
+
const timeRange = useMemo(()=>{
|
|
42
|
+
return readTimeRange(searchParams ? new URLSearchParams(searchParams.toString()) : new URLSearchParams());
|
|
43
|
+
}, [
|
|
44
|
+
searchParams
|
|
45
|
+
]);
|
|
46
|
+
const writeUrl = useCallback((nextFilters, nextRange)=>{
|
|
47
|
+
const qs = buildSearchString(nextFilters, nextRange);
|
|
48
|
+
// `pathname` can be null in some App Router edge cases (e.g. when
|
|
49
|
+
// the hook fires before the router context is ready). Skip the
|
|
50
|
+
// navigation in that case — next render will hit the path branch.
|
|
51
|
+
if (!pathname) return;
|
|
52
|
+
router.replace(`${pathname}${qs}`, {
|
|
53
|
+
scroll: false
|
|
54
|
+
});
|
|
55
|
+
}, [
|
|
56
|
+
pathname,
|
|
57
|
+
router
|
|
58
|
+
]);
|
|
59
|
+
const setFilters = useCallback((patch)=>{
|
|
60
|
+
const next = {
|
|
61
|
+
...filters,
|
|
62
|
+
...patch
|
|
63
|
+
};
|
|
64
|
+
writeUrl(next, timeRange);
|
|
65
|
+
}, [
|
|
66
|
+
filters,
|
|
67
|
+
timeRange,
|
|
68
|
+
writeUrl
|
|
69
|
+
]);
|
|
70
|
+
const setTimeRange = useCallback((range)=>{
|
|
71
|
+
writeUrl(filters, range);
|
|
72
|
+
}, [
|
|
73
|
+
filters,
|
|
74
|
+
writeUrl
|
|
75
|
+
]);
|
|
76
|
+
const clearAll = useCallback(()=>{
|
|
77
|
+
writeUrl(DEFAULT_FILTERS, '7d');
|
|
78
|
+
}, [
|
|
79
|
+
writeUrl
|
|
80
|
+
]);
|
|
81
|
+
return {
|
|
82
|
+
filters,
|
|
83
|
+
timeRange,
|
|
84
|
+
setFilters,
|
|
85
|
+
setTimeRange,
|
|
86
|
+
clearAll
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { readResponseError } from '../shared/fetch-error-body.js';
|
|
5
|
+
// Active polling cadence — used while at least one job is `running`.
|
|
6
|
+
// 5s matches the "refresh every 5s" copy in the header.
|
|
7
|
+
const POLL_INTERVAL_MS = 5000;
|
|
8
|
+
// Idle cadence — used when no jobs are running. Pre-1.2.8 the loop
|
|
9
|
+
// re-queued at POLL_INTERVAL_MS unconditionally, so the page hammered
|
|
10
|
+
// `/api/ai-translate-jobs` every 5s for the entire time the Hub was
|
|
11
|
+
// open — even hours after the last job finished. Under a bulk run with
|
|
12
|
+
// many concurrent admin tabs that crashed the server. We still need to
|
|
13
|
+
// poll when idle (a job can be triggered from another tab and the
|
|
14
|
+
// In-flight panel must reflect it), but the cadence can be much
|
|
15
|
+
// slower.
|
|
16
|
+
const IDLE_POLL_INTERVAL_MS = 60_000;
|
|
17
|
+
const SECTION_STYLE = {
|
|
18
|
+
background: 'var(--theme-elevation-50)',
|
|
19
|
+
border: '1px solid var(--theme-elevation-150)',
|
|
20
|
+
borderRadius: '6px',
|
|
21
|
+
padding: '1rem'
|
|
22
|
+
};
|
|
23
|
+
const ROW_STYLE = {
|
|
24
|
+
display: 'grid',
|
|
25
|
+
gridTemplateColumns: '1fr 0.7fr 1.2fr 0.8fr 0.6fr',
|
|
26
|
+
gap: '0.75rem',
|
|
27
|
+
padding: '0.5rem 0',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
fontSize: '0.875rem',
|
|
30
|
+
borderTop: '1px solid var(--theme-elevation-100)',
|
|
31
|
+
color: 'var(--theme-elevation-800)'
|
|
32
|
+
};
|
|
33
|
+
const HEADER_ROW_STYLE = {
|
|
34
|
+
...ROW_STYLE,
|
|
35
|
+
borderTop: 'none',
|
|
36
|
+
fontSize: '0.75rem',
|
|
37
|
+
fontWeight: 600,
|
|
38
|
+
textTransform: 'uppercase',
|
|
39
|
+
letterSpacing: '0.05em',
|
|
40
|
+
color: 'var(--theme-elevation-500)'
|
|
41
|
+
};
|
|
42
|
+
const STATUS_COLORS = {
|
|
43
|
+
running: 'var(--theme-warning-500, #d97706)',
|
|
44
|
+
completed: 'var(--theme-success-500, #16a34a)',
|
|
45
|
+
failed: 'var(--theme-error-500, #b91c1c)',
|
|
46
|
+
stale: 'var(--theme-elevation-500)'
|
|
47
|
+
};
|
|
48
|
+
// Threshold for treating a `running` job as stuck/zombied. The cms-plugins
|
|
49
|
+
// persistJobs feature writes a row when a job starts but doesn't sweep it
|
|
50
|
+
// on server-restart, so a translate that crashed mid-flight stays
|
|
51
|
+
// `running` in the DB forever. Operationally any real translate finishes
|
|
52
|
+
// in seconds; anything over 5 minutes is dead.
|
|
53
|
+
const STALE_AFTER_MS = 5 * 60 * 1000;
|
|
54
|
+
function effectiveStatus(j) {
|
|
55
|
+
if (j.status !== 'running') {
|
|
56
|
+
return j.status;
|
|
57
|
+
}
|
|
58
|
+
const startedMs = new Date(j.startedAt ?? j.createdAt).getTime();
|
|
59
|
+
return Date.now() - startedMs > STALE_AFTER_MS ? 'stale' : 'running';
|
|
60
|
+
}
|
|
61
|
+
// Builds the jobs-list URL for the last 15-minute window. Extracted to
|
|
62
|
+
// keep `fetchOnce` under biome's cognitive-complexity ceiling.
|
|
63
|
+
function buildJobsUrl(basePath) {
|
|
64
|
+
const since = new Date(Date.now() - 15 * 60 * 1000).toISOString();
|
|
65
|
+
// Bumped from 10 → 50 — during bulk-publish bursts more than 10 jobs
|
|
66
|
+
// can be in flight concurrently. Without headroom the in-flight
|
|
67
|
+
// panel quietly drops older `running` jobs.
|
|
68
|
+
const params = new URLSearchParams({
|
|
69
|
+
'where[createdAt][greater_than]': since,
|
|
70
|
+
limit: '50',
|
|
71
|
+
sort: '-createdAt',
|
|
72
|
+
depth: '0'
|
|
73
|
+
});
|
|
74
|
+
return `${basePath}/api/ai-translate-jobs?${params.toString()}`;
|
|
75
|
+
}
|
|
76
|
+
function relTime(iso) {
|
|
77
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
78
|
+
const s = Math.floor(diff / 1000);
|
|
79
|
+
if (s < 60) {
|
|
80
|
+
return `${s}s ago`;
|
|
81
|
+
}
|
|
82
|
+
if (s < 3600) {
|
|
83
|
+
return `${Math.floor(s / 60)}m ago`;
|
|
84
|
+
}
|
|
85
|
+
if (s < 86_400) {
|
|
86
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
87
|
+
}
|
|
88
|
+
return `${Math.floor(s / 86_400)}d ago`;
|
|
89
|
+
}
|
|
90
|
+
export const ActiveJobs = ({ basePath })=>{
|
|
91
|
+
const [jobs, setJobs] = useState(null);
|
|
92
|
+
const [error, setError] = useState(null);
|
|
93
|
+
useEffect(()=>{
|
|
94
|
+
let cancelled = false;
|
|
95
|
+
let timer;
|
|
96
|
+
// Tracks whether the most recent poll saw any `running` job. The
|
|
97
|
+
// next setTimeout uses this to pick the active vs idle cadence.
|
|
98
|
+
let hasActiveJob = false;
|
|
99
|
+
async function fetchJobs() {
|
|
100
|
+
const res = await fetch(buildJobsUrl(basePath), {
|
|
101
|
+
credentials: 'include'
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) {
|
|
104
|
+
throw new Error(await readResponseError(res));
|
|
105
|
+
}
|
|
106
|
+
const data = await res.json();
|
|
107
|
+
return data.docs ?? [];
|
|
108
|
+
}
|
|
109
|
+
async function fetchOnce() {
|
|
110
|
+
try {
|
|
111
|
+
const docs = await fetchJobs();
|
|
112
|
+
if (cancelled) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setJobs(docs);
|
|
116
|
+
setError(null);
|
|
117
|
+
hasActiveJob = docs.some((j)=>j.status === 'running');
|
|
118
|
+
} catch (e) {
|
|
119
|
+
if (!cancelled) {
|
|
120
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
121
|
+
}
|
|
122
|
+
} finally{
|
|
123
|
+
// Visibility-aware polling: skip polling when the tab is
|
|
124
|
+
// hidden — `fetchOnce` re-fires from `onVisibility` below when
|
|
125
|
+
// the tab returns to foreground.
|
|
126
|
+
//
|
|
127
|
+
// Cadence-aware polling: 5s while a job is in flight, 60s once
|
|
128
|
+
// every job has terminated. Avoids hammering the endpoint
|
|
129
|
+
// forever while the Hub sits idle.
|
|
130
|
+
if (!cancelled && document.visibilityState === 'visible') {
|
|
131
|
+
const nextDelay = hasActiveJob ? POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS;
|
|
132
|
+
timer = setTimeout(fetchOnce, nextDelay);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function onVisibility() {
|
|
137
|
+
if (document.visibilityState === 'visible' && !cancelled) {
|
|
138
|
+
// Tab came back to foreground — fire one fresh poll and the
|
|
139
|
+
// chain re-starts from there.
|
|
140
|
+
if (timer) {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
}
|
|
143
|
+
fetchOnce();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
document.addEventListener('visibilitychange', onVisibility);
|
|
147
|
+
fetchOnce();
|
|
148
|
+
return ()=>{
|
|
149
|
+
cancelled = true;
|
|
150
|
+
if (timer) {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
document.removeEventListener('visibilitychange', onVisibility);
|
|
154
|
+
};
|
|
155
|
+
}, [
|
|
156
|
+
basePath
|
|
157
|
+
]);
|
|
158
|
+
const running = jobs?.filter((j)=>effectiveStatus(j) === 'running') ?? [];
|
|
159
|
+
const stale = jobs?.filter((j)=>effectiveStatus(j) === 'stale') ?? [];
|
|
160
|
+
const recent = jobs ?? [];
|
|
161
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
162
|
+
style: SECTION_STYLE,
|
|
163
|
+
children: [
|
|
164
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
165
|
+
style: {
|
|
166
|
+
display: 'flex',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
justifyContent: 'space-between',
|
|
169
|
+
marginBottom: '0.75rem'
|
|
170
|
+
},
|
|
171
|
+
children: [
|
|
172
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
173
|
+
style: {
|
|
174
|
+
margin: 0,
|
|
175
|
+
fontSize: '1rem',
|
|
176
|
+
color: 'var(--theme-elevation-1000)'
|
|
177
|
+
},
|
|
178
|
+
children: "In-flight"
|
|
179
|
+
}),
|
|
180
|
+
/*#__PURE__*/ _jsx("span", {
|
|
181
|
+
style: {
|
|
182
|
+
fontSize: '0.75rem',
|
|
183
|
+
color: 'var(--theme-elevation-500)'
|
|
184
|
+
},
|
|
185
|
+
children: (()=>{
|
|
186
|
+
if (jobs === null) {
|
|
187
|
+
return 'Loading…';
|
|
188
|
+
}
|
|
189
|
+
// Cadence text mirrors the actual poll interval so the user
|
|
190
|
+
// isn't misled into thinking we're hammering the server.
|
|
191
|
+
// While idle (no `running` jobs) we back off to 60s; the
|
|
192
|
+
// first newly-enqueued job is picked up by the next tick.
|
|
193
|
+
const cadence = running.length > 0 ? '5s' : '60s';
|
|
194
|
+
if (stale.length > 0) {
|
|
195
|
+
return `${running.length} running · ${stale.length} stale · refresh every ${cadence}`;
|
|
196
|
+
}
|
|
197
|
+
return `${running.length} running · refresh every ${cadence}`;
|
|
198
|
+
})()
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
}),
|
|
202
|
+
error && /*#__PURE__*/ _jsx("p", {
|
|
203
|
+
style: {
|
|
204
|
+
color: 'var(--theme-error-500, #b91c1c)',
|
|
205
|
+
fontSize: '0.875rem',
|
|
206
|
+
margin: '0 0 0.5rem'
|
|
207
|
+
},
|
|
208
|
+
children: error
|
|
209
|
+
}),
|
|
210
|
+
recent.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
211
|
+
style: {
|
|
212
|
+
margin: 0,
|
|
213
|
+
color: 'var(--theme-elevation-500)',
|
|
214
|
+
fontSize: '0.875rem'
|
|
215
|
+
},
|
|
216
|
+
children: "Nothing in flight. Translations finish in seconds; historical runs live in the Recent translations table below."
|
|
217
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
218
|
+
children: [
|
|
219
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
220
|
+
style: HEADER_ROW_STYLE,
|
|
221
|
+
children: [
|
|
222
|
+
/*#__PURE__*/ _jsx("span", {
|
|
223
|
+
children: "Collection / doc"
|
|
224
|
+
}),
|
|
225
|
+
/*#__PURE__*/ _jsx("span", {
|
|
226
|
+
children: "Status"
|
|
227
|
+
}),
|
|
228
|
+
/*#__PURE__*/ _jsx("span", {
|
|
229
|
+
children: "Progress"
|
|
230
|
+
}),
|
|
231
|
+
/*#__PURE__*/ _jsx("span", {
|
|
232
|
+
children: "Started"
|
|
233
|
+
}),
|
|
234
|
+
/*#__PURE__*/ _jsx("span", {
|
|
235
|
+
style: {
|
|
236
|
+
textAlign: 'right'
|
|
237
|
+
},
|
|
238
|
+
children: "Locales"
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
}),
|
|
242
|
+
recent.map((j)=>{
|
|
243
|
+
const completed = Array.isArray(j.completedLocales) ? j.completedLocales.length : 0;
|
|
244
|
+
const failed = Array.isArray(j.failedLocales) ? j.failedLocales.length : 0;
|
|
245
|
+
const progress = `${completed}/${j.totalLocales}`;
|
|
246
|
+
const eff = effectiveStatus(j);
|
|
247
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
248
|
+
style: ROW_STYLE,
|
|
249
|
+
children: [
|
|
250
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
251
|
+
style: {
|
|
252
|
+
overflow: 'hidden',
|
|
253
|
+
textOverflow: 'ellipsis'
|
|
254
|
+
},
|
|
255
|
+
children: [
|
|
256
|
+
/*#__PURE__*/ _jsx("code", {
|
|
257
|
+
style: {
|
|
258
|
+
fontSize: '0.75rem'
|
|
259
|
+
},
|
|
260
|
+
children: j.collection
|
|
261
|
+
}),
|
|
262
|
+
' ',
|
|
263
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
264
|
+
style: {
|
|
265
|
+
color: 'var(--theme-elevation-500)'
|
|
266
|
+
},
|
|
267
|
+
children: [
|
|
268
|
+
"#",
|
|
269
|
+
j.documentId
|
|
270
|
+
]
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
}),
|
|
274
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
275
|
+
style: {
|
|
276
|
+
color: STATUS_COLORS[eff],
|
|
277
|
+
fontWeight: 500
|
|
278
|
+
},
|
|
279
|
+
children: [
|
|
280
|
+
eff === 'stale' ? 'stale (server died)' : eff,
|
|
281
|
+
failed > 0 ? ` (${failed} failed)` : ''
|
|
282
|
+
]
|
|
283
|
+
}),
|
|
284
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
285
|
+
children: [
|
|
286
|
+
progress,
|
|
287
|
+
Array.isArray(j.completedLocales) && j.completedLocales.length > 0 && /*#__PURE__*/ _jsxs("span", {
|
|
288
|
+
style: {
|
|
289
|
+
marginLeft: '0.5rem',
|
|
290
|
+
color: 'var(--theme-elevation-500)',
|
|
291
|
+
fontSize: '0.75rem'
|
|
292
|
+
},
|
|
293
|
+
children: [
|
|
294
|
+
"(",
|
|
295
|
+
j.completedLocales.join(', '),
|
|
296
|
+
")"
|
|
297
|
+
]
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
}),
|
|
301
|
+
/*#__PURE__*/ _jsx("span", {
|
|
302
|
+
style: {
|
|
303
|
+
color: 'var(--theme-elevation-500)'
|
|
304
|
+
},
|
|
305
|
+
children: relTime(j.startedAt ?? j.createdAt)
|
|
306
|
+
}),
|
|
307
|
+
/*#__PURE__*/ _jsx("span", {
|
|
308
|
+
style: {
|
|
309
|
+
textAlign: 'right'
|
|
310
|
+
},
|
|
311
|
+
children: j.totalLocales
|
|
312
|
+
})
|
|
313
|
+
]
|
|
314
|
+
}, j.id);
|
|
315
|
+
})
|
|
316
|
+
]
|
|
317
|
+
})
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Advanced tab content. Real diagnostic and developer surfaces, not just
|
|
4
|
+
* a link dump. Aimed at the admin trying to answer:
|
|
5
|
+
* - "Is the plugin healthy right now?"
|
|
6
|
+
* - "How big are the bookkeeping tables?"
|
|
7
|
+
* - "Are there stale running jobs from a crashed worker?"
|
|
8
|
+
* - "How do I force re-translate everything ignoring the hash skip?"
|
|
9
|
+
*
|
|
10
|
+
* Pulls live counts from the various plugin-owned collections (only
|
|
11
|
+
* totals — no row content — to keep this cheap).
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
basePath: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const AdvancedPanel: React.FC<Props>;
|
|
17
|
+
export default AdvancedPanel;
|