@open-mercato/core 0.6.6-develop.6229.1.ebe6706732 → 0.6.6-develop.6241.1.47c01ad4ed
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/commands/users.js +1 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/communication_channels/data/enrichers.js +9 -1
- package/dist/modules/communication_channels/data/enrichers.js.map +2 -2
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +3 -5
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +39 -1
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/route.js +107 -12
- package/dist/modules/customers/api/pipeline-stages/route.js.map +2 -2
- package/dist/modules/customers/api/pipelines/route.js +107 -12
- package/dist/modules/customers/api/pipelines/route.js.map +2 -2
- package/dist/modules/customers/api/settings/address-format/route.js +33 -1
- package/dist/modules/customers/api/settings/address-format/route.js.map +2 -2
- package/dist/modules/customers/api/tags/assign/route.js +37 -0
- package/dist/modules/customers/api/tags/assign/route.js.map +2 -2
- package/dist/modules/customers/api/tags/unassign/route.js +37 -0
- package/dist/modules/customers/api/tags/unassign/route.js.map +2 -2
- package/dist/modules/entities/api/encryption.js +109 -38
- package/dist/modules/entities/api/encryption.js.map +2 -2
- package/dist/modules/entities/components/EncryptionManager.js +22 -9
- package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +23 -1
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +10 -3
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/state/route.js +20 -5
- package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/version/route.js +17 -1
- package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +2 -0
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +36 -19
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +13 -9
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/page.js +4 -3
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/integrations/data/entities.js +2 -2
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +32 -0
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/lib/state-service.js +4 -2
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/notifications/lib/notificationService.js +42 -10
- package/dist/modules/notifications/lib/notificationService.js.map +2 -2
- package/dist/modules/payment_gateways/api/status/route.js +91 -6
- package/dist/modules/payment_gateways/api/status/route.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +25 -11
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +25 -7
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +25 -7
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js +52 -24
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
- package/dist/modules/sales/components/DocumentNumberSettings.js +26 -9
- package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
- package/dist/modules/sales/components/OrderEditingSettings.js +26 -9
- package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
- package/dist/modules/sales/components/StatusSettings.js +66 -31
- package/dist/modules/sales/components/StatusSettings.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +25 -7
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +38 -0
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +12 -5
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +7 -8
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/timerErrors.js +12 -0
- package/dist/modules/staff/lib/timesheets-ui/timerErrors.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +4 -3
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/page.js +19 -10
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/commands/users.ts +1 -0
- package/src/modules/communication_channels/data/enrichers.ts +25 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +7 -6
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +5 -2
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +41 -1
- package/src/modules/customers/api/pipeline-stages/route.ts +112 -13
- package/src/modules/customers/api/pipelines/route.ts +112 -13
- package/src/modules/customers/api/settings/address-format/route.ts +36 -1
- package/src/modules/customers/api/tags/assign/route.ts +39 -0
- package/src/modules/customers/api/tags/unassign/route.ts +39 -0
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/entities/api/encryption.ts +122 -39
- package/src/modules/entities/components/EncryptionManager.tsx +28 -9
- package/src/modules/integrations/api/[id]/credentials/route.ts +23 -0
- package/src/modules/integrations/api/[id]/route.ts +8 -1
- package/src/modules/integrations/api/[id]/state/route.ts +20 -4
- package/src/modules/integrations/api/[id]/version/route.ts +18 -1
- package/src/modules/integrations/api/route.ts +3 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +39 -20
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -11
- package/src/modules/integrations/backend/integrations/page.tsx +8 -5
- package/src/modules/integrations/data/entities.ts +2 -2
- package/src/modules/integrations/lib/credentials-service.ts +35 -0
- package/src/modules/integrations/lib/state-service.ts +3 -0
- package/src/modules/notifications/lib/notificationService.ts +74 -11
- package/src/modules/payment_gateways/api/status/route.ts +97 -5
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +27 -11
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +31 -7
- package/src/modules/sales/backend/sales/channels/page.tsx +31 -7
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +60 -24
- package/src/modules/sales/components/DocumentNumberSettings.tsx +32 -10
- package/src/modules/sales/components/OrderEditingSettings.tsx +32 -10
- package/src/modules/sales/components/StatusSettings.tsx +74 -33
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +30 -6
- package/src/modules/shipping_carriers/api/cancel/route.ts +46 -0
- package/src/modules/shipping_carriers/api/shipments/route.ts +21 -6
- package/src/modules/staff/i18n/de.json +5 -5
- package/src/modules/staff/i18n/es.json +5 -5
- package/src/modules/staff/i18n/pl.json +5 -5
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +7 -8
- package/src/modules/staff/lib/timesheets-ui/timerErrors.ts +23 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +4 -3
- package/src/modules/workflows/backend/definitions/page.tsx +19 -10
|
@@ -97,6 +97,7 @@ async function GET(req) {
|
|
|
97
97
|
lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,
|
|
98
98
|
lastHealthLatencyMs: state.lastHealthLatencyMs,
|
|
99
99
|
enabledAt: state.enabledAt?.toISOString() ?? null,
|
|
100
|
+
stateUpdatedAt: state.updatedAt?.toISOString() ?? null,
|
|
100
101
|
sortEnabledAtMs: enabledAtMs,
|
|
101
102
|
sortTitle: integration.title.toLowerCase(),
|
|
102
103
|
sortCategory: (integration.category ?? "").toLowerCase()
|
|
@@ -170,6 +171,7 @@ async function GET(req) {
|
|
|
170
171
|
lastHealthCheckedAt: row.lastHealthCheckedAt,
|
|
171
172
|
lastHealthLatencyMs: row.lastHealthLatencyMs,
|
|
172
173
|
enabledAt: row.enabledAt,
|
|
174
|
+
stateUpdatedAt: row.stateUpdatedAt,
|
|
173
175
|
analytics: analytics ?? {
|
|
174
176
|
lastActivityAt: null,
|
|
175
177
|
totalCount: 0,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/integrations/api/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CredentialsEncryptionUnavailableError, type CredentialsService } from '../lib/credentials-service'\nimport type { IntegrationStateService } from '../lib/state-service'\nimport type { IntegrationLogService } from '../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../lib/health-service'\nimport type { IntegrationHealthDisplayStatus } from '../lib/health-service'\nimport { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'\nimport { listIntegrationsQuerySchema } from '../data/validators'\nimport { buildIntegrationsCrudOpenApi, integrationsListResponseSchema } from './openapi'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from './umes-read'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = buildIntegrationsCrudOpenApi({\n resourceName: 'Integration',\n pluralName: 'Integrations',\n listResponseSchema: integrationsListResponseSchema,\n querySchema: listIntegrationsQuerySchema,\n})\n\nconst HEALTH_SORT_RANK: Record<IntegrationHealthDisplayStatus, number> = {\n healthy: 0,\n degraded: 1,\n unhealthy: 2,\n unconfigured: 3,\n}\n\nfunction matchesSearchQuery(\n integration: { title: string; description?: string; tags?: string[] },\n queryNormalized: string,\n): boolean {\n if (!queryNormalized) return true\n if (integration.title.toLowerCase().includes(queryNormalized)) return true\n if (integration.description?.toLowerCase().includes(queryNormalized)) return true\n const tags = integration.tags ?? []\n return tags.some((tag) => tag.toLowerCase().includes(queryNormalized))\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsedQuery = listIntegrationsQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsedQuery.error.flatten() }, { status: 400 })\n }\n const query = parsedQuery.data\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }\n const searchNeedle = query.q?.trim().toLowerCase() ?? ''\n\n type ListRow = {\n id: string\n title: string\n description: string | null\n category: string | null\n tags: string[]\n hub: string | null\n providerKey: string | null\n bundleId: string | null\n author: string | null\n company: string | null\n version: string | null\n hasCredentials: boolean\n isEnabled: boolean\n apiVersion: string | null\n healthStatus: IntegrationHealthDisplayStatus\n lastHealthCheckedAt: string | null\n lastHealthLatencyMs: number | null\n enabledAt: string | null\n sortEnabledAtMs: number\n sortTitle: string\n sortCategory: string\n }\n\n const baseRows: ListRow[] = await Promise.all(\n getAllIntegrations().map(async (integration) => {\n const [resolvedCredentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope).catch((err) => {\n if (err instanceof CredentialsEncryptionUnavailableError) return null\n throw err\n }),\n stateService.resolveState(integration.id, scope),\n ])\n\n const hasCredentials =\n resolvedCredentials != null && Object.keys(resolvedCredentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const enabledAtMs = state.enabledAt?.getTime() ?? 0\n\n return {\n id: integration.id,\n title: integration.title,\n description: integration.description ?? null,\n category: integration.category ?? null,\n tags: integration.tags ?? [],\n hub: integration.hub ?? null,\n providerKey: integration.providerKey ?? null,\n bundleId: integration.bundleId ?? null,\n author: integration.author ?? null,\n company: integration.company ?? null,\n version: integration.version ?? null,\n hasCredentials,\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n healthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n sortEnabledAtMs: enabledAtMs,\n sortTitle: integration.title.toLowerCase(),\n sortCategory: (integration.category ?? '').toLowerCase(),\n }\n }),\n )\n\n let filtered = baseRows.filter((row) => matchesSearchQuery(\n { title: row.title, description: row.description ?? undefined, tags: row.tags },\n searchNeedle,\n ))\n\n if (query.category) {\n filtered = filtered.filter((row) => row.category === query.category)\n }\n if (query.bundleId) {\n filtered = filtered.filter((row) => row.bundleId === query.bundleId)\n }\n if (query.isEnabled !== undefined) {\n filtered = filtered.filter((row) => row.isEnabled === query.isEnabled)\n }\n if (query.healthStatus) {\n filtered = filtered.filter((row) => row.healthStatus === query.healthStatus)\n }\n\n const sortKey = query.sort ?? 'title'\n const orderSign = query.order === 'desc' ? -1 : 1\n\n const sorted = [...filtered].sort((rowA, rowB) => {\n let cmp = 0\n if (sortKey === 'title') {\n cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'category') {\n cmp = rowA.sortCategory.localeCompare(rowB.sortCategory)\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'enabledAt') {\n cmp = rowA.sortEnabledAtMs - rowB.sortEnabledAtMs\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'healthStatus') {\n cmp = HEALTH_SORT_RANK[rowA.healthStatus] - HEALTH_SORT_RANK[rowB.healthStatus]\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n }\n return cmp * orderSign\n })\n\n const total = sorted.length\n const pageSize = query.pageSize\n const page = query.page\n const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1)\n const safePage = Math.min(page, totalPages)\n const offset = (safePage - 1) * pageSize\n const pageSlice = sorted.slice(offset, offset + pageSize)\n\n const analyticsMap = await logService.aggregateAnalytics(\n pageSlice.map((row) => row.id),\n scope,\n 30,\n )\n\n const items = pageSlice.map((row) => {\n const analytics = analyticsMap.get(row.id)\n return {\n id: row.id,\n title: row.title,\n description: row.description,\n category: row.category,\n tags: row.tags,\n hub: row.hub,\n providerKey: row.providerKey,\n bundleId: row.bundleId,\n author: row.author,\n company: row.company,\n version: row.version,\n hasCredentials: row.hasCredentials,\n isEnabled: row.isEnabled,\n apiVersion: row.apiVersion,\n healthStatus: row.healthStatus,\n lastHealthCheckedAt: row.lastHealthCheckedAt,\n lastHealthLatencyMs: row.lastHealthLatencyMs,\n enabledAt: row.enabledAt,\n analytics: analytics ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n },\n }\n })\n\n const bundles = getAllBundles().map((bundle) => {\n const bundleIntegrations = filtered.filter((row) => row.bundleId === bundle.id)\n const enabledCount = bundleIntegrations.reduce((count, integration) => count + (integration.isEnabled ? 1 : 0), 0)\n\n return {\n id: bundle.id,\n title: bundle.title,\n description: bundle.description,\n icon: bundle.icon ?? null,\n integrationCount: bundleIntegrations.length,\n enabledCount,\n }\n })\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n listKeys: ['items'],\n },\n body: {\n items,\n bundles,\n total,\n page: safePage,\n pageSize,\n totalPages,\n },\n })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,6CAAsE;AAG/E,SAAS,+BAA+B,qCAAqC;AAE7E,SAAS,eAAe,0BAA0B;AAClD,SAAS,mCAAmC;AAC5C,SAAS,8BAA8B,sCAAsC;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEO,MAAM,UAAU,6BAA6B;AAAA,EAClD,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,aAAa;AACf,CAAC;AAED,MAAM,mBAAmE;AAAA,EACvE,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAChB;AAEA,SAAS,mBACP,aACA,iBACS;AACT,MAAI,CAAC,gBAAiB,QAAO;AAC7B,MAAI,YAAY,MAAM,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AACtE,MAAI,YAAY,aAAa,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AAC7E,QAAM,OAAO,YAAY,QAAQ,CAAC;AAClC,SAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,EAAE,SAAS,eAAe,CAAC;AACvE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,cAAc,4BAA4B,UAAU,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC,CAAC;AACxG,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,SAAS,YAAY,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5G;AACA,QAAM,QAAQ,YAAY;AAE1B,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,MAAM,sCAAsC;AAAA,IACrE,WAAW,yBAAyB;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,mBAAmB,IAAI;AAC1B,WAAO,aAAa,KAAK,mBAAmB,MAAM,EAAE,QAAQ,mBAAmB,WAAW,CAAC;AAAA,EAC7F;AACA,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAChE,QAAM,aAAa,UAAU,QAAQ,uBAAuB;AAE5D,QAAM,QAAQ,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAmB;AACxF,QAAM,eAAe,MAAM,GAAG,KAAK,EAAE,YAAY,KAAK;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CredentialsEncryptionUnavailableError, type CredentialsService } from '../lib/credentials-service'\nimport type { IntegrationStateService } from '../lib/state-service'\nimport type { IntegrationLogService } from '../lib/log-service'\nimport { deriveIntegrationHealthStatus, getEffectiveHealthCheckConfig } from '../lib/health-service'\nimport type { IntegrationHealthDisplayStatus } from '../lib/health-service'\nimport { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'\nimport { listIntegrationsQuerySchema } from '../data/validators'\nimport { buildIntegrationsCrudOpenApi, integrationsListResponseSchema } from './openapi'\nimport {\n finalizeIntegrationsReadResponse,\n integrationApiRoutePaths,\n runIntegrationsReadBeforeInterceptors,\n} from './umes-read'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.view'] },\n}\n\nexport const openApi = buildIntegrationsCrudOpenApi({\n resourceName: 'Integration',\n pluralName: 'Integrations',\n listResponseSchema: integrationsListResponseSchema,\n querySchema: listIntegrationsQuerySchema,\n})\n\nconst HEALTH_SORT_RANK: Record<IntegrationHealthDisplayStatus, number> = {\n healthy: 0,\n degraded: 1,\n unhealthy: 2,\n unconfigured: 3,\n}\n\nfunction matchesSearchQuery(\n integration: { title: string; description?: string; tags?: string[] },\n queryNormalized: string,\n): boolean {\n if (!queryNormalized) return true\n if (integration.title.toLowerCase().includes(queryNormalized)) return true\n if (integration.description?.toLowerCase().includes(queryNormalized)) return true\n const tags = integration.tags ?? []\n return tags.some((tag) => tag.toLowerCase().includes(queryNormalized))\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsedQuery = listIntegrationsQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'Invalid query', details: parsedQuery.error.flatten() }, { status: 400 })\n }\n const query = parsedQuery.data\n\n const container = await createRequestContainer()\n const beforeInterceptors = await runIntegrationsReadBeforeInterceptors({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n })\n if (!beforeInterceptors.ok) {\n return NextResponse.json(beforeInterceptors.body, { status: beforeInterceptors.statusCode })\n }\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n const logService = container.resolve('integrationLogService') as IntegrationLogService\n\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }\n const searchNeedle = query.q?.trim().toLowerCase() ?? ''\n\n type ListRow = {\n id: string\n title: string\n description: string | null\n category: string | null\n tags: string[]\n hub: string | null\n providerKey: string | null\n bundleId: string | null\n author: string | null\n company: string | null\n version: string | null\n hasCredentials: boolean\n isEnabled: boolean\n apiVersion: string | null\n healthStatus: IntegrationHealthDisplayStatus\n lastHealthCheckedAt: string | null\n lastHealthLatencyMs: number | null\n enabledAt: string | null\n stateUpdatedAt: string | null\n sortEnabledAtMs: number\n sortTitle: string\n sortCategory: string\n }\n\n const baseRows: ListRow[] = await Promise.all(\n getAllIntegrations().map(async (integration) => {\n const [resolvedCredentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, scope).catch((err) => {\n if (err instanceof CredentialsEncryptionUnavailableError) return null\n throw err\n }),\n stateService.resolveState(integration.id, scope),\n ])\n\n const hasCredentials =\n resolvedCredentials != null && Object.keys(resolvedCredentials).length > 0\n const healthConfig = getEffectiveHealthCheckConfig(integration.id)\n const hasHealthCheck = Boolean(healthConfig?.service)\n const healthStatus = deriveIntegrationHealthStatus({\n hasHealthCheck,\n hasCredentials,\n lastHealthStatus: state.lastHealthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt,\n })\n\n const enabledAtMs = state.enabledAt?.getTime() ?? 0\n\n return {\n id: integration.id,\n title: integration.title,\n description: integration.description ?? null,\n category: integration.category ?? null,\n tags: integration.tags ?? [],\n hub: integration.hub ?? null,\n providerKey: integration.providerKey ?? null,\n bundleId: integration.bundleId ?? null,\n author: integration.author ?? null,\n company: integration.company ?? null,\n version: integration.version ?? null,\n hasCredentials,\n isEnabled: state.isEnabled,\n apiVersion: state.apiVersion,\n healthStatus,\n lastHealthCheckedAt: state.lastHealthCheckedAt?.toISOString() ?? null,\n lastHealthLatencyMs: state.lastHealthLatencyMs,\n enabledAt: state.enabledAt?.toISOString() ?? null,\n stateUpdatedAt: state.updatedAt?.toISOString() ?? null,\n sortEnabledAtMs: enabledAtMs,\n sortTitle: integration.title.toLowerCase(),\n sortCategory: (integration.category ?? '').toLowerCase(),\n }\n }),\n )\n\n let filtered = baseRows.filter((row) => matchesSearchQuery(\n { title: row.title, description: row.description ?? undefined, tags: row.tags },\n searchNeedle,\n ))\n\n if (query.category) {\n filtered = filtered.filter((row) => row.category === query.category)\n }\n if (query.bundleId) {\n filtered = filtered.filter((row) => row.bundleId === query.bundleId)\n }\n if (query.isEnabled !== undefined) {\n filtered = filtered.filter((row) => row.isEnabled === query.isEnabled)\n }\n if (query.healthStatus) {\n filtered = filtered.filter((row) => row.healthStatus === query.healthStatus)\n }\n\n const sortKey = query.sort ?? 'title'\n const orderSign = query.order === 'desc' ? -1 : 1\n\n const sorted = [...filtered].sort((rowA, rowB) => {\n let cmp = 0\n if (sortKey === 'title') {\n cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'category') {\n cmp = rowA.sortCategory.localeCompare(rowB.sortCategory)\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'enabledAt') {\n cmp = rowA.sortEnabledAtMs - rowB.sortEnabledAtMs\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n } else if (sortKey === 'healthStatus') {\n cmp = HEALTH_SORT_RANK[rowA.healthStatus] - HEALTH_SORT_RANK[rowB.healthStatus]\n if (cmp === 0) cmp = rowA.sortTitle.localeCompare(rowB.sortTitle)\n }\n return cmp * orderSign\n })\n\n const total = sorted.length\n const pageSize = query.pageSize\n const page = query.page\n const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1)\n const safePage = Math.min(page, totalPages)\n const offset = (safePage - 1) * pageSize\n const pageSlice = sorted.slice(offset, offset + pageSize)\n\n const analyticsMap = await logService.aggregateAnalytics(\n pageSlice.map((row) => row.id),\n scope,\n 30,\n )\n\n const items = pageSlice.map((row) => {\n const analytics = analyticsMap.get(row.id)\n return {\n id: row.id,\n title: row.title,\n description: row.description,\n category: row.category,\n tags: row.tags,\n hub: row.hub,\n providerKey: row.providerKey,\n bundleId: row.bundleId,\n author: row.author,\n company: row.company,\n version: row.version,\n hasCredentials: row.hasCredentials,\n isEnabled: row.isEnabled,\n apiVersion: row.apiVersion,\n healthStatus: row.healthStatus,\n lastHealthCheckedAt: row.lastHealthCheckedAt,\n lastHealthLatencyMs: row.lastHealthLatencyMs,\n enabledAt: row.enabledAt,\n stateUpdatedAt: row.stateUpdatedAt,\n analytics: analytics ?? {\n lastActivityAt: null,\n totalCount: 0,\n errorCount: 0,\n errorRate: 0,\n dailyCounts: Array.from({ length: 30 }, () => 0),\n },\n }\n })\n\n const bundles = getAllBundles().map((bundle) => {\n const bundleIntegrations = filtered.filter((row) => row.bundleId === bundle.id)\n const enabledCount = bundleIntegrations.reduce((count, integration) => count + (integration.isEnabled ? 1 : 0), 0)\n\n return {\n id: bundle.id,\n title: bundle.title,\n description: bundle.description,\n icon: bundle.icon ?? null,\n integrationCount: bundleIntegrations.length,\n enabledCount,\n }\n })\n\n return finalizeIntegrationsReadResponse({\n routePath: integrationApiRoutePaths.list,\n request: req,\n auth,\n container,\n interceptorRequest: beforeInterceptors.request,\n beforeMetadata: beforeInterceptors.metadataByInterceptor,\n enrich: {\n targetEntity: 'integrations.integration',\n listKeys: ['items'],\n },\n body: {\n items,\n bundles,\n total,\n page: safePage,\n pageSize,\n totalPages,\n },\n })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,6CAAsE;AAG/E,SAAS,+BAA+B,qCAAqC;AAE7E,SAAS,eAAe,0BAA0B;AAClD,SAAS,mCAAmC;AAC5C,SAAS,8BAA8B,sCAAsC;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEO,MAAM,UAAU,6BAA6B;AAAA,EAClD,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,aAAa;AACf,CAAC;AAED,MAAM,mBAAmE;AAAA,EACvE,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAChB;AAEA,SAAS,mBACP,aACA,iBACS;AACT,MAAI,CAAC,gBAAiB,QAAO;AAC7B,MAAI,YAAY,MAAM,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AACtE,MAAI,YAAY,aAAa,YAAY,EAAE,SAAS,eAAe,EAAG,QAAO;AAC7E,QAAM,OAAO,YAAY,QAAQ,CAAC;AAClC,SAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,EAAE,SAAS,eAAe,CAAC;AACvE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,cAAc,4BAA4B,UAAU,OAAO,YAAY,IAAI,aAAa,QAAQ,CAAC,CAAC;AACxG,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,SAAS,YAAY,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC5G;AACA,QAAM,QAAQ,YAAY;AAE1B,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,MAAM,sCAAsC;AAAA,IACrE,WAAW,yBAAyB;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,mBAAmB,IAAI;AAC1B,WAAO,aAAa,KAAK,mBAAmB,MAAM,EAAE,QAAQ,mBAAmB,WAAW,CAAC;AAAA,EAC7F;AACA,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAChE,QAAM,aAAa,UAAU,QAAQ,uBAAuB;AAE5D,QAAM,QAAQ,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAmB;AACxF,QAAM,eAAe,MAAM,GAAG,KAAK,EAAE,YAAY,KAAK;AA2BtD,QAAM,WAAsB,MAAM,QAAQ;AAAA,IACxC,mBAAmB,EAAE,IAAI,OAAO,gBAAgB;AAC9C,YAAM,CAAC,qBAAqB,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QACrD,mBAAmB,QAAQ,YAAY,IAAI,KAAK,EAAE,MAAM,CAAC,QAAQ;AAC/D,cAAI,eAAe,sCAAuC,QAAO;AACjE,gBAAM;AAAA,QACR,CAAC;AAAA,QACD,aAAa,aAAa,YAAY,IAAI,KAAK;AAAA,MACjD,CAAC;AAED,YAAM,iBACJ,uBAAuB,QAAQ,OAAO,KAAK,mBAAmB,EAAE,SAAS;AAC3E,YAAM,eAAe,8BAA8B,YAAY,EAAE;AACjE,YAAM,iBAAiB,QAAQ,cAAc,OAAO;AACpD,YAAM,eAAe,8BAA8B;AAAA,QACjD;AAAA,QACA;AAAA,QACA,kBAAkB,MAAM;AAAA,QACxB,qBAAqB,MAAM;AAAA,MAC7B,CAAC;AAED,YAAM,cAAc,MAAM,WAAW,QAAQ,KAAK;AAElD,aAAO;AAAA,QACL,IAAI,YAAY;AAAA,QAChB,OAAO,YAAY;AAAA,QACnB,aAAa,YAAY,eAAe;AAAA,QACxC,UAAU,YAAY,YAAY;AAAA,QAClC,MAAM,YAAY,QAAQ,CAAC;AAAA,QAC3B,KAAK,YAAY,OAAO;AAAA,QACxB,aAAa,YAAY,eAAe;AAAA,QACxC,UAAU,YAAY,YAAY;AAAA,QAClC,QAAQ,YAAY,UAAU;AAAA,QAC9B,SAAS,YAAY,WAAW;AAAA,QAChC,SAAS,YAAY,WAAW;AAAA,QAChC;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,YAAY,MAAM;AAAA,QAClB;AAAA,QACA,qBAAqB,MAAM,qBAAqB,YAAY,KAAK;AAAA,QACjE,qBAAqB,MAAM;AAAA,QAC3B,WAAW,MAAM,WAAW,YAAY,KAAK;AAAA,QAC7C,gBAAgB,MAAM,WAAW,YAAY,KAAK;AAAA,QAClD,iBAAiB;AAAA,QACjB,WAAW,YAAY,MAAM,YAAY;AAAA,QACzC,eAAe,YAAY,YAAY,IAAI,YAAY;AAAA,MACzD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,WAAW,SAAS,OAAO,CAAC,QAAQ;AAAA,IACtC,EAAE,OAAO,IAAI,OAAO,aAAa,IAAI,eAAe,QAAW,MAAM,IAAI,KAAK;AAAA,IAC9E;AAAA,EACF,CAAC;AAED,MAAI,MAAM,UAAU;AAClB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,MAAM,QAAQ;AAAA,EACrE;AACA,MAAI,MAAM,UAAU;AAClB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,MAAM,QAAQ;AAAA,EACrE;AACA,MAAI,MAAM,cAAc,QAAW;AACjC,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,cAAc,MAAM,SAAS;AAAA,EACvE;AACA,MAAI,MAAM,cAAc;AACtB,eAAW,SAAS,OAAO,CAAC,QAAQ,IAAI,iBAAiB,MAAM,YAAY;AAAA,EAC7E;AAEA,QAAM,UAAU,MAAM,QAAQ;AAC9B,QAAM,YAAY,MAAM,UAAU,SAAS,KAAK;AAEhD,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,MAAM,SAAS;AAChD,QAAI,MAAM;AACV,QAAI,YAAY,SAAS;AACvB,YAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IACnD,WAAW,YAAY,YAAY;AACjC,YAAM,KAAK,aAAa,cAAc,KAAK,YAAY;AACvD,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE,WAAW,YAAY,aAAa;AAClC,YAAM,KAAK,kBAAkB,KAAK;AAClC,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE,WAAW,YAAY,gBAAgB;AACrC,YAAM,iBAAiB,KAAK,YAAY,IAAI,iBAAiB,KAAK,YAAY;AAC9E,UAAI,QAAQ,EAAG,OAAM,KAAK,UAAU,cAAc,KAAK,SAAS;AAAA,IAClE;AACA,WAAO,MAAM;AAAA,EACf,CAAC;AAED,QAAM,QAAQ,OAAO;AACrB,QAAM,WAAW,MAAM;AACvB,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,QAAQ,KAAK,CAAC;AAC/D,QAAM,WAAW,KAAK,IAAI,MAAM,UAAU;AAC1C,QAAM,UAAU,WAAW,KAAK;AAChC,QAAM,YAAY,OAAO,MAAM,QAAQ,SAAS,QAAQ;AAExD,QAAM,eAAe,MAAM,WAAW;AAAA,IACpC,UAAU,IAAI,CAAC,QAAQ,IAAI,EAAE;AAAA,IAC7B;AAAA,IACA;AAAA,EACF;AAEA,QAAM,QAAQ,UAAU,IAAI,CAAC,QAAQ;AACnC,UAAM,YAAY,aAAa,IAAI,IAAI,EAAE;AACzC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,SAAS,IAAI;AAAA,MACb,SAAS,IAAI;AAAA,MACb,gBAAgB,IAAI;AAAA,MACpB,WAAW,IAAI;AAAA,MACf,YAAY,IAAI;AAAA,MAChB,cAAc,IAAI;AAAA,MAClB,qBAAqB,IAAI;AAAA,MACzB,qBAAqB,IAAI;AAAA,MACzB,WAAW,IAAI;AAAA,MACf,gBAAgB,IAAI;AAAA,MACpB,WAAW,aAAa;AAAA,QACtB,gBAAgB;AAAA,QAChB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,aAAa,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,MAAM,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,UAAU,cAAc,EAAE,IAAI,CAAC,WAAW;AAC9C,UAAM,qBAAqB,SAAS,OAAO,CAAC,QAAQ,IAAI,aAAa,OAAO,EAAE;AAC9E,UAAM,eAAe,mBAAmB,OAAO,CAAC,OAAO,gBAAgB,SAAS,YAAY,YAAY,IAAI,IAAI,CAAC;AAEjH,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,MAAM,OAAO,QAAQ;AAAA,MACrB,kBAAkB,mBAAmB;AAAA,MACrC;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,iCAAiC;AAAA,IACtC,WAAW,yBAAyB;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,oBAAoB,mBAAmB;AAAA,IACvC,gBAAgB,mBAAmB;AAAA,IACnC,QAAQ;AAAA,MACN,cAAc;AAAA,MACd,UAAU,CAAC,OAAO;AAAA,IACpB;AAAA,IACA,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -24,7 +24,8 @@ import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
|
|
|
24
24
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
25
25
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
|
|
26
26
|
import { JsonDisplay } from "@open-mercato/ui/backend/JsonDisplay";
|
|
27
|
-
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
27
|
+
import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
28
|
+
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
28
29
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
29
30
|
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
30
31
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -281,6 +282,7 @@ function IntegrationDetailPage({ params }) {
|
|
|
281
282
|
const [error, setError] = React.useState(null);
|
|
282
283
|
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
283
284
|
const [credValues, setCredValues] = React.useState({});
|
|
285
|
+
const [credentialsUpdatedAt, setCredentialsUpdatedAt] = React.useState(null);
|
|
284
286
|
const [credentialsFormKey, setCredentialsFormKey] = React.useState(0);
|
|
285
287
|
const [isSavingCredentials, setIsSavingCredentials] = React.useState(false);
|
|
286
288
|
const [logs, setLogs] = React.useState([]);
|
|
@@ -339,6 +341,9 @@ function IntegrationDetailPage({ params }) {
|
|
|
339
341
|
void 0,
|
|
340
342
|
{ fallback: null }
|
|
341
343
|
);
|
|
344
|
+
if (call.ok && call.result) {
|
|
345
|
+
setCredentialsUpdatedAt(call.result.updatedAt ?? null);
|
|
346
|
+
}
|
|
342
347
|
if (call.ok && call.result?.credentials) {
|
|
343
348
|
const next = { ...call.result.credentials };
|
|
344
349
|
if (currentIntegrationId === "storage_s3") {
|
|
@@ -509,11 +514,14 @@ function IntegrationDetailPage({ params }) {
|
|
|
509
514
|
const call = await runMutationWithContext({
|
|
510
515
|
actionId: "toggle-state",
|
|
511
516
|
mutationPayload: { integrationId: currentIntegrationId, isEnabled: enabled },
|
|
512
|
-
operation: () =>
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
operation: () => withScopedApiRequestHeaders(
|
|
518
|
+
buildOptimisticLockHeader(detail?.state.updatedAt),
|
|
519
|
+
() => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/state`, {
|
|
520
|
+
method: "PUT",
|
|
521
|
+
headers: { "Content-Type": "application/json" },
|
|
522
|
+
body: JSON.stringify({ isEnabled: enabled })
|
|
523
|
+
}, { fallback: null })
|
|
524
|
+
)
|
|
517
525
|
});
|
|
518
526
|
if (call.ok) {
|
|
519
527
|
setDetail((prev) => prev ? {
|
|
@@ -525,6 +533,7 @@ function IntegrationDetailPage({ params }) {
|
|
|
525
533
|
}
|
|
526
534
|
} : prev);
|
|
527
535
|
flash(t("integrations.detail.stateUpdated"), "success");
|
|
536
|
+
void refreshDetail();
|
|
528
537
|
} else {
|
|
529
538
|
flash(t("integrations.detail.stateError"), "error");
|
|
530
539
|
}
|
|
@@ -533,7 +542,7 @@ function IntegrationDetailPage({ params }) {
|
|
|
533
542
|
} finally {
|
|
534
543
|
setIsTogglingState(false);
|
|
535
544
|
}
|
|
536
|
-
}, [resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
545
|
+
}, [detail?.state.updatedAt, refreshDetail, resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
537
546
|
const handleSaveCredentials = React.useCallback(async (values) => {
|
|
538
547
|
const currentIntegrationId = resolveCurrentIntegrationId();
|
|
539
548
|
if (!currentIntegrationId) return;
|
|
@@ -556,16 +565,20 @@ function IntegrationDetailPage({ params }) {
|
|
|
556
565
|
actionId: "save-credentials",
|
|
557
566
|
tabId: "credentials",
|
|
558
567
|
mutationPayload: { integrationId: currentIntegrationId, credentials: sanitizedValues },
|
|
559
|
-
operation: () =>
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
568
|
+
operation: () => withScopedApiRequestHeaders(
|
|
569
|
+
buildOptimisticLockHeader(credentialsUpdatedAt),
|
|
570
|
+
() => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/credentials`, {
|
|
571
|
+
method: "PUT",
|
|
572
|
+
headers: { "Content-Type": "application/json" },
|
|
573
|
+
body: JSON.stringify({ credentials: sanitizedValues })
|
|
574
|
+
}, { fallback: null })
|
|
575
|
+
)
|
|
564
576
|
});
|
|
565
577
|
if (call.ok) {
|
|
566
578
|
setCredValues(sanitizedValues);
|
|
567
579
|
setCredentialsFormKey((current) => current + 1);
|
|
568
580
|
flash(t("integrations.detail.credentials.saved"), "success");
|
|
581
|
+
void loadCredentials();
|
|
569
582
|
return;
|
|
570
583
|
}
|
|
571
584
|
const result = call.result;
|
|
@@ -577,7 +590,7 @@ function IntegrationDetailPage({ params }) {
|
|
|
577
590
|
} finally {
|
|
578
591
|
setIsSavingCredentials(false);
|
|
579
592
|
}
|
|
580
|
-
}, [resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
593
|
+
}, [credentialsUpdatedAt, loadCredentials, resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
581
594
|
const handleVersionChange = React.useCallback(async (version) => {
|
|
582
595
|
const currentIntegrationId = resolveCurrentIntegrationId();
|
|
583
596
|
if (!currentIntegrationId) return;
|
|
@@ -586,22 +599,26 @@ function IntegrationDetailPage({ params }) {
|
|
|
586
599
|
actionId: "change-version",
|
|
587
600
|
tabId: "version",
|
|
588
601
|
mutationPayload: { integrationId: currentIntegrationId, apiVersion: version },
|
|
589
|
-
operation: () =>
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
602
|
+
operation: () => withScopedApiRequestHeaders(
|
|
603
|
+
buildOptimisticLockHeader(detail?.state.updatedAt),
|
|
604
|
+
() => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/version`, {
|
|
605
|
+
method: "PUT",
|
|
606
|
+
headers: { "Content-Type": "application/json" },
|
|
607
|
+
body: JSON.stringify({ apiVersion: version })
|
|
608
|
+
}, { fallback: null })
|
|
609
|
+
)
|
|
594
610
|
});
|
|
595
611
|
if (call.ok) {
|
|
596
612
|
setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev);
|
|
597
613
|
flash(t("integrations.detail.version.saved"), "success");
|
|
614
|
+
void refreshDetail();
|
|
598
615
|
} else {
|
|
599
616
|
flash(t("integrations.detail.version.saveError"), "error");
|
|
600
617
|
}
|
|
601
618
|
} catch {
|
|
602
619
|
flash(t("integrations.detail.version.saveError"), "error");
|
|
603
620
|
}
|
|
604
|
-
}, [resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
621
|
+
}, [detail?.state.updatedAt, refreshDetail, resolveCurrentIntegrationId, runMutationWithContext, t]);
|
|
605
622
|
const handleHealthCheck = React.useCallback(async () => {
|
|
606
623
|
const currentIntegrationId = resolveCurrentIntegrationId();
|
|
607
624
|
if (!currentIntegrationId) return;
|