@open-mercato/core 0.4.6-develop-a88276bc52 → 0.4.6-develop-806a2ed6b9

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 (226) hide show
  1. package/AGENTS.md +10 -0
  2. package/dist/generated/entities/integration_credentials/index.js +19 -0
  3. package/dist/generated/entities/integration_credentials/index.js.map +7 -0
  4. package/dist/generated/entities/integration_log/index.js +27 -0
  5. package/dist/generated/entities/integration_log/index.js.map +7 -0
  6. package/dist/generated/entities/integration_state/index.js +27 -0
  7. package/dist/generated/entities/integration_state/index.js.map +7 -0
  8. package/dist/generated/entities/sync_cursor/index.js +19 -0
  9. package/dist/generated/entities/sync_cursor/index.js.map +7 -0
  10. package/dist/generated/entities/sync_external_id_mapping/index.js +27 -0
  11. package/dist/generated/entities/sync_external_id_mapping/index.js.map +7 -0
  12. package/dist/generated/entities/sync_mapping/index.js +19 -0
  13. package/dist/generated/entities/sync_mapping/index.js.map +7 -0
  14. package/dist/generated/entities/sync_run/index.js +45 -0
  15. package/dist/generated/entities/sync_run/index.js.map +7 -0
  16. package/dist/generated/entities/sync_schedule/index.js +35 -0
  17. package/dist/generated/entities/sync_schedule/index.js.map +7 -0
  18. package/dist/generated/entities.ids.generated.js +14 -0
  19. package/dist/generated/entities.ids.generated.js.map +2 -2
  20. package/dist/generated/entity-fields-registry.js +16 -0
  21. package/dist/generated/entity-fields-registry.js.map +2 -2
  22. package/dist/modules/data_sync/acl.js +11 -0
  23. package/dist/modules/data_sync/acl.js.map +7 -0
  24. package/dist/modules/data_sync/api/mappings/[id]/route.js +137 -0
  25. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +7 -0
  26. package/dist/modules/data_sync/api/mappings/route.js +132 -0
  27. package/dist/modules/data_sync/api/mappings/route.js.map +7 -0
  28. package/dist/modules/data_sync/api/run.js +87 -0
  29. package/dist/modules/data_sync/api/run.js.map +7 -0
  30. package/dist/modules/data_sync/api/runs/[id]/cancel.js +49 -0
  31. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +7 -0
  32. package/dist/modules/data_sync/api/runs/[id]/retry.js +93 -0
  33. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +7 -0
  34. package/dist/modules/data_sync/api/runs/[id]/route.js +69 -0
  35. package/dist/modules/data_sync/api/runs/[id]/route.js.map +7 -0
  36. package/dist/modules/data_sync/api/runs.js +66 -0
  37. package/dist/modules/data_sync/api/runs.js.map +7 -0
  38. package/dist/modules/data_sync/api/validate.js +66 -0
  39. package/dist/modules/data_sync/api/validate.js.map +7 -0
  40. package/dist/modules/data_sync/backend/data-sync/page.js +216 -0
  41. package/dist/modules/data_sync/backend/data-sync/page.js.map +7 -0
  42. package/dist/modules/data_sync/backend/data-sync/page.meta.js +25 -0
  43. package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +7 -0
  44. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +178 -0
  45. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +7 -0
  46. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js +14 -0
  47. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.meta.js.map +7 -0
  48. package/dist/modules/data_sync/data/entities.js +228 -0
  49. package/dist/modules/data_sync/data/entities.js.map +7 -0
  50. package/dist/modules/data_sync/data/validators.js +32 -0
  51. package/dist/modules/data_sync/data/validators.js.map +7 -0
  52. package/dist/modules/data_sync/di.js +26 -0
  53. package/dist/modules/data_sync/di.js.map +7 -0
  54. package/dist/modules/data_sync/events.js +16 -0
  55. package/dist/modules/data_sync/events.js.map +7 -0
  56. package/dist/modules/data_sync/index.js +9 -0
  57. package/dist/modules/data_sync/index.js.map +7 -0
  58. package/dist/modules/data_sync/lib/adapter-registry.js +16 -0
  59. package/dist/modules/data_sync/lib/adapter-registry.js.map +7 -0
  60. package/dist/modules/data_sync/lib/adapter.js +1 -0
  61. package/dist/modules/data_sync/lib/adapter.js.map +7 -0
  62. package/dist/modules/data_sync/lib/id-mapping.js +79 -0
  63. package/dist/modules/data_sync/lib/id-mapping.js.map +7 -0
  64. package/dist/modules/data_sync/lib/queue.js +17 -0
  65. package/dist/modules/data_sync/lib/queue.js.map +7 -0
  66. package/dist/modules/data_sync/lib/sync-engine.js +309 -0
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +7 -0
  68. package/dist/modules/data_sync/lib/sync-run-service.js +148 -0
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +7 -0
  70. package/dist/modules/data_sync/migrations/Migration20260304113737.js +17 -0
  71. package/dist/modules/data_sync/migrations/Migration20260304113737.js.map +7 -0
  72. package/dist/modules/data_sync/setup.js +13 -0
  73. package/dist/modules/data_sync/setup.js.map +7 -0
  74. package/dist/modules/data_sync/workers/sync-export.js +14 -0
  75. package/dist/modules/data_sync/workers/sync-export.js.map +7 -0
  76. package/dist/modules/data_sync/workers/sync-import.js +14 -0
  77. package/dist/modules/data_sync/workers/sync-import.js.map +7 -0
  78. package/dist/modules/data_sync/workers/sync-scheduled.js +63 -0
  79. package/dist/modules/data_sync/workers/sync-scheduled.js.map +7 -0
  80. package/dist/modules/entities/lib/encryptionDefaults.js +4 -0
  81. package/dist/modules/entities/lib/encryptionDefaults.js.map +2 -2
  82. package/dist/modules/integrations/acl.js +4 -1
  83. package/dist/modules/integrations/acl.js.map +2 -2
  84. package/dist/modules/integrations/api/[id]/credentials/route.js +127 -0
  85. package/dist/modules/integrations/api/[id]/credentials/route.js.map +7 -0
  86. package/dist/modules/integrations/api/[id]/health/route.js +46 -0
  87. package/dist/modules/integrations/api/[id]/health/route.js.map +7 -0
  88. package/dist/modules/integrations/api/[id]/route.js +65 -0
  89. package/dist/modules/integrations/api/[id]/route.js.map +7 -0
  90. package/dist/modules/integrations/api/[id]/state/route.js +109 -0
  91. package/dist/modules/integrations/api/[id]/state/route.js.map +7 -0
  92. package/dist/modules/integrations/api/[id]/version/route.js +117 -0
  93. package/dist/modules/integrations/api/[id]/version/route.js.map +7 -0
  94. package/dist/modules/integrations/api/guards.js +31 -0
  95. package/dist/modules/integrations/api/guards.js.map +7 -0
  96. package/dist/modules/integrations/api/logs/route.js +60 -0
  97. package/dist/modules/integrations/api/logs/route.js.map +7 -0
  98. package/dist/modules/integrations/api/openapi.js +25 -0
  99. package/dist/modules/integrations/api/openapi.js.map +7 -0
  100. package/dist/modules/integrations/api/route.js +68 -0
  101. package/dist/modules/integrations/api/route.js.map +7 -0
  102. package/dist/modules/integrations/backend/integrations/[id]/page.js +313 -0
  103. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +7 -0
  104. package/dist/modules/integrations/backend/integrations/[id]/page.meta.js +15 -0
  105. package/dist/modules/integrations/backend/integrations/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +189 -0
  107. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +7 -0
  108. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js +15 -0
  109. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.meta.js.map +7 -0
  110. package/dist/modules/integrations/backend/integrations/page.js +212 -0
  111. package/dist/modules/integrations/backend/integrations/page.js.map +7 -0
  112. package/dist/modules/integrations/backend/integrations/page.meta.js +22 -0
  113. package/dist/modules/integrations/backend/integrations/page.meta.js.map +7 -0
  114. package/dist/modules/integrations/data/enrichers.js +27 -12
  115. package/dist/modules/integrations/data/enrichers.js.map +2 -2
  116. package/dist/modules/integrations/data/entities.js +136 -1
  117. package/dist/modules/integrations/data/entities.js.map +2 -2
  118. package/dist/modules/integrations/data/validators.js +36 -0
  119. package/dist/modules/integrations/data/validators.js.map +7 -0
  120. package/dist/modules/integrations/di.js +24 -0
  121. package/dist/modules/integrations/di.js.map +7 -0
  122. package/dist/modules/integrations/events.js +19 -0
  123. package/dist/modules/integrations/events.js.map +7 -0
  124. package/dist/modules/integrations/lib/credentials-service.js +159 -0
  125. package/dist/modules/integrations/lib/credentials-service.js.map +7 -0
  126. package/dist/modules/integrations/lib/health-service.js +37 -0
  127. package/dist/modules/integrations/lib/health-service.js.map +7 -0
  128. package/dist/modules/integrations/lib/log-service.js +66 -0
  129. package/dist/modules/integrations/lib/log-service.js.map +7 -0
  130. package/dist/modules/integrations/lib/registry-service.js +33 -0
  131. package/dist/modules/integrations/lib/registry-service.js.map +7 -0
  132. package/dist/modules/integrations/lib/state-service.js +55 -0
  133. package/dist/modules/integrations/lib/state-service.js.map +7 -0
  134. package/dist/modules/integrations/lib/types.js +1 -0
  135. package/dist/modules/integrations/lib/types.js.map +7 -0
  136. package/dist/modules/integrations/migrations/Migration20260304113737.js +19 -0
  137. package/dist/modules/integrations/migrations/Migration20260304113737.js.map +7 -0
  138. package/dist/modules/integrations/setup.js +2 -2
  139. package/dist/modules/integrations/setup.js.map +2 -2
  140. package/dist/modules/integrations/widgets/injection-table.js.map +1 -1
  141. package/dist/modules/integrations/workers/log-pruner.js +18 -0
  142. package/dist/modules/integrations/workers/log-pruner.js.map +7 -0
  143. package/generated/entities/integration_credentials/index.ts +8 -0
  144. package/generated/entities/integration_log/index.ts +12 -0
  145. package/generated/entities/integration_state/index.ts +12 -0
  146. package/generated/entities/sync_cursor/index.ts +8 -0
  147. package/generated/entities/sync_external_id_mapping/index.ts +12 -0
  148. package/generated/entities/sync_mapping/index.ts +8 -0
  149. package/generated/entities/sync_run/index.ts +21 -0
  150. package/generated/entities/sync_schedule/index.ts +16 -0
  151. package/generated/entities.ids.generated.ts +14 -0
  152. package/generated/entity-fields-registry.ts +16 -0
  153. package/package.json +2 -2
  154. package/src/modules/data_sync/AGENTS.md +157 -0
  155. package/src/modules/data_sync/acl.ts +7 -0
  156. package/src/modules/data_sync/api/mappings/[id]/route.ts +158 -0
  157. package/src/modules/data_sync/api/mappings/route.ts +144 -0
  158. package/src/modules/data_sync/api/run.ts +97 -0
  159. package/src/modules/data_sync/api/runs/[id]/cancel.ts +57 -0
  160. package/src/modules/data_sync/api/runs/[id]/retry.ts +108 -0
  161. package/src/modules/data_sync/api/runs/[id]/route.ts +81 -0
  162. package/src/modules/data_sync/api/runs.ts +69 -0
  163. package/src/modules/data_sync/api/validate.ts +73 -0
  164. package/src/modules/data_sync/backend/data-sync/page.meta.ts +21 -0
  165. package/src/modules/data_sync/backend/data-sync/page.tsx +244 -0
  166. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.meta.ts +10 -0
  167. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +278 -0
  168. package/src/modules/data_sync/data/entities.ts +180 -0
  169. package/src/modules/data_sync/data/validators.ts +35 -0
  170. package/src/modules/data_sync/di.ts +38 -0
  171. package/src/modules/data_sync/events.ts +12 -0
  172. package/src/modules/data_sync/i18n/de.json +48 -0
  173. package/src/modules/data_sync/i18n/en.json +48 -0
  174. package/src/modules/data_sync/i18n/es.json +48 -0
  175. package/src/modules/data_sync/i18n/pl.json +48 -0
  176. package/src/modules/data_sync/index.ts +5 -0
  177. package/src/modules/data_sync/lib/adapter-registry.ts +15 -0
  178. package/src/modules/data_sync/lib/adapter.ts +90 -0
  179. package/src/modules/data_sync/lib/id-mapping.ts +95 -0
  180. package/src/modules/data_sync/lib/queue.ts +19 -0
  181. package/src/modules/data_sync/lib/sync-engine.ts +375 -0
  182. package/src/modules/data_sync/lib/sync-run-service.ts +187 -0
  183. package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +653 -0
  184. package/src/modules/data_sync/migrations/Migration20260304113737.ts +19 -0
  185. package/src/modules/data_sync/setup.ts +11 -0
  186. package/src/modules/data_sync/workers/sync-export.ts +27 -0
  187. package/src/modules/data_sync/workers/sync-import.ts +27 -0
  188. package/src/modules/data_sync/workers/sync-scheduled.ts +84 -0
  189. package/src/modules/entities/lib/encryptionDefaults.ts +4 -0
  190. package/src/modules/integrations/AGENTS.md +160 -0
  191. package/src/modules/integrations/acl.ts +3 -0
  192. package/src/modules/integrations/api/[id]/credentials/route.ts +142 -0
  193. package/src/modules/integrations/api/[id]/health/route.ts +53 -0
  194. package/src/modules/integrations/api/[id]/route.ts +76 -0
  195. package/src/modules/integrations/api/[id]/state/route.ts +121 -0
  196. package/src/modules/integrations/api/[id]/version/route.ts +132 -0
  197. package/src/modules/integrations/api/guards.ts +59 -0
  198. package/src/modules/integrations/api/logs/route.ts +63 -0
  199. package/src/modules/integrations/api/openapi.ts +22 -0
  200. package/src/modules/integrations/api/route.ts +73 -0
  201. package/src/modules/integrations/backend/integrations/[id]/page.meta.ts +11 -0
  202. package/src/modules/integrations/backend/integrations/[id]/page.tsx +424 -0
  203. package/src/modules/integrations/backend/integrations/bundle/[id]/page.meta.ts +11 -0
  204. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +249 -0
  205. package/src/modules/integrations/backend/integrations/page.meta.ts +18 -0
  206. package/src/modules/integrations/backend/integrations/page.tsx +296 -0
  207. package/src/modules/integrations/data/enrichers.ts +35 -18
  208. package/src/modules/integrations/data/entities.ts +114 -5
  209. package/src/modules/integrations/data/validators.ts +41 -0
  210. package/src/modules/integrations/di.ts +31 -0
  211. package/src/modules/integrations/events.ts +17 -0
  212. package/src/modules/integrations/i18n/de.json +70 -0
  213. package/src/modules/integrations/i18n/en.json +70 -0
  214. package/src/modules/integrations/i18n/es.json +70 -0
  215. package/src/modules/integrations/i18n/pl.json +70 -0
  216. package/src/modules/integrations/lib/credentials-service.ts +204 -0
  217. package/src/modules/integrations/lib/health-service.ts +59 -0
  218. package/src/modules/integrations/lib/log-service.ts +84 -0
  219. package/src/modules/integrations/lib/registry-service.ts +42 -0
  220. package/src/modules/integrations/lib/state-service.ts +64 -0
  221. package/src/modules/integrations/lib/types.ts +4 -0
  222. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +582 -0
  223. package/src/modules/integrations/migrations/Migration20260304113737.ts +21 -0
  224. package/src/modules/integrations/setup.ts +2 -2
  225. package/src/modules/integrations/widgets/injection-table.ts +1 -1
  226. package/src/modules/integrations/workers/log-pruner.ts +30 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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 type { CredentialsService } from '../lib/credentials-service'\nimport type { IntegrationStateService } from '../lib/state-service'\nimport { getAllBundles, getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'\nimport { buildIntegrationsCrudOpenApi, createPagedListResponseSchema, integrationInfoSchema } from './openapi'\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: createPagedListResponseSchema(integrationInfoSchema),\n querySchema: undefined,\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 container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const stateService = container.resolve('integrationStateService') as IntegrationStateService\n\n const rows = await Promise.all(\n getAllIntegrations().map(async (integration) => {\n const [resolvedCredentials, state] = await Promise.all([\n credentialsService.resolve(integration.id, { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }),\n stateService.get(integration.id, { organizationId: auth.orgId as string, tenantId: auth.tenantId as string }),\n ])\n\n return {\n id: integration.id,\n title: integration.title,\n category: integration.category ?? null,\n hub: integration.hub ?? null,\n providerKey: integration.providerKey ?? null,\n bundleId: integration.bundleId ?? null,\n hasCredentials: Boolean(resolvedCredentials),\n isEnabled: state?.isEnabled ?? true,\n apiVersion: state?.apiVersion ?? null,\n }\n }),\n )\n\n const bundles = getAllBundles().map((bundle) => {\n const bundleIntegrations = rows.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 NextResponse.json({\n items: rows,\n bundles,\n total: rows.length,\n page: 1,\n pageSize: 100,\n totalPages: 1,\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,SAAS,eAAe,0BAA0B;AAClD,SAAS,8BAA8B,+BAA+B,6BAA6B;AAE5F,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,8BAA8B,qBAAqB;AAAA,EACvE,aAAa;AACf,CAAC;AAED,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,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,eAAe,UAAU,QAAQ,yBAAyB;AAEhE,QAAM,OAAO,MAAM,QAAQ;AAAA,IACzB,mBAAmB,EAAE,IAAI,OAAO,gBAAgB;AAC9C,YAAM,CAAC,qBAAqB,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QACrD,mBAAmB,QAAQ,YAAY,IAAI,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAmB,CAAC;AAAA,QACtH,aAAa,IAAI,YAAY,IAAI,EAAE,gBAAgB,KAAK,OAAiB,UAAU,KAAK,SAAmB,CAAC;AAAA,MAC9G,CAAC;AAED,aAAO;AAAA,QACL,IAAI,YAAY;AAAA,QAChB,OAAO,YAAY;AAAA,QACnB,UAAU,YAAY,YAAY;AAAA,QAClC,KAAK,YAAY,OAAO;AAAA,QACxB,aAAa,YAAY,eAAe;AAAA,QACxC,UAAU,YAAY,YAAY;AAAA,QAClC,gBAAgB,QAAQ,mBAAmB;AAAA,QAC3C,WAAW,OAAO,aAAa;AAAA,QAC/B,YAAY,OAAO,cAAc;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,cAAc,EAAE,IAAI,CAAC,WAAW;AAC9C,UAAM,qBAAqB,KAAK,OAAO,CAAC,QAAQ,IAAI,aAAa,OAAO,EAAE;AAC1E,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,aAAa,KAAK;AAAA,IACvB,OAAO;AAAA,IACP;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,EACd,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,313 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useParams } from "next/navigation";
6
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
+ import { Card, CardHeader, CardTitle, CardContent } from "@open-mercato/ui/primitives/card";
8
+ import { Badge } from "@open-mercato/ui/primitives/badge";
9
+ import { Button } from "@open-mercato/ui/primitives/button";
10
+ import { Switch } from "@open-mercato/ui/primitives/switch";
11
+ import { Input } from "@open-mercato/ui/primitives/input";
12
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
13
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
14
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
15
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
16
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
17
+ import { LoadingMessage } from "@open-mercato/ui/backend/detail";
18
+ import { ErrorMessage } from "@open-mercato/ui/backend/detail";
19
+ const LOG_LEVEL_STYLES = {
20
+ info: "bg-blue-100 text-blue-800",
21
+ warn: "bg-yellow-100 text-yellow-800",
22
+ error: "bg-red-100 text-red-800"
23
+ };
24
+ const HEALTH_STATUS_STYLES = {
25
+ healthy: "bg-green-100 text-green-800",
26
+ degraded: "bg-yellow-100 text-yellow-800",
27
+ unhealthy: "bg-red-100 text-red-800"
28
+ };
29
+ function IntegrationDetailPage() {
30
+ const params = useParams();
31
+ const integrationId = params.id;
32
+ const t = useT();
33
+ const [detail, setDetail] = React.useState(null);
34
+ const [isLoading, setIsLoading] = React.useState(true);
35
+ const [error, setError] = React.useState(null);
36
+ const [credValues, setCredValues] = React.useState({});
37
+ const [isSavingCreds, setIsSavingCreds] = React.useState(false);
38
+ const [logs, setLogs] = React.useState([]);
39
+ const [logLevel, setLogLevel] = React.useState("");
40
+ const [isLoadingLogs, setIsLoadingLogs] = React.useState(false);
41
+ const [isCheckingHealth, setIsCheckingHealth] = React.useState(false);
42
+ const [isTogglingState, setIsTogglingState] = React.useState(false);
43
+ const loadDetail = React.useCallback(async () => {
44
+ setIsLoading(true);
45
+ setError(null);
46
+ const call = await apiCall(
47
+ `/api/integrations/${encodeURIComponent(integrationId)}`,
48
+ void 0,
49
+ { fallback: null }
50
+ );
51
+ if (!call.ok || !call.result) {
52
+ setError(t("integrations.detail.loadError"));
53
+ setIsLoading(false);
54
+ return;
55
+ }
56
+ setDetail(call.result);
57
+ setIsLoading(false);
58
+ }, [integrationId, t]);
59
+ const loadCredentials = React.useCallback(async () => {
60
+ const call = await apiCall(
61
+ `/api/integrations/${encodeURIComponent(integrationId)}/credentials`,
62
+ void 0,
63
+ { fallback: null }
64
+ );
65
+ if (call.ok && call.result?.credentials) {
66
+ setCredValues(call.result.credentials);
67
+ }
68
+ }, [integrationId]);
69
+ const loadLogs = React.useCallback(async () => {
70
+ setIsLoadingLogs(true);
71
+ const params2 = new URLSearchParams({ integrationId, pageSize: "50" });
72
+ if (logLevel) params2.set("level", logLevel);
73
+ const call = await apiCall(
74
+ `/api/integrations/logs?${params2.toString()}`,
75
+ void 0,
76
+ { fallback: { items: [] } }
77
+ );
78
+ if (call.ok && call.result) {
79
+ setLogs(call.result.items);
80
+ }
81
+ setIsLoadingLogs(false);
82
+ }, [integrationId, logLevel]);
83
+ React.useEffect(() => {
84
+ void loadDetail();
85
+ }, [loadDetail]);
86
+ React.useEffect(() => {
87
+ void loadCredentials();
88
+ }, [loadCredentials]);
89
+ React.useEffect(() => {
90
+ void loadLogs();
91
+ }, [loadLogs]);
92
+ const handleToggleState = React.useCallback(async (enabled) => {
93
+ setIsTogglingState(true);
94
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
95
+ method: "PUT",
96
+ headers: { "Content-Type": "application/json" },
97
+ body: JSON.stringify({ isEnabled: enabled })
98
+ }, { fallback: null });
99
+ if (call.ok) {
100
+ setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev);
101
+ flash(t("integrations.detail.stateUpdated"), "success");
102
+ } else {
103
+ flash(t("integrations.detail.stateError"), "error");
104
+ }
105
+ setIsTogglingState(false);
106
+ }, [integrationId, t]);
107
+ const handleSaveCredentials = React.useCallback(async () => {
108
+ setIsSavingCreds(true);
109
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/credentials`, {
110
+ method: "PUT",
111
+ headers: { "Content-Type": "application/json" },
112
+ body: JSON.stringify({ credentials: credValues })
113
+ }, { fallback: null });
114
+ if (call.ok) {
115
+ flash(t("integrations.detail.credentials.saved"), "success");
116
+ } else {
117
+ flash(t("integrations.detail.credentials.saveError"), "error");
118
+ }
119
+ setIsSavingCreds(false);
120
+ }, [integrationId, credValues, t]);
121
+ const handleVersionChange = React.useCallback(async (version) => {
122
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/version`, {
123
+ method: "PUT",
124
+ headers: { "Content-Type": "application/json" },
125
+ body: JSON.stringify({ apiVersion: version })
126
+ }, { fallback: null });
127
+ if (call.ok) {
128
+ setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev);
129
+ flash(t("integrations.detail.version.saved"), "success");
130
+ } else {
131
+ flash(t("integrations.detail.version.saveError"), "error");
132
+ }
133
+ }, [integrationId, t]);
134
+ const handleHealthCheck = React.useCallback(async () => {
135
+ setIsCheckingHealth(true);
136
+ const call = await apiCall(
137
+ `/api/integrations/${encodeURIComponent(integrationId)}/health`,
138
+ { method: "POST" },
139
+ { fallback: null }
140
+ );
141
+ if (call.ok && call.result) {
142
+ setDetail((prev) => prev ? {
143
+ ...prev,
144
+ state: {
145
+ ...prev.state,
146
+ lastHealthStatus: call.result.status,
147
+ lastHealthCheckedAt: call.result.checkedAt
148
+ }
149
+ } : prev);
150
+ } else {
151
+ flash(t("integrations.detail.health.checkError"), "error");
152
+ }
153
+ setIsCheckingHealth(false);
154
+ }, [integrationId, t]);
155
+ if (isLoading) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("integrations.detail.title") }) }) });
156
+ if (error || !detail) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("integrations.detail.loadError") }) }) });
157
+ const { integration, state } = detail;
158
+ const credFields = integration.credentials?.fields ?? detail.bundle?.credentials?.fields ?? [];
159
+ const hasVersions = Boolean(integration.apiVersions?.length);
160
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
161
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Link, { href: "/backend/integrations", className: "text-sm text-muted-foreground hover:underline", children: t("integrations.detail.back") }) }),
162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
163
+ /* @__PURE__ */ jsxs("div", { children: [
164
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: integration.title }),
165
+ integration.description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: integration.description }),
166
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 mt-2", children: [
167
+ integration.category && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: integration.category }),
168
+ integration.hub && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: integration.hub })
169
+ ] })
170
+ ] }),
171
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
172
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: state.isEnabled ? t("integrations.detail.enable") : t("integrations.detail.disable") }),
173
+ /* @__PURE__ */ jsx(
174
+ Switch,
175
+ {
176
+ checked: state.isEnabled,
177
+ disabled: isTogglingState,
178
+ onCheckedChange: (checked) => void handleToggleState(checked)
179
+ }
180
+ )
181
+ ] })
182
+ ] }),
183
+ /* @__PURE__ */ jsxs(Tabs, { defaultValue: "credentials", children: [
184
+ /* @__PURE__ */ jsxs(TabsList, { children: [
185
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "credentials", children: t("integrations.detail.tabs.credentials") }),
186
+ hasVersions && /* @__PURE__ */ jsx(TabsTrigger, { value: "version", children: t("integrations.detail.tabs.version") }),
187
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "health", children: t("integrations.detail.tabs.health") }),
188
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "logs", children: t("integrations.detail.tabs.logs") })
189
+ ] }),
190
+ /* @__PURE__ */ jsxs(TabsContent, { value: "credentials", className: "space-y-4 mt-4", children: [
191
+ detail.bundle && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800", children: t("integrations.detail.credentials.bundleShared", { bundle: detail.bundle.title }) }),
192
+ credFields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: t("integrations.detail.credentials.notConfigured") }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "pt-6 space-y-4", children: [
193
+ credFields.filter((f) => f.type !== "oauth" && f.type !== "ssh_keypair").map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
194
+ /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium", children: [
195
+ field.label,
196
+ field.required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
197
+ ] }),
198
+ field.type === "select" && field.options ? /* @__PURE__ */ jsxs(
199
+ "select",
200
+ {
201
+ className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm",
202
+ value: credValues[field.key] ?? "",
203
+ onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value })),
204
+ children: [
205
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
206
+ field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
207
+ ]
208
+ }
209
+ ) : field.type === "boolean" ? /* @__PURE__ */ jsx(
210
+ Switch,
211
+ {
212
+ checked: Boolean(credValues[field.key]),
213
+ onCheckedChange: (checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))
214
+ }
215
+ ) : /* @__PURE__ */ jsx(
216
+ Input,
217
+ {
218
+ type: field.type === "secret" ? "password" : "text",
219
+ placeholder: field.placeholder,
220
+ value: credValues[field.key] ?? "",
221
+ onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))
222
+ }
223
+ )
224
+ ] }, field.key)),
225
+ /* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => void handleSaveCredentials(), disabled: isSavingCreds, children: [
226
+ isSavingCreds ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
227
+ t("integrations.detail.credentials.save")
228
+ ] })
229
+ ] }) })
230
+ ] }),
231
+ hasVersions && /* @__PURE__ */ jsx(TabsContent, { value: "version", className: "space-y-4 mt-4", children: /* @__PURE__ */ jsxs(Card, { children: [
232
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.detail.version.select") }) }),
233
+ /* @__PURE__ */ jsx(CardContent, { className: "space-y-3", children: integration.apiVersions.map((v) => {
234
+ const isSelected = (state.apiVersion ?? integration.apiVersions.find((x) => x.status === "stable")?.id) === v.id;
235
+ return /* @__PURE__ */ jsxs(
236
+ "div",
237
+ {
238
+ className: `flex items-center justify-between rounded-lg border p-3 cursor-pointer transition-colors ${isSelected ? "border-primary bg-primary/5" : "hover:bg-muted/50"}`,
239
+ onClick: () => void handleVersionChange(v.id),
240
+ children: [
241
+ /* @__PURE__ */ jsxs("div", { children: [
242
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: v.label ?? v.id }),
243
+ /* @__PURE__ */ jsx(
244
+ Badge,
245
+ {
246
+ variant: v.status === "stable" ? "default" : v.status === "deprecated" ? "destructive" : "secondary",
247
+ className: "ml-2",
248
+ children: t(`integrations.detail.version.${v.status}`)
249
+ }
250
+ ),
251
+ v.status === "deprecated" && v.sunsetAt && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-2", children: t("integrations.detail.version.sunsetAt", { date: new Date(v.sunsetAt).toLocaleDateString() }) })
252
+ ] }),
253
+ isSelected && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("integrations.detail.version.current") })
254
+ ]
255
+ },
256
+ v.id
257
+ );
258
+ }) })
259
+ ] }) }),
260
+ /* @__PURE__ */ jsx(TabsContent, { value: "health", className: "space-y-4 mt-4", children: /* @__PURE__ */ jsxs(Card, { children: [
261
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
262
+ /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.detail.health.title") }),
263
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleHealthCheck(), disabled: isCheckingHealth, children: [
264
+ isCheckingHealth ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
265
+ isCheckingHealth ? t("integrations.detail.health.checking") : t("integrations.detail.health.check")
266
+ ] })
267
+ ] }) }),
268
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
269
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
270
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
271
+ t("integrations.detail.health.title"),
272
+ ":"
273
+ ] }),
274
+ state.lastHealthStatus ? /* @__PURE__ */ jsx(Badge, { className: HEALTH_STATUS_STYLES[state.lastHealthStatus] ?? "", children: t(`integrations.detail.health.${state.lastHealthStatus}`) }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("integrations.detail.health.unknown") })
275
+ ] }),
276
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: state.lastHealthCheckedAt ? t("integrations.detail.health.lastChecked", { date: new Date(state.lastHealthCheckedAt).toLocaleString() }) : t("integrations.detail.health.neverChecked") })
277
+ ] })
278
+ ] }) }),
279
+ /* @__PURE__ */ jsxs(TabsContent, { value: "logs", className: "space-y-4 mt-4", children: [
280
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxs(
281
+ "select",
282
+ {
283
+ className: "flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm",
284
+ value: logLevel,
285
+ onChange: (e) => setLogLevel(e.target.value),
286
+ children: [
287
+ /* @__PURE__ */ jsx("option", { value: "", children: t("integrations.detail.logs.level.all") }),
288
+ /* @__PURE__ */ jsx("option", { value: "info", children: t("integrations.detail.logs.level.info") }),
289
+ /* @__PURE__ */ jsx("option", { value: "warn", children: t("integrations.detail.logs.level.warn") }),
290
+ /* @__PURE__ */ jsx("option", { value: "error", children: t("integrations.detail.logs.level.error") })
291
+ ]
292
+ }
293
+ ) }),
294
+ isLoadingLogs ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) }) : logs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm py-4", children: t("integrations.detail.logs.empty") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
295
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
296
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.time") }),
297
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.level") }),
298
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.message") })
299
+ ] }) }),
300
+ /* @__PURE__ */ jsx("tbody", { children: logs.map((log) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
301
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground whitespace-nowrap", children: new Date(log.createdAt).toLocaleString() }),
302
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: LOG_LEVEL_STYLES[log.level] ?? "", children: log.level }) }),
303
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: log.message })
304
+ ] }, log.id)) })
305
+ ] }) })
306
+ ] })
307
+ ] })
308
+ ] }) });
309
+ }
310
+ export {
311
+ IntegrationDetailPage as default
312
+ };
313
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/integrations/backend/integrations/%5Bid%5D/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\ntype ApiVersion = {\n id: string\n label?: string\n status: 'stable' | 'deprecated' | 'experimental'\n sunsetAt?: string\n migrationGuide?: string\n}\n\ntype IntegrationDetail = {\n integration: {\n id: string\n title: string\n description?: string\n category?: string\n hub?: string\n bundleId?: string\n docsUrl?: string\n apiVersions?: ApiVersion[]\n credentials?: { fields: CredentialField[] }\n }\n bundle?: { id: string; title: string; credentials?: { fields: CredentialField[] } }\n state: {\n isEnabled: boolean\n apiVersion: string | null\n reauthRequired: boolean\n lastHealthStatus: string | null\n lastHealthCheckedAt: string | null\n }\n hasCredentials: boolean\n}\n\ntype LogEntry = {\n id: string\n level: 'info' | 'warn' | 'error'\n message: string\n createdAt: string\n code?: string\n}\n\nconst LOG_LEVEL_STYLES: Record<string, string> = {\n info: 'bg-blue-100 text-blue-800',\n warn: 'bg-yellow-100 text-yellow-800',\n error: 'bg-red-100 text-red-800',\n}\n\nconst HEALTH_STATUS_STYLES: Record<string, string> = {\n healthy: 'bg-green-100 text-green-800',\n degraded: 'bg-yellow-100 text-yellow-800',\n unhealthy: 'bg-red-100 text-red-800',\n}\n\nexport default function IntegrationDetailPage() {\n const params = useParams<{ id: string }>()\n const integrationId = params.id\n const t = useT()\n\n const [detail, setDetail] = React.useState<IntegrationDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n\n const [logs, setLogs] = React.useState<LogEntry[]>([])\n const [logLevel, setLogLevel] = React.useState<string>('')\n const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)\n\n const [isCheckingHealth, setIsCheckingHealth] = React.useState(false)\n const [isTogglingState, setIsTogglingState] = React.useState(false)\n\n const loadDetail = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n const call = await apiCall<IntegrationDetail>(\n `/api/integrations/${encodeURIComponent(integrationId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n setIsLoading(false)\n }, [integrationId, t])\n\n const loadCredentials = React.useCallback(async () => {\n const call = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (call.ok && call.result?.credentials) {\n setCredValues(call.result.credentials)\n }\n }, [integrationId])\n\n const loadLogs = React.useCallback(async () => {\n setIsLoadingLogs(true)\n const params = new URLSearchParams({ integrationId, pageSize: '50' })\n if (logLevel) params.set('level', logLevel)\n const call = await apiCall<{ items: LogEntry[] }>(\n `/api/integrations/logs?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (call.ok && call.result) {\n setLogs(call.result.items)\n }\n setIsLoadingLogs(false)\n }, [integrationId, logLevel])\n\n React.useEffect(() => { void loadDetail() }, [loadDetail])\n React.useEffect(() => { void loadCredentials() }, [loadCredentials])\n React.useEffect(() => { void loadLogs() }, [loadLogs])\n\n const handleToggleState = React.useCallback(async (enabled: boolean) => {\n setIsTogglingState(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev)\n flash(t('integrations.detail.stateUpdated'), 'success')\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setIsTogglingState(false)\n }, [integrationId, t])\n\n const handleSaveCredentials = React.useCallback(async () => {\n setIsSavingCreds(true)\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [integrationId, credValues, t])\n\n const handleVersionChange = React.useCallback(async (version: string) => {\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/version`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiVersion: version }),\n }, { fallback: null })\n if (call.ok) {\n setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev)\n flash(t('integrations.detail.version.saved'), 'success')\n } else {\n flash(t('integrations.detail.version.saveError'), 'error')\n }\n }, [integrationId, t])\n\n const handleHealthCheck = React.useCallback(async () => {\n setIsCheckingHealth(true)\n const call = await apiCall<{ status: string; checkedAt: string }>(\n `/api/integrations/${encodeURIComponent(integrationId)}/health`,\n { method: 'POST' },\n { fallback: null },\n )\n if (call.ok && call.result) {\n setDetail((prev) => prev ? {\n ...prev,\n state: {\n ...prev.state,\n lastHealthStatus: call.result!.status,\n lastHealthCheckedAt: call.result!.checkedAt,\n },\n } : prev)\n } else {\n flash(t('integrations.detail.health.checkError'), 'error')\n }\n setIsCheckingHealth(false)\n }, [integrationId, t])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.detail.title')} /></PageBody></Page>\n if (error || !detail) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const { integration, state } = detail\n const credFields = integration.credentials?.fields ?? detail.bundle?.credentials?.fields ?? []\n const hasVersions = Boolean(integration.apiVersions?.length)\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{integration.title}</h1>\n {integration.description && (\n <p className=\"text-muted-foreground mt-1\">{integration.description}</p>\n )}\n <div className=\"flex gap-2 mt-2\">\n {integration.category && <Badge variant=\"secondary\">{integration.category}</Badge>}\n {integration.hub && <Badge variant=\"outline\">{integration.hub}</Badge>}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm text-muted-foreground\">\n {state.isEnabled ? t('integrations.detail.enable') : t('integrations.detail.disable')}\n </span>\n <Switch\n checked={state.isEnabled}\n disabled={isTogglingState}\n onCheckedChange={(checked) => void handleToggleState(checked)}\n />\n </div>\n </div>\n\n <Tabs defaultValue=\"credentials\">\n <TabsList>\n <TabsTrigger value=\"credentials\">{t('integrations.detail.tabs.credentials')}</TabsTrigger>\n {hasVersions && <TabsTrigger value=\"version\">{t('integrations.detail.tabs.version')}</TabsTrigger>}\n <TabsTrigger value=\"health\">{t('integrations.detail.tabs.health')}</TabsTrigger>\n <TabsTrigger value=\"logs\">{t('integrations.detail.tabs.logs')}</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"credentials\" className=\"space-y-4 mt-4\">\n {detail.bundle && (\n <div className=\"rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800\">\n {t('integrations.detail.credentials.bundleShared', { bundle: detail.bundle.title })}\n </div>\n )}\n {credFields.length === 0 ? (\n <p className=\"text-muted-foreground text-sm\">{t('integrations.detail.credentials.notConfigured')}</p>\n ) : (\n <Card>\n <CardContent className=\"pt-6 space-y-4\">\n {credFields.filter((f) => f.type !== 'oauth' && f.type !== 'ssh_keypair').map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <select\n className=\"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n >\n <option value=\"\">\u2014</option>\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n </TabsContent>\n\n {hasVersions && (\n <TabsContent value=\"version\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.detail.version.select')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {integration.apiVersions!.map((v) => {\n const isSelected = (state.apiVersion ?? integration.apiVersions!.find((x) => x.status === 'stable')?.id) === v.id\n return (\n <div\n key={v.id}\n className={`flex items-center justify-between rounded-lg border p-3 cursor-pointer transition-colors ${isSelected ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'}`}\n onClick={() => void handleVersionChange(v.id)}\n >\n <div>\n <span className=\"font-medium text-sm\">{v.label ?? v.id}</span>\n <Badge\n variant={v.status === 'stable' ? 'default' : v.status === 'deprecated' ? 'destructive' : 'secondary'}\n className=\"ml-2\"\n >\n {t(`integrations.detail.version.${v.status}`)}\n </Badge>\n {v.status === 'deprecated' && v.sunsetAt && (\n <span className=\"text-xs text-muted-foreground ml-2\">\n {t('integrations.detail.version.sunsetAt', { date: new Date(v.sunsetAt).toLocaleDateString() })}\n </span>\n )}\n </div>\n {isSelected && <Badge variant=\"outline\">{t('integrations.detail.version.current')}</Badge>}\n </div>\n )\n })}\n </CardContent>\n </Card>\n </TabsContent>\n )}\n\n <TabsContent value=\"health\" className=\"space-y-4 mt-4\">\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.detail.health.title')}</CardTitle>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleHealthCheck()} disabled={isCheckingHealth}>\n {isCheckingHealth ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {isCheckingHealth ? t('integrations.detail.health.checking') : t('integrations.detail.health.check')}\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm font-medium\">{t('integrations.detail.health.title')}:</span>\n {state.lastHealthStatus ? (\n <Badge className={HEALTH_STATUS_STYLES[state.lastHealthStatus] ?? ''}>\n {t(`integrations.detail.health.${state.lastHealthStatus}`)}\n </Badge>\n ) : (\n <span className=\"text-sm text-muted-foreground\">{t('integrations.detail.health.unknown')}</span>\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {state.lastHealthCheckedAt\n ? t('integrations.detail.health.lastChecked', { date: new Date(state.lastHealthCheckedAt).toLocaleString() })\n : t('integrations.detail.health.neverChecked')\n }\n </p>\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"logs\" className=\"space-y-4 mt-4\">\n <div className=\"flex items-center gap-3\">\n <select\n className=\"flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm\"\n value={logLevel}\n onChange={(e) => setLogLevel(e.target.value)}\n >\n <option value=\"\">{t('integrations.detail.logs.level.all')}</option>\n <option value=\"info\">{t('integrations.detail.logs.level.info')}</option>\n <option value=\"warn\">{t('integrations.detail.logs.level.warn')}</option>\n <option value=\"error\">{t('integrations.detail.logs.level.error')}</option>\n </select>\n </div>\n {isLoadingLogs ? (\n <div className=\"flex justify-center py-8\"><Spinner /></div>\n ) : logs.length === 0 ? (\n <p className=\"text-muted-foreground text-sm py-4\">{t('integrations.detail.logs.empty')}</p>\n ) : (\n <div className=\"rounded-lg border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.time')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.level')}</th>\n <th className=\"px-4 py-2 text-left font-medium\">{t('integrations.detail.logs.columns.message')}</th>\n </tr>\n </thead>\n <tbody>\n {logs.map((log) => (\n <tr key={log.id} className=\"border-b last:border-0\">\n <td className=\"px-4 py-2 text-muted-foreground whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleString()}\n </td>\n <td className=\"px-4 py-2\">\n <Badge variant=\"secondary\" className={LOG_LEVEL_STYLES[log.level] ?? ''}>\n {log.level}\n </Badge>\n </td>\n <td className=\"px-4 py-2\">{log.message}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </TabsContent>\n </Tabs>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["params"]
7
+ }
@@ -0,0 +1,15 @@
1
+ const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ["integrations.view"],
4
+ pageContext: "settings",
5
+ pageTitle: "Integration Detail",
6
+ pageTitleKey: "integrations.detail.title",
7
+ navHidden: true,
8
+ breadcrumb: [
9
+ { label: "Integrations", labelKey: "integrations.nav.title", href: "/backend/integrations" }
10
+ ]
11
+ };
12
+ export {
13
+ metadata
14
+ };
15
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/integrations/backend/integrations/%5Bid%5D/page.meta.ts"],
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['integrations.view'],\n pageContext: 'settings' as const,\n pageTitle: 'Integration Detail',\n pageTitleKey: 'integrations.detail.title',\n navHidden: true,\n breadcrumb: [\n { label: 'Integrations', labelKey: 'integrations.nav.title', href: '/backend/integrations' },\n ],\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,mBAAmB;AAAA,EACrC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,IACV,EAAE,OAAO,gBAAgB,UAAU,0BAA0B,MAAM,wBAAwB;AAAA,EAC7F;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,189 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useParams } from "next/navigation";
6
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
+ import { Card, CardHeader, CardTitle, CardContent } from "@open-mercato/ui/primitives/card";
8
+ import { Badge } from "@open-mercato/ui/primitives/badge";
9
+ import { Button } from "@open-mercato/ui/primitives/button";
10
+ import { Switch } from "@open-mercato/ui/primitives/switch";
11
+ import { Input } from "@open-mercato/ui/primitives/input";
12
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
13
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
14
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
15
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
16
+ import { LoadingMessage } from "@open-mercato/ui/backend/detail";
17
+ import { ErrorMessage } from "@open-mercato/ui/backend/detail";
18
+ function BundleConfigPage() {
19
+ const params = useParams();
20
+ const bundleId = params.id;
21
+ const t = useT();
22
+ const [detail, setDetail] = React.useState(null);
23
+ const [isLoading, setIsLoading] = React.useState(true);
24
+ const [error, setError] = React.useState(null);
25
+ const [credValues, setCredValues] = React.useState({});
26
+ const [isSavingCreds, setIsSavingCreds] = React.useState(false);
27
+ const [togglingIds, setTogglingIds] = React.useState(/* @__PURE__ */ new Set());
28
+ const load = React.useCallback(async () => {
29
+ setIsLoading(true);
30
+ setError(null);
31
+ const call = await apiCall(
32
+ `/api/integrations/${encodeURIComponent(bundleId)}`,
33
+ void 0,
34
+ { fallback: null }
35
+ );
36
+ if (!call.ok || !call.result) {
37
+ setError(t("integrations.detail.loadError"));
38
+ setIsLoading(false);
39
+ return;
40
+ }
41
+ setDetail(call.result);
42
+ const credCall = await apiCall(
43
+ `/api/integrations/${encodeURIComponent(bundleId)}/credentials`,
44
+ void 0,
45
+ { fallback: null }
46
+ );
47
+ if (credCall.ok && credCall.result?.credentials) {
48
+ setCredValues(credCall.result.credentials);
49
+ }
50
+ setIsLoading(false);
51
+ }, [bundleId, t]);
52
+ React.useEffect(() => {
53
+ void load();
54
+ }, [load]);
55
+ const handleSaveCredentials = React.useCallback(async () => {
56
+ setIsSavingCreds(true);
57
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(bundleId)}/credentials`, {
58
+ method: "PUT",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ credentials: credValues })
61
+ }, { fallback: null });
62
+ if (call.ok) {
63
+ flash(t("integrations.detail.credentials.saved"), "success");
64
+ } else {
65
+ flash(t("integrations.detail.credentials.saveError"), "error");
66
+ }
67
+ setIsSavingCreds(false);
68
+ }, [bundleId, credValues, t]);
69
+ const handleToggle = React.useCallback(async (integrationId, enabled) => {
70
+ setTogglingIds((prev) => new Set(prev).add(integrationId));
71
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
72
+ method: "PUT",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({ isEnabled: enabled })
75
+ }, { fallback: null });
76
+ if (call.ok) {
77
+ setDetail((prev) => {
78
+ if (!prev) return prev;
79
+ return {
80
+ ...prev,
81
+ bundleIntegrations: prev.bundleIntegrations.map(
82
+ (item) => item.id === integrationId ? { ...item, isEnabled: enabled } : item
83
+ )
84
+ };
85
+ });
86
+ } else {
87
+ flash(t("integrations.detail.stateError"), "error");
88
+ }
89
+ setTogglingIds((prev) => {
90
+ const next = new Set(prev);
91
+ next.delete(integrationId);
92
+ return next;
93
+ });
94
+ }, [t]);
95
+ const handleBulkToggle = React.useCallback(async (enabled) => {
96
+ if (!detail) return;
97
+ const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled);
98
+ await Promise.all(targets.map((item) => handleToggle(item.id, enabled)));
99
+ }, [detail, handleToggle]);
100
+ if (isLoading) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("integrations.bundle.title") }) }) });
101
+ if (error || !detail?.bundle) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("integrations.detail.loadError") }) }) });
102
+ const credFields = detail.bundle.credentials?.fields ?? [];
103
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
104
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Link, { href: "/backend/integrations", className: "text-sm text-muted-foreground hover:underline", children: t("integrations.detail.back") }) }),
105
+ /* @__PURE__ */ jsxs("div", { children: [
106
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: detail.bundle.title }),
107
+ detail.bundle.description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: detail.bundle.description })
108
+ ] }),
109
+ credFields.length > 0 && /* @__PURE__ */ jsxs(Card, { children: [
110
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.bundle.sharedCredentials") }) }),
111
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
112
+ credFields.map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
113
+ /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium", children: [
114
+ field.label,
115
+ field.required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
116
+ ] }),
117
+ field.type === "select" && field.options ? /* @__PURE__ */ jsxs(
118
+ "select",
119
+ {
120
+ className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm",
121
+ value: credValues[field.key] ?? "",
122
+ onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value })),
123
+ children: [
124
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
125
+ field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
126
+ ]
127
+ }
128
+ ) : field.type === "boolean" ? /* @__PURE__ */ jsx(
129
+ Switch,
130
+ {
131
+ checked: Boolean(credValues[field.key]),
132
+ onCheckedChange: (checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))
133
+ }
134
+ ) : /* @__PURE__ */ jsx(
135
+ Input,
136
+ {
137
+ type: field.type === "secret" ? "password" : "text",
138
+ placeholder: field.placeholder,
139
+ value: credValues[field.key] ?? "",
140
+ onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))
141
+ }
142
+ )
143
+ ] }, field.key)),
144
+ /* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => void handleSaveCredentials(), disabled: isSavingCreds, children: [
145
+ isSavingCreds ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
146
+ t("integrations.detail.credentials.save")
147
+ ] })
148
+ ] })
149
+ ] }),
150
+ /* @__PURE__ */ jsxs(Card, { children: [
151
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
152
+ /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.bundle.integrationToggles") }),
153
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
154
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleBulkToggle(true), children: t("integrations.marketplace.enableAll") }),
155
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleBulkToggle(false), children: t("integrations.marketplace.disableAll") })
156
+ ] })
157
+ ] }) }),
158
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { className: "space-y-3", children: detail.bundleIntegrations.map((item) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [
159
+ /* @__PURE__ */ jsxs("div", { children: [
160
+ /* @__PURE__ */ jsx(
161
+ Link,
162
+ {
163
+ href: `/backend/integrations/${encodeURIComponent(item.id)}`,
164
+ className: "text-sm font-medium hover:underline",
165
+ children: item.title
166
+ }
167
+ ),
168
+ item.category && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "ml-2 text-xs", children: item.category }),
169
+ item.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: item.description })
170
+ ] }),
171
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
172
+ /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: `/backend/integrations/${encodeURIComponent(item.id)}`, children: t("integrations.bundle.configureIntegration") }) }),
173
+ /* @__PURE__ */ jsx(
174
+ Switch,
175
+ {
176
+ checked: item.isEnabled,
177
+ disabled: togglingIds.has(item.id),
178
+ onCheckedChange: (checked) => void handleToggle(item.id, checked)
179
+ }
180
+ )
181
+ ] })
182
+ ] }, item.id)) }) })
183
+ ] })
184
+ ] }) });
185
+ }
186
+ export {
187
+ BundleConfigPage as default
188
+ };
189
+ //# sourceMappingURL=page.js.map