@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.
- package/dist/adaptercloud/AdapterCloudRoot.js +52 -44
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoutes.js +22 -14
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.js +27 -0
- package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -0
- package/dist/adaptercloud/components/DataState.js +1 -1
- package/dist/adaptercloud/components/DataState.js.map +1 -1
- package/dist/adaptercloud/components/viz/BarChart.js +32 -0
- package/dist/adaptercloud/components/viz/BarChart.js.map +1 -0
- package/dist/adaptercloud/components/viz/Sparkline.js +50 -0
- package/dist/adaptercloud/components/viz/Sparkline.js.map +1 -0
- package/dist/adaptercloud/components/viz/StackedBar.js +1 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js +96 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js.map +1 -0
- package/dist/adaptercloud/components/viz/index.js +4 -0
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +71 -34
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +159 -156
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +179 -113
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +263 -179
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +2 -0
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +141 -124
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +2 -0
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -93
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +183 -109
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +172 -71
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +179 -96
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +109 -91
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js +211 -184
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +183 -127
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +121 -84
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +236 -153
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/topology/TopologyPage.js +397 -0
- package/dist/adaptercloud/pages/topology/TopologyPage.js.map +1 -0
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +192 -131
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- 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":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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
|
|
16
|
+
var O = [
|
|
16
17
|
"CONNECTED",
|
|
17
18
|
"DEGRADED",
|
|
18
19
|
"ERROR",
|
|
19
20
|
"PENDING"
|
|
20
|
-
],
|
|
21
|
+
], k = [
|
|
21
22
|
"DISCOVERY",
|
|
22
23
|
"RECONCILE",
|
|
23
24
|
"COST_INGEST",
|
|
24
25
|
"FULL"
|
|
25
26
|
];
|
|
26
|
-
function
|
|
27
|
-
let { id:
|
|
28
|
-
let n =
|
|
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
|
-
}, [
|
|
31
|
-
|
|
32
|
-
id:
|
|
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:
|
|
35
|
+
}, { onError: G }));
|
|
35
36
|
}, [
|
|
36
|
-
|
|
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
|
-
|
|
45
|
-
]),
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
z,
|
|
60
|
+
L,
|
|
61
|
+
j,
|
|
62
|
+
G
|
|
59
63
|
]);
|
|
60
|
-
return /* @__PURE__ */
|
|
61
|
-
title:
|
|
62
|
-
description:
|
|
63
|
-
actions: /* @__PURE__ */
|
|
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__ */
|
|
69
|
+
children: [/* @__PURE__ */ E("button", {
|
|
66
70
|
type: "button",
|
|
67
|
-
onClick: () =>
|
|
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__ */
|
|
70
|
-
}),
|
|
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: () =>
|
|
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__ */
|
|
78
|
+
children: [/* @__PURE__ */ T(b, { className: "h-4 w-4" }), N("adaptercloud.common.edit", "Edit")]
|
|
75
79
|
})]
|
|
76
80
|
}),
|
|
77
|
-
children: /* @__PURE__ */
|
|
78
|
-
isLoading:
|
|
79
|
-
isError:
|
|
80
|
-
isEmpty: !
|
|
81
|
-
error:
|
|
82
|
-
onRetry: () => void
|
|
83
|
-
emptyTitle:
|
|
84
|
-
children:
|
|
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
|
-
|
|
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:
|
|
95
|
+
children: V
|
|
90
96
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
97
|
+
/* @__PURE__ */ E(D, {
|
|
92
98
|
className: "p-6",
|
|
93
99
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ E("div", {
|
|
95
101
|
className: "mb-4 flex flex-wrap items-center gap-3",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
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[
|
|
104
|
+
children: n[R.authMethod]
|
|
99
105
|
})]
|
|
100
106
|
}),
|
|
101
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ E("dl", {
|
|
102
108
|
className: "grid grid-cols-2 gap-4 text-sm sm:grid-cols-4",
|
|
103
109
|
children: [
|
|
104
|
-
/* @__PURE__ */
|
|
110
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
|
|
105
111
|
className: "text-text-secondary",
|
|
106
|
-
children:
|
|
107
|
-
}), /* @__PURE__ */
|
|
112
|
+
children: N("adaptercloud.connections.region", "Region")
|
|
113
|
+
}), /* @__PURE__ */ T("dd", {
|
|
108
114
|
className: "mt-0.5 font-medium text-text-primary",
|
|
109
|
-
children:
|
|
115
|
+
children: R.region ?? "—"
|
|
110
116
|
})] }),
|
|
111
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
|
|
112
118
|
className: "text-text-secondary",
|
|
113
|
-
children:
|
|
114
|
-
}), /* @__PURE__ */
|
|
119
|
+
children: N("adaptercloud.connections.environment", "Environment")
|
|
120
|
+
}), /* @__PURE__ */ T("dd", {
|
|
115
121
|
className: "mt-0.5 font-medium text-text-primary",
|
|
116
|
-
children:
|
|
122
|
+
children: R.environment ?? "—"
|
|
117
123
|
})] }),
|
|
118
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
|
|
119
125
|
className: "text-text-secondary",
|
|
120
|
-
children:
|
|
121
|
-
}), /* @__PURE__ */
|
|
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:
|
|
129
|
+
children: R.resourceCount
|
|
124
130
|
})] }),
|
|
125
|
-
/* @__PURE__ */
|
|
131
|
+
/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
|
|
126
132
|
className: "text-text-secondary",
|
|
127
|
-
children:
|
|
128
|
-
}), /* @__PURE__ */
|
|
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:
|
|
136
|
+
children: R.lastSyncedAt ? p(R.lastSyncedAt) : "—"
|
|
131
137
|
})] })
|
|
132
138
|
]
|
|
133
139
|
}),
|
|
134
|
-
|
|
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:
|
|
142
|
+
children: R.lastError
|
|
137
143
|
}),
|
|
138
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ E("div", {
|
|
139
145
|
className: "mt-5 border-t border-border-subtle pt-5",
|
|
140
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ T("h3", {
|
|
141
147
|
className: "mb-3 text-sm font-semibold text-text-primary",
|
|
142
|
-
children:
|
|
143
|
-
}), /* @__PURE__ */
|
|
148
|
+
children: N("adaptercloud.connections.setHealth", "Set health")
|
|
149
|
+
}), /* @__PURE__ */ E("div", {
|
|
144
150
|
className: "flex flex-wrap gap-2",
|
|
145
|
-
children: [
|
|
151
|
+
children: [O.map((e) => /* @__PURE__ */ T("button", {
|
|
146
152
|
type: "button",
|
|
147
|
-
disabled:
|
|
148
|
-
onClick: () =>
|
|
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)),
|
|
157
|
+
}, e)), R.status !== "DISABLED" && /* @__PURE__ */ E("button", {
|
|
152
158
|
type: "button",
|
|
153
|
-
disabled:
|
|
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__ */
|
|
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__ */
|
|
166
|
+
/* @__PURE__ */ E("div", {
|
|
161
167
|
className: "mt-5 border-t border-border-subtle pt-5",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
165
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
168
|
-
value:
|
|
169
|
-
onChange: (e) =>
|
|
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:
|
|
178
|
+
children: k.map((e) => /* @__PURE__ */ T("option", {
|
|
172
179
|
value: e,
|
|
173
180
|
children: o[e]
|
|
174
181
|
}, e))
|
|
175
|
-
}), /* @__PURE__ */
|
|
182
|
+
}), /* @__PURE__ */ E("button", {
|
|
176
183
|
type: "button",
|
|
177
|
-
disabled:
|
|
178
|
-
onClick:
|
|
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__ */
|
|
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
|
-
|
|
193
|
+
R.config != null && /* @__PURE__ */ E(D, {
|
|
187
194
|
className: "p-6",
|
|
188
|
-
children: [/* @__PURE__ */
|
|
195
|
+
children: [/* @__PURE__ */ T("h3", {
|
|
189
196
|
className: "mb-3 text-sm font-semibold text-text-primary",
|
|
190
|
-
children:
|
|
191
|
-
}), /* @__PURE__ */
|
|
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(
|
|
200
|
+
children: JSON.stringify(R.config, null, 2)
|
|
194
201
|
})]
|
|
195
202
|
}),
|
|
196
|
-
/* @__PURE__ */
|
|
203
|
+
/* @__PURE__ */ E("section", { children: [/* @__PURE__ */ E("div", {
|
|
197
204
|
className: "mb-3 flex items-center justify-between",
|
|
198
|
-
children: [/* @__PURE__ */
|
|
205
|
+
children: [/* @__PURE__ */ T("h3", {
|
|
199
206
|
className: "text-base font-semibold text-text-primary",
|
|
200
|
-
children:
|
|
201
|
-
}), /* @__PURE__ */
|
|
207
|
+
children: N("adaptercloud.connections.discoveredResources", "Discovered Resources")
|
|
208
|
+
}), /* @__PURE__ */ T("button", {
|
|
202
209
|
type: "button",
|
|
203
|
-
onClick: () =>
|
|
204
|
-
className: "text-sm font-medium text-
|
|
205
|
-
children:
|
|
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
|
-
}),
|
|
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:
|
|
216
|
+
children: R.resources.slice(0, 10).map((e) => /* @__PURE__ */ E("button", {
|
|
210
217
|
type: "button",
|
|
211
|
-
onClick: () =>
|
|
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__ */
|
|
215
|
-
/* @__PURE__ */
|
|
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__ */
|
|
224
|
+
children: [/* @__PURE__ */ T("p", {
|
|
218
225
|
className: "truncate text-sm font-medium text-text-primary",
|
|
219
226
|
children: e.name
|
|
220
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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:
|
|
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 {
|
|
262
|
+
export { A as ConnectionDetailPage };
|
|
246
263
|
|
|
247
264
|
//# sourceMappingURL=ConnectionDetailPage.js.map
|