@svadmin/ui 0.73.0 → 0.74.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/README.md +53 -28
- package/dist/app.css +3733 -4119
- package/dist/app.theme.css +3733 -4119
- package/dist/component-registry.d.ts +14977 -0
- package/dist/component-registry.js +216 -0
- package/dist/components/AboutSettings.svelte +7 -1
- package/dist/components/AboutSettings.svelte.d.ts +4 -1
- package/dist/components/ActivityFeed.svelte +9 -3
- package/dist/components/ActivityFeed.svelte.d.ts +3 -0
- package/dist/components/AdminApp.svelte +19 -10
- package/dist/components/AdminApp.svelte.d.ts +6 -0
- package/dist/components/ApiSettings.svelte +41 -5
- package/dist/components/ApprovalCenter.svelte +283 -0
- package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
- package/dist/components/AuditLogViewer.svelte +3 -1
- package/dist/components/AutoForm.svelte +33 -3
- package/dist/components/AutoForm.svelte.d.ts +1 -0
- package/dist/components/AutoTable.svelte +610 -47
- package/dist/components/AutoTable.svelte.d.ts +17 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/Breadcrumbs.svelte +11 -1
- package/dist/components/CancelTaskButton.svelte +12 -42
- package/dist/components/ColorPicker.svelte +73 -0
- package/dist/components/ColorPicker.svelte.d.ts +15 -0
- package/dist/components/ComboboxField.svelte +114 -27
- package/dist/components/ComboboxField.svelte.d.ts +1 -0
- package/dist/components/DashboardView.svelte +99 -0
- package/dist/components/DashboardView.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +10 -0
- package/dist/components/DatePicker.svelte.d.ts +6 -0
- package/dist/components/DateRangeInput.svelte +85 -5
- package/dist/components/DateRangeInput.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +74 -5
- package/dist/components/DateTimeInput.svelte.d.ts +8 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +34 -26
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +82 -81
- package/dist/components/FileUpload.svelte +214 -52
- package/dist/components/FileUpload.svelte.d.ts +11 -0
- package/dist/components/FilterBuilder.svelte +88 -66
- package/dist/components/FilterCollectionInput.svelte +80 -0
- package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
- package/dist/components/GanttChart.svelte +66 -14
- package/dist/components/GanttChart.svelte.d.ts +6 -0
- package/dist/components/Header.svelte +18 -2
- package/dist/components/ImportWizard.svelte +339 -17
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +120 -52
- package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -2
- package/dist/components/LazyAutoForm.svelte +28 -0
- package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
- package/dist/components/LazyAutoTable.svelte +28 -0
- package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
- package/dist/components/LazySettingsPage.svelte +28 -0
- package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
- package/dist/components/LazyShowPage.svelte +28 -0
- package/dist/components/LazyShowPage.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +5 -1
- package/dist/components/ListPage.svelte.d.ts +3 -0
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MultiTabKeepAlive.svelte +11 -6
- package/dist/components/NotificationsSettings.svelte +69 -2
- package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
- package/dist/components/PermissionMatrix.svelte +20 -1
- package/dist/components/PivotExportButton.svelte +157 -0
- package/dist/components/PivotExportButton.svelte.d.ts +16 -0
- package/dist/components/PivotTable.svelte +229 -74
- package/dist/components/PivotTable.svelte.d.ts +19 -2
- package/dist/components/RadioGroup.svelte +121 -0
- package/dist/components/RadioGroup.svelte.d.ts +43 -0
- package/dist/components/RangeSlider.svelte +127 -0
- package/dist/components/RangeSlider.svelte.d.ts +22 -0
- package/dist/components/Rate.svelte +121 -0
- package/dist/components/Rate.svelte.d.ts +16 -0
- package/dist/components/RecordDetailDrawer.svelte +4 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +18 -18
- package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
- package/dist/components/RetryTaskButton.svelte +12 -42
- package/dist/components/SecuritySettings.svelte +29 -5
- package/dist/components/SegmentedControl.svelte +145 -0
- package/dist/components/SegmentedControl.svelte.d.ts +38 -0
- package/dist/components/SettingsPage.svelte +3 -1
- package/dist/components/SettingsPage.svelte.d.ts +1 -0
- package/dist/components/ShowPage.svelte +18 -3
- package/dist/components/ShowPage.svelte.d.ts +3 -0
- package/dist/components/Sidebar.svelte +2 -2
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1213 -95
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +267 -53
- package/dist/components/TreeSelect.svelte.d.ts +3 -0
- package/dist/components/TreeTable.svelte +484 -82
- package/dist/components/TreeTable.svelte.d.ts +24 -1
- package/dist/components/TwoFactorAuthPage.svelte +85 -48
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
- package/dist/components/VirtualTable.svelte +116 -20
- package/dist/components/VirtualTable.svelte.d.ts +5 -0
- package/dist/components/account/CompanyProfilePage.svelte +74 -38
- package/dist/components/account/GetStartedPage.svelte +2 -1
- package/dist/components/account/ImportMembersPage.svelte +22 -167
- package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
- package/dist/components/account/MemberDirectory.svelte +156 -0
- package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
- package/dist/components/account/MembersStarterPage.svelte +9 -55
- package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
- package/dist/components/account/SecurityLogPage.svelte +4 -1
- package/dist/components/account/SettingsPlainPage.svelte +7 -38
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
- package/dist/components/account/SettingsSidebarPage.svelte +7 -20
- package/dist/components/account/TeamMembersPage.svelte +4 -20
- package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
- package/dist/components/account/UserProfilePage.svelte +3 -25
- package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
- package/dist/components/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +1 -1
- package/dist/components/content/ContentPageShell.svelte +1 -1
- package/dist/components/content/MetricBlock.svelte +1 -1
- package/dist/components/content/PageToolbar.svelte +1 -1
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +1 -1
- package/dist/components/content/SettingsFieldRow.svelte +1 -1
- package/dist/components/content/SettingsGroup.svelte +1 -1
- package/dist/components/content/StatusBadge.svelte +1 -1
- package/dist/components/content/WorkspaceLayout.svelte +1 -1
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -5
- package/dist/components/fields/DateField.svelte +12 -13
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -0
- package/dist/components/network/TeamCrewTablePage.svelte +6 -1
- package/dist/components/network/UserCardsNFTPage.svelte +2 -0
- package/dist/components/profile/PublicProfilePage.svelte +2 -0
- package/dist/components/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- package/dist/components/table-contract.d.ts +12 -1
- package/dist/components/task-action.svelte.d.ts +13 -0
- package/dist/components/task-action.svelte.js +73 -0
- package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
- package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge-variants.d.ts +3 -2
- package/dist/components/ui/badge/badge-variants.js +2 -3
- package/dist/components/ui/badge/badge.svelte +3 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
- package/dist/components/ui/button/button-variants.d.ts +5 -4
- package/dist/components/ui/button/button-variants.js +7 -5
- package/dist/components/ui/checkbox/checkbox.svelte +5 -1
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
- package/dist/components/ui/input/input.svelte +7 -2
- package/dist/components/ui/input/input.svelte.d.ts +4 -1
- package/dist/components/ui/select/select.svelte +4 -1
- package/dist/components/ui/select/select.svelte.d.ts +2 -1
- package/dist/components/ui/textarea/textarea.svelte +2 -2
- package/dist/components.css +33 -2
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.js +1 -1
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +37 -2
- package/dist/index.js +23 -2
- package/dist/recipes.d.ts +960 -4
- package/dist/recipes.js +264 -4
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/package.json +22 -10
- package/dist/styled-system/css/conditions.js +0 -36
- package/dist/styled-system/css/css.d.ts +0 -22
- package/dist/styled-system/css/css.js +0 -45
- package/dist/styled-system/css/cva.d.ts +0 -6
- package/dist/styled-system/css/cva.js +0 -87
- package/dist/styled-system/css/cx.d.ts +0 -5
- package/dist/styled-system/css/cx.js +0 -15
- package/dist/styled-system/css/index.d.ts +0 -5
- package/dist/styled-system/css/index.js +0 -4
- package/dist/styled-system/css/sva.d.ts +0 -4
- package/dist/styled-system/css/sva.js +0 -46
- package/dist/styled-system/helpers.js +0 -316
- package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
- package/dist/styled-system/patterns/aspect-ratio.js +0 -38
- package/dist/styled-system/patterns/bleed.d.ts +0 -21
- package/dist/styled-system/patterns/bleed.js +0 -24
- package/dist/styled-system/patterns/box.d.ts +0 -20
- package/dist/styled-system/patterns/box.js +0 -15
- package/dist/styled-system/patterns/center.d.ts +0 -20
- package/dist/styled-system/patterns/center.js +0 -21
- package/dist/styled-system/patterns/circle.d.ts +0 -20
- package/dist/styled-system/patterns/circle.js +0 -25
- package/dist/styled-system/patterns/container.d.ts +0 -20
- package/dist/styled-system/patterns/container.js +0 -21
- package/dist/styled-system/patterns/cq.d.ts +0 -21
- package/dist/styled-system/patterns/cq.js +0 -21
- package/dist/styled-system/patterns/divider.d.ts +0 -22
- package/dist/styled-system/patterns/divider.js +0 -25
- package/dist/styled-system/patterns/flex.d.ts +0 -26
- package/dist/styled-system/patterns/flex.js +0 -26
- package/dist/styled-system/patterns/float.d.ts +0 -23
- package/dist/styled-system/patterns/float.js +0 -52
- package/dist/styled-system/patterns/grid-item.d.ts +0 -25
- package/dist/styled-system/patterns/grid-item.js +0 -25
- package/dist/styled-system/patterns/grid.d.ts +0 -24
- package/dist/styled-system/patterns/grid.js +0 -27
- package/dist/styled-system/patterns/hstack.d.ts +0 -21
- package/dist/styled-system/patterns/hstack.js +0 -24
- package/dist/styled-system/patterns/index.d.ts +0 -21
- package/dist/styled-system/patterns/index.js +0 -20
- package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
- package/dist/styled-system/patterns/link-overlay.js +0 -24
- package/dist/styled-system/patterns/spacer.d.ts +0 -20
- package/dist/styled-system/patterns/spacer.js +0 -25
- package/dist/styled-system/patterns/square.d.ts +0 -20
- package/dist/styled-system/patterns/square.js +0 -24
- package/dist/styled-system/patterns/stack.d.ts +0 -23
- package/dist/styled-system/patterns/stack.js +0 -24
- package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
- package/dist/styled-system/patterns/visually-hidden.js +0 -18
- package/dist/styled-system/patterns/vstack.d.ts +0 -21
- package/dist/styled-system/patterns/vstack.js +0 -24
- package/dist/styled-system/patterns/wrap.d.ts +0 -24
- package/dist/styled-system/patterns/wrap.js +0 -25
- package/dist/styled-system/recipes/content-header.d.ts +0 -31
- package/dist/styled-system/recipes/content-header.js +0 -65
- package/dist/styled-system/recipes/content-page.d.ts +0 -34
- package/dist/styled-system/recipes/content-page.js +0 -43
- package/dist/styled-system/recipes/create-recipe.js +0 -82
- package/dist/styled-system/recipes/index.d.ts +0 -17
- package/dist/styled-system/recipes/index.js +0 -16
- package/dist/styled-system/recipes/metric-block.d.ts +0 -34
- package/dist/styled-system/recipes/metric-block.js +0 -76
- package/dist/styled-system/recipes/product-list.d.ts +0 -31
- package/dist/styled-system/recipes/product-list.js +0 -73
- package/dist/styled-system/recipes/product-section.d.ts +0 -31
- package/dist/styled-system/recipes/product-section.js +0 -49
- package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
- package/dist/styled-system/recipes/product-settings-row.js +0 -58
- package/dist/styled-system/recipes/product-settings.d.ts +0 -31
- package/dist/styled-system/recipes/product-settings.js +0 -57
- package/dist/styled-system/recipes/product-status.d.ts +0 -34
- package/dist/styled-system/recipes/product-status.js +0 -45
- package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
- package/dist/styled-system/recipes/product-toolbar.js +0 -41
- package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
- package/dist/styled-system/recipes/product-workspace.js +0 -58
- package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
- package/dist/styled-system/recipes/surface-metric.js +0 -63
- package/dist/styled-system/recipes/surface-table.d.ts +0 -34
- package/dist/styled-system/recipes/surface-table.js +0 -62
- package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
- package/dist/styled-system/recipes/ui-badge.js +0 -38
- package/dist/styled-system/recipes/ui-button.d.ts +0 -32
- package/dist/styled-system/recipes/ui-button.js +0 -43
- package/dist/styled-system/recipes/ui-input.d.ts +0 -31
- package/dist/styled-system/recipes/ui-input.js +0 -49
- package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
- package/dist/styled-system/recipes/ui-textarea.js +0 -24
- package/dist/styled-system/styles.css +0 -1257
- package/dist/styled-system/tokens/index.d.ts +0 -9
- package/dist/styled-system/tokens/index.js +0 -268
- package/dist/styled-system/tokens/tokens.d.ts +0 -36
- package/dist/styled-system/types/composition.d.ts +0 -227
- package/dist/styled-system/types/conditions.d.ts +0 -236
- package/dist/styled-system/types/csstype.d.ts +0 -22570
- package/dist/styled-system/types/index.d.ts +0 -6
- package/dist/styled-system/types/parts.d.ts +0 -8
- package/dist/styled-system/types/pattern.d.ts +0 -78
- package/dist/styled-system/types/prop-type.d.ts +0 -223
- package/dist/styled-system/types/recipe.d.ts +0 -181
- package/dist/styled-system/types/selectors.d.ts +0 -59
- package/dist/styled-system/types/static-css.d.ts +0 -56
- package/dist/styled-system/types/style-props.d.ts +0 -8088
- package/dist/styled-system/types/system-types.d.ts +0 -151
- package/dist/styles/recipes.css +0 -1262
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { captureAdminContext, captureAuthSession, useCan, useImport, useResourceContract } from '@svadmin/core';
|
|
2
|
+
import { captureAdminContext, captureAuthSession, IMPORT_LIMITS, snapshotImportArtifact, snapshotImportTaskResult, useCan, useImport, useResourceContract, useSubmitTask, useTask } from '@svadmin/core';
|
|
3
3
|
import { untrack } from 'svelte';
|
|
4
4
|
import { definedReactiveOptions } from '@svadmin/core/options';
|
|
5
5
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
6
|
+
import { parseContractCreateInput } from '@svadmin/core/resource-contract';
|
|
7
|
+
import type { ImportArtifactProvider, TaskProvider } from '@svadmin/core';
|
|
8
|
+
import { normalizeTaskStatus, resolveTaskProgress, canCancelTask } from './task-utils.js';
|
|
9
|
+
import TaskStatusBadge from './TaskStatusBadge.svelte';
|
|
10
|
+
import CancelTaskButton from './CancelTaskButton.svelte';
|
|
6
11
|
import { parseCSV, toCsv } from '@svadmin/core';
|
|
7
12
|
import * as Dialog from './ui/dialog/index.js';
|
|
8
13
|
import { Button } from './ui/button/index.js';
|
|
9
14
|
import { Progress } from './ui/progress/index.js';
|
|
10
15
|
import { Badge } from './ui/badge/index.js';
|
|
11
|
-
import { Upload, ArrowRight, ArrowLeft, AlertCircle, Download, FileSpreadsheet, Loader2 } from '@lucide/svelte';
|
|
16
|
+
import { Upload, ArrowRight, ArrowLeft, AlertCircle, Download, FileSpreadsheet, Loader2, RotateCw } from '@lucide/svelte';
|
|
12
17
|
|
|
13
18
|
interface Props {
|
|
14
19
|
resourceName: string;
|
|
15
20
|
open?: boolean;
|
|
16
21
|
batchSize?: number;
|
|
22
|
+
maxRows?: number;
|
|
23
|
+
maxBytes?: number;
|
|
24
|
+
taskName?: string;
|
|
25
|
+
retryTaskName?: string;
|
|
26
|
+
taskProvider?: TaskProvider;
|
|
27
|
+
/** 上传原文件并以 artifact 引用提交任务,避免任务请求重复携带 records。 */
|
|
28
|
+
taskArtifactProvider?: ImportArtifactProvider;
|
|
29
|
+
taskIdempotencyKey?: string;
|
|
30
|
+
initialTaskId?: string;
|
|
31
|
+
onTaskSubmitted?: (taskId: string) => void;
|
|
17
32
|
onSuccess?: (result: { succeeded: number; failed: number }) => void;
|
|
18
33
|
}
|
|
19
34
|
|
|
@@ -21,6 +36,15 @@
|
|
|
21
36
|
resourceName,
|
|
22
37
|
open = $bindable(false),
|
|
23
38
|
batchSize = 20,
|
|
39
|
+
maxRows = IMPORT_LIMITS.maxRows,
|
|
40
|
+
maxBytes = IMPORT_LIMITS.maxBytes,
|
|
41
|
+
taskName,
|
|
42
|
+
retryTaskName,
|
|
43
|
+
taskProvider,
|
|
44
|
+
taskArtifactProvider,
|
|
45
|
+
taskIdempotencyKey,
|
|
46
|
+
initialTaskId,
|
|
47
|
+
onTaskSubmitted,
|
|
24
48
|
onSuccess,
|
|
25
49
|
}: Props = $props();
|
|
26
50
|
|
|
@@ -31,10 +55,13 @@
|
|
|
31
55
|
const can = useCan(() => ({ resource: resourceName, action: 'import' }));
|
|
32
56
|
const session = $derived(captureAuthSession(context.authProvider));
|
|
33
57
|
const allowed = $derived(can.allowed === true && resource.canCreate !== false && session.available);
|
|
58
|
+
const activeTaskProvider = $derived(taskProvider ?? context.taskProvider);
|
|
34
59
|
const scope = $derived({
|
|
35
60
|
contract: binding.resource, provider: context.providers?.[binding.dataProviderName],
|
|
36
61
|
tenant: context.tenantCacheKey?.__svadminTenant, meta: binding.meta,
|
|
37
62
|
auth: context.authProvider, router: context.routerProvider, session, allowed,
|
|
63
|
+
taskProvider: activeTaskProvider, taskName, retryTaskName, taskIdempotencyKey, taskArtifactProvider,
|
|
64
|
+
access: context.accessControlProvider,
|
|
38
65
|
});
|
|
39
66
|
const availableFields = $derived(
|
|
40
67
|
resource.fields.filter((f) => f.key !== (resource.primaryKey ?? 'id') && f.showInForm !== false)
|
|
@@ -50,6 +77,32 @@
|
|
|
50
77
|
let importError = $state<string | null>(null);
|
|
51
78
|
let parseToken = 0;
|
|
52
79
|
let mounted = true;
|
|
80
|
+
let cancelled = $state(false);
|
|
81
|
+
let durableTaskId = $state<string | undefined>(untrack(() => initialTaskId));
|
|
82
|
+
let durableTaskScope = $state.raw<typeof scope | undefined>(untrack(() => scope));
|
|
83
|
+
const ownedTaskId = $derived(durableTaskScope && sameScope(durableTaskScope, scope) ? durableTaskId : undefined);
|
|
84
|
+
let durableHandledTaskId = $state<string | undefined>();
|
|
85
|
+
let durableSubmitAttempt = $state.raw<object | undefined>();
|
|
86
|
+
let artifactUploadController: AbortController | undefined;
|
|
87
|
+
let durablePolling = $state(true);
|
|
88
|
+
const durableMode = $derived(taskName !== undefined);
|
|
89
|
+
const submitTask = useSubmitTask();
|
|
90
|
+
const taskQuery = useTask(definedReactiveOptions({
|
|
91
|
+
get taskId() { return ownedTaskId; },
|
|
92
|
+
get taskProvider() { return activeTaskProvider; },
|
|
93
|
+
get queryOptions() {
|
|
94
|
+
return {
|
|
95
|
+
enabled: open && allowed && durableMode && !!activeTaskProvider && !!ownedTaskId,
|
|
96
|
+
refetchInterval: open && allowed && durableMode && ownedTaskId && durablePolling ? 2000 : false as const,
|
|
97
|
+
refetchOnWindowFocus: true,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
const durableStatus = $derived(normalizeTaskStatus(taskQuery.data?.status));
|
|
102
|
+
const retryReceipt = $derived(durableStatus === 'completed'
|
|
103
|
+
? snapshotImportTaskResult(taskQuery.data?.result ?? taskQuery.data?.result_data)?.retry : undefined);
|
|
104
|
+
const durableActive = $derived(durableStatus === 'queued' || durableStatus === 'processing'
|
|
105
|
+
|| !!durableSubmitAttempt);
|
|
53
106
|
|
|
54
107
|
let importResult = $state<{ succeeded: number; failed: number } | null>(null);
|
|
55
108
|
let succeededCount = $state(0);
|
|
@@ -59,10 +112,12 @@
|
|
|
59
112
|
get resource() { return binding.resource; },
|
|
60
113
|
get dataProviderName() { return binding.dataProviderName; },
|
|
61
114
|
get batchSize() { return batchSize; },
|
|
115
|
+
get maxRows() { return maxRows; },
|
|
116
|
+
get maxBytes() { return maxBytes; },
|
|
62
117
|
get enabled() { return open && allowed; },
|
|
63
118
|
mapData: mapRow,
|
|
64
119
|
}));
|
|
65
|
-
const isImporting = $derived(importer.isLoading);
|
|
120
|
+
const isImporting = $derived(importer.isLoading || durableActive);
|
|
66
121
|
const processedCount = $derived(importer.progress.processedAmount);
|
|
67
122
|
const totalCount = $derived(importer.progress.totalAmount || rawRows.length);
|
|
68
123
|
const progressPercent = $derived(
|
|
@@ -75,24 +130,35 @@
|
|
|
75
130
|
const token = ++parseToken;
|
|
76
131
|
fileError = null;
|
|
77
132
|
importError = null;
|
|
133
|
+
durableTaskId = undefined;
|
|
134
|
+
durableHandledTaskId = undefined;
|
|
135
|
+
durableSubmitAttempt = undefined;
|
|
78
136
|
selectedFile = null;
|
|
79
137
|
fileName = file.name;
|
|
80
138
|
rawHeaders = [];
|
|
81
139
|
rawRows = [];
|
|
82
140
|
columnMapping = {};
|
|
83
141
|
try {
|
|
142
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > IMPORT_LIMITS.maxRows
|
|
143
|
+
|| !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > IMPORT_LIMITS.maxBytes) {
|
|
144
|
+
throw new Error(i18n.t('import.invalidLimits'));
|
|
145
|
+
}
|
|
146
|
+
if (file.size > maxBytes) throw new Error(i18n.t('import.limit'));
|
|
84
147
|
const text = await file.text();
|
|
85
148
|
if (!current(origin, token)) return;
|
|
149
|
+
if (text.length > maxBytes) throw new Error(i18n.t('import.limit'));
|
|
86
150
|
const cleanText = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
87
151
|
|
|
88
152
|
if (file.name.toLowerCase().endsWith('.json')) {
|
|
89
153
|
const json: unknown = JSON.parse(cleanText);
|
|
154
|
+
if (Array.isArray(json) && json.length > maxRows) throw new Error(i18n.t('import.limit'));
|
|
90
155
|
if (!Array.isArray(json) || json.length === 0 || json.some((item) => (
|
|
91
156
|
typeof item !== 'object' || item === null || Array.isArray(item)
|
|
92
157
|
))) {
|
|
93
158
|
throw new Error('The JSON file must contain a non-empty array of records.');
|
|
94
159
|
}
|
|
95
160
|
const records = json as Record<string, unknown>[];
|
|
161
|
+
if (records.length > maxRows) throw new Error(i18n.t('import.limit'));
|
|
96
162
|
const keys = [...new Set(records.flatMap(item => Object.keys(item)))];
|
|
97
163
|
if (keys.length === 0) throw new Error('The import file has no columns.');
|
|
98
164
|
rawHeaders = keys;
|
|
@@ -109,6 +175,7 @@
|
|
|
109
175
|
const records = rows.slice(1).filter((r: string[]) =>
|
|
110
176
|
r.some((cell: string) => cell.trim().length > 0));
|
|
111
177
|
if (records.length === 0) throw new Error('The import file has no records.');
|
|
178
|
+
if (records.length > maxRows) throw new Error(i18n.t('import.limit'));
|
|
112
179
|
if (records.some((row) => row.length !== firstRow.length)) {
|
|
113
180
|
throw new Error('The CSV file contains rows with inconsistent column counts.');
|
|
114
181
|
}
|
|
@@ -161,6 +228,13 @@
|
|
|
161
228
|
const normalized = val.trim().toLowerCase();
|
|
162
229
|
if (['true', '1', 'yes', '是'].includes(normalized)) val = true;
|
|
163
230
|
else if (['false', '0', 'no', '否'].includes(normalized)) val = false;
|
|
231
|
+
} else if (fieldDef?.type === 'select' && fieldDef.options && selectedFile?.name.toLowerCase().endsWith('.csv')) {
|
|
232
|
+
const matches = fieldDef.options.filter(option => String(option.value) === val);
|
|
233
|
+
const match = matches[0];
|
|
234
|
+
if (matches.length !== 1 || !match || match.disabled) throw new Error('Invalid import option.');
|
|
235
|
+
val = match.value;
|
|
236
|
+
} else if (fieldDef?.type === 'json' && selectedFile?.name.toLowerCase().endsWith('.csv')) {
|
|
237
|
+
val = JSON.parse(val);
|
|
164
238
|
}
|
|
165
239
|
}
|
|
166
240
|
if (val !== undefined) {
|
|
@@ -170,13 +244,34 @@
|
|
|
170
244
|
return record;
|
|
171
245
|
}
|
|
172
246
|
|
|
247
|
+
function validateMappedRows(): string | null {
|
|
248
|
+
for (const [index, source] of rawRows.entries()) {
|
|
249
|
+
try {
|
|
250
|
+
const mapped = mapRow(Object.fromEntries(rawHeaders.map((header, column) => [header, source[column]])));
|
|
251
|
+
parseContractCreateInput(binding.resource, mapped);
|
|
252
|
+
} catch {
|
|
253
|
+
return i18n.t('import.invalidRow', { row: index + 1 });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
173
259
|
async function startImport() {
|
|
174
260
|
if (!selectedFile || rawRows.length === 0 || isImporting || !open || !allowed) return;
|
|
261
|
+
if (durableMode && (!activeTaskProvider || !taskName?.trim() || !taskIdempotencyKey?.trim())) {
|
|
262
|
+
fileError = i18n.t('import.taskConfiguration');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
175
265
|
const targets = Object.values(columnMapping).filter(Boolean);
|
|
176
266
|
if (targets.length === 0 || new Set(targets).size !== targets.length) {
|
|
177
267
|
fileError = 'Map at least one column, and use each target field only once.';
|
|
178
268
|
return;
|
|
179
269
|
}
|
|
270
|
+
const mappingError = validateMappedRows();
|
|
271
|
+
if (mappingError) {
|
|
272
|
+
fileError = mappingError;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
180
275
|
const origin = scope;
|
|
181
276
|
const token = parseToken;
|
|
182
277
|
const notifySuccess = onSuccess;
|
|
@@ -185,6 +280,83 @@
|
|
|
185
280
|
failedRecords = [];
|
|
186
281
|
importResult = null;
|
|
187
282
|
importError = null;
|
|
283
|
+
cancelled = false;
|
|
284
|
+
if (durableMode) {
|
|
285
|
+
if (durableTaskId || durableSubmitAttempt) return;
|
|
286
|
+
const attempt = {};
|
|
287
|
+
durableSubmitAttempt = attempt;
|
|
288
|
+
const uploadController = new AbortController();
|
|
289
|
+
artifactUploadController = uploadController;
|
|
290
|
+
try {
|
|
291
|
+
if (!activeTaskProvider || !taskName?.trim() || !taskIdempotencyKey?.trim()) {
|
|
292
|
+
throw new Error(i18n.t('import.taskConfiguration'));
|
|
293
|
+
}
|
|
294
|
+
const records = taskArtifactProvider ? undefined : rawRows.map((source, index) => {
|
|
295
|
+
const mapped = mapRow(Object.fromEntries(rawHeaders.map((header, column) => [header, source[column]])));
|
|
296
|
+
try { return parseContractCreateInput(binding.resource, mapped); }
|
|
297
|
+
catch { throw new Error(i18n.t('import.invalidRow', { row: index + 1 })); }
|
|
298
|
+
});
|
|
299
|
+
const uploaded = taskArtifactProvider
|
|
300
|
+
? snapshotImportArtifact(await taskArtifactProvider.upload({
|
|
301
|
+
file: selectedFile,
|
|
302
|
+
fileName,
|
|
303
|
+
size: selectedFile.size,
|
|
304
|
+
resource: binding.resource.name,
|
|
305
|
+
idempotencyKey: JSON.stringify(['import-artifact', taskIdempotencyKey]),
|
|
306
|
+
signal: uploadController.signal,
|
|
307
|
+
}))
|
|
308
|
+
: undefined;
|
|
309
|
+
if (durableSubmitAttempt !== attempt || !current(origin, token) || uploadController.signal.aborted) return;
|
|
310
|
+
if (taskArtifactProvider && !uploaded) throw new Error(i18n.t('import.taskPayloadLimit'));
|
|
311
|
+
if (uploaded && (uploaded.size !== selectedFile.size || uploaded.fileName !== fileName)) {
|
|
312
|
+
throw new Error(i18n.t('import.taskPayloadLimit'));
|
|
313
|
+
}
|
|
314
|
+
const body = uploaded ? {
|
|
315
|
+
protocolVersion: 2,
|
|
316
|
+
resource: binding.resource.name,
|
|
317
|
+
fileName,
|
|
318
|
+
mapping: { ...columnMapping },
|
|
319
|
+
artifact: uploaded,
|
|
320
|
+
} : {
|
|
321
|
+
protocolVersion: 1,
|
|
322
|
+
resource: binding.resource.name,
|
|
323
|
+
fileName,
|
|
324
|
+
mapping: { ...columnMapping },
|
|
325
|
+
records,
|
|
326
|
+
};
|
|
327
|
+
const encoded = JSON.stringify(body);
|
|
328
|
+
if (new TextEncoder().encode(encoded).byteLength > maxBytes) {
|
|
329
|
+
fileError = i18n.t('import.taskPayloadLimit');
|
|
330
|
+
currentStep = 2;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const handle = await submitTask.mutation.mutateAsync({
|
|
334
|
+
taskName: taskName.trim(),
|
|
335
|
+
taskProvider: activeTaskProvider,
|
|
336
|
+
successNotification: false,
|
|
337
|
+
errorNotification: false,
|
|
338
|
+
options: {
|
|
339
|
+
body,
|
|
340
|
+
idempotencyKey: taskIdempotencyKey,
|
|
341
|
+
...(binding.meta === undefined ? {} : { meta: binding.meta }),
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
|
|
345
|
+
durableTaskId = handle.id;
|
|
346
|
+
durableTaskScope = scope;
|
|
347
|
+
durableHandledTaskId = undefined;
|
|
348
|
+
currentStep = 3;
|
|
349
|
+
try { void Promise.resolve(onTaskSubmitted?.(handle.id)).catch(() => {}); }
|
|
350
|
+
catch { /* 观察者失败不重发任务。 */ }
|
|
351
|
+
} catch {
|
|
352
|
+
if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
|
|
353
|
+
importError = i18n.t('import.taskUnconfirmed');
|
|
354
|
+
} finally {
|
|
355
|
+
if (artifactUploadController === uploadController) artifactUploadController = undefined;
|
|
356
|
+
if (durableSubmitAttempt === attempt) durableSubmitAttempt = undefined;
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
188
360
|
try {
|
|
189
361
|
const result = await importer.handleChange({ file: selectedFile });
|
|
190
362
|
if (!current(origin, token)) return;
|
|
@@ -206,7 +378,63 @@
|
|
|
206
378
|
}
|
|
207
379
|
|
|
208
380
|
function current(origin: typeof scope, token: number): boolean {
|
|
209
|
-
return mounted && open && allowed && origin
|
|
381
|
+
return mounted && open && allowed && sameScope(origin, scope) && origin.session.isCurrent() && token === parseToken;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function sameScope(left: typeof scope, right: typeof scope): boolean {
|
|
385
|
+
return left.contract === right.contract && left.provider === right.provider &&
|
|
386
|
+
left.tenant === right.tenant && JSON.stringify(left.meta) === JSON.stringify(right.meta) &&
|
|
387
|
+
left.auth === right.auth && left.session.cacheKey === right.session.cacheKey &&
|
|
388
|
+
left.router === right.router && left.access === right.access &&
|
|
389
|
+
left.taskProvider === right.taskProvider && left.taskArtifactProvider === right.taskArtifactProvider &&
|
|
390
|
+
left.taskName === right.taskName && left.retryTaskName === right.retryTaskName &&
|
|
391
|
+
left.taskIdempotencyKey === right.taskIdempotencyKey;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function retryFailedRows(): Promise<void> {
|
|
395
|
+
const receipt = retryReceipt;
|
|
396
|
+
const parentTaskId = ownedTaskId;
|
|
397
|
+
if (!open || !allowed || !receipt || !parentTaskId || !retryTaskName?.trim()
|
|
398
|
+
|| !activeTaskProvider || durableSubmitAttempt || taskQuery.isError) return;
|
|
399
|
+
const origin = scope;
|
|
400
|
+
const token = parseToken;
|
|
401
|
+
const attempt = {};
|
|
402
|
+
durableSubmitAttempt = attempt;
|
|
403
|
+
importError = null;
|
|
404
|
+
try {
|
|
405
|
+
// 同一父任务和不可变回执始终使用同一键;未知提交结果不得转成新写入。
|
|
406
|
+
const handle = await submitTask.mutation.mutateAsync({
|
|
407
|
+
taskName: retryTaskName.trim(),
|
|
408
|
+
taskProvider: activeTaskProvider,
|
|
409
|
+
successNotification: false,
|
|
410
|
+
errorNotification: false,
|
|
411
|
+
options: {
|
|
412
|
+
idempotencyKey: JSON.stringify(['import-failed-rows', parentTaskId, receipt.receiptId]),
|
|
413
|
+
body: {
|
|
414
|
+
protocolVersion: 1,
|
|
415
|
+
operation: 'retry-failed-rows',
|
|
416
|
+
resource: binding.resource.name,
|
|
417
|
+
parentTaskId,
|
|
418
|
+
receiptId: receipt.receiptId,
|
|
419
|
+
rows: [...receipt.rows].sort((a, b) => a - b),
|
|
420
|
+
},
|
|
421
|
+
...(binding.meta === undefined ? {} : { meta: binding.meta }),
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
|
|
425
|
+
if (handle.id === parentTaskId) throw new Error('Retry must create a child task');
|
|
426
|
+
durableTaskId = handle.id;
|
|
427
|
+
durableTaskScope = scope;
|
|
428
|
+
durableHandledTaskId = undefined;
|
|
429
|
+
durablePolling = true;
|
|
430
|
+
importResult = null;
|
|
431
|
+
try { void Promise.resolve(onTaskSubmitted?.(handle.id)).catch(() => {}); }
|
|
432
|
+
catch { /* 通知失败不重复提交。 */ }
|
|
433
|
+
} catch {
|
|
434
|
+
if (durableSubmitAttempt === attempt && current(origin, token)) importError = i18n.t('import.taskUnconfirmed');
|
|
435
|
+
} finally {
|
|
436
|
+
if (durableSubmitAttempt === attempt) durableSubmitAttempt = undefined;
|
|
437
|
+
}
|
|
210
438
|
}
|
|
211
439
|
|
|
212
440
|
function downloadErrors() {
|
|
@@ -220,12 +448,20 @@
|
|
|
220
448
|
const url = URL.createObjectURL(blob);
|
|
221
449
|
const link = document.createElement('a');
|
|
222
450
|
link.href = url;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
451
|
+
const safeResourceName = resourceName.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
452
|
+
.replace(/^[._-]+/, '').slice(0, 100).replace(/[._-]+$/, '') || 'resource';
|
|
453
|
+
link.download = `${safeResourceName}-import-errors.csv`;
|
|
454
|
+
try {
|
|
455
|
+
link.click();
|
|
456
|
+
} finally {
|
|
457
|
+
// 浏览器需要在 click 返回后仍能读取对象 URL,延迟回收避免空下载。
|
|
458
|
+
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
|
459
|
+
}
|
|
226
460
|
}
|
|
227
461
|
|
|
228
462
|
function reset() {
|
|
463
|
+
artifactUploadController?.abort();
|
|
464
|
+
artifactUploadController = undefined;
|
|
229
465
|
parseToken += 1;
|
|
230
466
|
currentStep = 1;
|
|
231
467
|
fileName = '';
|
|
@@ -238,15 +474,62 @@
|
|
|
238
474
|
importResult = null;
|
|
239
475
|
fileError = null;
|
|
240
476
|
importError = null;
|
|
477
|
+
cancelled = false;
|
|
478
|
+
durableSubmitAttempt = undefined;
|
|
479
|
+
durableHandledTaskId = undefined;
|
|
480
|
+
durablePolling = true;
|
|
241
481
|
importer.reset();
|
|
242
482
|
}
|
|
243
483
|
|
|
484
|
+
function cancelImport() {
|
|
485
|
+
if (durableMode && durableTaskId && durableActive) return;
|
|
486
|
+
if (!isImporting) return;
|
|
487
|
+
// 废弃 UI 回执与核心执行令牌;已经发出的请求并不因此回滚。
|
|
488
|
+
parseToken++;
|
|
489
|
+
artifactUploadController?.abort();
|
|
490
|
+
artifactUploadController = undefined;
|
|
491
|
+
durableSubmitAttempt = undefined;
|
|
492
|
+
importer.reset();
|
|
493
|
+
cancelled = true;
|
|
494
|
+
importResult = null;
|
|
495
|
+
importError = null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let observedScope = untrack(() => scope);
|
|
499
|
+
let observedOpen = untrack(() => open);
|
|
244
500
|
$effect.pre(() => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
501
|
+
const changed = !sameScope(observedScope, scope) || (observedScope.allowed && !allowed);
|
|
502
|
+
if (changed) {
|
|
503
|
+
durableTaskId = undefined;
|
|
504
|
+
durableTaskScope = undefined;
|
|
505
|
+
durableHandledTaskId = undefined;
|
|
506
|
+
}
|
|
507
|
+
if (changed || observedOpen !== open) untrack(reset);
|
|
508
|
+
observedScope = scope;
|
|
509
|
+
observedOpen = open;
|
|
248
510
|
});
|
|
249
|
-
$effect(() =>
|
|
511
|
+
$effect(() => {
|
|
512
|
+
const task = taskQuery.data;
|
|
513
|
+
if (!open || !allowed || !durableTaskId || !task || durableHandledTaskId === durableTaskId) return;
|
|
514
|
+
if (!['completed', 'failed', 'cancelled'].includes(durableStatus)) return;
|
|
515
|
+
durableHandledTaskId = durableTaskId;
|
|
516
|
+
durablePolling = false;
|
|
517
|
+
if (durableStatus === 'completed') {
|
|
518
|
+
const result = snapshotImportTaskResult(task.result ?? task.result_data);
|
|
519
|
+
if (result) {
|
|
520
|
+
succeededCount = result.succeeded;
|
|
521
|
+
importResult = { succeeded: result.succeeded, failed: result.failed };
|
|
522
|
+
try { void Promise.resolve(onSuccess?.(importResult)).catch(() => {}); }
|
|
523
|
+
catch { /* 任务完成通知失败不改变服务端任务结果。 */ }
|
|
524
|
+
} else {
|
|
525
|
+
importError = i18n.t('import.invalidTaskResult');
|
|
526
|
+
}
|
|
527
|
+
} else if (durableStatus === 'failed' || durableStatus === 'cancelled') {
|
|
528
|
+
importError = i18n.t('common.operationFailed');
|
|
529
|
+
importResult = null;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
$effect(() => () => { mounted = false; parseToken += 1; artifactUploadController?.abort(); });
|
|
250
533
|
</script>
|
|
251
534
|
|
|
252
535
|
{#if open}
|
|
@@ -272,7 +555,41 @@
|
|
|
272
555
|
{#if !allowed}
|
|
273
556
|
<p role="status">{i18n.t('common.operationFailed', { defaultValue: 'Import is unavailable.' })}</p>
|
|
274
557
|
{/if}
|
|
275
|
-
{#if currentStep ===
|
|
558
|
+
{#if durableMode && (durableTaskId || durableSubmitAttempt || currentStep === 3)}
|
|
559
|
+
{#if durableTaskId}
|
|
560
|
+
<p>{durableTaskId}</p>
|
|
561
|
+
{#if taskQuery.isError}
|
|
562
|
+
<p role="alert">{i18n.t('task.fetchFailed')}</p>
|
|
563
|
+
<Button type="button" onclick={() => taskQuery.refetch()}>{i18n.t('common.retry')}</Button>
|
|
564
|
+
{:else if taskQuery.data && allowed}
|
|
565
|
+
<TaskStatusBadge status={taskQuery.data.status ?? 'pending'} />
|
|
566
|
+
{@const progress = resolveTaskProgress(taskQuery.data)}
|
|
567
|
+
{#if progress !== undefined}
|
|
568
|
+
<Progress value={progress} />
|
|
569
|
+
{/if}
|
|
570
|
+
{#if importResult}
|
|
571
|
+
<p role="status">{i18n.t('common.succeeded')}: {importResult.succeeded};
|
|
572
|
+
{i18n.t('common.failed')}: {importResult.failed}</p>
|
|
573
|
+
{/if}
|
|
574
|
+
{#if retryReceipt && retryTaskName?.trim()}
|
|
575
|
+
<Button type="button" disabled={!allowed || !!durableSubmitAttempt}
|
|
576
|
+
onclick={retryFailedRows}><RotateCw aria-hidden="true" />{i18n.t('import.retryFailedRows')}</Button>
|
|
577
|
+
{/if}
|
|
578
|
+
{#if activeTaskProvider?.cancel && canCancelTask(taskQuery.data)}
|
|
579
|
+
<CancelTaskButton taskId={durableTaskId} taskProvider={activeTaskProvider}
|
|
580
|
+
disabled={!allowed || !open}
|
|
581
|
+
onSuccess={() => { void taskQuery.refetch(); }}
|
|
582
|
+
onError={() => { importError = i18n.t('common.operationFailed'); }} />
|
|
583
|
+
{/if}
|
|
584
|
+
{:else}
|
|
585
|
+
<p role="status">{i18n.t('common.loading')}</p>
|
|
586
|
+
{/if}
|
|
587
|
+
{:else if durableSubmitAttempt}
|
|
588
|
+
<p role="status">{i18n.t('common.processing')}</p>
|
|
589
|
+
{/if}
|
|
590
|
+
{#if importError}<p role="alert">{importError}</p>{/if}
|
|
591
|
+
<Button type="button" onclick={() => { open = false; }}>{i18n.t('common.close')}</Button>
|
|
592
|
+
{:else if currentStep === 1}
|
|
276
593
|
<div
|
|
277
594
|
class="svadmin-u-65935df577ba svadmin-u-a29b7a649c77 svadmin-u-c9ed8c5f79ae svadmin-u-8b9d5d768973 svadmin-u-a217b4eaa918 svadmin-u-845f53365c8d svadmin-u-ca6bf63030aa svadmin-u-ceb69a6b0e5f svadmin-u-34516836730d svadmin-u-967d113a1451"
|
|
278
595
|
ondragover={(e) => e.preventDefault()}
|
|
@@ -309,7 +626,7 @@
|
|
|
309
626
|
{/if}
|
|
310
627
|
|
|
311
628
|
<!-- Step 2: Column Mapping -->
|
|
312
|
-
{#if currentStep === 2}
|
|
629
|
+
{#if currentStep === 2 && !durableTaskId}
|
|
313
630
|
<div class="svadmin-u-3e7ce58d64fa">
|
|
314
631
|
{#if fileError}<p role="alert">{fileError}</p>{/if}
|
|
315
632
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-359090c2d529 svadmin-u-bfa603190748 svadmin-u-2859c861d7de svadmin-u-9fe52d5d506c svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-05faf5c801ff">
|
|
@@ -364,16 +681,19 @@
|
|
|
364
681
|
{/if}
|
|
365
682
|
|
|
366
683
|
<!-- Step 3: Progress & Summary -->
|
|
367
|
-
{#if currentStep === 3}
|
|
684
|
+
{#if currentStep === 3 && !durableMode}
|
|
368
685
|
<div class="svadmin-u-b43b4c086d9a svadmin-u-03b4dd7f172b">
|
|
369
686
|
<div class="svadmin-u-6f7e013d6499">
|
|
370
687
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-359090c2d529">
|
|
371
|
-
<span class="svadmin-u-bfa603190748">{isImporting ? i18n.t('common.importing', { defaultValue: 'Importing...' }) : importError ? i18n.t('common.operationFailed', { defaultValue: 'Import failed' }) : i18n.t('common.completed', { defaultValue: 'Completed' })}</span>
|
|
372
|
-
<span class="svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{progressPercent}%</span>
|
|
688
|
+
<span class="svadmin-u-bfa603190748">{cancelled ? i18n.t('import.stopped') : isImporting ? i18n.t('common.importing', { defaultValue: 'Importing...' }) : importError ? i18n.t('common.operationFailed', { defaultValue: 'Import failed' }) : i18n.t('common.completed', { defaultValue: 'Completed' })}</span>
|
|
689
|
+
{#if !cancelled}<span class="svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{progressPercent}%</span>{/if}
|
|
373
690
|
</div>
|
|
374
|
-
<Progress value={progressPercent} class="svadmin-u-2f2a842e50fa" />
|
|
691
|
+
{#if !cancelled}<Progress value={progressPercent} class="svadmin-u-2f2a842e50fa" />{/if}
|
|
375
692
|
</div>
|
|
376
693
|
|
|
694
|
+
{#if cancelled}
|
|
695
|
+
<p role="status">{i18n.t('import.stoppedHint')}</p>
|
|
696
|
+
{:else}
|
|
377
697
|
<div class="svadmin-u-f3c543ad5fe9 svadmin-u-8e75e3db482b svadmin-u-1004c0c3954c svadmin-u-ca6bf63030aa">
|
|
378
698
|
<div class="svadmin-u-eb6e8b881acd svadmin-u-5f22e64f2282 svadmin-u-17a9f7af2265 svadmin-u-ca6bcd4b6f3f svadmin-u-95b7dea5a67f">
|
|
379
699
|
<span class="svadmin-u-359090c2d529 svadmin-u-bfa603190748 svadmin-u-0214b4b355d1">{i18n.t('common.succeeded', { defaultValue: 'Succeeded' })}</span>
|
|
@@ -384,6 +704,7 @@
|
|
|
384
704
|
<span class="svadmin-u-d5c9b0001e7e svadmin-u-69450ef1487e svadmin-u-811148b13d1e svadmin-u-3032cae0badb">{failedRecords.length}</span>
|
|
385
705
|
</div>
|
|
386
706
|
</div>
|
|
707
|
+
{/if}
|
|
387
708
|
|
|
388
709
|
{#if importError}
|
|
389
710
|
<p role="alert">{importError}</p>
|
|
@@ -411,6 +732,7 @@
|
|
|
411
732
|
{i18n.t('common.close', { defaultValue: 'Close' })}
|
|
412
733
|
</Button>
|
|
413
734
|
{:else}
|
|
735
|
+
<Button variant="outline" size="sm" onclick={cancelImport}>{i18n.t('common.cancel')}</Button>
|
|
414
736
|
<Button size="sm" disabled class="svadmin-u-77a2a20e90d4">
|
|
415
737
|
<Loader2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-afbdd13a380e" />
|
|
416
738
|
{i18n.t('common.processing', { defaultValue: 'Processing...' })}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
import type { ImportArtifactProvider, TaskProvider } from '@svadmin/core';
|
|
1
2
|
interface Props {
|
|
2
3
|
resourceName: string;
|
|
3
4
|
open?: boolean;
|
|
4
5
|
batchSize?: number;
|
|
6
|
+
maxRows?: number;
|
|
7
|
+
maxBytes?: number;
|
|
8
|
+
taskName?: string;
|
|
9
|
+
retryTaskName?: string;
|
|
10
|
+
taskProvider?: TaskProvider;
|
|
11
|
+
/** 上传原文件并以 artifact 引用提交任务,避免任务请求重复携带 records。 */
|
|
12
|
+
taskArtifactProvider?: ImportArtifactProvider;
|
|
13
|
+
taskIdempotencyKey?: string;
|
|
14
|
+
initialTaskId?: string;
|
|
15
|
+
onTaskSubmitted?: (taskId: string) => void;
|
|
5
16
|
onSuccess?: (result: {
|
|
6
17
|
succeeded: number;
|
|
7
18
|
failed: number;
|