@burdenoff/microfe-adaptercloud 2026.706.1 → 2026.706.3

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 (53) hide show
  1. package/dist/adaptercloud/AdapterCloudRoot.js +52 -44
  2. package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
  3. package/dist/adaptercloud/AdapterCloudRoutes.js +22 -14
  4. package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
  5. package/dist/adaptercloud/components/ConfirmDialog.js +27 -0
  6. package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -0
  7. package/dist/adaptercloud/components/DataState.js +1 -1
  8. package/dist/adaptercloud/components/DataState.js.map +1 -1
  9. package/dist/adaptercloud/components/viz/BarChart.js +32 -0
  10. package/dist/adaptercloud/components/viz/BarChart.js.map +1 -0
  11. package/dist/adaptercloud/components/viz/Sparkline.js +50 -0
  12. package/dist/adaptercloud/components/viz/Sparkline.js.map +1 -0
  13. package/dist/adaptercloud/components/viz/StackedBar.js +1 -0
  14. package/dist/adaptercloud/components/viz/TopologyGraph.js +96 -0
  15. package/dist/adaptercloud/components/viz/TopologyGraph.js.map +1 -0
  16. package/dist/adaptercloud/components/viz/index.js +4 -0
  17. package/dist/adaptercloud/hooks/useAdapterCloudApi.js +71 -34
  18. package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
  19. package/dist/adaptercloud/pages/OverviewPage.js +159 -156
  20. package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
  21. package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +179 -113
  22. package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
  23. package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +263 -179
  24. package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
  25. package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +2 -0
  26. package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
  27. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +141 -124
  28. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
  29. package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +2 -0
  30. package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -1
  31. package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -93
  32. package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
  33. package/dist/adaptercloud/pages/costs/CostsPage.js +183 -109
  34. package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
  35. package/dist/adaptercloud/pages/drift/DriftListPage.js +172 -71
  36. package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
  37. package/dist/adaptercloud/pages/policies/PoliciesListPage.js +179 -96
  38. package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
  39. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +109 -91
  40. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
  41. package/dist/adaptercloud/pages/policies/PolicyFormPage.js +211 -184
  42. package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
  43. package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +183 -127
  44. package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
  45. package/dist/adaptercloud/pages/resources/ResourcesListPage.js +121 -84
  46. package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
  47. package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +236 -153
  48. package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
  49. package/dist/adaptercloud/pages/topology/TopologyPage.js +397 -0
  50. package/dist/adaptercloud/pages/topology/TopologyPage.js.map +1 -0
  51. package/dist/adaptercloud/pages/violations/ViolationsListPage.js +192 -131
  52. package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
  53. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AdapterFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/adapters/AdapterFormPage.tsx"],"sourcesContent":["/**\n * Adapter Create / Edit Form Page\n * @module adaptercloud/pages/adapters\n *\n * Creates a new adapter (`adaCreateAdapter`) or edits an existing one\n * (`adaUpdateAdapter`). The presence of a route `:id` switches to edit mode and\n * pre-fills from `adaAdapter`.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useAdapter, useCreateAdapter, useUpdateAdapter } from '../../hooks/useAdapterCloudApi';\nimport { adapterKindLabels, adapterStatusLabels } from '../../utils/domain';\nimport type { AdaAdapterKind, AdaAdapterStatus } from '../../types';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\ninterface FormState {\n name: string;\n slug: string;\n kind: AdaAdapterKind;\n status: AdaAdapterStatus;\n vendor: string;\n version: string;\n description: string;\n capabilities: string;\n tags: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n slug: '',\n kind: 'CUSTOM',\n status: 'DRAFT',\n vendor: '',\n version: '1.0.0',\n description: '',\n capabilities: '',\n tags: '',\n};\n\nexport function AdapterFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const existing = useAdapter(isEdit ? id : undefined);\n const create = useCreateAdapter();\n const update = useUpdateAdapter();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [slugTouched, setSlugTouched] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Pre-fill in edit mode\n useEffect(() => {\n const a = existing.data;\n if (isEdit && a) {\n setForm({\n name: a.name,\n slug: a.slug,\n kind: a.kind,\n status: a.status,\n vendor: a.vendor ?? '',\n version: a.version,\n description: a.description ?? '',\n capabilities: a.capabilities.join(', '),\n tags: a.tags.join(', '),\n });\n setSlugTouched(true);\n }\n }, [isEdit, existing.data]);\n\n const setField = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const onNameChange = useCallback(\n (value: string) => {\n setForm((prev) => ({\n ...prev,\n name: value,\n slug: slugTouched ? prev.slug : slugify(value),\n }));\n },\n [slugTouched]\n );\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError(t('adaptercloud.adapters.form.nameRequired', 'Name is required.'));\n return;\n }\n if (!isEdit && !form.slug.trim()) {\n setError(t('adaptercloud.adapters.form.slugRequired', 'Slug is required.'));\n return;\n }\n\n const common = {\n name: form.name.trim(),\n kind: form.kind,\n status: form.status,\n vendor: form.vendor.trim() || undefined,\n version: form.version.trim() || undefined,\n description: form.description.trim() || undefined,\n capabilities: parseList(form.capabilities),\n tags: parseList(form.tags),\n };\n\n const onError = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save adapter.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (a) => navigateTo(`/adapters/${a.id}`), onError }\n );\n } else {\n create.mutate(\n { ...common, slug: form.slug.trim() },\n { onSuccess: (a) => navigateTo(`/adapters/${a.id}`), onError }\n );\n }\n },\n [create, update, form, id, isEdit, navigateTo, t]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.adapters.editTitle', 'Edit Adapter')\n : t('adaptercloud.adapters.newTitle', 'New Adapter')\n }\n description={t(\n 'adaptercloud.adapters.formSubtitle',\n 'Define how AdapterCloud connects to a class of system.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/adapters/${id}` : '/adapters')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.adapters.notFound', 'Adapter not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-name\" className={labelClass}>\n {t('adaptercloud.adapters.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-name\"\n type=\"text\"\n value={form.name}\n onChange={(e) => onNameChange(e.target.value)}\n className={fieldClass}\n placeholder=\"AWS Production\"\n />\n </div>\n <div>\n <label htmlFor=\"ada-slug\" className={labelClass}>\n {t('adaptercloud.adapters.form.slug', 'Slug')} {isEdit ? '' : '*'}\n </label>\n <input\n id=\"ada-slug\"\n type=\"text\"\n value={form.slug}\n disabled={isEdit}\n onChange={(e) => {\n setSlugTouched(true);\n setField('slug', slugify(e.target.value));\n }}\n className={`${fieldClass} disabled:cursor-not-allowed disabled:opacity-60`}\n placeholder=\"aws-production\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-kind\" className={labelClass}>\n {t('adaptercloud.adapters.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-kind\"\n value={form.kind}\n onChange={(e) => setField('kind', e.target.value as AdaAdapterKind)}\n className={fieldClass}\n >\n {(Object.keys(adapterKindLabels) as AdaAdapterKind[]).map((k) => (\n <option key={k} value={k}>\n {adapterKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-status\" className={labelClass}>\n {t('adaptercloud.adapters.form.status', 'Status')}\n </label>\n <select\n id=\"ada-status\"\n value={form.status}\n onChange={(e) => setField('status', e.target.value as AdaAdapterStatus)}\n className={fieldClass}\n >\n {(Object.keys(adapterStatusLabels) as AdaAdapterStatus[]).map((s) => (\n <option key={s} value={s}>\n {adapterStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-vendor\" className={labelClass}>\n {t('adaptercloud.adapters.form.vendor', 'Vendor')}\n </label>\n <input\n id=\"ada-vendor\"\n type=\"text\"\n value={form.vendor}\n onChange={(e) => setField('vendor', e.target.value)}\n className={fieldClass}\n placeholder=\"Amazon Web Services\"\n />\n </div>\n <div>\n <label htmlFor=\"ada-version\" className={labelClass}>\n {t('adaptercloud.adapters.form.version', 'Version')}\n </label>\n <input\n id=\"ada-version\"\n type=\"text\"\n value={form.version}\n onChange={(e) => setField('version', e.target.value)}\n className={fieldClass}\n placeholder=\"1.0.0\"\n />\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-description\" className={labelClass}>\n {t('adaptercloud.adapters.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-description\"\n value={form.description}\n onChange={(e) => setField('description', e.target.value)}\n rows={3}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.adapters.form.descriptionPlaceholder',\n 'What this adapter connects to and how.'\n )}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-capabilities\" className={labelClass}>\n {t('adaptercloud.adapters.form.capabilities', 'Capabilities')}\n </label>\n <input\n id=\"ada-capabilities\"\n type=\"text\"\n value={form.capabilities}\n onChange={(e) => setField('capabilities', e.target.value)}\n className={fieldClass}\n placeholder=\"discovery, cost, reconcile\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-tags\"\n type=\"text\"\n value={form.tags}\n onChange={(e) => setField('tags', e.target.value)}\n className={fieldClass}\n placeholder=\"production, cloud\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.adapters.create', 'Create adapter')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdapterFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/adapters/AdapterFormPage.tsx"],"sourcesContent":["/**\n * Adapter Create / Edit Form Page\n * @module adaptercloud/pages/adapters\n *\n * Creates a new adapter (`adaCreateAdapter`) or edits an existing one\n * (`adaUpdateAdapter`). The presence of a route `:id` switches to edit mode and\n * pre-fills from `adaAdapter`.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useAdapter, useCreateAdapter, useUpdateAdapter } from '../../hooks/useAdapterCloudApi';\nimport { adapterKindLabels, adapterStatusLabels } from '../../utils/domain';\nimport type { AdaAdapterKind, AdaAdapterStatus } from '../../types';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\ninterface FormState {\n name: string;\n slug: string;\n kind: AdaAdapterKind;\n status: AdaAdapterStatus;\n vendor: string;\n version: string;\n description: string;\n /** Committed chip values. */\n capabilities: string[];\n tags: string[];\n /** Raw JSON text — validated on blur, parsed to JSON on submit. */\n configSchema: string;\n}\n\nconst EMPTY: FormState = {\n name: '',\n slug: '',\n kind: 'CUSTOM',\n status: 'DRAFT',\n vendor: '',\n version: '1.0.0',\n description: '',\n capabilities: [],\n tags: [],\n configSchema: '',\n};\n\n/**\n * Removable chip input — commits the current draft on Enter / comma / blur,\n * removes a chip via its × button, and pops the last chip on Backspace when the\n * draft is empty. Kept module-level so its input keeps focus across re-renders.\n */\ninterface ChipInputProps {\n id: string;\n label: string;\n values: string[];\n onChange: (next: string[]) => void;\n placeholder?: string;\n chipClass: string;\n}\n\nfunction ChipInput({ id, label, values, onChange, placeholder, chipClass }: ChipInputProps) {\n const [draft, setDraft] = useState('');\n\n const addChip = useCallback(\n (raw: string) => {\n const value = raw.trim();\n if (!value || values.includes(value)) return;\n onChange([...values, value]);\n },\n [values, onChange]\n );\n\n const commitDraft = useCallback(() => {\n if (draft.trim()) {\n addChip(draft);\n setDraft('');\n }\n }, [draft, addChip]);\n\n const handleChange = useCallback(\n (raw: string) => {\n if (raw.includes(',')) {\n const parts = raw.split(',');\n parts.slice(0, -1).forEach((part) => addChip(part));\n setDraft(parts[parts.length - 1] ?? '');\n } else {\n setDraft(raw);\n }\n },\n [addChip]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n commitDraft();\n } else if (e.key === 'Backspace' && !draft && values.length > 0) {\n onChange(values.slice(0, -1));\n }\n },\n [commitDraft, draft, values, onChange]\n );\n\n return (\n <div>\n <label htmlFor={id} className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n {label}\n </label>\n <div className=\"flex w-full flex-wrap items-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5 focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]\">\n {values.map((value) => (\n <span\n key={value}\n className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ${chipClass}`}\n >\n {value}\n <button\n type=\"button\"\n onClick={() => onChange(values.filter((v) => v !== value))}\n className=\"rounded-full p-0.5 transition-opacity hover:opacity-70 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label={`Remove ${value}`}\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id={id}\n type=\"text\"\n value={draft}\n onChange={(e) => handleChange(e.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={commitDraft}\n className=\"min-w-[8rem] flex-1 bg-transparent px-1 py-1 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none\"\n placeholder={values.length === 0 ? placeholder : ''}\n />\n </div>\n </div>\n );\n}\n\nexport function AdapterFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const existing = useAdapter(isEdit ? id : undefined);\n const create = useCreateAdapter();\n const update = useUpdateAdapter();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [slugTouched, setSlugTouched] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [configSchemaError, setConfigSchemaError] = useState<string | null>(null);\n\n // Pre-fill in edit mode\n useEffect(() => {\n const a = existing.data;\n if (isEdit && a) {\n setForm({\n name: a.name,\n slug: a.slug,\n kind: a.kind,\n status: a.status,\n vendor: a.vendor ?? '',\n version: a.version,\n description: a.description ?? '',\n capabilities: a.capabilities,\n tags: a.tags,\n configSchema: a.configSchema != null ? JSON.stringify(a.configSchema, null, 2) : '',\n });\n setSlugTouched(true);\n }\n }, [isEdit, existing.data]);\n\n const setField = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const onNameChange = useCallback(\n (value: string) => {\n setForm((prev) => ({\n ...prev,\n name: value,\n slug: slugTouched ? prev.slug : slugify(value),\n }));\n },\n [slugTouched]\n );\n\n const validateConfigSchema = useCallback(() => {\n const raw = form.configSchema.trim();\n if (!raw) {\n setConfigSchemaError(null);\n return;\n }\n try {\n JSON.parse(raw);\n setConfigSchemaError(null);\n } catch {\n setConfigSchemaError(\n t('adaptercloud.adapters.form.configSchemaInvalid', 'Config schema must be valid JSON.')\n );\n }\n }, [form.configSchema, t]);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError(t('adaptercloud.adapters.form.nameRequired', 'Name is required.'));\n return;\n }\n if (!isEdit && !form.slug.trim()) {\n setError(t('adaptercloud.adapters.form.slugRequired', 'Slug is required.'));\n return;\n }\n\n // Parse the config-schema editor. Empty → omit; invalid → block submit.\n const rawSchema = form.configSchema.trim();\n let configSchema: unknown;\n if (rawSchema) {\n try {\n configSchema = JSON.parse(rawSchema);\n } catch {\n const message = t(\n 'adaptercloud.adapters.form.configSchemaInvalid',\n 'Config schema must be valid JSON.'\n );\n setConfigSchemaError(message);\n setError(message);\n return;\n }\n }\n setConfigSchemaError(null);\n\n const common = {\n name: form.name.trim(),\n kind: form.kind,\n status: form.status,\n vendor: form.vendor.trim() || undefined,\n version: form.version.trim() || undefined,\n description: form.description.trim() || undefined,\n capabilities: form.capabilities,\n tags: form.tags,\n configSchema: rawSchema ? configSchema : undefined,\n };\n\n const onError = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save adapter.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (a) => navigateTo(`/adapters/${a.id}`), onError }\n );\n } else {\n create.mutate(\n { ...common, slug: form.slug.trim() },\n { onSuccess: (a) => navigateTo(`/adapters/${a.id}`), onError }\n );\n }\n },\n [create, update, form, id, isEdit, navigateTo, t]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.adapters.editTitle', 'Edit Adapter')\n : t('adaptercloud.adapters.newTitle', 'New Adapter')\n }\n description={t(\n 'adaptercloud.adapters.formSubtitle',\n 'Define how AdapterCloud connects to a class of system.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/adapters/${id}` : '/adapters')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.adapters.notFound', 'Adapter not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-5xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Left column: identity + config schema */}\n <div className=\"space-y-5\">\n <div>\n <label htmlFor=\"ada-name\" className={labelClass}>\n {t('adaptercloud.adapters.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-name\"\n type=\"text\"\n value={form.name}\n onChange={(e) => onNameChange(e.target.value)}\n className={fieldClass}\n placeholder=\"AWS Production\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-slug\" className={labelClass}>\n {t('adaptercloud.adapters.form.slug', 'Slug')} {isEdit ? '' : '*'}\n </label>\n <input\n id=\"ada-slug\"\n type=\"text\"\n value={form.slug}\n disabled={isEdit}\n onChange={(e) => {\n setSlugTouched(true);\n setField('slug', slugify(e.target.value));\n }}\n className={`${fieldClass} disabled:cursor-not-allowed disabled:opacity-60`}\n placeholder=\"aws-production\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-vendor\" className={labelClass}>\n {t('adaptercloud.adapters.form.vendor', 'Vendor')}\n </label>\n <input\n id=\"ada-vendor\"\n type=\"text\"\n value={form.vendor}\n onChange={(e) => setField('vendor', e.target.value)}\n className={fieldClass}\n placeholder=\"Amazon Web Services\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-version\" className={labelClass}>\n {t('adaptercloud.adapters.form.version', 'Version')}\n </label>\n <input\n id=\"ada-version\"\n type=\"text\"\n value={form.version}\n onChange={(e) => setField('version', e.target.value)}\n className={fieldClass}\n placeholder=\"1.0.0\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-description\" className={labelClass}>\n {t('adaptercloud.adapters.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-description\"\n value={form.description}\n onChange={(e) => setField('description', e.target.value)}\n rows={3}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.adapters.form.descriptionPlaceholder',\n 'What this adapter connects to and how.'\n )}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-config-schema\" className={labelClass}>\n {t('adaptercloud.adapters.form.configSchema', 'Config schema')}\n </label>\n <textarea\n id=\"ada-config-schema\"\n value={form.configSchema}\n onChange={(e) => {\n setField('configSchema', e.target.value);\n if (configSchemaError) setConfigSchemaError(null);\n }}\n onBlur={validateConfigSchema}\n rows={10}\n spellCheck={false}\n aria-invalid={configSchemaError ? true : undefined}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-sunken px-3 py-2 font-mono text-xs leading-relaxed text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={'{\\n \"type\": \"object\",\\n \"properties\": {}\\n}'}\n />\n {configSchemaError ? (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mt-1 text-xs text-status-error-text\"\n >\n {configSchemaError}\n </p>\n ) : (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'adaptercloud.adapters.form.configSchemaHint',\n 'Optional JSON schema describing this adapter’s configuration.'\n )}\n </p>\n )}\n </div>\n </div>\n\n {/* Right column: classification + chips */}\n <div className=\"space-y-5\">\n <div>\n <label htmlFor=\"ada-kind\" className={labelClass}>\n {t('adaptercloud.adapters.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-kind\"\n value={form.kind}\n onChange={(e) => setField('kind', e.target.value as AdaAdapterKind)}\n className={fieldClass}\n >\n {(Object.keys(adapterKindLabels) as AdaAdapterKind[]).map((k) => (\n <option key={k} value={k}>\n {adapterKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label htmlFor=\"ada-status\" className={labelClass}>\n {t('adaptercloud.adapters.form.status', 'Status')}\n </label>\n <select\n id=\"ada-status\"\n value={form.status}\n onChange={(e) => setField('status', e.target.value as AdaAdapterStatus)}\n className={fieldClass}\n >\n {(Object.keys(adapterStatusLabels) as AdaAdapterStatus[]).map((s) => (\n <option key={s} value={s}>\n {adapterStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n\n <ChipInput\n id=\"ada-capabilities\"\n label={t('adaptercloud.adapters.form.capabilities', 'Capabilities')}\n values={form.capabilities}\n onChange={(next) => setField('capabilities', next)}\n placeholder={t(\n 'adaptercloud.adapters.form.capabilitiesPlaceholder',\n 'Add a capability and press Enter'\n )}\n chipClass=\"bg-accent-blue-subtle text-accent-blue\"\n />\n\n <ChipInput\n id=\"ada-tags\"\n label={t('adaptercloud.common.tags', 'Tags')}\n values={form.tags}\n onChange={(next) => setField('tags', next)}\n placeholder={t(\n 'adaptercloud.adapters.form.tagsPlaceholder',\n 'Add a tag and press Enter'\n )}\n chipClass=\"bg-bg-sunken text-text-secondary\"\n />\n </div>\n </div>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.adapters.create', 'Create adapter')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,IAAM,KAAW,MACf,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,EAiBtB,IAAmB;CACvB,MAAM;CACN,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,aAAa;CACb,cAAc,EAAE;CAChB,MAAM,EAAE;CACR,cAAc;CACf;AAgBD,SAAS,EAAU,EAAE,OAAI,UAAO,WAAQ,aAAU,gBAAa,gBAA6B;CAC1F,IAAM,CAAC,GAAO,KAAY,EAAS,GAAG,EAEhC,IAAU,GACb,MAAgB;EACf,IAAM,IAAQ,EAAI,MAAM;AACpB,GAAC,KAAS,EAAO,SAAS,EAAM,IACpC,EAAS,CAAC,GAAG,GAAQ,EAAM,CAAC;IAE9B,CAAC,GAAQ,EAAS,CACnB,EAEK,IAAc,QAAkB;AACpC,EAAI,EAAM,MAAM,KACd,EAAQ,EAAM,EACd,EAAS,GAAG;IAEb,CAAC,GAAO,EAAQ,CAAC,EAEd,IAAe,GAClB,MAAgB;AACf,MAAI,EAAI,SAAS,IAAI,EAAE;GACrB,IAAM,IAAQ,EAAI,MAAM,IAAI;AAE5B,GADA,EAAM,MAAM,GAAG,GAAG,CAAC,SAAS,MAAS,EAAQ,EAAK,CAAC,EACnD,EAAS,EAAM,EAAM,SAAS,MAAM,GAAG;QAEvC,GAAS,EAAI;IAGjB,CAAC,EAAQ,CACV,EAEK,IAAgB,GACnB,MAA6C;AAC5C,EAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,OACjC,EAAE,gBAAgB,EAClB,GAAa,IACJ,EAAE,QAAQ,eAAe,CAAC,KAAS,EAAO,SAAS,KAC5D,EAAS,EAAO,MAAM,GAAG,GAAG,CAAC;IAGjC;EAAC;EAAa;EAAO;EAAQ;EAAS,CACvC;AAED,QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;EAAO,SAAS;EAAI,WAAU;YAC3B;EACK,CAAA,EACR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAO,KAAK,MACX,kBAAC,QAAD;GAEE,WAAW,iFAAiF;aAF9F,CAIG,GACD,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAS,EAAO,QAAQ,MAAM,MAAM,EAAM,CAAC;IAC1D,WAAU;IACV,cAAY,UAAU;cAEtB,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;IAClB,CAAA,CACJ;KAZA,EAYA,CACP,EACF,kBAAC,SAAD;GACM;GACJ,MAAK;GACL,OAAO;GACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;GAC7C,WAAW;GACX,QAAQ;GACR,WAAU;GACV,aAAa,EAAO,WAAW,IAAI,IAAc;GACjD,CAAA,CACE;IACF,EAAA,CAAA;;AAIV,SAAgB,IAAkB;CAChC,IAAM,EAAE,UAAO,GAA2B,EACpC,IAAS,EAAQ,GACjB,EAAE,kBAAe,GAAiB,EAClC,EAAE,GAAG,MAAc,GAAS,EAC5B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAU,CACZ,EAEK,IAAW,EAAW,IAAS,IAAK,KAAA,EAAU,EAC9C,IAAS,GAAkB,EAC3B,IAAS,GAAkB,EAE3B,CAAC,GAAM,KAAW,EAAoB,EAAM,EAC5C,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAmB,KAAwB,EAAwB,KAAK;AAG/E,SAAgB;EACd,IAAM,IAAI,EAAS;AACnB,EAAI,KAAU,MACZ,EAAQ;GACN,MAAM,EAAE;GACR,MAAM,EAAE;GACR,MAAM,EAAE;GACR,QAAQ,EAAE;GACV,QAAQ,EAAE,UAAU;GACpB,SAAS,EAAE;GACX,aAAa,EAAE,eAAe;GAC9B,cAAc,EAAE;GAChB,MAAM,EAAE;GACR,cAAc,EAAE,gBAAgB,OAAiD,KAA1C,KAAK,UAAU,EAAE,cAAc,MAAM,EAAE;GAC/E,CAAC,EACF,EAAe,GAAK;IAErB,CAAC,GAAQ,EAAS,KAAK,CAAC;CAE3B,IAAM,IAAW,GAAwC,GAAQ,MAAwB;AACvF,KAAS,OAAU;GAAE,GAAG;IAAO,IAAM;GAAO,EAAE;IAC7C,EAAE,CAAC,EAEA,IAAe,GAClB,MAAkB;AACjB,KAAS,OAAU;GACjB,GAAG;GACH,MAAM;GACN,MAAM,IAAc,EAAK,OAAO,EAAQ,EAAM;GAC/C,EAAE;IAEL,CAAC,EAAY,CACd,EAEK,IAAuB,QAAkB;EAC7C,IAAM,IAAM,EAAK,aAAa,MAAM;AACpC,MAAI,CAAC,GAAK;AACR,KAAqB,KAAK;AAC1B;;AAEF,MAAI;AAEF,GADA,KAAK,MAAM,EAAI,EACf,EAAqB,KAAK;UACpB;AACN,KACE,EAAE,kDAAkD,oCAAoC,CACzF;;IAEF,CAAC,EAAK,cAAc,EAAE,CAAC,EAEpB,IAAY,EAAO,aAAa,EAAO,WAEvC,IAAe,GAClB,MAAuB;AAItB,MAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EAEV,CAAC,EAAK,KAAK,MAAM,EAAE;AACrB,KAAS,EAAE,2CAA2C,oBAAoB,CAAC;AAC3E;;AAEF,MAAI,CAAC,KAAU,CAAC,EAAK,KAAK,MAAM,EAAE;AAChC,KAAS,EAAE,2CAA2C,oBAAoB,CAAC;AAC3E;;EAIF,IAAM,IAAY,EAAK,aAAa,MAAM,EACtC;AACJ,MAAI,EACF,KAAI;AACF,OAAe,KAAK,MAAM,EAAU;UAC9B;GACN,IAAM,IAAU,EACd,kDACA,oCACD;AAED,GADA,EAAqB,EAAQ,EAC7B,EAAS,EAAQ;AACjB;;AAGJ,IAAqB,KAAK;EAE1B,IAAM,IAAS;GACb,MAAM,EAAK,KAAK,MAAM;GACtB,MAAM,EAAK;GACX,QAAQ,EAAK;GACb,QAAQ,EAAK,OAAO,MAAM,IAAI,KAAA;GAC9B,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;GAChC,aAAa,EAAK,YAAY,MAAM,IAAI,KAAA;GACxC,cAAc,EAAK;GACnB,MAAM,EAAK;GACX,cAAc,IAAY,IAAe,KAAA;GAC1C,EAEK,KAAW,MACf,EAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;AAE1E,EAAI,KAAU,IACZ,EAAO,OACL;GAAE;GAAI,OAAO;GAAQ,EACrB;GAAE,YAAY,MAAM,EAAW,aAAa,EAAE,KAAK;GAAE;GAAS,CAC/D,GAED,EAAO,OACL;GAAE,GAAG;GAAQ,MAAM,EAAK,KAAK,MAAM;GAAE,EACrC;GAAE,YAAY,MAAM,EAAW,aAAa,EAAE,KAAK;GAAE;GAAS,CAC/D;IAGL;EAAC;EAAQ;EAAQ;EAAM;EAAI;EAAQ;EAAY;EAAE,CAClD,EAEK,IACJ,wMACI,IAAa;AAEnB,QACE,kBAAC,GAAD;EACE,OACE,IACI,EAAE,mCAAmC,eAAe,GACpD,EAAE,kCAAkC,cAAc;EAExD,aAAa,EACX,sCACA,yDACD;EACD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,KAAU,IAAK,aAAa,MAAO,YAAY;GACzE,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EAAE,8BAA8B,SAAS,CACnC;;YAGX,kBAAC,GAAD;GACE,WAAW,KAAU,EAAS;GAC9B,SAAS,KAAU,EAAS;GAC5B,SAAS,KAAU,CAAC,EAAS,aAAa,CAAC,EAAS;GACpD,OAAO,EAAS;GAChB,eAAe,KAAK,EAAS,SAAS;GACtC,YAAY,EAAE,kCAAkC,oBAAoB;aAEpE,kBAAC,QAAD;IAAM,UAAU;IAAc,WAAU;cACtC,kBAAC,GAAD;KAAW,WAAU;eAArB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAW,WAAW;oBAArC,CACG,EAAE,mCAAmC,OAAO,EAAC,KACxC;aACR,kBAAC,SAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;UAC7C,WAAW;UACX,aAAY;UACZ,CAAA,CACE,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAW,WAAW;oBAArC;WACG,EAAE,mCAAmC,OAAO;WAAC;WAAE,IAAS,KAAK;WACxD;aACR,kBAAC,SAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,UAAU;UACV,WAAW,MAAM;AAEf,WADA,EAAe,GAAK,EACpB,EAAS,QAAQ,EAAQ,EAAE,OAAO,MAAM,CAAC;;UAE3C,WAAW,GAAG,EAAW;UACzB,aAAY;UACZ,CAAA,CACE,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAa,WAAW;oBACpC,EAAE,qCAAqC,SAAS;UAC3C,CAAA,EACR,kBAAC,SAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,UAAU,EAAE,OAAO,MAAM;UACnD,WAAW;UACX,aAAY;UACZ,CAAA,CACE,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAc,WAAW;oBACrC,EAAE,sCAAsC,UAAU;UAC7C,CAAA,EACR,kBAAC,SAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,MAAM;UACpD,WAAW;UACX,aAAY;UACZ,CAAA,CACE,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAkB,WAAW;oBACzC,EAAE,0CAA0C,cAAc;UACrD,CAAA,EACR,kBAAC,YAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,eAAe,EAAE,OAAO,MAAM;UACxD,MAAM;UACN,WAAW;UACX,aAAa,EACX,qDACA,yCACD;UACD,CAAA,CACE,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA;UACE,kBAAC,SAAD;WAAO,SAAQ;WAAoB,WAAW;qBAC3C,EAAE,2CAA2C,gBAAgB;WACxD,CAAA;UACR,kBAAC,YAAD;WACE,IAAG;WACH,OAAO,EAAK;WACZ,WAAW,MAAM;AAEf,YADA,EAAS,gBAAgB,EAAE,OAAO,MAAM,EACpC,KAAmB,EAAqB,KAAK;;WAEnD,QAAQ;WACR,MAAM;WACN,YAAY;WACZ,gBAAc,IAAoB,KAAO,KAAA;WACzC,WAAU;WACV,aAAa;WACb,CAAA;UACD,IACC,kBAAC,KAAD;WACE,MAAK;WACL,aAAU;WACV,WAAU;qBAET;WACC,CAAA,GAEJ,kBAAC,KAAD;WAAG,WAAU;qBACV,EACC,+CACA,gEACD;WACC,CAAA;UAEF,EAAA,CAAA;SACF;WAGN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAW,WAAW;oBAClC,EAAE,mCAAmC,OAAO;UACvC,CAAA,EACR,kBAAC,UAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAwB;UACnE,WAAW;oBAET,OAAO,KAAK,EAAkB,CAAsB,KAAK,MACzD,kBAAC,UAAD;WAAgB,OAAO;qBACpB,EAAkB;WACZ,EAFI,EAEJ,CACT;UACK,CAAA,CACL,EAAA,CAAA;SAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,SAAQ;UAAa,WAAW;oBACpC,EAAE,qCAAqC,SAAS;UAC3C,CAAA,EACR,kBAAC,UAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,UAAU,EAAE,OAAO,MAA0B;UACvE,WAAW;oBAET,OAAO,KAAK,EAAoB,CAAwB,KAAK,MAC7D,kBAAC,UAAD;WAAgB,OAAO;qBACpB,EAAoB;WACd,EAFI,EAEJ,CACT;UACK,CAAA,CACL,EAAA,CAAA;SAEN,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAE,2CAA2C,eAAe;UACnE,QAAQ,EAAK;UACb,WAAW,MAAS,EAAS,gBAAgB,EAAK;UAClD,aAAa,EACX,sDACA,mCACD;UACD,WAAU;UACV,CAAA;SAEF,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAE,4BAA4B,OAAO;UAC5C,QAAQ,EAAK;UACb,WAAW,MAAS,EAAS,QAAQ,EAAK;UAC1C,aAAa,EACX,8CACA,4BACD;UACD,WAAU;UACV,CAAA;SACE;UACF;;MAEL,KACC,kBAAC,KAAD;OACE,MAAK;OACL,aAAU;OACV,WAAU;iBAET;OACC,CAAA;MAGN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,UAAD;QACE,MAAK;QACL,UAAU;QACV,WAAU;kBAHZ,CAKG,IACC,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAE7B,IACG,EAAE,mCAAmC,eAAe,GACpD,EAAE,gCAAgC,iBAAiB,CAChD;;OACL,CAAA;MACI;;IACP,CAAA;GACG,CAAA;EACD,CAAA"}
@@ -40,6 +40,7 @@ function y() {
40
40
  children: [/* @__PURE__ */ g("select", {
41
41
  value: S,
42
42
  onChange: (e) => j(C)(e.target.value),
43
+ "aria-label": x("adaptercloud.adapters.filterByKind", "Filter by kind"),
43
44
  className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
44
45
  children: [/* @__PURE__ */ h("option", {
45
46
  value: "",
@@ -51,6 +52,7 @@ function y() {
51
52
  }), /* @__PURE__ */ g("select", {
52
53
  value: w,
53
54
  onChange: (e) => j(T)(e.target.value),
55
+ "aria-label": x("adaptercloud.adapters.filterByStatus", "Filter by status"),
54
56
  className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
55
57
  children: [/* @__PURE__ */ h("option", {
56
58
  value: "",
@@ -1 +1 @@
1
- {"version":3,"file":"AdaptersListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/adapters/AdaptersListPage.tsx"],"sourcesContent":["/**\n * Adapters List Page\n * @module adaptercloud/pages/adapters\n *\n * The adapter catalog — every reusable connector definition in the workspace.\n * Lists from `adaListAdapters` with kind/status filters and offset pagination\n * (driven by `AdaPageInfo.hasMore`).\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Boxes, Plus, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useAdapters } from '../../hooks/useAdapterCloudApi';\nimport { adapterKindLabels, adapterStatusLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaAdapterKind, AdaAdapterStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nexport function AdaptersListPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [kind, setKind] = useState<AdaAdapterKind | ''>('');\n const [status, setStatus] = useState<AdaAdapterStatus | ''>('');\n const [offset, setOffset] = useState(0);\n\n const query = useAdapters({\n kind: kind || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const resetTo = useCallback(<T,>(setter: (v: T) => void) => {\n return (v: T) => {\n setter(v);\n setOffset(0);\n };\n }, []);\n\n return (\n <PageLayout\n title={t('adaptercloud.adapters.title', 'Adapters')}\n description={t(\n 'adaptercloud.adapters.subtitle',\n 'The catalog of connectors that bridge each class of system into the fabric.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/adapters/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t('adaptercloud.adapters.new', 'New adapter')}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={kind}\n onChange={(e) => resetTo(setKind)(e.target.value as AdaAdapterKind | '')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.adapters.allKinds', 'All kinds')}</option>\n {(Object.keys(adapterKindLabels) as AdaAdapterKind[]).map((k) => (\n <option key={k} value={k}>\n {adapterKindLabels[k]}\n </option>\n ))}\n </select>\n <select\n value={status}\n onChange={(e) => resetTo(setStatus)(e.target.value as AdaAdapterStatus | '')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.adapters.allStatuses', 'All statuses')}</option>\n {(Object.keys(adapterStatusLabels) as AdaAdapterStatus[]).map((s) => (\n <option key={s} value={s}>\n {adapterStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.adapters.emptyTitle', 'No adapters yet')}\n emptyDescription={t(\n 'adaptercloud.adapters.emptyDescription',\n 'Define your first adapter to start bridging a class of system into the fabric.'\n )}\n emptyIcon={<Boxes className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/adapters/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.adapters.new', 'New adapter')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((adapter) => (\n <GlassCard\n key={adapter.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/adapters/${adapter.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/adapters/${adapter.id}`);\n }\n }}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {adapter.name}\n </h3>\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">{adapter.slug}</p>\n </div>\n <StatusBadge status={adapter.status} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5\">\n {adapterKindLabels[adapter.kind]}\n </span>\n <span>v{adapter.version}</span>\n {adapter.vendor && <span>· {adapter.vendor}</span>}\n </div>\n\n {adapter.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">\n {adapter.description}\n </p>\n )}\n\n {adapter.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {adapter.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"inline-flex items-center rounded-full bg-accent-blue-subtle px-2 py-0.5 text-xs text-accent-blue\"\n >\n {cap}\n </span>\n ))}\n {adapter.capabilities.length > 4 && (\n <span className=\"text-xs text-text-secondary\">\n +{adapter.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}{' '}\n {formatRelativeTime(adapter.updatedAt)}\n </p>\n </GlassCard>\n ))}\n </div>\n\n {/* Pagination */}\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AdaptersListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/adapters/AdaptersListPage.tsx"],"sourcesContent":["/**\n * Adapters List Page\n * @module adaptercloud/pages/adapters\n *\n * The adapter catalog — every reusable connector definition in the workspace.\n * Lists from `adaListAdapters` with kind/status filters and offset pagination\n * (driven by `AdaPageInfo.hasMore`).\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Boxes,\n Cloud,\n Cpu,\n Database,\n HardDrive,\n Network,\n Plus,\n Puzzle,\n Radio,\n Search,\n ServerCog,\n X,\n ChevronLeft,\n ChevronRight,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useAdapters } from '../../hooks/useAdapterCloudApi';\nimport { adapterKindLabels, adapterStatusLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaAdapterKind, AdaAdapterStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\n/** Per-kind glyph — mirrors the mock's vendor logo tile on each catalog card. */\nconst kindIcons: Record<AdaAdapterKind, LucideIcon> = {\n PUBLIC_CLOUD: Cloud,\n SAAS: Cloud,\n PRIVATE_CLOUD: ServerCog,\n ON_PREM: HardDrive,\n EDGE: Radio,\n IOT: Cpu,\n KUBERNETES: Boxes,\n NETWORK: Network,\n DATA_SYSTEM: Database,\n CUSTOM: Puzzle,\n};\n\nexport function AdaptersListPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [kind, setKind] = useState<AdaAdapterKind | ''>('');\n const [status, setStatus] = useState<AdaAdapterStatus | ''>('');\n const [offset, setOffset] = useState(0);\n\n const query = useAdapters({\n kind: kind || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const resetTo = useCallback(<T,>(setter: (v: T) => void) => {\n return (v: T) => {\n setter(v);\n setOffset(0);\n };\n }, []);\n\n return (\n <PageLayout\n title={t('adaptercloud.adapters.title', 'Adapters')}\n description={t(\n 'adaptercloud.adapters.subtitle',\n 'The catalog of connectors that bridge each class of system into the fabric.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/adapters/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t('adaptercloud.adapters.new', 'New adapter')}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={kind}\n onChange={(e) => resetTo(setKind)(e.target.value as AdaAdapterKind | '')}\n aria-label={t('adaptercloud.adapters.filterByKind', 'Filter by kind')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.adapters.allKinds', 'All kinds')}</option>\n {(Object.keys(adapterKindLabels) as AdaAdapterKind[]).map((k) => (\n <option key={k} value={k}>\n {adapterKindLabels[k]}\n </option>\n ))}\n </select>\n <select\n value={status}\n onChange={(e) => resetTo(setStatus)(e.target.value as AdaAdapterStatus | '')}\n aria-label={t('adaptercloud.adapters.filterByStatus', 'Filter by status')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.adapters.allStatuses', 'All statuses')}</option>\n {(Object.keys(adapterStatusLabels) as AdaAdapterStatus[]).map((s) => (\n <option key={s} value={s}>\n {adapterStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.adapters.emptyTitle', 'No adapters yet')}\n emptyDescription={t(\n 'adaptercloud.adapters.emptyDescription',\n 'Define your first adapter to start bridging a class of system into the fabric.'\n )}\n emptyIcon={<Boxes className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/adapters/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.adapters.new', 'New adapter')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((adapter) => (\n <GlassCard\n key={adapter.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/adapters/${adapter.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/adapters/${adapter.id}`);\n }\n }}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {adapter.name}\n </h3>\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">{adapter.slug}</p>\n </div>\n <StatusBadge status={adapter.status} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5\">\n {adapterKindLabels[adapter.kind]}\n </span>\n <span>v{adapter.version}</span>\n {adapter.vendor && <span>· {adapter.vendor}</span>}\n </div>\n\n {adapter.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">\n {adapter.description}\n </p>\n )}\n\n {adapter.capabilities.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {adapter.capabilities.slice(0, 4).map((cap) => (\n <span\n key={cap}\n className=\"inline-flex items-center rounded-full bg-accent-blue-subtle px-2 py-0.5 text-xs text-accent-blue\"\n >\n {cap}\n </span>\n ))}\n {adapter.capabilities.length > 4 && (\n <span className=\"text-xs text-text-secondary\">\n +{adapter.capabilities.length - 4}\n </span>\n )}\n </div>\n )}\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}{' '}\n {formatRelativeTime(adapter.updatedAt)}\n </p>\n </GlassCard>\n ))}\n </div>\n\n {/* Pagination */}\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAsCA,IAAM,IAAY;AAgBlB,SAAgB,IAAmB;CACjC,IAAM,EAAE,kBAAe,GAAiB,EAClC,EAAE,GAAG,MAAc,GAAS,EAC5B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAU,CACZ,EAEK,CAAC,GAAM,KAAW,EAA8B,GAAG,EACnD,CAAC,GAAQ,KAAa,EAAgC,GAAG,EACzD,CAAC,GAAQ,KAAa,EAAS,EAAE,EAEjC,IAAQ,EAAY;EACxB,MAAM,KAAQ,KAAA;EACd,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW;GAAQ;EACzC,CAAC,EAEI,IAAQ,EAAM,MAAM,SAAS,EAAE,EAC/B,IAAU,EAAM,MAAM,SAAS,WAAW,IAE1C,IAAU,GAAiB,OACvB,MAAS;AAEf,EADA,EAAO,EAAE,EACT,EAAU,EAAE;IAEb,EAAE,CAAC;AAEN,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,+BAA+B,WAAW;EACnD,aAAa,EACX,kCACA,8EACD;EACD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,gBAAgB;GAC1C,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAE,6BAA6B,cAAc,CACvC;;YAdb,CAkBE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,UAAD;IACE,OAAO;IACP,WAAW,MAAM,EAAQ,EAAQ,CAAC,EAAE,OAAO,MAA6B;IACxE,cAAY,EAAE,sCAAsC,iBAAiB;IACrE,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eAAI,EAAE,kCAAkC,YAAY;KAAU,CAAA,EAC1E,OAAO,KAAK,EAAkB,CAAsB,KAAK,MACzD,kBAAC,UAAD;KAAgB,OAAO;eACpB,EAAkB;KACZ,EAFI,EAEJ,CACT,CACK;OACT,kBAAC,UAAD;IACE,OAAO;IACP,WAAW,MAAM,EAAQ,EAAU,CAAC,EAAE,OAAO,MAA+B;IAC5E,cAAY,EAAE,wCAAwC,mBAAmB;IACzE,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eAAI,EAAE,qCAAqC,eAAe;KAAU,CAAA,EAChF,OAAO,KAAK,EAAoB,CAAwB,KAAK,MAC7D,kBAAC,UAAD;KAAgB,OAAO;eACpB,EAAoB;KACd,EAFI,EAEJ,CACT,CACK;MACL;MAEN,kBAAC,GAAD;GACE,WAAW,EAAM;GACjB,SAAS,EAAM;GACf,SAAS,EAAM,WAAW;GAC1B,OAAO,EAAM;GACb,eAAe,KAAK,EAAM,SAAS;GACnC,YAAY,EAAE,oCAAoC,kBAAkB;GACpE,kBAAkB,EAChB,0CACA,iFACD;GACD,WAAW,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GAC1C,aACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,gBAAgB;IAC1C,WAAU;cAET,EAAE,6BAA6B,cAAc;IACvC,CAAA;aAnBb,CAsBE,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,KAAK,MACV,kBAAC,GAAD;KAEE,MAAK;KACL,UAAU;KACV,eAAe,EAAW,aAAa,EAAQ,KAAK;KACpD,YAAY,MAAM;AAChB,OAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,EAAW,aAAa,EAAQ,KAAK;;KAGzC,WAAU;eAXZ;MAaE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ;SACN,CAAA,EACL,kBAAC,KAAD;SAAG,WAAU;mBAA+C,EAAQ;SAAS,CAAA,CACzE;WACN,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAkB,EAAQ;SACtB,CAAA;QACP,kBAAC,QAAD,EAAA,UAAA,CAAM,KAAE,EAAQ,QAAe,EAAA,CAAA;QAC9B,EAAQ,UAAU,kBAAC,QAAD,EAAA,UAAA,CAAM,MAAG,EAAQ,OAAc,EAAA,CAAA;QAC9C;;MAEL,EAAQ,eACP,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ;OACP,CAAA;MAGL,EAAQ,aAAa,SAAS,KAC7B,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,EAAQ,aAAa,MAAM,GAAG,EAAE,CAAC,KAAK,MACrC,kBAAC,QAAD;QAEE,WAAU;kBAET;QACI,EAJA,EAIA,CACP,EACD,EAAQ,aAAa,SAAS,KAC7B,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA8C,KAC1C,EAAQ,aAAa,SAAS,EAC3B;UAEL;;MAGR,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAE,+BAA+B,UAAU;QAAE;QAC7C,EAAmB,EAAQ,UAAU;QACpC;;MACM;OA1DL,EAAQ,GA0DH,CACZ;IACE,CAAA,GAGJ,IAAS,KAAK,MACd,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,MAAW;MACrB,eAAe,GAAW,MAAM,KAAK,IAAI,GAAG,IAAI,EAAU,CAAC;MAC3D,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAClC,EAAE,4BAA4B,WAAW,CACnC;;KACT,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAE,+BAA+B,UAAU;OAAC;OAAE,IAAS;OAAE;OAAE,IAAS,EAAM;OACtE;;KACP,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,CAAC;MACX,eAAe,GAAW,MAAM,IAAI,EAAU;MAC9C,WAAU;gBAJZ,CAMG,EAAE,4BAA4B,OAAO,EACtC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;KACL;MAEE;KACD"}
@@ -5,243 +5,260 @@ import { StatusBadge as s } from "../../components/StatusBadge.js";
5
5
  import { DataState as c } from "../../components/DataState.js";
6
6
  import { useConnection as l, useDisableConnection as u, useSetConnectionStatus as d, useStartSyncRun as f } from "../../hooks/useAdapterCloudApi.js";
7
7
  import { formatDate as p } from "../../utils/formatters.js";
8
- import { useCallback as m, useState as h } from "react";
9
- import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
- import { ArrowLeft as _, Ban as v, Pencil as y, Play as b, RefreshCw as x, Server as S } from "lucide-react";
11
- import { useParams as C } from "react-router-dom";
12
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
13
- import { GlassCard as E } from "@burdenoff/fe-libs/ui";
8
+ import { ConfirmDialog as m } from "../../components/ConfirmDialog.js";
9
+ import { useCallback as h, useState as g } from "react";
10
+ import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
+ import { ArrowLeft as v, Ban as y, Pencil as b, Play as x, RefreshCw as S, Server as C } from "lucide-react";
12
+ import { useParams as w } from "react-router-dom";
13
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
14
+ import { GlassCard as D } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/adaptercloud/pages/connections/ConnectionDetailPage.tsx
15
- var D = [
16
+ var O = [
16
17
  "CONNECTED",
17
18
  "DEGRADED",
18
19
  "ERROR",
19
20
  "PENDING"
20
- ], O = [
21
+ ], k = [
21
22
  "DISCOVERY",
22
23
  "RECONCILE",
23
24
  "COST_INGEST",
24
25
  "FULL"
25
26
  ];
26
- function k() {
27
- let { id: k } = C(), { navigateTo: A } = e(), { t: j } = g(), M = m((e, t) => {
28
- let n = j(e);
27
+ function A() {
28
+ let { id: A } = w(), { navigateTo: j } = e(), { t: M } = _(), N = h((e, t) => {
29
+ let n = M(e);
29
30
  return n === e ? t : n;
30
- }, [j]), N = l(k), P = d(), F = u(), I = f(), L = N.data, [R, z] = h("DISCOVERY"), [B, V] = h(null), H = m((e) => V(e instanceof Error ? e.message : "Action failed. Please try again."), []), U = m((e) => {
31
- k && (V(null), P.mutate({
32
- id: k,
31
+ }, [M]), P = l(A), F = d(), I = u(), L = f(), R = P.data, [z, B] = g("DISCOVERY"), [V, H] = g(null), [U, W] = g(!1), G = h((e) => H(e instanceof Error ? e.message : "Action failed. Please try again."), []), K = h((e) => {
32
+ A && (H(null), F.mutate({
33
+ id: A,
33
34
  status: e
34
- }, { onError: H }));
35
+ }, { onError: G }));
35
36
  }, [
36
- k,
37
- P,
38
- H
39
- ]), W = m(() => {
40
- k && (V(null), F.mutate(k, { onError: H }));
41
- }, [
42
- k,
37
+ A,
43
38
  F,
44
- H
45
- ]), G = m(() => {
46
- k && (V(null), I.mutate({
47
- connectionId: k,
48
- kind: R
49
- }, {
50
- onSuccess: () => A("/sync-runs"),
51
- onError: H
39
+ G
40
+ ]), q = h(() => {
41
+ A && (H(null), I.mutate(A, {
42
+ onSuccess: () => W(!1),
43
+ onError: G
52
44
  }));
53
45
  }, [
54
- k,
55
- R,
46
+ A,
56
47
  I,
48
+ G
49
+ ]), J = h(() => {
50
+ A && (H(null), L.mutate({
51
+ connectionId: A,
52
+ kind: z
53
+ }, {
54
+ onSuccess: () => j("/sync-runs"),
55
+ onError: G
56
+ }));
57
+ }, [
57
58
  A,
58
- H
59
+ z,
60
+ L,
61
+ j,
62
+ G
59
63
  ]);
60
- return /* @__PURE__ */ w(t, {
61
- title: L?.name ?? M("adaptercloud.connections.detailTitle", "Connection"),
62
- description: L?.adapter?.name,
63
- actions: /* @__PURE__ */ T("div", {
64
+ return /* @__PURE__ */ E(t, {
65
+ title: R?.name ?? N("adaptercloud.connections.detailTitle", "Connection"),
66
+ description: R?.adapter?.name,
67
+ actions: /* @__PURE__ */ E("div", {
64
68
  className: "flex items-center gap-2",
65
- children: [/* @__PURE__ */ T("button", {
69
+ children: [/* @__PURE__ */ E("button", {
66
70
  type: "button",
67
- onClick: () => A("/connections"),
71
+ onClick: () => j("/connections"),
68
72
  className: "inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent",
69
- children: [/* @__PURE__ */ w(_, { className: "h-4 w-4" }), M("adaptercloud.common.back", "Back")]
70
- }), L && /* @__PURE__ */ T("button", {
73
+ children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), N("adaptercloud.common.back", "Back")]
74
+ }), R && /* @__PURE__ */ E("button", {
71
75
  type: "button",
72
- onClick: () => A(`/connections/${L.id}/edit`),
76
+ onClick: () => j(`/connections/${R.id}/edit`),
73
77
  className: "inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent",
74
- children: [/* @__PURE__ */ w(y, { className: "h-4 w-4" }), M("adaptercloud.common.edit", "Edit")]
78
+ children: [/* @__PURE__ */ T(b, { className: "h-4 w-4" }), N("adaptercloud.common.edit", "Edit")]
75
79
  })]
76
80
  }),
77
- children: /* @__PURE__ */ w(c, {
78
- isLoading: N.isLoading,
79
- isError: N.isError,
80
- isEmpty: !N.isLoading && !L,
81
- error: N.error,
82
- onRetry: () => void N.refetch(),
83
- emptyTitle: M("adaptercloud.connections.notFound", "Connection not found"),
84
- children: L && /* @__PURE__ */ T("div", {
81
+ children: [/* @__PURE__ */ T(c, {
82
+ isLoading: P.isLoading,
83
+ isError: P.isError,
84
+ isEmpty: !P.isLoading && !R,
85
+ error: P.error,
86
+ onRetry: () => void P.refetch(),
87
+ emptyTitle: N("adaptercloud.connections.notFound", "Connection not found"),
88
+ children: R && /* @__PURE__ */ E("div", {
85
89
  className: "space-y-6",
86
90
  children: [
87
- B && /* @__PURE__ */ w("p", {
91
+ V && /* @__PURE__ */ T("p", {
92
+ role: "alert",
93
+ "aria-live": "polite",
88
94
  className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
89
- children: B
95
+ children: V
90
96
  }),
91
- /* @__PURE__ */ T(E, {
97
+ /* @__PURE__ */ E(D, {
92
98
  className: "p-6",
93
99
  children: [
94
- /* @__PURE__ */ T("div", {
100
+ /* @__PURE__ */ E("div", {
95
101
  className: "mb-4 flex flex-wrap items-center gap-3",
96
- children: [/* @__PURE__ */ w(s, { status: L.status }), /* @__PURE__ */ w("span", {
102
+ children: [/* @__PURE__ */ T(s, { status: R.status }), /* @__PURE__ */ T("span", {
97
103
  className: "inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
98
- children: n[L.authMethod]
104
+ children: n[R.authMethod]
99
105
  })]
100
106
  }),
101
- /* @__PURE__ */ T("dl", {
107
+ /* @__PURE__ */ E("dl", {
102
108
  className: "grid grid-cols-2 gap-4 text-sm sm:grid-cols-4",
103
109
  children: [
104
- /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
110
+ /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
105
111
  className: "text-text-secondary",
106
- children: M("adaptercloud.connections.region", "Region")
107
- }), /* @__PURE__ */ w("dd", {
112
+ children: N("adaptercloud.connections.region", "Region")
113
+ }), /* @__PURE__ */ T("dd", {
108
114
  className: "mt-0.5 font-medium text-text-primary",
109
- children: L.region ?? "—"
115
+ children: R.region ?? "—"
110
116
  })] }),
111
- /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
117
+ /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
112
118
  className: "text-text-secondary",
113
- children: M("adaptercloud.connections.environment", "Environment")
114
- }), /* @__PURE__ */ w("dd", {
119
+ children: N("adaptercloud.connections.environment", "Environment")
120
+ }), /* @__PURE__ */ T("dd", {
115
121
  className: "mt-0.5 font-medium text-text-primary",
116
- children: L.environment ?? "—"
122
+ children: R.environment ?? "—"
117
123
  })] }),
118
- /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
124
+ /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
119
125
  className: "text-text-secondary",
120
- children: M("adaptercloud.connections.resourceCount", "Resources")
121
- }), /* @__PURE__ */ w("dd", {
126
+ children: N("adaptercloud.connections.resourceCount", "Resources")
127
+ }), /* @__PURE__ */ T("dd", {
122
128
  className: "mt-0.5 font-medium tabular-nums text-text-primary",
123
- children: L.resourceCount
129
+ children: R.resourceCount
124
130
  })] }),
125
- /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("dt", {
131
+ /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
126
132
  className: "text-text-secondary",
127
- children: M("adaptercloud.connections.lastSynced", "Last synced")
128
- }), /* @__PURE__ */ w("dd", {
133
+ children: N("adaptercloud.connections.lastSynced", "Last synced")
134
+ }), /* @__PURE__ */ T("dd", {
129
135
  className: "mt-0.5 font-medium text-text-primary",
130
- children: L.lastSyncedAt ? p(L.lastSyncedAt) : "—"
136
+ children: R.lastSyncedAt ? p(R.lastSyncedAt) : "—"
131
137
  })] })
132
138
  ]
133
139
  }),
134
- L.lastError && /* @__PURE__ */ w("p", {
140
+ R.lastError && /* @__PURE__ */ T("p", {
135
141
  className: "mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
136
- children: L.lastError
142
+ children: R.lastError
137
143
  }),
138
- /* @__PURE__ */ T("div", {
144
+ /* @__PURE__ */ E("div", {
139
145
  className: "mt-5 border-t border-border-subtle pt-5",
140
- children: [/* @__PURE__ */ w("h3", {
146
+ children: [/* @__PURE__ */ T("h3", {
141
147
  className: "mb-3 text-sm font-semibold text-text-primary",
142
- children: M("adaptercloud.connections.setHealth", "Set health")
143
- }), /* @__PURE__ */ T("div", {
148
+ children: N("adaptercloud.connections.setHealth", "Set health")
149
+ }), /* @__PURE__ */ E("div", {
144
150
  className: "flex flex-wrap gap-2",
145
- children: [D.map((e) => /* @__PURE__ */ w("button", {
151
+ children: [O.map((e) => /* @__PURE__ */ T("button", {
146
152
  type: "button",
147
- disabled: P.isPending || L.status === e,
148
- onClick: () => U(e),
153
+ disabled: F.isPending || R.status === e,
154
+ onClick: () => K(e),
149
155
  className: "rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40",
150
156
  children: r[e]
151
- }, e)), L.status !== "DISABLED" && /* @__PURE__ */ T("button", {
157
+ }, e)), R.status !== "DISABLED" && /* @__PURE__ */ E("button", {
152
158
  type: "button",
153
- disabled: F.isPending,
154
- onClick: W,
159
+ disabled: I.isPending,
160
+ onClick: () => W(!0),
155
161
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-40",
156
- children: [/* @__PURE__ */ w(v, { className: "h-3.5 w-3.5" }), M("adaptercloud.connections.disable", "Disable")]
162
+ children: [/* @__PURE__ */ T(y, { className: "h-3.5 w-3.5" }), N("adaptercloud.connections.disable", "Disable")]
157
163
  })]
158
164
  })]
159
165
  }),
160
- /* @__PURE__ */ T("div", {
166
+ /* @__PURE__ */ E("div", {
161
167
  className: "mt-5 border-t border-border-subtle pt-5",
162
- children: [/* @__PURE__ */ T("h3", {
168
+ children: [/* @__PURE__ */ E("h3", {
163
169
  className: "mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary",
164
- children: [/* @__PURE__ */ w(x, { className: "h-4 w-4" }), M("adaptercloud.connections.startSync", "Start a sync run")]
165
- }), /* @__PURE__ */ T("div", {
170
+ children: [/* @__PURE__ */ T(S, { className: "h-4 w-4" }), N("adaptercloud.connections.startSync", "Start a sync run")]
171
+ }), /* @__PURE__ */ E("div", {
166
172
  className: "flex flex-wrap items-center gap-3",
167
- children: [/* @__PURE__ */ w("select", {
168
- value: R,
169
- onChange: (e) => z(e.target.value),
173
+ children: [/* @__PURE__ */ T("select", {
174
+ value: z,
175
+ onChange: (e) => B(e.target.value),
176
+ "aria-label": N("adaptercloud.connections.syncKind", "Sync run kind"),
170
177
  className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
171
- children: O.map((e) => /* @__PURE__ */ w("option", {
178
+ children: k.map((e) => /* @__PURE__ */ T("option", {
172
179
  value: e,
173
180
  children: o[e]
174
181
  }, e))
175
- }), /* @__PURE__ */ T("button", {
182
+ }), /* @__PURE__ */ E("button", {
176
183
  type: "button",
177
- disabled: I.isPending,
178
- onClick: G,
184
+ disabled: L.isPending,
185
+ onClick: J,
179
186
  className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50",
180
- children: [/* @__PURE__ */ w(b, { className: "h-4 w-4" }), M("adaptercloud.connections.run", "Run")]
187
+ children: [/* @__PURE__ */ T(x, { className: "h-4 w-4" }), N("adaptercloud.connections.run", "Run")]
181
188
  })]
182
189
  })]
183
190
  })
184
191
  ]
185
192
  }),
186
- L.config != null && /* @__PURE__ */ T(E, {
193
+ R.config != null && /* @__PURE__ */ E(D, {
187
194
  className: "p-6",
188
- children: [/* @__PURE__ */ w("h3", {
195
+ children: [/* @__PURE__ */ T("h3", {
189
196
  className: "mb-3 text-sm font-semibold text-text-primary",
190
- children: M("adaptercloud.connections.config", "Configuration")
191
- }), /* @__PURE__ */ w("pre", {
197
+ children: N("adaptercloud.connections.config", "Configuration")
198
+ }), /* @__PURE__ */ T("pre", {
192
199
  className: "overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary",
193
- children: JSON.stringify(L.config, null, 2)
200
+ children: JSON.stringify(R.config, null, 2)
194
201
  })]
195
202
  }),
196
- /* @__PURE__ */ T("section", { children: [/* @__PURE__ */ T("div", {
203
+ /* @__PURE__ */ E("section", { children: [/* @__PURE__ */ E("div", {
197
204
  className: "mb-3 flex items-center justify-between",
198
- children: [/* @__PURE__ */ w("h3", {
205
+ children: [/* @__PURE__ */ T("h3", {
199
206
  className: "text-base font-semibold text-text-primary",
200
- children: M("adaptercloud.connections.discoveredResources", "Discovered Resources")
201
- }), /* @__PURE__ */ w("button", {
207
+ children: N("adaptercloud.connections.discoveredResources", "Discovered Resources")
208
+ }), /* @__PURE__ */ T("button", {
202
209
  type: "button",
203
- onClick: () => A(`/resources?connectionId=${L.id}`),
204
- className: "text-sm font-medium text-primary hover:underline",
205
- children: M("adaptercloud.common.viewAll", "View all")
210
+ onClick: () => j(`/resources?connectionId=${R.id}`),
211
+ className: "text-sm font-medium text-accent-blue hover:underline",
212
+ children: N("adaptercloud.common.viewAll", "View all")
206
213
  })]
207
- }), L.resources && L.resources.length > 0 ? /* @__PURE__ */ w("div", {
214
+ }), R.resources && R.resources.length > 0 ? /* @__PURE__ */ T("div", {
208
215
  className: "divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface",
209
- children: L.resources.slice(0, 10).map((e) => /* @__PURE__ */ T("button", {
216
+ children: R.resources.slice(0, 10).map((e) => /* @__PURE__ */ E("button", {
210
217
  type: "button",
211
- onClick: () => A(`/resources/${e.id}`),
218
+ onClick: () => j(`/resources/${e.id}`),
212
219
  className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50",
213
220
  children: [
214
- /* @__PURE__ */ w(S, { className: "h-4 w-4 shrink-0 text-text-secondary" }),
215
- /* @__PURE__ */ T("div", {
221
+ /* @__PURE__ */ T(C, { className: "h-4 w-4 shrink-0 text-text-secondary" }),
222
+ /* @__PURE__ */ E("div", {
216
223
  className: "min-w-0 flex-1",
217
- children: [/* @__PURE__ */ w("p", {
224
+ children: [/* @__PURE__ */ T("p", {
218
225
  className: "truncate text-sm font-medium text-text-primary",
219
226
  children: e.name
220
- }), /* @__PURE__ */ T("p", {
227
+ }), /* @__PURE__ */ E("p", {
221
228
  className: "mt-0.5 text-xs text-text-secondary",
222
229
  children: [e.resourceType, e.region ? ` · ${e.region}` : ""]
223
230
  })]
224
231
  }),
225
- /* @__PURE__ */ w("span", {
232
+ /* @__PURE__ */ T("span", {
226
233
  className: "hidden text-xs text-text-secondary sm:inline",
227
234
  children: i(e.monthlyCost)
228
235
  }),
229
- /* @__PURE__ */ w(s, {
236
+ /* @__PURE__ */ T(s, {
230
237
  status: e.state,
231
238
  label: a[e.state]
232
239
  })
233
240
  ]
234
241
  }, e.id))
235
- }) : /* @__PURE__ */ w("p", {
242
+ }) : /* @__PURE__ */ T("p", {
236
243
  className: "rounded-xl border border-border-subtle bg-bg-surface px-4 py-6 text-center text-sm text-text-secondary",
237
- children: M("adaptercloud.connections.noResources", "No resources discovered yet. Start a discovery sync run.")
244
+ children: N("adaptercloud.connections.noResources", "No resources discovered yet. Start a discovery sync run.")
238
245
  })] })
239
246
  ]
240
247
  })
241
- })
248
+ }), /* @__PURE__ */ T(m, {
249
+ open: U,
250
+ onOpenChange: W,
251
+ title: N("adaptercloud.connections.disableTitle", "Disable this connection?"),
252
+ description: N("adaptercloud.connections.disablePrompt", "Disabling stops discovery and sync runs against this target. You can re-enable it later by setting its health."),
253
+ confirmLabel: N("adaptercloud.connections.disable", "Disable"),
254
+ cancelLabel: N("adaptercloud.common.cancel", "Cancel"),
255
+ onConfirm: q,
256
+ isPending: I.isPending,
257
+ tone: "danger"
258
+ })]
242
259
  });
243
260
  }
244
261
  //#endregion
245
- export { k as ConnectionDetailPage };
262
+ export { A as ConnectionDetailPage };
246
263
 
247
264
  //# sourceMappingURL=ConnectionDetailPage.js.map