@burdenoff/microfe-adaptercloud 2026.706.2 → 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 +18 -14
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.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 +22 -4
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +176 -123
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +261 -179
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -94
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +160 -111
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +163 -84
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +177 -96
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +145 -131
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +10 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +226 -165
- 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 +184 -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\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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"}
|
|
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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"}
|
|
@@ -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 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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"}
|
|
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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"}
|
|
@@ -3,159 +3,223 @@ import { PageLayout as t } from "../../components/PageLayout.js";
|
|
|
3
3
|
import { authMethodLabels as n, connectionStatusLabels as r } from "../../utils/domain.js";
|
|
4
4
|
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
5
5
|
import { DataState as a } from "../../components/DataState.js";
|
|
6
|
-
import {
|
|
7
|
-
import { formatRelativeTime as
|
|
8
|
-
import { useCallback as
|
|
9
|
-
import { useI18n as
|
|
10
|
-
import { Cable as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { GlassCard as
|
|
6
|
+
import { useAdapters as o, useConnections as s } from "../../hooks/useAdapterCloudApi.js";
|
|
7
|
+
import { formatRelativeTime as c } from "../../utils/formatters.js";
|
|
8
|
+
import { useCallback as l, useMemo as u, useState as d } from "react";
|
|
9
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
|
+
import { Boxes as p, Cable as m, ChevronLeft as h, ChevronRight as g, Cloud as _, Cpu as v, Database as y, HardDrive as b, MapPin as x, Network as S, Plus as C, Puzzle as w, Radio as T, Search as E, Server as D } from "lucide-react";
|
|
11
|
+
import { jsx as O, jsxs as k } from "react/jsx-runtime";
|
|
12
|
+
import { GlassCard as A } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
//#region src/adaptercloud/pages/connections/ConnectionsListPage.tsx
|
|
14
|
-
var
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
var j = 20, M = {
|
|
15
|
+
PUBLIC_CLOUD: _,
|
|
16
|
+
PRIVATE_CLOUD: _,
|
|
17
|
+
SAAS: _,
|
|
18
|
+
ON_PREM: b,
|
|
19
|
+
EDGE: T,
|
|
20
|
+
IOT: v,
|
|
21
|
+
KUBERNETES: p,
|
|
22
|
+
NETWORK: S,
|
|
23
|
+
DATA_SYSTEM: y,
|
|
24
|
+
CUSTOM: w
|
|
25
|
+
};
|
|
26
|
+
function N() {
|
|
27
|
+
let { navigateTo: p } = e(), { t: _ } = f(), v = l((e, t) => {
|
|
28
|
+
let n = _(e);
|
|
18
29
|
return n === e ? t : n;
|
|
19
|
-
}, [
|
|
20
|
-
|
|
30
|
+
}, [_]), [y, b] = d(""), [S, w] = d(""), [T, N] = d(""), [P, F] = d(0), I = s({
|
|
31
|
+
adapterId: S || void 0,
|
|
32
|
+
status: y || void 0,
|
|
21
33
|
pagination: {
|
|
22
|
-
limit:
|
|
23
|
-
offset:
|
|
34
|
+
limit: j,
|
|
35
|
+
offset: P
|
|
24
36
|
}
|
|
25
|
-
}),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
}), L = o({
|
|
38
|
+
status: "PUBLISHED",
|
|
39
|
+
pagination: { limit: 100 }
|
|
40
|
+
}).data?.items ?? [], R = I.data?.items ?? [], z = I.data?.pageInfo.hasMore ?? !1, B = u(() => {
|
|
41
|
+
let e = T.trim().toLowerCase();
|
|
42
|
+
return e ? R.filter((t) => t.name.toLowerCase().includes(e) || (t.region?.toLowerCase().includes(e) ?? !1) || (t.environment?.toLowerCase().includes(e) ?? !1)) : R;
|
|
43
|
+
}, [R, T]);
|
|
44
|
+
return /* @__PURE__ */ k(t, {
|
|
45
|
+
title: v("adaptercloud.connections.title", "Connections"),
|
|
46
|
+
description: v("adaptercloud.connections.subtitle", "Configured, authenticated targets — each connection is where the fabric actually reaches."),
|
|
47
|
+
actions: /* @__PURE__ */ k("button", {
|
|
30
48
|
type: "button",
|
|
31
|
-
onClick: () =>
|
|
49
|
+
onClick: () => p("/connections/new"),
|
|
32
50
|
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",
|
|
33
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ O(C, { className: "h-4 w-4" }), v("adaptercloud.connections.new", "New connection")]
|
|
34
52
|
}),
|
|
35
|
-
children: [/* @__PURE__ */
|
|
36
|
-
className: "mb-6 flex flex-wrap gap-3",
|
|
37
|
-
children:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
children: [/* @__PURE__ */ k("div", {
|
|
54
|
+
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ k("select", {
|
|
57
|
+
value: y,
|
|
58
|
+
onChange: (e) => {
|
|
59
|
+
b(e.target.value), F(0);
|
|
60
|
+
},
|
|
61
|
+
"aria-label": v("adaptercloud.connections.filterByStatus", "Filter by status"),
|
|
62
|
+
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)]",
|
|
63
|
+
children: [/* @__PURE__ */ O("option", {
|
|
64
|
+
value: "",
|
|
65
|
+
children: v("adaptercloud.connections.allStatuses", "All statuses")
|
|
66
|
+
}), Object.keys(r).map((e) => /* @__PURE__ */ O("option", {
|
|
67
|
+
value: e,
|
|
68
|
+
children: r[e]
|
|
69
|
+
}, e))]
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ k("select", {
|
|
72
|
+
value: S,
|
|
73
|
+
onChange: (e) => {
|
|
74
|
+
w(e.target.value), F(0);
|
|
75
|
+
},
|
|
76
|
+
"aria-label": v("adaptercloud.connections.filterByAdapter", "Filter by adapter"),
|
|
77
|
+
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)]",
|
|
78
|
+
children: [/* @__PURE__ */ O("option", {
|
|
79
|
+
value: "",
|
|
80
|
+
children: v("adaptercloud.connections.allAdapters", "All adapters")
|
|
81
|
+
}), L.map((e) => /* @__PURE__ */ O("option", {
|
|
82
|
+
value: e.id,
|
|
83
|
+
children: e.name
|
|
84
|
+
}, e.id))]
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ k("div", {
|
|
87
|
+
className: "relative w-full sm:ml-auto sm:w-64",
|
|
88
|
+
children: [/* @__PURE__ */ O(E, {
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary"
|
|
91
|
+
}), /* @__PURE__ */ O("input", {
|
|
92
|
+
type: "search",
|
|
93
|
+
value: T,
|
|
94
|
+
onChange: (e) => N(e.target.value),
|
|
95
|
+
"aria-label": v("adaptercloud.connections.search", "Search connections"),
|
|
96
|
+
placeholder: v("adaptercloud.connections.searchPlaceholder", "Search…"),
|
|
97
|
+
className: "w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
98
|
+
})]
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
}), /* @__PURE__ */ k(a, {
|
|
102
|
+
isLoading: I.isLoading,
|
|
103
|
+
isError: I.isError,
|
|
104
|
+
isEmpty: R.length === 0,
|
|
105
|
+
error: I.error,
|
|
106
|
+
onRetry: () => void I.refetch(),
|
|
107
|
+
emptyTitle: v("adaptercloud.connections.emptyTitle", "No connections yet"),
|
|
108
|
+
emptyDescription: v("adaptercloud.connections.emptyDescription", "Create a connection to a real target to start discovering resources."),
|
|
109
|
+
emptyIcon: /* @__PURE__ */ O(m, { className: "h-10 w-10" }),
|
|
110
|
+
emptyAction: /* @__PURE__ */ O("button", {
|
|
62
111
|
type: "button",
|
|
63
|
-
onClick: () =>
|
|
112
|
+
onClick: () => p("/connections/new"),
|
|
64
113
|
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
65
|
-
children:
|
|
114
|
+
children: v("adaptercloud.connections.new", "New connection")
|
|
66
115
|
}),
|
|
67
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [B.length === 0 ? /* @__PURE__ */ O("p", {
|
|
117
|
+
className: "rounded-xl border border-border-subtle bg-bg-surface px-4 py-10 text-center text-sm text-text-secondary",
|
|
118
|
+
children: v("adaptercloud.connections.noSearchMatch", "No connections match your search.")
|
|
119
|
+
}) : /* @__PURE__ */ O("div", {
|
|
68
120
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
69
|
-
children:
|
|
121
|
+
children: B.map((e) => /* @__PURE__ */ k(A, {
|
|
70
122
|
role: "button",
|
|
71
123
|
tabIndex: 0,
|
|
72
|
-
onClick: () =>
|
|
124
|
+
onClick: () => p(`/connections/${e.id}`),
|
|
73
125
|
onKeyDown: (t) => {
|
|
74
|
-
(t.key === "Enter" || t.key === " ") && (t.preventDefault(),
|
|
126
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), p(`/connections/${e.id}`));
|
|
75
127
|
},
|
|
76
128
|
className: "cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
77
129
|
children: [
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
className: "flex items-start
|
|
80
|
-
children: [/* @__PURE__ */
|
|
81
|
-
className: "
|
|
82
|
-
children:
|
|
83
|
-
className: "
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
/* @__PURE__ */ k("div", {
|
|
131
|
+
className: "flex items-start gap-3",
|
|
132
|
+
children: [/* @__PURE__ */ O("div", {
|
|
133
|
+
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-accent-blue",
|
|
134
|
+
children: /* @__PURE__ */ O(e.adapter?.kind ? M[e.adapter.kind] : m, {
|
|
135
|
+
className: "h-5 w-5",
|
|
136
|
+
"aria-hidden": "true"
|
|
137
|
+
})
|
|
138
|
+
}), /* @__PURE__ */ O("div", {
|
|
139
|
+
className: "min-w-0 flex-1",
|
|
140
|
+
children: /* @__PURE__ */ k("div", {
|
|
141
|
+
className: "flex items-start justify-between gap-2",
|
|
142
|
+
children: [/* @__PURE__ */ k("div", {
|
|
143
|
+
className: "min-w-0",
|
|
144
|
+
children: [/* @__PURE__ */ O("h3", {
|
|
145
|
+
className: "truncate text-base font-semibold text-text-primary",
|
|
146
|
+
children: e.name
|
|
147
|
+
}), e.adapter && /* @__PURE__ */ O("p", {
|
|
148
|
+
className: "mt-0.5 truncate text-xs text-text-secondary",
|
|
149
|
+
children: e.adapter.name
|
|
150
|
+
})]
|
|
151
|
+
}), /* @__PURE__ */ O(i, { status: e.status })]
|
|
152
|
+
})
|
|
153
|
+
})]
|
|
90
154
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
155
|
+
/* @__PURE__ */ k("div", {
|
|
92
156
|
className: "mt-3 flex flex-wrap items-center gap-3 text-xs text-text-secondary",
|
|
93
157
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ O("span", {
|
|
95
159
|
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5",
|
|
96
160
|
children: n[e.authMethod]
|
|
97
161
|
}),
|
|
98
|
-
e.region && /* @__PURE__ */
|
|
162
|
+
e.region && /* @__PURE__ */ k("span", {
|
|
99
163
|
className: "flex items-center gap-1",
|
|
100
164
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ O(x, { className: "h-3 w-3" }),
|
|
102
166
|
" ",
|
|
103
167
|
e.region
|
|
104
168
|
]
|
|
105
169
|
}),
|
|
106
|
-
e.environment && /* @__PURE__ */
|
|
170
|
+
e.environment && /* @__PURE__ */ k("span", { children: ["· ", e.environment] })
|
|
107
171
|
]
|
|
108
172
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ k("div", {
|
|
110
174
|
className: "mt-4 flex items-center gap-2 text-sm text-text-primary",
|
|
111
175
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
113
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ O(D, { className: "h-4 w-4 text-text-secondary" }),
|
|
177
|
+
/* @__PURE__ */ O("span", {
|
|
114
178
|
className: "font-semibold tabular-nums",
|
|
115
179
|
children: e.resourceCount
|
|
116
180
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
181
|
+
/* @__PURE__ */ O("span", {
|
|
118
182
|
className: "text-text-secondary",
|
|
119
|
-
children:
|
|
183
|
+
children: v("adaptercloud.connections.resources", "resources")
|
|
120
184
|
})
|
|
121
185
|
]
|
|
122
186
|
}),
|
|
123
|
-
e.lastError && /* @__PURE__ */
|
|
187
|
+
e.lastError && /* @__PURE__ */ O("p", {
|
|
124
188
|
className: "mt-3 line-clamp-2 rounded-lg bg-status-error-bg-subtle px-2 py-1 text-xs text-status-error-text",
|
|
125
189
|
children: e.lastError
|
|
126
190
|
}),
|
|
127
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ O("p", {
|
|
128
192
|
className: "mt-4 text-xs text-text-secondary",
|
|
129
|
-
children: e.lastSyncedAt ? `${
|
|
193
|
+
children: e.lastSyncedAt ? `${v("adaptercloud.connections.lastSynced", "Last synced")} ${c(e.lastSyncedAt)}` : v("adaptercloud.connections.neverSynced", "Never synced")
|
|
130
194
|
})
|
|
131
195
|
]
|
|
132
196
|
}, e.id))
|
|
133
|
-
}), (
|
|
197
|
+
}), (P > 0 || z) && /* @__PURE__ */ k("div", {
|
|
134
198
|
className: "mt-6 flex items-center justify-between",
|
|
135
199
|
children: [
|
|
136
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ k("button", {
|
|
137
201
|
type: "button",
|
|
138
|
-
disabled:
|
|
139
|
-
onClick: () =>
|
|
202
|
+
disabled: P === 0,
|
|
203
|
+
onClick: () => F((e) => Math.max(0, e - j)),
|
|
140
204
|
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",
|
|
141
|
-
children: [/* @__PURE__ */
|
|
205
|
+
children: [/* @__PURE__ */ O(h, { className: "h-4 w-4" }), v("adaptercloud.common.prev", "Previous")]
|
|
142
206
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
207
|
+
/* @__PURE__ */ k("span", {
|
|
144
208
|
className: "text-sm text-text-secondary",
|
|
145
209
|
children: [
|
|
146
|
-
|
|
210
|
+
v("adaptercloud.common.showing", "Showing"),
|
|
147
211
|
" ",
|
|
148
|
-
|
|
212
|
+
P + 1,
|
|
149
213
|
"–",
|
|
150
|
-
|
|
214
|
+
P + R.length
|
|
151
215
|
]
|
|
152
216
|
}),
|
|
153
|
-
/* @__PURE__ */
|
|
217
|
+
/* @__PURE__ */ k("button", {
|
|
154
218
|
type: "button",
|
|
155
|
-
disabled: !
|
|
156
|
-
onClick: () =>
|
|
219
|
+
disabled: !z,
|
|
220
|
+
onClick: () => F((e) => e + j),
|
|
157
221
|
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",
|
|
158
|
-
children: [
|
|
222
|
+
children: [v("adaptercloud.common.next", "Next"), /* @__PURE__ */ O(g, { className: "h-4 w-4" })]
|
|
159
223
|
})
|
|
160
224
|
]
|
|
161
225
|
})]
|
|
@@ -163,6 +227,6 @@ function x() {
|
|
|
163
227
|
});
|
|
164
228
|
}
|
|
165
229
|
//#endregion
|
|
166
|
-
export {
|
|
230
|
+
export { N as ConnectionsListPage };
|
|
167
231
|
|
|
168
232
|
//# sourceMappingURL=ConnectionsListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionsListPage.tsx"],"sourcesContent":["/**\n * Connections List Page\n * @module adaptercloud/pages/connections\n *\n * Every configured, authenticated target in the workspace. Lists from\n * `adaListConnections` with a status filter and offset pagination.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Cable, Plus, ChevronLeft, ChevronRight, MapPin, Server } 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 { useConnections } from '../../hooks/useAdapterCloudApi';\nimport { authMethodLabels, connectionStatusLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaConnectionStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nexport function ConnectionsListPage() {\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 [status, setStatus] = useState<AdaConnectionStatus | ''>('');\n const [offset, setOffset] = useState(0);\n\n const query = useConnections({\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 return (\n <PageLayout\n title={t('adaptercloud.connections.title', 'Connections')}\n description={t(\n 'adaptercloud.connections.subtitle',\n 'Configured, authenticated targets — each connection is where the fabric actually reaches.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections/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.connections.new', 'New connection')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaConnectionStatus | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.connections.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.connections.allStatuses', 'All statuses')}</option>\n {(Object.keys(connectionStatusLabels) as AdaConnectionStatus[]).map((s) => (\n <option key={s} value={s}>\n {connectionStatusLabels[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.connections.emptyTitle', 'No connections yet')}\n emptyDescription={t(\n 'adaptercloud.connections.emptyDescription',\n 'Create a connection to a real target to start discovering resources.'\n )}\n emptyIcon={<Cable className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections/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.connections.new', 'New connection')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((conn) => (\n <GlassCard\n key={conn.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/connections/${conn.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/connections/${conn.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 {conn.name}\n </h3>\n {conn.adapter && (\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">\n {conn.adapter.name}\n </p>\n )}\n </div>\n <StatusBadge status={conn.status} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5\">\n {authMethodLabels[conn.authMethod]}\n </span>\n {conn.region && (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" /> {conn.region}\n </span>\n )}\n {conn.environment && <span>· {conn.environment}</span>}\n </div>\n\n <div className=\"mt-4 flex items-center gap-2 text-sm text-text-primary\">\n <Server className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"font-semibold tabular-nums\">{conn.resourceCount}</span>\n <span className=\"text-text-secondary\">\n {t('adaptercloud.connections.resources', 'resources')}\n </span>\n </div>\n\n {conn.lastError && (\n <p className=\"mt-3 line-clamp-2 rounded-lg bg-status-error-bg-subtle px-2 py-1 text-xs text-status-error-text\">\n {conn.lastError}\n </p>\n )}\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {conn.lastSyncedAt\n ? `${t('adaptercloud.connections.lastSynced', 'Last synced')} ${formatRelativeTime(conn.lastSyncedAt)}`\n : t('adaptercloud.connections.neverSynced', 'Never synced')}\n </p>\n </GlassCard>\n ))}\n </div>\n\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":"ConnectionsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionsListPage.tsx"],"sourcesContent":["/**\n * Connections List Page\n * @module adaptercloud/pages/connections\n *\n * Every configured, authenticated target in the workspace. Lists from\n * `adaListConnections` with an adapter + status filter, a client-side search,\n * and offset pagination. Each card carries a per-adapter-kind glyph tile so the\n * fabric's reach reads at a glance (mirrors the aws/azure/gke/snowflake logos in\n * the mock).\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Cable,\n Plus,\n ChevronLeft,\n ChevronRight,\n MapPin,\n Server,\n Search,\n Cloud,\n Radio,\n Cpu,\n Boxes,\n Network,\n Database,\n HardDrive,\n Puzzle,\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 { useConnections, useAdapters } from '../../hooks/useAdapterCloudApi';\nimport { authMethodLabels, connectionStatusLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaAdapterKind, AdaConnectionStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Per-adapter-kind glyph — stands in for the vendor logos (aws / azure / gke /\n * snowflake) in the mock. Keyed off the connection's bound adapter kind; every\n * `AdaAdapterKind` maps to one of the eight allowed lucide marks.\n */\nconst adapterKindIcon: Record<AdaAdapterKind, LucideIcon> = {\n PUBLIC_CLOUD: Cloud,\n PRIVATE_CLOUD: Cloud,\n SAAS: Cloud,\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 ConnectionsListPage() {\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 [status, setStatus] = useState<AdaConnectionStatus | ''>('');\n const [adapterId, setAdapterId] = useState('');\n const [search, setSearch] = useState('');\n const [offset, setOffset] = useState(0);\n\n const query = useConnections({\n adapterId: adapterId || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n // Published adapters power the adapter filter dropdown (mock's \"Adapter filter\").\n const adaptersQuery = useAdapters({ status: 'PUBLISHED', pagination: { limit: 100 } });\n const adapterOptions = adaptersQuery.data?.items ?? [];\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n // Client-side search over the loaded page (name / region / environment).\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return items;\n return items.filter(\n (c) =>\n c.name.toLowerCase().includes(q) ||\n (c.region?.toLowerCase().includes(q) ?? false) ||\n (c.environment?.toLowerCase().includes(q) ?? false)\n );\n }, [items, search]);\n\n return (\n <PageLayout\n title={t('adaptercloud.connections.title', 'Connections')}\n description={t(\n 'adaptercloud.connections.subtitle',\n 'Configured, authenticated targets — each connection is where the fabric actually reaches.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections/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.connections.new', 'New connection')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaConnectionStatus | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.connections.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.connections.allStatuses', 'All statuses')}</option>\n {(Object.keys(connectionStatusLabels) as AdaConnectionStatus[]).map((s) => (\n <option key={s} value={s}>\n {connectionStatusLabels[s]}\n </option>\n ))}\n </select>\n\n <select\n value={adapterId}\n onChange={(e) => {\n setAdapterId(e.target.value);\n setOffset(0);\n }}\n aria-label={t('adaptercloud.connections.filterByAdapter', 'Filter by adapter')}\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.connections.allAdapters', 'All adapters')}</option>\n {adapterOptions.map((a) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n\n <div className=\"relative w-full sm:ml-auto sm:w-64\">\n <Search\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n aria-label={t('adaptercloud.connections.search', 'Search connections')}\n placeholder={t('adaptercloud.connections.searchPlaceholder', 'Search…')}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\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.connections.emptyTitle', 'No connections yet')}\n emptyDescription={t(\n 'adaptercloud.connections.emptyDescription',\n 'Create a connection to a real target to start discovering resources.'\n )}\n emptyIcon={<Cable className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections/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.connections.new', 'New connection')}\n </button>\n }\n >\n {filtered.length === 0 ? (\n <p className=\"rounded-xl border border-border-subtle bg-bg-surface px-4 py-10 text-center text-sm text-text-secondary\">\n {t('adaptercloud.connections.noSearchMatch', 'No connections match your search.')}\n </p>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((conn) => {\n const KindIcon = conn.adapter?.kind ? adapterKindIcon[conn.adapter.kind] : Cable;\n return (\n <GlassCard\n key={conn.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/connections/${conn.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/connections/${conn.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 gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-accent-blue\">\n <KindIcon className=\"h-5 w-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\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 {conn.name}\n </h3>\n {conn.adapter && (\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">\n {conn.adapter.name}\n </p>\n )}\n </div>\n <StatusBadge status={conn.status} />\n </div>\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-3 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5\">\n {authMethodLabels[conn.authMethod]}\n </span>\n {conn.region && (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" /> {conn.region}\n </span>\n )}\n {conn.environment && <span>· {conn.environment}</span>}\n </div>\n\n <div className=\"mt-4 flex items-center gap-2 text-sm text-text-primary\">\n <Server className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"font-semibold tabular-nums\">{conn.resourceCount}</span>\n <span className=\"text-text-secondary\">\n {t('adaptercloud.connections.resources', 'resources')}\n </span>\n </div>\n\n {conn.lastError && (\n <p className=\"mt-3 line-clamp-2 rounded-lg bg-status-error-bg-subtle px-2 py-1 text-xs text-status-error-text\">\n {conn.lastError}\n </p>\n )}\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {conn.lastSyncedAt\n ? `${t('adaptercloud.connections.lastSynced', 'Last synced')} ${formatRelativeTime(conn.lastSyncedAt)}`\n : t('adaptercloud.connections.neverSynced', 'Never synced')}\n </p>\n </GlassCard>\n );\n })}\n </div>\n )}\n\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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"}
|