@vendure/dashboard 3.6.3-master-202604160307 → 3.6.3-master-202604170307
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/package.json +3 -3
- package/src/app/routes/_authenticated/_system/components/cancel-jobs-bulk-action.tsx +80 -0
- package/src/app/routes/_authenticated/_system/job-queue.tsx +49 -20
- package/src/app/styles.css +7 -0
- package/src/i18n/locales/ar.po +77 -41
- package/src/i18n/locales/bg.po +77 -41
- package/src/i18n/locales/cs.po +77 -41
- package/src/i18n/locales/de.po +77 -41
- package/src/i18n/locales/en.po +77 -41
- package/src/i18n/locales/es.po +77 -41
- package/src/i18n/locales/fa.po +77 -41
- package/src/i18n/locales/fr.po +77 -41
- package/src/i18n/locales/he.po +77 -41
- package/src/i18n/locales/hr.po +77 -41
- package/src/i18n/locales/hu.po +77 -41
- package/src/i18n/locales/it.po +77 -41
- package/src/i18n/locales/ja.po +77 -41
- package/src/i18n/locales/nb.po +77 -41
- package/src/i18n/locales/ne.po +77 -41
- package/src/i18n/locales/nl.po +77 -41
- package/src/i18n/locales/pl.po +77 -41
- package/src/i18n/locales/pt_BR.po +77 -41
- package/src/i18n/locales/pt_PT.po +77 -41
- package/src/i18n/locales/ro.po +77 -41
- package/src/i18n/locales/ru.po +77 -41
- package/src/i18n/locales/sv.po +77 -41
- package/src/i18n/locales/tr.po +77 -41
- package/src/i18n/locales/uk.po +77 -41
- package/src/i18n/locales/zh_Hans.po +77 -41
- package/src/i18n/locales/zh_Hant.po +77 -41
- package/src/lib/components/shared/custom-fields-form.tsx +70 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.6.3-master-
|
|
4
|
+
"version": "3.6.3-master-202604170307",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
"@storybook/addon-vitest": "^10.3.1",
|
|
138
138
|
"@storybook/react-vite": "^10.3.1",
|
|
139
139
|
"@types/node": "^22.19.0",
|
|
140
|
-
"@vendure/common": "^3.6.3-master-
|
|
141
|
-
"@vendure/core": "^3.6.3-master-
|
|
140
|
+
"@vendure/common": "^3.6.3-master-202604170307",
|
|
141
|
+
"@vendure/core": "^3.6.3-master-202604170307",
|
|
142
142
|
"@vitest/browser": "^3.2.4",
|
|
143
143
|
"@vitest/coverage-v8": "^3.2.4",
|
|
144
144
|
"eslint": "^9.39.0",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
2
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
3
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
+
import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
|
|
5
|
+
import { plural } from '@lingui/core/macro';
|
|
6
|
+
import { Plural, useLingui } from '@lingui/react/macro';
|
|
7
|
+
import { useMutation } from '@tanstack/react-query';
|
|
8
|
+
import { Ban } from 'lucide-react';
|
|
9
|
+
import { toast } from 'sonner';
|
|
10
|
+
import { cancelJobDocument } from '../job-queue.graphql.js';
|
|
11
|
+
|
|
12
|
+
export const CancelJobsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
13
|
+
const { refetchPaginatedList } = usePaginatedList();
|
|
14
|
+
const { t } = useLingui();
|
|
15
|
+
|
|
16
|
+
const cancellableJobs = selection.filter(job => job.state === 'RUNNING' || job.state === 'PENDING');
|
|
17
|
+
const cancellableCount = cancellableJobs.length;
|
|
18
|
+
|
|
19
|
+
const { mutate, isPending } = useMutation({
|
|
20
|
+
mutationFn: async () => {
|
|
21
|
+
const results = await Promise.allSettled(
|
|
22
|
+
cancellableJobs.map(job => api.mutate(cancelJobDocument, { jobId: job.id })),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const fulfilled = results.filter(r => r.status === 'fulfilled').length;
|
|
26
|
+
const rejected = results.filter(r => r.status === 'rejected').length;
|
|
27
|
+
|
|
28
|
+
return { fulfilled, rejected };
|
|
29
|
+
},
|
|
30
|
+
onSuccess: ({ fulfilled, rejected }) => {
|
|
31
|
+
if (fulfilled > 0) {
|
|
32
|
+
toast.success(
|
|
33
|
+
plural(fulfilled, {
|
|
34
|
+
one: t`Successfully cancelled ${fulfilled} job`,
|
|
35
|
+
other: t`Successfully cancelled ${fulfilled} jobs`,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (rejected > 0) {
|
|
40
|
+
toast.error(
|
|
41
|
+
plural(rejected, {
|
|
42
|
+
one: t`Failed to cancel ${rejected} job`,
|
|
43
|
+
other: t`Failed to cancel ${rejected} jobs`,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
refetchPaginatedList();
|
|
49
|
+
table.resetRowSelection();
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (cancellableCount === 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<DataTableBulkActionItem
|
|
59
|
+
requiresPermission={['DeleteSettings', 'DeleteSystem']}
|
|
60
|
+
onClick={() => mutate()}
|
|
61
|
+
disabled={isPending}
|
|
62
|
+
label={
|
|
63
|
+
<Plural
|
|
64
|
+
value={cancellableCount}
|
|
65
|
+
one={`Cancel ${cancellableCount} job`}
|
|
66
|
+
other={`Cancel ${cancellableCount} jobs`}
|
|
67
|
+
/>
|
|
68
|
+
}
|
|
69
|
+
confirmationText={
|
|
70
|
+
<Plural
|
|
71
|
+
value={cancellableCount}
|
|
72
|
+
one={`Are you sure you want to cancel ${cancellableCount} job? This action cannot be undone.`}
|
|
73
|
+
other={`Are you sure you want to cancel ${cancellableCount} jobs? This action cannot be undone.`}
|
|
74
|
+
/>
|
|
75
|
+
}
|
|
76
|
+
icon={Ban}
|
|
77
|
+
className="text-destructive"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
@@ -25,9 +25,38 @@ import {
|
|
|
25
25
|
RotateCcw,
|
|
26
26
|
} from 'lucide-react';
|
|
27
27
|
import { useEffect, useRef, useState } from 'react';
|
|
28
|
+
import { CancelJobsBulkAction } from './components/cancel-jobs-bulk-action.js';
|
|
28
29
|
import { PayloadDialog } from './components/payload-dialog.js';
|
|
29
30
|
import { cancelJobDocument, jobListDocument, jobQueueListDocument } from './job-queue.graphql.js';
|
|
30
31
|
|
|
32
|
+
function formatDuration(ms: number): string {
|
|
33
|
+
if (ms < 1000) {
|
|
34
|
+
return `${ms}ms`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const seconds = Math.floor(ms / 1000);
|
|
38
|
+
const minutes = Math.floor(seconds / 60);
|
|
39
|
+
const hours = Math.floor(minutes / 60);
|
|
40
|
+
const days = Math.floor(hours / 24);
|
|
41
|
+
|
|
42
|
+
const parts: string[] = [];
|
|
43
|
+
|
|
44
|
+
if (days > 0) {
|
|
45
|
+
parts.push(`${days}d`);
|
|
46
|
+
}
|
|
47
|
+
if (hours % 24 > 0) {
|
|
48
|
+
parts.push(`${hours % 24}h`);
|
|
49
|
+
}
|
|
50
|
+
if (minutes % 60 > 0) {
|
|
51
|
+
parts.push(`${minutes % 60}m`);
|
|
52
|
+
}
|
|
53
|
+
if (seconds % 60 > 0) {
|
|
54
|
+
parts.push(`${seconds % 60}s`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parts.join(' ');
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
function getJobStateBadgeVariant(state: string) {
|
|
32
61
|
switch (state) {
|
|
33
62
|
case 'PENDING':
|
|
@@ -122,9 +151,7 @@ function JobQueuePage() {
|
|
|
122
151
|
customizeColumns={{
|
|
123
152
|
createdAt: {
|
|
124
153
|
cell: ({ row }) => (
|
|
125
|
-
<div title={row.original.createdAt}>
|
|
126
|
-
{formatRelativeDate(row.original.createdAt)}
|
|
127
|
-
</div>
|
|
154
|
+
<div title={row.original.createdAt}>{formatRelativeDate(row.original.createdAt)}</div>
|
|
128
155
|
),
|
|
129
156
|
},
|
|
130
157
|
data: {
|
|
@@ -177,26 +204,22 @@ function JobQueuePage() {
|
|
|
177
204
|
const state = STATES.find(s => s.value === row.original.state);
|
|
178
205
|
return (
|
|
179
206
|
<div className="flex items-center gap-2">
|
|
180
|
-
<Badge
|
|
181
|
-
variant={getJobStateBadgeVariant(row.original.state)}
|
|
182
|
-
>
|
|
207
|
+
<Badge variant={getJobStateBadgeVariant(row.original.state)}>
|
|
183
208
|
{state && (
|
|
184
209
|
<state.icon
|
|
185
210
|
className={
|
|
186
|
-
row.original.state === 'RUNNING'
|
|
187
|
-
? 'animate-spin'
|
|
188
|
-
: undefined
|
|
211
|
+
row.original.state === 'RUNNING' ? 'animate-spin' : undefined
|
|
189
212
|
}
|
|
190
213
|
/>
|
|
191
214
|
)}
|
|
192
215
|
{row.original.state}
|
|
193
216
|
</Badge>
|
|
194
217
|
{row.original.state === 'RUNNING' && (
|
|
195
|
-
<DropdownMenu
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
218
|
+
<DropdownMenu onOpenChange={open => (isActionMenuOpenRef.current = open)}>
|
|
219
|
+
<DropdownMenuTrigger
|
|
220
|
+
render={<Button variant="ghost" size="icon-xs" />}
|
|
221
|
+
>
|
|
222
|
+
<MoreVertical />
|
|
200
223
|
</DropdownMenuTrigger>
|
|
201
224
|
<DropdownMenuContent align="end">
|
|
202
225
|
<DropdownMenuItem
|
|
@@ -216,7 +239,7 @@ function JobQueuePage() {
|
|
|
216
239
|
},
|
|
217
240
|
duration: {
|
|
218
241
|
cell: ({ row }) => {
|
|
219
|
-
return row.original.duration ?
|
|
242
|
+
return row.original.duration ? formatDuration(row.original.duration) : null;
|
|
220
243
|
},
|
|
221
244
|
},
|
|
222
245
|
}}
|
|
@@ -246,6 +269,12 @@ function JobQueuePage() {
|
|
|
246
269
|
options: STATES,
|
|
247
270
|
},
|
|
248
271
|
}}
|
|
272
|
+
bulkActions={[
|
|
273
|
+
{
|
|
274
|
+
component: CancelJobsBulkAction,
|
|
275
|
+
order: 100,
|
|
276
|
+
},
|
|
277
|
+
]}
|
|
249
278
|
registerRefresher={refresher => {
|
|
250
279
|
refreshRef.current = refresher;
|
|
251
280
|
}}
|
|
@@ -253,11 +282,11 @@ function JobQueuePage() {
|
|
|
253
282
|
<ActionBarItem itemId="auto-refresh-button">
|
|
254
283
|
<DropdownMenu>
|
|
255
284
|
<DropdownMenuTrigger render={<Button variant="outline" size="sm" className="gap-2" />}>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
285
|
+
<RefreshCw className="h-4 w-4" />
|
|
286
|
+
<span>
|
|
287
|
+
<Trans>Auto refresh: {currentInterval?.label}</Trans>
|
|
288
|
+
</span>
|
|
289
|
+
<ChevronDown className="h-4 w-4" />
|
|
261
290
|
</DropdownMenuTrigger>
|
|
262
291
|
<DropdownMenuContent align="end">
|
|
263
292
|
{REFRESH_INTERVALS.map(interval => (
|
package/src/app/styles.css
CHANGED
|
@@ -53,6 +53,13 @@ html[dir="rtl"] * {
|
|
|
53
53
|
@utility col-main {
|
|
54
54
|
grid-column: span 3 / span 3;
|
|
55
55
|
}
|
|
56
|
+
@utility scrollbar-none {
|
|
57
|
+
-ms-overflow-style: none;
|
|
58
|
+
scrollbar-width: none;
|
|
59
|
+
&::-webkit-scrollbar {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
56
63
|
@utility col-side {
|
|
57
64
|
grid-column: span 2 / span 2;
|
|
58
65
|
}
|