@oronts/vendure-data-hub-plugin 0.1.1 → 0.1.2

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.
Files changed (235) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/dashboard/components/common/ConnectionConfigEditor.tsx +589 -0
  3. package/dist/dashboard/components/common/HeadersEditor.tsx +90 -0
  4. package/dist/dashboard/components/common/ValidationFeedback.tsx +17 -0
  5. package/dist/dashboard/components/common/index.ts +10 -0
  6. package/dist/dashboard/components/pipelines/PipelineEditor.tsx +504 -0
  7. package/dist/dashboard/components/pipelines/PipelineExport.tsx +63 -0
  8. package/dist/dashboard/components/pipelines/PipelineImport.tsx +87 -0
  9. package/dist/dashboard/components/pipelines/ReactFlowPipelineEditor.tsx +539 -0
  10. package/dist/dashboard/components/pipelines/shared/NodePropertiesPanel.tsx +146 -0
  11. package/dist/dashboard/components/pipelines/shared/PipelineNode.tsx +155 -0
  12. package/dist/dashboard/components/pipelines/shared/PipelineSettingsPanel.tsx +392 -0
  13. package/dist/dashboard/components/pipelines/shared/StepListItem.tsx +144 -0
  14. package/dist/dashboard/components/pipelines/shared/index.ts +33 -0
  15. package/dist/dashboard/components/pipelines/shared/visual-node-config.ts +169 -0
  16. package/dist/dashboard/components/shared/LoadMoreButton.tsx +18 -0
  17. package/dist/dashboard/components/shared/entity-selector/EntitySelector.tsx +59 -0
  18. package/dist/dashboard/components/shared/entity-selector/index.ts +1 -0
  19. package/dist/dashboard/components/shared/error-boundary/ErrorBoundary.tsx +90 -0
  20. package/dist/dashboard/components/shared/error-boundary/index.ts +1 -0
  21. package/dist/dashboard/components/shared/feedback/EmptyState.tsx +36 -0
  22. package/dist/dashboard/components/shared/feedback/ErrorState.tsx +69 -0
  23. package/dist/dashboard/components/shared/feedback/LoadingState.tsx +104 -0
  24. package/dist/dashboard/components/shared/feedback/ValidationErrorDisplay.tsx +29 -0
  25. package/dist/dashboard/components/shared/feedback/index.ts +4 -0
  26. package/dist/dashboard/components/shared/file-dropzone/FileDropzone.tsx +167 -0
  27. package/dist/dashboard/components/shared/file-dropzone/index.ts +1 -0
  28. package/dist/dashboard/components/shared/filter-conditions-editor/FilterConditionsEditor.tsx +226 -0
  29. package/dist/dashboard/components/shared/filter-conditions-editor/index.ts +1 -0
  30. package/dist/dashboard/components/shared/index.ts +45 -0
  31. package/dist/dashboard/components/shared/schema-form/SchemaFormRenderer.tsx +248 -0
  32. package/dist/dashboard/components/shared/schema-form/fields/BooleanField.tsx +26 -0
  33. package/dist/dashboard/components/shared/schema-form/fields/FieldWrapper.tsx +28 -0
  34. package/dist/dashboard/components/shared/schema-form/fields/FileUploadField.tsx +171 -0
  35. package/dist/dashboard/components/shared/schema-form/fields/JsonField.tsx +132 -0
  36. package/dist/dashboard/components/shared/schema-form/fields/NumberField.tsx +33 -0
  37. package/dist/dashboard/components/shared/schema-form/fields/SelectField.tsx +70 -0
  38. package/dist/dashboard/components/shared/schema-form/fields/StringField.tsx +36 -0
  39. package/dist/dashboard/components/shared/schema-form/fields/TextareaField.tsx +31 -0
  40. package/dist/dashboard/components/shared/schema-form/fields/index.ts +23 -0
  41. package/dist/dashboard/components/shared/schema-form/index.ts +2 -0
  42. package/dist/dashboard/components/shared/schema-form/utils.ts +3 -0
  43. package/dist/dashboard/components/shared/selectable-card/SelectableCard.tsx +65 -0
  44. package/dist/dashboard/components/shared/selectable-card/index.ts +1 -0
  45. package/dist/dashboard/components/shared/stat-card/StatCard.tsx +121 -0
  46. package/dist/dashboard/components/shared/stat-card/index.ts +1 -0
  47. package/dist/dashboard/components/shared/step-config/AdapterRequiredWarning.tsx +25 -0
  48. package/dist/dashboard/components/shared/step-config/AdapterSelector.tsx +109 -0
  49. package/dist/dashboard/components/shared/step-config/AdvancedEditors.tsx +634 -0
  50. package/dist/dashboard/components/shared/step-config/EnrichConfigComponent.tsx +295 -0
  51. package/dist/dashboard/components/shared/step-config/ExtractTestResults.tsx +143 -0
  52. package/dist/dashboard/components/shared/step-config/GateConfigComponent.tsx +127 -0
  53. package/dist/dashboard/components/shared/step-config/LoadTestResults.tsx +104 -0
  54. package/dist/dashboard/components/shared/step-config/OperatorCard.tsx +266 -0
  55. package/dist/dashboard/components/shared/step-config/OperatorCheatSheetButton.tsx +54 -0
  56. package/dist/dashboard/components/shared/step-config/OperatorFieldInput.tsx +209 -0
  57. package/dist/dashboard/components/shared/step-config/RetrySettingsComponent.tsx +111 -0
  58. package/dist/dashboard/components/shared/step-config/RouteConfigComponent.tsx +125 -0
  59. package/dist/dashboard/components/shared/step-config/StepConfigPanel.tsx +564 -0
  60. package/dist/dashboard/components/shared/step-config/StepTester.tsx +165 -0
  61. package/dist/dashboard/components/shared/step-config/TestResultContainer.tsx +57 -0
  62. package/dist/dashboard/components/shared/step-config/TransformTestResults.tsx +130 -0
  63. package/dist/dashboard/components/shared/step-config/ValidateConfigComponent.tsx +334 -0
  64. package/dist/dashboard/components/shared/step-config/index.ts +29 -0
  65. package/dist/dashboard/components/shared/step-config/step-test-handlers.ts +297 -0
  66. package/dist/dashboard/components/shared/trigger-config/TriggerForm.tsx +478 -0
  67. package/dist/dashboard/components/shared/trigger-config/index.ts +1 -0
  68. package/dist/dashboard/components/shared/triggers-panel/TriggersPanel.tsx +281 -0
  69. package/dist/dashboard/components/shared/triggers-panel/index.ts +1 -0
  70. package/dist/dashboard/components/shared/wizard/ConfigurationNameCard.tsx +59 -0
  71. package/dist/dashboard/components/shared/wizard/SummaryCard.tsx +53 -0
  72. package/dist/dashboard/components/shared/wizard/WizardFooter.tsx +47 -0
  73. package/dist/dashboard/components/shared/wizard/WizardProgressBar.tsx +78 -0
  74. package/dist/dashboard/components/shared/wizard/index.ts +4 -0
  75. package/dist/dashboard/components/shared/wizard-trigger/TriggerSchemaFields.tsx +128 -0
  76. package/dist/dashboard/components/shared/wizard-trigger/TriggerSelector.tsx +33 -0
  77. package/dist/dashboard/components/shared/wizard-trigger/index.ts +2 -0
  78. package/dist/dashboard/components/templates/TemplateGallery.tsx +210 -0
  79. package/dist/dashboard/components/templates/TemplatePreview.tsx +214 -0
  80. package/dist/dashboard/components/templates/index.ts +4 -0
  81. package/dist/dashboard/components/wizards/export-wizard/DestinationStep.tsx +207 -0
  82. package/dist/dashboard/components/wizards/export-wizard/ExportWizard.tsx +221 -0
  83. package/dist/dashboard/components/wizards/export-wizard/FieldsStep.tsx +159 -0
  84. package/dist/dashboard/components/wizards/export-wizard/FormatStep.tsx +246 -0
  85. package/dist/dashboard/components/wizards/export-wizard/ReviewStep.tsx +231 -0
  86. package/dist/dashboard/components/wizards/export-wizard/SourceStep.tsx +154 -0
  87. package/dist/dashboard/components/wizards/export-wizard/TriggerStep.tsx +234 -0
  88. package/dist/dashboard/components/wizards/export-wizard/constants.ts +73 -0
  89. package/dist/dashboard/components/wizards/export-wizard/index.ts +2 -0
  90. package/dist/dashboard/components/wizards/export-wizard/types.ts +39 -0
  91. package/dist/dashboard/components/wizards/import-wizard/ImportWizard.tsx +350 -0
  92. package/dist/dashboard/components/wizards/import-wizard/MappingStep.tsx +286 -0
  93. package/dist/dashboard/components/wizards/import-wizard/PreviewStep.tsx +79 -0
  94. package/dist/dashboard/components/wizards/import-wizard/ReviewStep.tsx +266 -0
  95. package/dist/dashboard/components/wizards/import-wizard/SourceStep.tsx +537 -0
  96. package/dist/dashboard/components/wizards/import-wizard/StrategyStep.tsx +328 -0
  97. package/dist/dashboard/components/wizards/import-wizard/TargetStep.tsx +76 -0
  98. package/dist/dashboard/components/wizards/import-wizard/TemplateStep.tsx +116 -0
  99. package/dist/dashboard/components/wizards/import-wizard/TransformStep.tsx +666 -0
  100. package/dist/dashboard/components/wizards/import-wizard/TriggerStep.tsx +51 -0
  101. package/dist/dashboard/components/wizards/import-wizard/constants.ts +104 -0
  102. package/dist/dashboard/components/wizards/import-wizard/index.ts +3 -0
  103. package/dist/dashboard/components/wizards/import-wizard/types.ts +35 -0
  104. package/dist/dashboard/components/wizards/index.ts +7 -0
  105. package/dist/dashboard/components/wizards/shared/WizardStepContainer.tsx +27 -0
  106. package/dist/dashboard/components/wizards/shared/constants.ts +16 -0
  107. package/dist/dashboard/components/wizards/shared/index.ts +10 -0
  108. package/dist/dashboard/constants/colors.ts +25 -0
  109. package/dist/dashboard/constants/connection-defaults.ts +7 -0
  110. package/dist/dashboard/constants/connection-types.ts +1 -0
  111. package/dist/dashboard/constants/defaults.ts +18 -0
  112. package/dist/dashboard/constants/editor.ts +69 -0
  113. package/dist/dashboard/constants/enum-maps.ts +18 -0
  114. package/dist/dashboard/constants/fallbacks.ts +44 -0
  115. package/dist/dashboard/constants/file-format-registry.ts +206 -0
  116. package/dist/dashboard/constants/index.ts +24 -0
  117. package/dist/dashboard/constants/navigation.ts +29 -0
  118. package/dist/dashboard/constants/permissions.ts +41 -0
  119. package/dist/dashboard/constants/placeholders.ts +77 -0
  120. package/dist/dashboard/constants/routes.ts +12 -0
  121. package/dist/dashboard/constants/run-status.ts +1 -0
  122. package/dist/dashboard/constants/sentinel-values.ts +12 -0
  123. package/dist/dashboard/constants/step-configs.ts +9 -0
  124. package/dist/dashboard/constants/step-mappings.ts +170 -0
  125. package/dist/dashboard/constants/steps.ts +37 -0
  126. package/dist/dashboard/constants/toast-messages.ts +149 -0
  127. package/dist/dashboard/constants/triggers.ts +5 -0
  128. package/dist/dashboard/constants/ui-config.ts +139 -0
  129. package/dist/dashboard/constants/ui-dimensions.ts +145 -0
  130. package/dist/dashboard/constants/ui-states.ts +28 -0
  131. package/dist/dashboard/constants/ui-types.ts +85 -0
  132. package/dist/dashboard/constants/validation-patterns.ts +26 -0
  133. package/dist/dashboard/gql/gql.ts +370 -0
  134. package/dist/dashboard/gql/graphql.ts +10378 -0
  135. package/dist/dashboard/gql/index.ts +1 -0
  136. package/dist/dashboard/hooks/api/index.ts +115 -0
  137. package/dist/dashboard/hooks/api/mutation-helpers.ts +34 -0
  138. package/dist/dashboard/hooks/api/use-adapters.ts +92 -0
  139. package/dist/dashboard/hooks/api/use-config-options.ts +513 -0
  140. package/dist/dashboard/hooks/api/use-connections.ts +84 -0
  141. package/dist/dashboard/hooks/api/use-entity-field-schemas.ts +99 -0
  142. package/dist/dashboard/hooks/api/use-entity-loaders.ts +45 -0
  143. package/dist/dashboard/hooks/api/use-hooks.ts +68 -0
  144. package/dist/dashboard/hooks/api/use-logs.ts +102 -0
  145. package/dist/dashboard/hooks/api/use-pipeline-runs.ts +221 -0
  146. package/dist/dashboard/hooks/api/use-pipelines.ts +279 -0
  147. package/dist/dashboard/hooks/api/use-queues.ts +141 -0
  148. package/dist/dashboard/hooks/api/use-secrets.ts +75 -0
  149. package/dist/dashboard/hooks/api/use-settings.ts +55 -0
  150. package/dist/dashboard/hooks/api/use-step-tester.ts +79 -0
  151. package/dist/dashboard/hooks/index.ts +13 -0
  152. package/dist/dashboard/hooks/use-adapter-catalog.ts +253 -0
  153. package/dist/dashboard/hooks/use-export-templates.ts +80 -0
  154. package/dist/dashboard/hooks/use-import-templates.ts +139 -0
  155. package/dist/dashboard/hooks/use-load-more.ts +29 -0
  156. package/dist/dashboard/hooks/use-stable-keys.ts +54 -0
  157. package/dist/dashboard/hooks/use-trigger-types.ts +100 -0
  158. package/dist/dashboard/hooks/use-wizard-navigation.ts +128 -0
  159. package/dist/dashboard/index.tsx +55 -0
  160. package/dist/dashboard/routes/adapters/AdapterCard.tsx +102 -0
  161. package/dist/dashboard/routes/adapters/AdapterConstants.tsx +20 -0
  162. package/dist/dashboard/routes/adapters/AdapterDetail.tsx +208 -0
  163. package/dist/dashboard/routes/adapters/AdapterTypeSection.tsx +105 -0
  164. package/dist/dashboard/routes/adapters/AdaptersPage.tsx +276 -0
  165. package/dist/dashboard/routes/adapters/AdaptersTable.tsx +107 -0
  166. package/dist/dashboard/routes/adapters/index.ts +1 -0
  167. package/dist/dashboard/routes/connections/ConnectionDetail.tsx +218 -0
  168. package/dist/dashboard/routes/connections/ConnectionsList.tsx +34 -0
  169. package/dist/dashboard/routes/connections/index.ts +2 -0
  170. package/dist/dashboard/routes/hooks/Hooks.tsx +425 -0
  171. package/dist/dashboard/routes/hooks/hook-stages.ts +52 -0
  172. package/dist/dashboard/routes/hooks/index.ts +1 -0
  173. package/dist/dashboard/routes/index.ts +8 -0
  174. package/dist/dashboard/routes/logs/Logs.tsx +93 -0
  175. package/dist/dashboard/routes/logs/components/LogDetailDrawer.tsx +118 -0
  176. package/dist/dashboard/routes/logs/components/LogExplorerTab.tsx +367 -0
  177. package/dist/dashboard/routes/logs/components/LogLevelBadge.tsx +34 -0
  178. package/dist/dashboard/routes/logs/components/LogTableRow.tsx +70 -0
  179. package/dist/dashboard/routes/logs/components/LogsOverviewTab.tsx +178 -0
  180. package/dist/dashboard/routes/logs/components/RealtimeLogTab.tsx +122 -0
  181. package/dist/dashboard/routes/logs/index.ts +1 -0
  182. package/dist/dashboard/routes/pipelines/ErrorAuditList.tsx +39 -0
  183. package/dist/dashboard/routes/pipelines/ExportWizardPage.tsx +96 -0
  184. package/dist/dashboard/routes/pipelines/ImportWizardPage.tsx +104 -0
  185. package/dist/dashboard/routes/pipelines/PipelineDetail.tsx +211 -0
  186. package/dist/dashboard/routes/pipelines/PipelineRunsBlock.tsx +377 -0
  187. package/dist/dashboard/routes/pipelines/PipelinesList.tsx +87 -0
  188. package/dist/dashboard/routes/pipelines/RetryPatchHelper.tsx +51 -0
  189. package/dist/dashboard/routes/pipelines/RunDetailsPanel.tsx +238 -0
  190. package/dist/dashboard/routes/pipelines/RunErrorsList.tsx +116 -0
  191. package/dist/dashboard/routes/pipelines/StepCounters.tsx +24 -0
  192. package/dist/dashboard/routes/pipelines/StepSummaryTable.tsx +36 -0
  193. package/dist/dashboard/routes/pipelines/components/DryRunDialog.tsx +341 -0
  194. package/dist/dashboard/routes/pipelines/components/PipelineActionButtons.tsx +201 -0
  195. package/dist/dashboard/routes/pipelines/components/PipelineEditorToggle.tsx +116 -0
  196. package/dist/dashboard/routes/pipelines/components/PipelineFormFields.tsx +156 -0
  197. package/dist/dashboard/routes/pipelines/components/PipelineWebhookInfo.tsx +111 -0
  198. package/dist/dashboard/routes/pipelines/components/ReviewActionsPanel.tsx +342 -0
  199. package/dist/dashboard/routes/pipelines/components/ValidationPanel.tsx +121 -0
  200. package/dist/dashboard/routes/pipelines/components/VersionHistoryDialog.tsx +131 -0
  201. package/dist/dashboard/routes/pipelines/components/index.ts +25 -0
  202. package/dist/dashboard/routes/pipelines/hooks/index.ts +1 -0
  203. package/dist/dashboard/routes/pipelines/hooks/use-pipeline-validation.ts +114 -0
  204. package/dist/dashboard/routes/pipelines/index.ts +4 -0
  205. package/dist/dashboard/routes/pipelines/utils/index.ts +1 -0
  206. package/dist/dashboard/routes/pipelines/utils/pipeline-conversion.ts +261 -0
  207. package/dist/dashboard/routes/queues/ConsumersTable.tsx +134 -0
  208. package/dist/dashboard/routes/queues/DeadLettersTable.tsx +118 -0
  209. package/dist/dashboard/routes/queues/FailedRunsTable.tsx +74 -0
  210. package/dist/dashboard/routes/queues/QueuesPage.tsx +290 -0
  211. package/dist/dashboard/routes/queues/index.ts +1 -0
  212. package/dist/dashboard/routes/queues/types.ts +22 -0
  213. package/dist/dashboard/routes/secrets/SecretDetail.tsx +278 -0
  214. package/dist/dashboard/routes/secrets/SecretsList.tsx +34 -0
  215. package/dist/dashboard/routes/secrets/index.ts +2 -0
  216. package/dist/dashboard/routes/settings/Settings.tsx +343 -0
  217. package/dist/dashboard/routes/settings/index.ts +1 -0
  218. package/dist/dashboard/types/index.ts +89 -0
  219. package/dist/dashboard/types/pipeline.ts +51 -0
  220. package/dist/dashboard/types/ui-types.ts +400 -0
  221. package/dist/dashboard/types/wizard.ts +235 -0
  222. package/dist/dashboard/utils/adapter-grouping.ts +43 -0
  223. package/dist/dashboard/utils/column-analysis.ts +11 -0
  224. package/dist/dashboard/utils/field-preparation.ts +31 -0
  225. package/dist/dashboard/utils/form-validation.ts +373 -0
  226. package/dist/dashboard/utils/formatters.ts +92 -0
  227. package/dist/dashboard/utils/icon-resolver.ts +35 -0
  228. package/dist/dashboard/utils/index.ts +60 -0
  229. package/dist/dashboard/utils/query-key-factory.ts +54 -0
  230. package/dist/dashboard/utils/step-helpers.ts +32 -0
  231. package/dist/dashboard/utils/string-helpers.ts +4 -0
  232. package/dist/dashboard/utils/template-helpers.ts +26 -0
  233. package/dist/dashboard/utils/trigger-sync.ts +138 -0
  234. package/dist/dashboard/utils/wizard-to-pipeline.ts +569 -0
  235. package/package.json +2 -2
@@ -0,0 +1,134 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ PermissionGuard,
5
+ Badge,
6
+ } from '@vendure/dashboard';
7
+ import { Play, Square, Radio } from 'lucide-react';
8
+ import { DATAHUB_PERMISSIONS, ITEMS_PER_PAGE } from '../../constants';
9
+ import { useLoadMore } from '../../hooks';
10
+ import { LoadMoreButton } from '../../components/shared';
11
+ import type { Consumer } from './types';
12
+ import { formatDateTime } from '../../utils';
13
+
14
+ // Memoized row component for consumers
15
+ const ConsumerRow = React.memo(function ConsumerRow({
16
+ consumer,
17
+ onStop,
18
+ onStart,
19
+ isStopPending,
20
+ isStartPending,
21
+ }: {
22
+ consumer: Consumer;
23
+ onStop: (pipelineCode: string) => void;
24
+ onStart: (pipelineCode: string) => void;
25
+ isStopPending: boolean;
26
+ isStartPending: boolean;
27
+ }) {
28
+ const handleStop = React.useCallback(() => {
29
+ onStop(consumer.pipelineCode);
30
+ }, [consumer.pipelineCode, onStop]);
31
+
32
+ const handleStart = React.useCallback(() => {
33
+ onStart(consumer.pipelineCode);
34
+ }, [consumer.pipelineCode, onStart]);
35
+
36
+ return (
37
+ <tr className="border-t align-top">
38
+ <td className="px-3 py-2 font-mono text-muted-foreground">{consumer.pipelineCode}</td>
39
+ <td className="px-3 py-2 font-mono text-muted-foreground text-xs">{consumer.queueName}</td>
40
+ <td className="px-3 py-2">
41
+ <Badge variant={consumer.isActive ? 'default' : 'secondary'}>
42
+ {consumer.isActive ? 'Active' : 'Stopped'}
43
+ </Badge>
44
+ </td>
45
+ <td className="px-3 py-2">{consumer.messagesProcessed}</td>
46
+ <td className="px-3 py-2">
47
+ {consumer.messagesFailed > 0 ? (
48
+ <span className="text-destructive">{consumer.messagesFailed}</span>
49
+ ) : (
50
+ consumer.messagesFailed
51
+ )}
52
+ </td>
53
+ <td className="px-3 py-2">
54
+ {consumer.lastMessageAt ? formatDateTime(consumer.lastMessageAt) : '—'}
55
+ </td>
56
+ <td className="px-3 py-2">
57
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.UPDATE_PIPELINE]}>
58
+ {consumer.isActive ? (
59
+ <Button size="sm" variant="outline" onClick={handleStop} disabled={isStopPending}>
60
+ <Square className="w-3 h-3 mr-1" />
61
+ Stop
62
+ </Button>
63
+ ) : (
64
+ <Button size="sm" variant="outline" onClick={handleStart} disabled={isStartPending}>
65
+ <Play className="w-3 h-3 mr-1" />
66
+ Start
67
+ </Button>
68
+ )}
69
+ </PermissionGuard>
70
+ </td>
71
+ </tr>
72
+ );
73
+ });
74
+
75
+ // Consumers Table with virtualization
76
+ export function ConsumersTable({
77
+ consumers,
78
+ onStop,
79
+ onStart,
80
+ isStopPending,
81
+ isStartPending,
82
+ }: {
83
+ consumers: Consumer[];
84
+ onStop: (pipelineCode: string) => void;
85
+ onStart: (pipelineCode: string) => void;
86
+ isStopPending: boolean;
87
+ isStartPending: boolean;
88
+ }) {
89
+ const { displayed: displayedConsumers, hasMore, remaining, loadMore } = useLoadMore(consumers, { pageSize: ITEMS_PER_PAGE });
90
+
91
+ return (
92
+ <>
93
+ <div className="mb-4">
94
+ <p className="text-sm text-muted-foreground">
95
+ Message queue consumers that process pipeline triggers. Start/stop consumers to manage message processing.
96
+ </p>
97
+ </div>
98
+ <table className="w-full text-sm">
99
+ <thead>
100
+ <tr className="bg-muted">
101
+ <th className="text-left px-3 py-2">Pipeline</th>
102
+ <th className="text-left px-3 py-2">Queue</th>
103
+ <th className="text-left px-3 py-2">Status</th>
104
+ <th className="text-left px-3 py-2">Processed</th>
105
+ <th className="text-left px-3 py-2">Failed</th>
106
+ <th className="text-left px-3 py-2">Last Message</th>
107
+ <th className="text-left px-3 py-2">Actions</th>
108
+ </tr>
109
+ </thead>
110
+ <tbody>
111
+ {displayedConsumers.map((c) => (
112
+ <ConsumerRow
113
+ key={c.pipelineCode}
114
+ consumer={c}
115
+ onStop={onStop}
116
+ onStart={onStart}
117
+ isStopPending={isStopPending}
118
+ isStartPending={isStartPending}
119
+ />
120
+ ))}
121
+ {consumers.length === 0 && (
122
+ <tr>
123
+ <td className="px-3 py-8 text-muted-foreground text-center" colSpan={7}>
124
+ <Radio className="w-8 h-8 mx-auto mb-2 text-muted-foreground/50" />
125
+ No message queue consumers configured
126
+ </td>
127
+ </tr>
128
+ )}
129
+ </tbody>
130
+ </table>
131
+ {hasMore && <LoadMoreButton remaining={remaining} onClick={loadMore} />}
132
+ </>
133
+ );
134
+ }
@@ -0,0 +1,118 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Button,
4
+ PermissionGuard,
5
+ Json,
6
+ } from '@vendure/dashboard';
7
+ import { AlertTriangle } from 'lucide-react';
8
+ import { DATAHUB_PERMISSIONS, ITEMS_PER_PAGE } from '../../constants';
9
+ import { useLoadMore } from '../../hooks';
10
+ import { LoadMoreButton } from '../../components/shared';
11
+ import type { DeadLetter } from './types';
12
+
13
+ // Memoized row component for dead letters
14
+ const DeadLetterRow = React.memo(function DeadLetterRow({
15
+ deadLetter,
16
+ onRetry,
17
+ onUnmark,
18
+ isRetryPending,
19
+ isUnmarkPending,
20
+ }: {
21
+ deadLetter: DeadLetter;
22
+ onRetry: (id: string) => void;
23
+ onUnmark: (id: string) => void;
24
+ isRetryPending: boolean;
25
+ isUnmarkPending: boolean;
26
+ }) {
27
+ const handleRetry = React.useCallback(() => {
28
+ onRetry(deadLetter.id);
29
+ }, [deadLetter.id, onRetry]);
30
+
31
+ const handleUnmark = React.useCallback(() => {
32
+ onUnmark(deadLetter.id);
33
+ }, [deadLetter.id, onUnmark]);
34
+
35
+ return (
36
+ <tr className="border-t align-top">
37
+ <td className="px-3 py-2 font-mono text-muted-foreground">{deadLetter.id}</td>
38
+ <td className="px-3 py-2 font-mono text-muted-foreground">{deadLetter.stepKey}</td>
39
+ <td className="px-3 py-2">{deadLetter.message}</td>
40
+ <td className="px-3 py-2">
41
+ <Json value={deadLetter.payload} />
42
+ </td>
43
+ <td className="px-3 py-2">
44
+ <div className="flex items-center gap-2">
45
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.REPLAY_RECORD]}>
46
+ <Button size="sm" variant="outline" onClick={handleRetry} disabled={isRetryPending}>
47
+ Replay
48
+ </Button>
49
+ </PermissionGuard>
50
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.EDIT_QUARANTINE]}>
51
+ <Button size="sm" variant="destructive" onClick={handleUnmark} disabled={isUnmarkPending}>
52
+ Unmark
53
+ </Button>
54
+ </PermissionGuard>
55
+ </div>
56
+ </td>
57
+ </tr>
58
+ );
59
+ });
60
+
61
+ // Dead Letters Table with virtualization
62
+ export function DeadLettersTable({
63
+ deadLetters,
64
+ onRetry,
65
+ onUnmark,
66
+ isRetryPending,
67
+ isUnmarkPending,
68
+ }: {
69
+ deadLetters: DeadLetter[];
70
+ onRetry: (id: string) => void;
71
+ onUnmark: (id: string) => void;
72
+ isRetryPending: boolean;
73
+ isUnmarkPending: boolean;
74
+ }) {
75
+ const { displayed: displayedLetters, hasMore, remaining, loadMore } = useLoadMore(deadLetters, { pageSize: ITEMS_PER_PAGE });
76
+
77
+ return (
78
+ <div data-testid="datahub-dead-letters-table">
79
+ <div className="mb-4">
80
+ <p className="text-sm text-muted-foreground">
81
+ Dead letters are records that failed processing and have been marked for manual review.
82
+ </p>
83
+ </div>
84
+ <table className="w-full text-sm">
85
+ <thead>
86
+ <tr className="bg-muted">
87
+ <th className="text-left px-3 py-2">ID</th>
88
+ <th className="text-left px-3 py-2">Step</th>
89
+ <th className="text-left px-3 py-2">Message</th>
90
+ <th className="text-left px-3 py-2">Payload</th>
91
+ <th className="text-left px-3 py-2">Actions</th>
92
+ </tr>
93
+ </thead>
94
+ <tbody>
95
+ {displayedLetters.map((r) => (
96
+ <DeadLetterRow
97
+ key={r.id}
98
+ deadLetter={r}
99
+ onRetry={onRetry}
100
+ onUnmark={onUnmark}
101
+ isRetryPending={isRetryPending}
102
+ isUnmarkPending={isUnmarkPending}
103
+ />
104
+ ))}
105
+ {deadLetters.length === 0 && (
106
+ <tr>
107
+ <td className="px-3 py-8 text-muted-foreground text-center" colSpan={5}>
108
+ <AlertTriangle className="w-8 h-8 mx-auto mb-2 text-muted-foreground/50" />
109
+ No dead letters
110
+ </td>
111
+ </tr>
112
+ )}
113
+ </tbody>
114
+ </table>
115
+ {hasMore && <LoadMoreButton remaining={remaining} onClick={loadMore} />}
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,74 @@
1
+ import * as React from 'react';
2
+ import type { FailedRun } from './types';
3
+ import { formatDateTime } from '../../utils';
4
+ import { COMPONENT_WIDTHS } from '../../constants';
5
+ import { useLoadMore } from '../../hooks';
6
+ import { LoadMoreButton } from '../../components/shared';
7
+
8
+ // Memoized row component for failed runs
9
+ const FailedRunRow = React.memo(function FailedRunRow({
10
+ run,
11
+ onSelectRun,
12
+ }: {
13
+ run: FailedRun;
14
+ onSelectRun: (id: string) => void;
15
+ }) {
16
+ const handleClick = React.useCallback(() => {
17
+ onSelectRun(run.id);
18
+ }, [run.id, onSelectRun]);
19
+
20
+ return (
21
+ <tr className="border-t align-top">
22
+ <td className="px-3 py-2 font-mono text-muted-foreground">
23
+ <button type="button" className="underline underline-offset-2 hover:text-foreground" onClick={handleClick}>
24
+ {run.id}
25
+ </button>
26
+ </td>
27
+ <td className="px-3 py-2 font-mono text-muted-foreground">{run.code}</td>
28
+ <td className="px-3 py-2">{run.finishedAt ? formatDateTime(run.finishedAt) : '—'}</td>
29
+ <td className={`px-3 py-2 ${COMPONENT_WIDTHS.TABLE_CELL_MAX_LG} truncate`} title={run.error ?? ''}>
30
+ {run.error ?? '—'}
31
+ </td>
32
+ </tr>
33
+ );
34
+ });
35
+
36
+ // Failed Runs Table with virtualization
37
+ export function FailedRunsTable({
38
+ recentFailed,
39
+ onSelectRun,
40
+ }: {
41
+ recentFailed: FailedRun[];
42
+ onSelectRun: (id: string) => void;
43
+ }) {
44
+ const { displayed: displayedRuns, hasMore, remaining, loadMore } = useLoadMore(recentFailed);
45
+
46
+ return (
47
+ <div className="mt-6" data-testid="datahub-failed-runs-table">
48
+ <div className="text-sm font-medium mb-2">Recent Failed Runs</div>
49
+ <table className="w-full text-sm">
50
+ <thead>
51
+ <tr className="bg-muted">
52
+ <th className="text-left px-3 py-2">Run ID</th>
53
+ <th className="text-left px-3 py-2">Pipeline</th>
54
+ <th className="text-left px-3 py-2">Finished</th>
55
+ <th className="text-left px-3 py-2">Error</th>
56
+ </tr>
57
+ </thead>
58
+ <tbody>
59
+ {displayedRuns.map((r) => (
60
+ <FailedRunRow key={r.id} run={r} onSelectRun={onSelectRun} />
61
+ ))}
62
+ {recentFailed.length === 0 && (
63
+ <tr>
64
+ <td className="px-3 py-4 text-muted-foreground" colSpan={4}>
65
+ No recent failures
66
+ </td>
67
+ </tr>
68
+ )}
69
+ </tbody>
70
+ </table>
71
+ {hasMore && <LoadMoreButton remaining={remaining} onClick={loadMore} />}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,290 @@
1
+ import * as React from 'react';
2
+ import {
3
+ DashboardRouteDefinition,
4
+ Page,
5
+ PageActionBar,
6
+ PageActionBarRight,
7
+ PageBlock,
8
+ Button,
9
+ PermissionGuard,
10
+ Drawer,
11
+ DrawerContent,
12
+ DrawerHeader,
13
+ DrawerTitle,
14
+ DrawerDescription,
15
+ Json,
16
+ Tabs,
17
+ TabsContent,
18
+ TabsList,
19
+ TabsTrigger,
20
+ Badge,
21
+ } from '@vendure/dashboard';
22
+ import { Link } from '@tanstack/react-router';
23
+ import { RefreshCw, AlertTriangle, Clock, CheckCircle, XCircle, Radio } from 'lucide-react';
24
+ import { StatCard, LoadingState, ErrorState } from '../../components/shared';
25
+ import { formatDateTime } from '../../utils';
26
+ import { DATAHUB_NAV_SECTION, ROUTES, DATAHUB_PERMISSIONS } from '../../constants';
27
+ import {
28
+ useQueueStats,
29
+ useDeadLetters,
30
+ useConsumers,
31
+ useStartConsumer,
32
+ useStopConsumer,
33
+ useMarkDeadLetter,
34
+ useRetryError,
35
+ usePipelineRun,
36
+ } from '../../hooks';
37
+ import { FailedRunsTable } from './FailedRunsTable';
38
+ import { DeadLettersTable } from './DeadLettersTable';
39
+ import { ConsumersTable } from './ConsumersTable';
40
+
41
+ export const queuesPage: DashboardRouteDefinition = {
42
+ navMenuItem: {
43
+ sectionId: DATAHUB_NAV_SECTION,
44
+ id: 'data-hub-queues',
45
+ url: ROUTES.QUEUES,
46
+ title: 'Queues',
47
+ },
48
+ path: ROUTES.QUEUES,
49
+ loader: () => ({ breadcrumb: 'Queues' }),
50
+ component: () => (
51
+ <PermissionGuard requires={[DATAHUB_PERMISSIONS.VIEW_RUNS]}>
52
+ <QueuesPage />
53
+ </PermissionGuard>
54
+ ),
55
+ };
56
+
57
+ function QueuesPage() {
58
+ const statsQuery = useQueueStats();
59
+ const deadLettersQuery = useDeadLetters();
60
+ const consumersQueryResult = useConsumers();
61
+ const startConsumer = useStartConsumer();
62
+ const stopConsumer = useStopConsumer();
63
+ const mark = useMarkDeadLetter();
64
+ const retry = useRetryError();
65
+
66
+ const stats = statsQuery.data;
67
+ const deadLetters = deadLettersQuery.data ?? [];
68
+ const consumers = consumersQueryResult.data ?? [];
69
+ const [selectedRunId, setSelectedRunId] = React.useState<string | null>(null);
70
+
71
+ const isLoading = statsQuery.isLoading || deadLettersQuery.isLoading || consumersQueryResult.isLoading;
72
+ const hasError = statsQuery.isError || deadLettersQuery.isError || consumersQueryResult.isError;
73
+ const errorMessage =
74
+ statsQuery.error?.message || deadLettersQuery.error?.message || consumersQueryResult.error?.message;
75
+
76
+ const runDetails = usePipelineRun(selectedRunId ?? undefined);
77
+
78
+ const handleRefresh = React.useCallback(() => {
79
+ statsQuery.refetch();
80
+ deadLettersQuery.refetch();
81
+ consumersQueryResult.refetch();
82
+ }, [statsQuery.refetch, deadLettersQuery.refetch, consumersQueryResult.refetch]);
83
+
84
+ // Callbacks for memoized row components
85
+ const handleSelectRun = React.useCallback((runId: string) => {
86
+ setSelectedRunId(runId);
87
+ }, []);
88
+
89
+ const handleRetryDeadLetter = React.useCallback(
90
+ (errorId: string) => {
91
+ retry.mutate({ errorId });
92
+ },
93
+ [retry.mutate],
94
+ );
95
+
96
+ const handleUnmarkDeadLetter = React.useCallback(
97
+ (id: string) => {
98
+ mark.mutate({ id, deadLetter: false });
99
+ },
100
+ [mark.mutate],
101
+ );
102
+
103
+ const handleStopConsumer = React.useCallback(
104
+ (pipelineCode: string) => {
105
+ stopConsumer.mutate({ pipelineCode });
106
+ },
107
+ [stopConsumer.mutate],
108
+ );
109
+
110
+ const handleStartConsumer = React.useCallback(
111
+ (pipelineCode: string) => {
112
+ startConsumer.mutate({ pipelineCode });
113
+ },
114
+ [startConsumer.mutate],
115
+ );
116
+
117
+ const handleDrawerOpenChange = React.useCallback((open: boolean) => {
118
+ if (!open) {
119
+ setSelectedRunId(null);
120
+ }
121
+ }, []);
122
+
123
+ return (
124
+ <Page pageId="data-hub-queues">
125
+ <PageActionBar>
126
+ <PageActionBarRight>
127
+ <Button variant="ghost" onClick={handleRefresh} disabled={statsQuery.isFetching || deadLettersQuery.isFetching}>
128
+ <RefreshCw className="w-4 h-4 mr-2" />
129
+ Refresh
130
+ </Button>
131
+ </PageActionBarRight>
132
+ </PageActionBar>
133
+
134
+ {hasError && (
135
+ <PageBlock column="main" blockId="error">
136
+ <ErrorState
137
+ title="Failed to load queue data"
138
+ message={errorMessage || 'An unexpected error occurred'}
139
+ onRetry={handleRefresh}
140
+ />
141
+ </PageBlock>
142
+ )}
143
+
144
+ {isLoading && !hasError && (
145
+ <PageBlock column="main" blockId="loading">
146
+ <LoadingState type="card" rows={4} message="Loading queue data..." />
147
+ </PageBlock>
148
+ )}
149
+
150
+ <PageBlock column="main" blockId="queues-tabs">
151
+ <Tabs defaultValue="overview">
152
+ <TabsList>
153
+ <TabsTrigger value="overview">
154
+ <Clock className="w-4 h-4 mr-1" />
155
+ Queue Overview
156
+ </TabsTrigger>
157
+ <TabsTrigger value="dead-letters">
158
+ <AlertTriangle className="w-4 h-4 mr-1" />
159
+ Dead Letters
160
+ {deadLetters.length > 0 && (
161
+ <Badge variant="destructive" className="ml-2">{deadLetters.length}</Badge>
162
+ )}
163
+ </TabsTrigger>
164
+ <TabsTrigger value="consumers">
165
+ <Radio className="w-4 h-4 mr-1" />
166
+ Consumers
167
+ {consumers.filter(c => c.isActive).length > 0 && (
168
+ <Badge variant="secondary" className="ml-2">{consumers.filter(c => c.isActive).length}</Badge>
169
+ )}
170
+ </TabsTrigger>
171
+ </TabsList>
172
+
173
+ <TabsContent value="overview" className="mt-4">
174
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
175
+ <StatCard title="Pending" value={stats?.pending ?? 0} icon={<Clock className="w-4 h-4" />} />
176
+ <StatCard title="Running" value={stats?.running ?? 0} icon={<RefreshCw className="w-4 h-4 animate-spin" />} />
177
+ <StatCard title="Failed" value={stats?.failed ?? 0} icon={<XCircle className="w-4 h-4" />} variant="error" />
178
+ <StatCard title="Completed today" value={stats?.completedToday ?? 0} icon={<CheckCircle className="w-4 h-4" />} variant="success" />
179
+ </div>
180
+
181
+ <div className="mt-6">
182
+ <div className="text-sm font-medium mb-2">Queue by Pipeline</div>
183
+ <div className="overflow-x-auto">
184
+ <table className="w-full text-sm">
185
+ <thead>
186
+ <tr className="bg-muted">
187
+ <th className="text-left px-3 py-2">Pipeline</th>
188
+ <th className="text-left px-3 py-2">Pending</th>
189
+ <th className="text-left px-3 py-2">Running</th>
190
+ </tr>
191
+ </thead>
192
+ <tbody>
193
+ {(stats?.byPipeline ?? []).map((r) => (
194
+ <tr key={r.code} className="border-t">
195
+ <td className="px-3 py-2 font-mono text-muted-foreground">{r.code}</td>
196
+ <td className="px-3 py-2">{r.pending}</td>
197
+ <td className="px-3 py-2">{r.running}</td>
198
+ </tr>
199
+ ))}
200
+ {(stats?.byPipeline ?? []).length === 0 && (
201
+ <tr><td className="px-3 py-4 text-muted-foreground" colSpan={3}>No active pipelines</td></tr>
202
+ )}
203
+ </tbody>
204
+ </table>
205
+ </div>
206
+ </div>
207
+
208
+ <FailedRunsTable
209
+ recentFailed={stats?.recentFailed ?? []}
210
+ onSelectRun={handleSelectRun}
211
+ />
212
+ </TabsContent>
213
+
214
+ <TabsContent value="dead-letters" className="mt-4">
215
+ <DeadLettersTable
216
+ deadLetters={deadLetters}
217
+ onRetry={handleRetryDeadLetter}
218
+ onUnmark={handleUnmarkDeadLetter}
219
+ isRetryPending={retry.isPending}
220
+ isUnmarkPending={mark.isPending}
221
+ />
222
+ </TabsContent>
223
+
224
+ <TabsContent value="consumers" className="mt-4">
225
+ <ConsumersTable
226
+ consumers={consumers}
227
+ onStop={handleStopConsumer}
228
+ onStart={handleStartConsumer}
229
+ isStopPending={stopConsumer.isPending}
230
+ isStartPending={startConsumer.isPending}
231
+ />
232
+ </TabsContent>
233
+ </Tabs>
234
+ </PageBlock>
235
+
236
+ <Drawer open={!!selectedRunId} onOpenChange={handleDrawerOpenChange}>
237
+ <DrawerContent>
238
+ <DrawerHeader>
239
+ <DrawerTitle>Run Details</DrawerTitle>
240
+ <DrawerDescription>{selectedRunId ? `Run ${selectedRunId}` : '—'}</DrawerDescription>
241
+ </DrawerHeader>
242
+ <div className="p-4 space-y-3">
243
+ {runDetails.isError ? (
244
+ <div className="p-4 text-center text-sm text-destructive">
245
+ Failed to load run details.{' '}
246
+ <Button variant="link" className="p-0 h-auto" onClick={() => runDetails.refetch()}>Retry</Button>
247
+ </div>
248
+ ) : runDetails.data ? (
249
+ <>
250
+ <div className="text-sm">Status: {runDetails.data?.status}</div>
251
+ <div className="text-sm text-muted-foreground">Pipeline: <span className="font-mono">{runDetails.data?.pipeline?.code ?? '—'}</span></div>
252
+ {runDetails.data?.pipeline?.id ? (
253
+ <div>
254
+ <Button asChild size="sm" variant="secondary">
255
+ <Link to={`${ROUTES.PIPELINES}/$id`} params={{ id: String(runDetails.data?.pipeline.id) }} hash="runs">
256
+ Open pipeline runs
257
+ </Link>
258
+ </Button>
259
+ </div>
260
+ ) : null}
261
+ <div className="grid grid-cols-2 gap-2 text-sm">
262
+ <div>
263
+ <div className="text-muted-foreground">Started</div>
264
+ <div>{runDetails.data?.startedAt ? formatDateTime(String(runDetails.data?.startedAt)) : '—'}</div>
265
+ </div>
266
+ <div>
267
+ <div className="text-muted-foreground">Finished</div>
268
+ <div>{runDetails.data?.finishedAt ? formatDateTime(String(runDetails.data?.finishedAt)) : '—'}</div>
269
+ </div>
270
+ </div>
271
+ <div>
272
+ <div className="text-sm font-medium mb-1">Metrics</div>
273
+ <Json value={runDetails.data?.metrics ?? {}} />
274
+ </div>
275
+ {runDetails.data?.error ? (
276
+ <div>
277
+ <div className="text-sm font-medium mb-1">Error</div>
278
+ <pre className="bg-muted p-2 rounded text-xs overflow-auto">{runDetails.data?.error}</pre>
279
+ </div>
280
+ ) : null}
281
+ </>
282
+ ) : (
283
+ <div className="text-sm text-muted-foreground">Loading...</div>
284
+ )}
285
+ </div>
286
+ </DrawerContent>
287
+ </Drawer>
288
+ </Page>
289
+ );
290
+ }
@@ -0,0 +1 @@
1
+ export { queuesPage } from './QueuesPage';
@@ -0,0 +1,22 @@
1
+ export interface FailedRun {
2
+ id: string;
3
+ code: string;
4
+ finishedAt?: string | null;
5
+ error?: string | null;
6
+ }
7
+
8
+ export interface DeadLetter {
9
+ id: string;
10
+ stepKey: string;
11
+ message: string;
12
+ payload: unknown;
13
+ }
14
+
15
+ export interface Consumer {
16
+ pipelineCode: string;
17
+ queueName: string;
18
+ isActive: boolean;
19
+ messagesProcessed: number;
20
+ messagesFailed: number;
21
+ lastMessageAt?: string | null;
22
+ }