@open-mercato/core 0.4.7-develop-78d7541539 → 0.4.7-develop-74069040de
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/AGENTS.md +1 -0
- package/dist/modules/catalog/api/bulk-delete/route.js +86 -0
- package/dist/modules/catalog/api/bulk-delete/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/route.js +39 -6
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +6 -11
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +2 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +9 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/lib/bulkDelete.js +70 -0
- package/dist/modules/catalog/lib/bulkDelete.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js +185 -0
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection-table.js +9 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/catalog/workers/catalog-product-bulk-delete.js +40 -0
- package/dist/modules/catalog/workers/catalog-product-bulk-delete.js.map +7 -0
- package/dist/modules/data_sync/api/options.js +52 -0
- package/dist/modules/data_sync/api/options.js.map +7 -0
- package/dist/modules/data_sync/api/run.js +30 -35
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +15 -30
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +109 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/route.js +72 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/serialize.js +21 -0
- package/dist/modules/data_sync/api/schedules/serialize.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.js +656 -47
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +116 -34
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +394 -0
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +7 -0
- package/dist/modules/data_sync/data/validators.js +32 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/di.js +2 -0
- package/dist/modules/data_sync/di.js.map +2 -2
- package/dist/modules/data_sync/lib/id-mapping.js +24 -2
- package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
- package/dist/modules/data_sync/lib/start-run.js +57 -0
- package/dist/modules/data_sync/lib/start-run.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +93 -4
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +5 -1
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +138 -0
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-export.js +28 -2
- package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-import.js +28 -2
- package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-scheduled.js +5 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +5 -2
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/lib/field-definitions.js +3 -1
- package/dist/modules/entities/lib/field-definitions.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +14 -15
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +3 -3
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +148 -33
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/lib/state-service.js +15 -1
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +24 -22
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/progress/api/active/route.js +3 -1
- package/dist/modules/progress/api/active/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +1 -1
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +1 -1
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/progress/lib/events.js.map +1 -1
- package/dist/modules/progress/lib/progressService.js.map +2 -2
- package/dist/modules/progress/lib/progressServiceImpl.js +42 -1
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +35 -1
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +91 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +2 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/sales/api/adjustment-kinds/route.js +3 -9
- package/dist/modules/sales/api/adjustment-kinds/route.js.map +2 -2
- package/dist/modules/sales/api/channels/route.js +3 -10
- package/dist/modules/sales/api/channels/route.js.map +2 -2
- package/dist/modules/sales/api/delivery-windows/route.js +3 -10
- package/dist/modules/sales/api/delivery-windows/route.js.map +2 -2
- package/dist/modules/sales/api/payment-methods/route.js +3 -11
- package/dist/modules/sales/api/payment-methods/route.js.map +2 -2
- package/dist/modules/sales/api/price-kinds/route.js +3 -5
- package/dist/modules/sales/api/price-kinds/route.js.map +2 -2
- package/dist/modules/sales/api/shipping-methods/route.js +3 -11
- package/dist/modules/sales/api/shipping-methods/route.js.map +2 -2
- package/dist/modules/sales/api/tags/route.js +3 -9
- package/dist/modules/sales/api/tags/route.js.map +2 -2
- package/dist/modules/sales/api/tax-rates/route.js +3 -13
- package/dist/modules/sales/api/tax-rates/route.js.map +2 -2
- package/dist/modules/sales/api/utils.js +9 -0
- package/dist/modules/sales/api/utils.js.map +2 -2
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +3 -9
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +3 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/route.js +4 -3
- package/dist/modules/workflows/api/definitions/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/serialize.js +25 -0
- package/dist/modules/workflows/api/definitions/serialize.js.map +7 -0
- package/package.json +3 -3
- package/src/modules/catalog/api/bulk-delete/route.ts +93 -0
- package/src/modules/catalog/api/prices/route.ts +53 -6
- package/src/modules/catalog/api/products/route.ts +6 -11
- package/src/modules/catalog/commands/products.ts +2 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -0
- package/src/modules/catalog/i18n/de.json +10 -0
- package/src/modules/catalog/i18n/en.json +10 -0
- package/src/modules/catalog/i18n/es.json +10 -0
- package/src/modules/catalog/i18n/pl.json +10 -0
- package/src/modules/catalog/lib/bulkDelete.ts +106 -0
- package/src/modules/catalog/widgets/injection/product-bulk-delete/widget.ts +242 -0
- package/src/modules/catalog/widgets/injection-table.ts +8 -0
- package/src/modules/catalog/workers/catalog-product-bulk-delete.ts +48 -0
- package/src/modules/data_sync/AGENTS.md +11 -3
- package/src/modules/data_sync/api/options.ts +58 -0
- package/src/modules/data_sync/api/run.ts +34 -36
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +2 -2
- package/src/modules/data_sync/api/runs/[id]/retry.ts +14 -31
- package/src/modules/data_sync/api/schedules/[id]/route.ts +130 -0
- package/src/modules/data_sync/api/schedules/route.ts +77 -0
- package/src/modules/data_sync/api/schedules/serialize.ts +31 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +756 -2
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +179 -53
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +512 -0
- package/src/modules/data_sync/data/validators.ts +35 -0
- package/src/modules/data_sync/di.ts +6 -0
- package/src/modules/data_sync/i18n/de.json +72 -0
- package/src/modules/data_sync/i18n/en.json +72 -0
- package/src/modules/data_sync/i18n/es.json +72 -0
- package/src/modules/data_sync/i18n/pl.json +72 -0
- package/src/modules/data_sync/lib/adapter.ts +4 -1
- package/src/modules/data_sync/lib/id-mapping.ts +32 -2
- package/src/modules/data_sync/lib/start-run.ts +90 -0
- package/src/modules/data_sync/lib/sync-engine.ts +111 -4
- package/src/modules/data_sync/lib/sync-run-service.ts +5 -1
- package/src/modules/data_sync/lib/sync-schedule-service.ts +207 -0
- package/src/modules/data_sync/workers/sync-export.ts +33 -2
- package/src/modules/data_sync/workers/sync-import.ts +33 -2
- package/src/modules/data_sync/workers/sync-scheduled.ts +7 -0
- package/src/modules/entities/api/definitions.ts +12 -2
- package/src/modules/entities/lib/field-definitions.ts +2 -0
- package/src/modules/integrations/AGENTS.md +16 -3
- package/src/modules/integrations/api/[id]/route.ts +14 -15
- package/src/modules/integrations/api/route.ts +3 -3
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +176 -54
- package/src/modules/integrations/lib/state-service.ts +25 -1
- package/src/modules/messages/api/[id]/route.ts +25 -22
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +3 -3
- package/src/modules/progress/api/active/route.ts +4 -1
- package/src/modules/progress/api/jobs/[id]/route.ts +1 -1
- package/src/modules/progress/api/jobs/route.ts +1 -1
- package/src/modules/progress/lib/events.ts +6 -0
- package/src/modules/progress/lib/progressService.ts +1 -0
- package/src/modules/progress/lib/progressServiceImpl.ts +47 -1
- package/src/modules/query_index/lib/document.ts +52 -1
- package/src/modules/query_index/lib/engine.ts +104 -4
- package/src/modules/query_index/lib/indexer.ts +2 -0
- package/src/modules/sales/api/adjustment-kinds/route.ts +3 -9
- package/src/modules/sales/api/channels/route.ts +3 -10
- package/src/modules/sales/api/delivery-windows/route.ts +3 -10
- package/src/modules/sales/api/payment-methods/route.ts +3 -11
- package/src/modules/sales/api/price-kinds/route.ts +3 -5
- package/src/modules/sales/api/shipping-methods/route.ts +3 -11
- package/src/modules/sales/api/tags/route.ts +3 -9
- package/src/modules/sales/api/tax-rates/route.ts +3 -13
- package/src/modules/sales/api/utils.ts +9 -0
- package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +3 -9
- package/src/modules/workflows/api/definitions/[id]/route.ts +3 -2
- package/src/modules/workflows/api/definitions/route.ts +4 -3
- package/src/modules/workflows/api/definitions/serialize.ts +23 -0
|
@@ -20,18 +20,21 @@ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
|
20
20
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
21
21
|
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
22
22
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
23
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
23
24
|
import { LEGACY_INTEGRATION_DETAIL_TABS_SPOT_ID, type CredentialFieldType, type IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'
|
|
24
25
|
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
25
|
-
import { Bell, ChevronDown, ChevronRight, CreditCard, HardDrive, MessageSquare, RefreshCw, Truck, Webhook, Zap } from 'lucide-react'
|
|
26
|
+
import { Activity, AlertTriangle, Bell, Calendar, CheckCircle2, ChevronDown, ChevronRight, CreditCard, FileText, HardDrive, Key, MessageSquare, RefreshCw, Settings, Truck, Webhook, XCircle, Zap } from 'lucide-react'
|
|
27
|
+
import { IntegrationScheduleTab } from '../../../../data_sync/components/IntegrationScheduleTab'
|
|
26
28
|
import {
|
|
27
29
|
buildIntegrationDetailInjectedTabs,
|
|
28
30
|
filterIntegrationDetailWidgetsByKind,
|
|
31
|
+
type IntegrationDetailInjectedTab,
|
|
29
32
|
resolveIntegrationDetailWidgetSpotId,
|
|
30
33
|
resolveRequestedIntegrationDetailTab,
|
|
31
34
|
} from '../detail-page-widgets'
|
|
32
35
|
|
|
33
36
|
type CredentialField = IntegrationCredentialField
|
|
34
|
-
type BuiltInIntegrationDetailTab = 'credentials' | 'version' | 'health' | 'logs'
|
|
37
|
+
type BuiltInIntegrationDetailTab = 'credentials' | 'version' | 'health' | 'logs' | 'data-sync-schedule'
|
|
35
38
|
type IntegrationDetailTab = BuiltInIntegrationDetailTab | string
|
|
36
39
|
|
|
37
40
|
const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])
|
|
@@ -55,6 +58,7 @@ type IntegrationDetail = {
|
|
|
55
58
|
description?: string
|
|
56
59
|
category?: string
|
|
57
60
|
hub?: string
|
|
61
|
+
providerKey?: string | null
|
|
58
62
|
bundleId?: string
|
|
59
63
|
docsUrl?: string
|
|
60
64
|
apiVersions?: ApiVersion[]
|
|
@@ -111,6 +115,12 @@ const HEALTH_STATUS_STYLES: Record<string, string> = {
|
|
|
111
115
|
unhealthy: 'bg-red-100 text-red-800',
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
const HEALTH_STATUS_ICONS: Record<string, React.ElementType> = {
|
|
119
|
+
healthy: CheckCircle2,
|
|
120
|
+
degraded: AlertTriangle,
|
|
121
|
+
unhealthy: XCircle,
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
const CATEGORY_ICONS: Record<string, React.ElementType> = {
|
|
115
125
|
payment: CreditCard,
|
|
116
126
|
shipping: Truck,
|
|
@@ -230,6 +240,10 @@ function formatLogPrimitiveValue(value: string | number | boolean | null): strin
|
|
|
230
240
|
return String(value)
|
|
231
241
|
}
|
|
232
242
|
|
|
243
|
+
function isAkeneoSettingsTab(tab: IntegrationDetailInjectedTab): boolean {
|
|
244
|
+
return tab.id.includes('sync_akeneo') || tab.label.toLowerCase().includes('akeneo')
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
function splitLogPayload(payload: Record<string, unknown> | null | undefined) {
|
|
234
248
|
if (!payload) {
|
|
235
249
|
return {
|
|
@@ -419,9 +433,17 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
419
433
|
),
|
|
420
434
|
[detailWidgets, t],
|
|
421
435
|
)
|
|
436
|
+
const hasDataSyncScheduleTab = Boolean(
|
|
437
|
+
detail?.integration.hub === 'data_sync'
|
|
438
|
+
&& detail?.integration.providerKey
|
|
439
|
+
&& detail.integration.providerKey.trim().length > 0,
|
|
440
|
+
)
|
|
422
441
|
const customTabIds = React.useMemo(
|
|
423
|
-
() =>
|
|
424
|
-
|
|
442
|
+
() => [
|
|
443
|
+
...(hasDataSyncScheduleTab ? ['data-sync-schedule'] : []),
|
|
444
|
+
...injectedTabs.map((tab) => tab.id),
|
|
445
|
+
],
|
|
446
|
+
[hasDataSyncScheduleTab, injectedTabs],
|
|
425
447
|
)
|
|
426
448
|
const runMutationWithContext = React.useCallback(
|
|
427
449
|
async <T,>({
|
|
@@ -703,6 +725,25 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
703
725
|
const resolvedIntegration = detail.integration
|
|
704
726
|
const resolvedState = detail.state
|
|
705
727
|
const CategoryIcon = resolvedIntegration.category ? CATEGORY_ICONS[resolvedIntegration.category] : null
|
|
728
|
+
const HealthStatusIcon = resolvedState.lastHealthStatus ? HEALTH_STATUS_ICONS[resolvedState.lastHealthStatus] : null
|
|
729
|
+
const prioritizedInjectedTabs = resolvedIntegration.id === 'sync_akeneo'
|
|
730
|
+
? [...injectedTabs].sort((left, right) => {
|
|
731
|
+
const leftPriority = isAkeneoSettingsTab(left) ? 1 : 0
|
|
732
|
+
const rightPriority = isAkeneoSettingsTab(right) ? 1 : 0
|
|
733
|
+
if (leftPriority !== rightPriority) return rightPriority - leftPriority
|
|
734
|
+
return 0
|
|
735
|
+
})
|
|
736
|
+
: injectedTabs
|
|
737
|
+
const leadingInjectedTab = resolvedIntegration.id === 'sync_akeneo'
|
|
738
|
+
? prioritizedInjectedTabs.find(isAkeneoSettingsTab) ?? null
|
|
739
|
+
: null
|
|
740
|
+
const trailingInjectedTabs = leadingInjectedTab
|
|
741
|
+
? prioritizedInjectedTabs.filter((tab) => tab.id !== leadingInjectedTab.id)
|
|
742
|
+
: prioritizedInjectedTabs
|
|
743
|
+
const StateIcon = resolvedState.isEnabled ? CheckCircle2 : XCircle
|
|
744
|
+
const stateBadgeClass = resolvedState.isEnabled
|
|
745
|
+
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300'
|
|
746
|
+
: 'border-zinc-500/30 bg-zinc-500/10 text-zinc-300'
|
|
706
747
|
|
|
707
748
|
const showCredentialActions = activeTab === 'credentials' && credentialFormFields.length > 0
|
|
708
749
|
|
|
@@ -749,15 +790,16 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
749
790
|
|
|
750
791
|
<section className="rounded-lg border bg-card p-4">
|
|
751
792
|
<div className="flex items-center justify-between gap-4">
|
|
752
|
-
<div className="space-y-
|
|
793
|
+
<div className="space-y-2">
|
|
753
794
|
<p className="text-[11px] uppercase tracking-wide text-muted-foreground">
|
|
754
795
|
{t('integrations.detail.state.label', 'State')}
|
|
755
796
|
</p>
|
|
756
|
-
<
|
|
797
|
+
<Badge variant="outline" className={cn('gap-1.5 rounded-full px-3 py-1 text-xs font-medium', stateBadgeClass)}>
|
|
798
|
+
<StateIcon className="h-3.5 w-3.5" />
|
|
757
799
|
{resolvedState.isEnabled
|
|
758
800
|
? t('integrations.detail.state.enabled', 'Enabled')
|
|
759
801
|
: t('integrations.detail.state.disabled', 'Disabled')}
|
|
760
|
-
</
|
|
802
|
+
</Badge>
|
|
761
803
|
</div>
|
|
762
804
|
<Switch
|
|
763
805
|
checked={resolvedState.isEnabled}
|
|
@@ -810,14 +852,79 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
810
852
|
</section>
|
|
811
853
|
) : null}
|
|
812
854
|
|
|
813
|
-
<Tabs value={activeTab} onValueChange={handleTabChange} className="space-y-
|
|
814
|
-
<TabsList>
|
|
815
|
-
<TabsTrigger
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
855
|
+
<Tabs value={activeTab} onValueChange={handleTabChange} className="space-y-5">
|
|
856
|
+
<TabsList className="h-auto w-full justify-start overflow-x-auto rounded-none border-b border-border bg-transparent p-0">
|
|
857
|
+
<TabsTrigger
|
|
858
|
+
value="credentials"
|
|
859
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
860
|
+
>
|
|
861
|
+
<span className="inline-flex items-center gap-2">
|
|
862
|
+
<Key className="h-4 w-4" />
|
|
863
|
+
<span>{t('integrations.detail.tabs.credentials')}</span>
|
|
864
|
+
</span>
|
|
865
|
+
</TabsTrigger>
|
|
866
|
+
{leadingInjectedTab ? (
|
|
867
|
+
<TabsTrigger
|
|
868
|
+
value={leadingInjectedTab.id}
|
|
869
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
870
|
+
>
|
|
871
|
+
<span className="inline-flex items-center gap-2">
|
|
872
|
+
<Settings className="h-4 w-4" />
|
|
873
|
+
<span>{leadingInjectedTab.label}</span>
|
|
874
|
+
</span>
|
|
875
|
+
</TabsTrigger>
|
|
876
|
+
) : null}
|
|
877
|
+
{hasVersions ? (
|
|
878
|
+
<TabsTrigger
|
|
879
|
+
value="version"
|
|
880
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
881
|
+
>
|
|
882
|
+
<span className="inline-flex items-center gap-2">
|
|
883
|
+
<RefreshCw className="h-4 w-4" />
|
|
884
|
+
<span>{t('integrations.detail.tabs.version')}</span>
|
|
885
|
+
</span>
|
|
886
|
+
</TabsTrigger>
|
|
887
|
+
) : null}
|
|
888
|
+
{hasDataSyncScheduleTab ? (
|
|
889
|
+
<TabsTrigger
|
|
890
|
+
value="data-sync-schedule"
|
|
891
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
892
|
+
>
|
|
893
|
+
<span className="inline-flex items-center gap-2">
|
|
894
|
+
<Calendar className="h-4 w-4" />
|
|
895
|
+
<span>{t('data_sync.integrationTab.title', 'Sync schedules')}</span>
|
|
896
|
+
</span>
|
|
897
|
+
</TabsTrigger>
|
|
898
|
+
) : null}
|
|
899
|
+
<TabsTrigger
|
|
900
|
+
value="health"
|
|
901
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
902
|
+
>
|
|
903
|
+
<span className="inline-flex items-center gap-2">
|
|
904
|
+
<Activity className="h-4 w-4" />
|
|
905
|
+
<span>{t('integrations.detail.tabs.health')}</span>
|
|
906
|
+
</span>
|
|
907
|
+
</TabsTrigger>
|
|
908
|
+
<TabsTrigger
|
|
909
|
+
value="logs"
|
|
910
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
911
|
+
>
|
|
912
|
+
<span className="inline-flex items-center gap-2">
|
|
913
|
+
<FileText className="h-4 w-4" />
|
|
914
|
+
<span>{t('integrations.detail.tabs.logs')}</span>
|
|
915
|
+
</span>
|
|
916
|
+
</TabsTrigger>
|
|
917
|
+
{trailingInjectedTabs.map((tab) => (
|
|
918
|
+
<TabsTrigger
|
|
919
|
+
key={tab.id}
|
|
920
|
+
value={tab.id}
|
|
921
|
+
className="mr-8 h-auto rounded-none border-b-2 border-transparent bg-transparent px-0 py-2.5 text-sm font-medium text-muted-foreground shadow-none transition-colors hover:bg-transparent hover:text-foreground aria-selected:border-foreground aria-selected:bg-transparent aria-selected:text-foreground aria-selected:shadow-none last:mr-0"
|
|
922
|
+
>
|
|
923
|
+
<span className="inline-flex items-center gap-2">
|
|
924
|
+
<Settings className="h-4 w-4" />
|
|
925
|
+
<span>{tab.label}</span>
|
|
926
|
+
</span>
|
|
927
|
+
</TabsTrigger>
|
|
821
928
|
))}
|
|
822
929
|
</TabsList>
|
|
823
930
|
|
|
@@ -887,9 +994,19 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
887
994
|
</TabsContent>
|
|
888
995
|
) : null}
|
|
889
996
|
|
|
997
|
+
{hasDataSyncScheduleTab ? (
|
|
998
|
+
<TabsContent value="data-sync-schedule" className="mt-0">
|
|
999
|
+
<IntegrationScheduleTab
|
|
1000
|
+
integrationId={resolvedIntegration.id}
|
|
1001
|
+
hasCredentials={detail.hasCredentials}
|
|
1002
|
+
isEnabled={resolvedState.isEnabled}
|
|
1003
|
+
/>
|
|
1004
|
+
</TabsContent>
|
|
1005
|
+
) : null}
|
|
1006
|
+
|
|
890
1007
|
<TabsContent value="health" className="mt-0 space-y-4">
|
|
891
|
-
<Card>
|
|
892
|
-
<CardHeader>
|
|
1008
|
+
<Card className="gap-4 py-4">
|
|
1009
|
+
<CardHeader className="px-5">
|
|
893
1010
|
<div className="flex items-center justify-between">
|
|
894
1011
|
<CardTitle>{t('integrations.detail.health.title')}</CardTitle>
|
|
895
1012
|
<Button
|
|
@@ -904,51 +1021,56 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
904
1021
|
</Button>
|
|
905
1022
|
</div>
|
|
906
1023
|
</CardHeader>
|
|
907
|
-
<CardContent className="space-y-3">
|
|
908
|
-
<div className="flex items-center gap-3">
|
|
909
|
-
<span className="text-sm font-medium">{t('integrations.detail.health.title')}:</span>
|
|
1024
|
+
<CardContent className="space-y-3 px-5">
|
|
1025
|
+
<div className="flex flex-wrap items-center gap-3 rounded-lg border bg-muted/20 px-4 py-3">
|
|
910
1026
|
{resolvedState.lastHealthStatus ? (
|
|
911
|
-
<Badge className={HEALTH_STATUS_STYLES[resolvedState.lastHealthStatus] ?? ''}>
|
|
1027
|
+
<Badge className={`gap-1.5 ${HEALTH_STATUS_STYLES[resolvedState.lastHealthStatus] ?? ''}`}>
|
|
1028
|
+
{HealthStatusIcon ? <HealthStatusIcon className="h-3.5 w-3.5" /> : null}
|
|
912
1029
|
{t(`integrations.detail.health.${resolvedState.lastHealthStatus}`)}
|
|
913
1030
|
</Badge>
|
|
914
1031
|
) : (
|
|
915
|
-
<
|
|
916
|
-
|
|
917
|
-
|
|
1032
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
1033
|
+
<AlertTriangle className="h-4 w-4" />
|
|
1034
|
+
<span>{t('integrations.detail.health.unknown')}</span>
|
|
1035
|
+
</div>
|
|
918
1036
|
)}
|
|
1037
|
+
{healthStatusDescription ? (
|
|
1038
|
+
<p className="min-w-0 flex-1 text-sm text-muted-foreground">{healthStatusDescription}</p>
|
|
1039
|
+
) : null}
|
|
1040
|
+
<p className="text-xs text-muted-foreground md:ml-auto">
|
|
1041
|
+
{resolvedState.lastHealthCheckedAt
|
|
1042
|
+
? t('integrations.detail.health.lastChecked', { date: new Date(resolvedState.lastHealthCheckedAt).toLocaleString() })
|
|
1043
|
+
: t('integrations.detail.health.neverChecked')
|
|
1044
|
+
}
|
|
1045
|
+
</p>
|
|
919
1046
|
</div>
|
|
920
|
-
{
|
|
921
|
-
<
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1047
|
+
{healthMessage || healthDetailEntries.length > 0 ? (
|
|
1048
|
+
<div className={`grid gap-3 ${healthMessage && healthDetailEntries.length > 0 ? 'xl:grid-cols-[minmax(0,1.25fr)_minmax(0,1fr)]' : ''}`}>
|
|
1049
|
+
{healthMessage ? (
|
|
1050
|
+
<div className="rounded-lg border px-4 py-3">
|
|
1051
|
+
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
|
|
1052
|
+
{t('integrations.detail.health.lastResult', 'Last result')}
|
|
1053
|
+
</p>
|
|
1054
|
+
<p className="mt-1.5 text-sm">{healthMessage}</p>
|
|
1055
|
+
</div>
|
|
1056
|
+
) : null}
|
|
1057
|
+
{healthDetailEntries.length > 0 ? (
|
|
1058
|
+
<div className="rounded-lg border px-4 py-3">
|
|
1059
|
+
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
|
|
1060
|
+
{t('integrations.detail.health.details', 'Details')}
|
|
1061
|
+
</p>
|
|
1062
|
+
<dl className="mt-2 grid gap-x-6 gap-y-2 sm:grid-cols-2">
|
|
1063
|
+
{healthDetailEntries.map(([key, value]) => (
|
|
1064
|
+
<div key={key}>
|
|
1065
|
+
<dt className="text-xs font-medium text-muted-foreground">{formatLogDetailLabel(key)}</dt>
|
|
1066
|
+
<dd className="mt-0.5 text-sm">{formatHealthValue(value)}</dd>
|
|
1067
|
+
</div>
|
|
1068
|
+
))}
|
|
1069
|
+
</dl>
|
|
1070
|
+
</div>
|
|
1071
|
+
) : null}
|
|
944
1072
|
</div>
|
|
945
1073
|
) : null}
|
|
946
|
-
<p className="text-xs text-muted-foreground">
|
|
947
|
-
{resolvedState.lastHealthCheckedAt
|
|
948
|
-
? t('integrations.detail.health.lastChecked', { date: new Date(resolvedState.lastHealthCheckedAt).toLocaleString() })
|
|
949
|
-
: t('integrations.detail.health.neverChecked')
|
|
950
|
-
}
|
|
951
|
-
</p>
|
|
952
1074
|
</CardContent>
|
|
953
1075
|
</Card>
|
|
954
1076
|
</TabsContent>
|
|
@@ -3,6 +3,14 @@ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
|
3
3
|
import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
|
|
4
4
|
import { IntegrationState } from '../data/entities'
|
|
5
5
|
|
|
6
|
+
export type ResolvedIntegrationState = {
|
|
7
|
+
isEnabled: boolean
|
|
8
|
+
apiVersion: string | null
|
|
9
|
+
reauthRequired: boolean
|
|
10
|
+
lastHealthStatus: string | null
|
|
11
|
+
lastHealthCheckedAt: Date | null
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
export function createIntegrationStateService(em: EntityManager) {
|
|
7
15
|
return {
|
|
8
16
|
async get(integrationId: string, scope: IntegrationScope): Promise<IntegrationState | null> {
|
|
@@ -20,6 +28,22 @@ export function createIntegrationStateService(em: EntityManager) {
|
|
|
20
28
|
)
|
|
21
29
|
},
|
|
22
30
|
|
|
31
|
+
async resolveState(integrationId: string, scope: IntegrationScope): Promise<ResolvedIntegrationState> {
|
|
32
|
+
const state = await this.get(integrationId, scope)
|
|
33
|
+
return {
|
|
34
|
+
isEnabled: state?.isEnabled ?? false,
|
|
35
|
+
apiVersion: state?.apiVersion ?? null,
|
|
36
|
+
reauthRequired: state?.reauthRequired ?? false,
|
|
37
|
+
lastHealthStatus: state?.lastHealthStatus ?? null,
|
|
38
|
+
lastHealthCheckedAt: state?.lastHealthCheckedAt ?? null,
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async isEnabled(integrationId: string, scope: IntegrationScope): Promise<boolean> {
|
|
43
|
+
const state = await this.resolveState(integrationId, scope)
|
|
44
|
+
return state.isEnabled
|
|
45
|
+
},
|
|
46
|
+
|
|
23
47
|
async upsert(
|
|
24
48
|
integrationId: string,
|
|
25
49
|
input: Partial<Pick<IntegrationState, 'isEnabled' | 'apiVersion' | 'reauthRequired' | 'lastHealthStatus' | 'lastHealthCheckedAt'>>,
|
|
@@ -38,7 +62,7 @@ export function createIntegrationStateService(em: EntityManager) {
|
|
|
38
62
|
|
|
39
63
|
const created = em.create(IntegrationState, {
|
|
40
64
|
integrationId,
|
|
41
|
-
isEnabled: input.isEnabled ??
|
|
65
|
+
isEnabled: input.isEnabled ?? false,
|
|
42
66
|
apiVersion: input.apiVersion,
|
|
43
67
|
reauthRequired: input.reauthRequired ?? false,
|
|
44
68
|
lastHealthStatus: input.lastHealthStatus,
|
|
@@ -65,25 +65,8 @@ export async function GET(req: Request, { params }: { params: { id: string } })
|
|
|
65
65
|
return Response.json({ error: 'Access denied' }, { status: 403 })
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await commandBus.execute('messages.recipients.mark_read', {
|
|
71
|
-
input: {
|
|
72
|
-
messageId: params.id,
|
|
73
|
-
tenantId: scope.tenantId,
|
|
74
|
-
organizationId: scope.organizationId,
|
|
75
|
-
userId: scope.userId,
|
|
76
|
-
},
|
|
77
|
-
ctx: {
|
|
78
|
-
container: ctx.container,
|
|
79
|
-
auth: ctx.auth ?? null,
|
|
80
|
-
organizationScope: null,
|
|
81
|
-
selectedOrganizationId: scope.organizationId,
|
|
82
|
-
organizationIds: scope.organizationId ? [scope.organizationId] : null,
|
|
83
|
-
request: req,
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
}
|
|
68
|
+
const autoMarkRead = !skipMarkRead && recipient?.status === 'unread'
|
|
69
|
+
const readAt = autoMarkRead ? new Date() : recipient?.readAt ?? null
|
|
87
70
|
|
|
88
71
|
const objects = await em.find(MessageObject, { messageId: params.id })
|
|
89
72
|
const objectPreviews = await Promise.all(
|
|
@@ -162,6 +145,26 @@ export async function GET(req: Request, { params }: { params: { id: string } })
|
|
|
162
145
|
const messageType = getMessageTypeOrDefault(message.type)
|
|
163
146
|
const resolvedActionData = buildResolvedMessageActions(message, objects)
|
|
164
147
|
|
|
148
|
+
if (autoMarkRead) {
|
|
149
|
+
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
150
|
+
await commandBus.execute('messages.recipients.mark_read', {
|
|
151
|
+
input: {
|
|
152
|
+
messageId: params.id,
|
|
153
|
+
tenantId: scope.tenantId,
|
|
154
|
+
organizationId: scope.organizationId,
|
|
155
|
+
userId: scope.userId,
|
|
156
|
+
},
|
|
157
|
+
ctx: {
|
|
158
|
+
container: ctx.container,
|
|
159
|
+
auth: ctx.auth ?? null,
|
|
160
|
+
organizationScope: null,
|
|
161
|
+
selectedOrganizationId: scope.organizationId,
|
|
162
|
+
organizationIds: scope.organizationId ? [scope.organizationId] : null,
|
|
163
|
+
request: req,
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
165
168
|
return Response.json({
|
|
166
169
|
id: message.id,
|
|
167
170
|
type: message.type,
|
|
@@ -201,8 +204,8 @@ export async function GET(req: Request, { params }: { params: { id: string } })
|
|
|
201
204
|
recipients: allRecipients.map((item) => ({
|
|
202
205
|
userId: item.recipientUserId,
|
|
203
206
|
type: item.recipientType,
|
|
204
|
-
status: item.status,
|
|
205
|
-
readAt: item.readAt,
|
|
207
|
+
status: autoMarkRead && item.recipientUserId === scope.userId ? 'read' : item.status,
|
|
208
|
+
readAt: autoMarkRead && item.recipientUserId === scope.userId ? readAt : item.readAt,
|
|
206
209
|
})),
|
|
207
210
|
objects: objects.map((item, index) => ({
|
|
208
211
|
id: item.id,
|
|
@@ -231,7 +234,7 @@ export async function GET(req: Request, { params }: { params: { id: string } })
|
|
|
231
234
|
sentAt: threadMessage.sentAt,
|
|
232
235
|
}
|
|
233
236
|
}),
|
|
234
|
-
isRead: recipient ? recipient.status !== 'unread' : true,
|
|
237
|
+
isRead: recipient ? (autoMarkRead || recipient.status !== 'unread') : true,
|
|
235
238
|
})
|
|
236
239
|
}
|
|
237
240
|
|
|
@@ -69,9 +69,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ provide
|
|
|
69
69
|
)
|
|
70
70
|
: []
|
|
71
71
|
|
|
72
|
-
let transaction
|
|
73
|
-
let matchedScope
|
|
74
|
-
let event
|
|
72
|
+
let transaction: GatewayTransaction | null = null
|
|
73
|
+
let matchedScope: { organizationId: string; tenantId: string } | null = null
|
|
74
|
+
let event: Awaited<ReturnType<typeof registration.handler>> | null = null
|
|
75
75
|
let lastVerificationError: unknown = null
|
|
76
76
|
|
|
77
77
|
for (const candidate of candidates) {
|
|
@@ -4,7 +4,7 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
|
4
4
|
import type { ProgressJob } from '../../data/entities'
|
|
5
5
|
|
|
6
6
|
const routeMetadata = {
|
|
7
|
-
GET: { requireAuth: true
|
|
7
|
+
GET: { requireAuth: true },
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const metadata = routeMetadata
|
|
@@ -20,6 +20,8 @@ export async function GET(req: Request) {
|
|
|
20
20
|
|
|
21
21
|
const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId }
|
|
22
22
|
|
|
23
|
+
await progressService.markStaleJobsFailed(auth.tenantId)
|
|
24
|
+
|
|
23
25
|
const [jobs, recentlyCompleted] = await Promise.all([
|
|
24
26
|
progressService.getActiveJobs(ctx),
|
|
25
27
|
progressService.getRecentlyCompletedJobs(ctx),
|
|
@@ -43,6 +45,7 @@ function formatJob(job: ProgressJob) {
|
|
|
43
45
|
totalCount: job.totalCount,
|
|
44
46
|
etaSeconds: job.etaSeconds,
|
|
45
47
|
cancellable: job.cancellable,
|
|
48
|
+
meta: job.meta ?? null,
|
|
46
49
|
startedAt: job.startedAt?.toISOString() ?? null,
|
|
47
50
|
finishedAt: job.finishedAt?.toISOString() ?? null,
|
|
48
51
|
errorMessage: job.errorMessage,
|
|
@@ -7,7 +7,7 @@ import { updateProgressSchema } from '../../../data/validators'
|
|
|
7
7
|
import type { ProgressService } from '../../../lib/progressService'
|
|
8
8
|
|
|
9
9
|
const routeMetadata = {
|
|
10
|
-
GET: { requireAuth: true
|
|
10
|
+
GET: { requireAuth: true },
|
|
11
11
|
PUT: { requireAuth: true, requireFeatures: ['progress.update'] },
|
|
12
12
|
DELETE: { requireAuth: true, requireFeatures: ['progress.cancel'] },
|
|
13
13
|
}
|
|
@@ -27,6 +27,7 @@ export type ProgressJobCreatedPayload = {
|
|
|
27
27
|
cancellable?: boolean
|
|
28
28
|
startedAt?: string | null
|
|
29
29
|
finishedAt?: string | null
|
|
30
|
+
meta?: Record<string, unknown> | null
|
|
30
31
|
tenantId: string
|
|
31
32
|
organizationId?: string | null
|
|
32
33
|
}
|
|
@@ -44,6 +45,7 @@ export type ProgressJobStartedPayload = {
|
|
|
44
45
|
cancellable?: boolean
|
|
45
46
|
startedAt?: string | null
|
|
46
47
|
finishedAt?: string | null
|
|
48
|
+
meta?: Record<string, unknown> | null
|
|
47
49
|
tenantId: string
|
|
48
50
|
organizationId?: string | null
|
|
49
51
|
}
|
|
@@ -63,6 +65,7 @@ export type ProgressJobUpdatedPayload = {
|
|
|
63
65
|
cancellable?: boolean
|
|
64
66
|
startedAt?: string | null
|
|
65
67
|
finishedAt?: string | null
|
|
68
|
+
meta?: Record<string, unknown> | null
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export type ProgressJobCompletedPayload = {
|
|
@@ -78,6 +81,7 @@ export type ProgressJobCompletedPayload = {
|
|
|
78
81
|
cancellable?: boolean
|
|
79
82
|
startedAt?: string | null
|
|
80
83
|
finishedAt?: string | null
|
|
84
|
+
meta?: Record<string, unknown> | null
|
|
81
85
|
resultSummary?: Record<string, unknown> | null
|
|
82
86
|
tenantId: string
|
|
83
87
|
organizationId?: string | null
|
|
@@ -96,6 +100,7 @@ export type ProgressJobFailedPayload = {
|
|
|
96
100
|
cancellable?: boolean
|
|
97
101
|
startedAt?: string | null
|
|
98
102
|
finishedAt?: string | null
|
|
103
|
+
meta?: Record<string, unknown> | null
|
|
99
104
|
errorMessage: string
|
|
100
105
|
tenantId: string
|
|
101
106
|
organizationId?: string | null
|
|
@@ -115,6 +120,7 @@ export type ProgressJobCancelledPayload = {
|
|
|
115
120
|
cancellable?: boolean
|
|
116
121
|
startedAt?: string | null
|
|
117
122
|
finishedAt?: string | null
|
|
123
|
+
meta?: Record<string, unknown> | null
|
|
118
124
|
tenantId: string
|
|
119
125
|
organizationId?: string | null
|
|
120
126
|
}
|
|
@@ -15,6 +15,7 @@ export interface ProgressService {
|
|
|
15
15
|
completeJob(jobId: string, input: CompleteJobInput | undefined, ctx: ProgressServiceContext): Promise<ProgressJob>
|
|
16
16
|
failJob(jobId: string, input: FailJobInput, ctx: ProgressServiceContext): Promise<ProgressJob>
|
|
17
17
|
cancelJob(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>
|
|
18
|
+
markCancelled(jobId: string, ctx: ProgressServiceContext): Promise<ProgressJob>
|
|
18
19
|
isCancellationRequested(jobId: string): Promise<boolean>
|
|
19
20
|
getActiveJobs(ctx: ProgressServiceContext): Promise<ProgressJob[]>
|
|
20
21
|
getRecentlyCompletedJobs(ctx: ProgressServiceContext, sinceSeconds?: number): Promise<ProgressJob[]>
|