@burdenoff/microfe-adaptercloud 2026.825.4 → 2026.829.1
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.d.ts.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoot.js +52 -55
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/components/ActiveSwitch.d.ts +17 -0
- package/dist/adaptercloud/components/ActiveSwitch.d.ts.map +1 -0
- package/dist/adaptercloud/components/ActiveSwitch.js +28 -0
- package/dist/adaptercloud/components/ActiveSwitch.js.map +1 -0
- package/dist/adaptercloud/components/index.d.ts +1 -0
- package/dist/adaptercloud/components/index.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/index.d.ts +1 -0
- package/dist/adaptercloud/hooks/index.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts +28 -2
- package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +318 -291
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/hooks/useTr.d.ts +2 -0
- package/dist/adaptercloud/hooks/useTr.d.ts.map +1 -0
- package/dist/adaptercloud/hooks/useTr.js +14 -0
- package/dist/adaptercloud/hooks/useTr.js.map +1 -0
- package/dist/adaptercloud/pages/OverviewPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +117 -120
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +6 -5
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +217 -104
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +6 -5
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +183 -156
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +108 -107
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +141 -131
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +171 -117
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +85 -66
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +148 -121
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/violations/ViolationsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +145 -144
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/dist/utils/pageContext.d.ts +0 -2
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +1 -3
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionDetailPage.tsx"],"sourcesContent":["/**\n * Connection Detail Page\n * @module adaptercloud/pages/connections\n *\n * One connection (`adaConnection`) — its adapter, health, config, and the\n * resources it has discovered. Supports the validated health transition\n * (`adaSetConnectionStatus`), disable (`adaDisableConnection`), and starting a\n * sync run (`adaStartSyncRun`) against it.\n */\n\nimport { useCallback, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Ban, Pencil, Play, RefreshCw, 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 { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport {\n useConnection,\n useDisableConnection,\n useSetConnectionStatus,\n useStartSyncRun,\n} from '../../hooks/useAdapterCloudApi';\nimport {\n authMethodLabels,\n connectionStatusLabels,\n resourceStateLabels,\n syncKindLabels,\n} from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport { formatMoney } from '../../utils/domain';\nimport type { AdaConnectionStatus, AdaSyncKind } from '../../types';\nimport {\n canStartConnectionSync,\n canStartConnectionSyncKind,\n isConnectionSyncKindExecutable,\n} from './connectionSync';\n\nconst TRANSITIONABLE: AdaConnectionStatus[] = ['CONNECTED', 'DEGRADED', 'ERROR', 'PENDING'];\nconst SYNC_KINDS: AdaSyncKind[] = ['DISCOVERY', 'RECONCILE', 'COST_INGEST', 'FULL'];\n\nexport function ConnectionDetailPage() {\n const { id } = useParams<{ id: string }>();\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 query = useConnection(id);\n const setStatus = useSetConnectionStatus();\n const disable = useDisableConnection();\n const startRun = useStartSyncRun();\n const conn = query.data;\n const syncEnabled = conn ? canStartConnectionSync(conn.status) : false;\n\n const [runningKind, setRunningKind] = useState<AdaSyncKind | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmDisableOpen, setConfirmDisableOpen] = useState(false);\n\n const onError = useCallback(\n (err: unknown) =>\n setActionError(err instanceof Error ? err.message : 'Action failed. Please try again.'),\n []\n );\n\n const handleSetStatus = useCallback(\n (newStatus: AdaConnectionStatus) => {\n if (!id) return;\n setActionError(null);\n setStatus.mutate({ id, status: newStatus }, { onError });\n },\n [id, setStatus, onError]\n );\n\n const handleDisable = useCallback(() => {\n if (!id) return;\n setActionError(null);\n disable.mutate(id, {\n onSuccess: () => setConfirmDisableOpen(false),\n onError,\n });\n }, [id, disable, onError]);\n\n const handleStartKind = useCallback(\n (kind: AdaSyncKind) => {\n if (!id || !conn || !canStartConnectionSyncKind(conn.status, kind)) return;\n setActionError(null);\n setRunningKind(kind);\n startRun.mutate(\n { connectionId: id, kind },\n {\n onSuccess: () => {\n setRunningKind(null);\n navigateTo('/sync-runs');\n },\n onError: (err) => {\n setRunningKind(null);\n onError(err);\n },\n }\n );\n },\n [id, conn, startRun, navigateTo, onError]\n );\n\n return (\n <PageLayout\n title={conn?.name ?? t('adaptercloud.connections.detailTitle', 'Connection')}\n description={conn?.adapter?.name}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\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.back', 'Back')}\n </button>\n {conn && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/connections/${conn.id}/edit`)}\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 <Pencil className=\"h-4 w-4\" />\n {t('adaptercloud.common.edit', 'Edit')}\n </button>\n )}\n </div>\n }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !conn}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.connections.notFound', 'Connection not found')}\n >\n {conn && (\n <div className=\"space-y-6\">\n {actionError && (\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 {actionError}\n </p>\n )}\n\n {/* Health + actions */}\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <StatusBadge status={conn.status} />\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {authMethodLabels[conn.authMethod]}\n </span>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-4\">\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.region', 'Region')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">{conn.region ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.environment', 'Environment')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {conn.environment ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.resourceCount', 'Resources')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {conn.resourceCount}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.lastSynced', 'Last synced')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {conn.lastSyncedAt ? formatDate(conn.lastSyncedAt) : '—'}\n </dd>\n </div>\n </dl>\n\n {conn.lastError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {conn.lastError}\n </p>\n )}\n\n {/* Health transitions */}\n <div className=\"mt-5 border-t border-border-subtle pt-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.connections.setHealth', 'Set health')}\n </h3>\n <div className=\"flex flex-wrap gap-2\">\n {TRANSITIONABLE.map((s) => (\n <button\n key={s}\n type=\"button\"\n disabled={setStatus.isPending || conn.status === s}\n onClick={() => handleSetStatus(s)}\n 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\"\n >\n {connectionStatusLabels[s]}\n </button>\n ))}\n {conn.status !== 'DISABLED' && (\n <button\n type=\"button\"\n disabled={disable.isPending}\n onClick={() => setConfirmDisableOpen(true)}\n 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\"\n >\n <Ban className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.connections.disable', 'Disable')}\n </button>\n )}\n </div>\n </div>\n\n {/* Start sync run */}\n <div className=\"mt-5 border-t border-border-subtle pt-5\">\n <h3 className=\"mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <RefreshCw className=\"h-4 w-4\" />\n {t('adaptercloud.connections.startSync', 'Start a sync run')}\n </h3>\n {!syncEnabled && (\n <p\n id=\"disabled-connection-sync-help\"\n className=\"mb-3 text-sm text-text-secondary\"\n >\n {t(\n 'adaptercloud.connections.disabledSyncHelp',\n 'Sync runs are unavailable while this connection is disabled. Set its health to re-enable sync.'\n )}\n </p>\n )}\n <p id=\"unsupported-sync-kind-help\" className=\"mb-3 text-sm text-text-secondary\">\n {t(\n 'adaptercloud.connections.syncModeAvailabilityHelp',\n 'Discovery is executable today. Reconcile, Cost Ingest, and Full need distinct backend workers and are not available yet.'\n )}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {SYNC_KINDS.map((k) => {\n const pending = startRun.isPending && runningKind === k;\n const executable = isConnectionSyncKindExecutable(k);\n return (\n <button\n key={k}\n type=\"button\"\n disabled={startRun.isPending || !syncEnabled || !executable}\n aria-describedby={\n !executable\n ? 'unsupported-sync-kind-help'\n : syncEnabled\n ? undefined\n : 'disabled-connection-sync-help'\n }\n title={\n !executable\n ? t(\n 'adaptercloud.connections.unsupportedSyncModeTooltip',\n 'This sync mode is not available yet because it needs a distinct backend worker.'\n )\n : !syncEnabled\n ? t(\n 'adaptercloud.connections.disabledSyncTooltip',\n 'Re-enable this connection before starting a sync run.'\n )\n : undefined\n }\n onClick={() => handleStartKind(k)}\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 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {pending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Play className=\"h-4 w-4\" />\n )}\n {syncKindLabels[k]}\n {!executable && (\n <span className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-[10px] font-normal text-text-secondary\">\n {t('adaptercloud.connections.notAvailable', 'Not available')}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n </GlassCard>\n\n {/* Config */}\n {conn.config != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.connections.config', 'Configuration')}\n </h3>\n <pre className=\"overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(conn.config, null, 2)}\n </pre>\n </GlassCard>\n )}\n\n {/* Resources */}\n <section>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {t('adaptercloud.connections.discoveredResources', 'Discovered Resources')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources?connectionId=${conn.id}`)}\n className=\"text-sm font-medium text-accent-blue hover:underline\"\n >\n {t('adaptercloud.common.viewAll', 'View all')}\n </button>\n </div>\n {conn.resources && conn.resources.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {conn.resources.slice(0, 10).map((r) => (\n <button\n key={r.id}\n type=\"button\"\n onClick={() => navigateTo(`/resources/${r.id}`)}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50\"\n >\n <Server className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{r.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {r.resourceType}\n {r.region ? ` · ${r.region}` : ''}\n </p>\n </div>\n <span className=\"hidden text-xs text-text-secondary sm:inline\">\n {formatMoney(r.monthlyCost)}\n </span>\n <StatusBadge status={r.state} label={resourceStateLabels[r.state]} />\n </button>\n ))}\n </div>\n ) : (\n <p className=\"rounded-xl border border-border-subtle bg-bg-surface px-4 py-6 text-center text-sm text-text-secondary\">\n {t(\n 'adaptercloud.connections.noResources',\n 'No resources discovered yet. Start a discovery sync run.'\n )}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={confirmDisableOpen}\n onOpenChange={setConfirmDisableOpen}\n title={t('adaptercloud.connections.disableTitle', 'Disable this connection?')}\n description={t(\n 'adaptercloud.connections.disablePrompt',\n 'Disabling stops discovery and sync runs against this target. You can re-enable it later by setting its health.'\n )}\n confirmLabel={t('adaptercloud.connections.disable', 'Disable')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={handleDisable}\n isPending={disable.isPending}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA0CA,IAAM,IAAwC;CAAC;CAAa;CAAY;CAAS;AAAS,GACpF,IAA4B;CAAC;CAAa;CAAa;CAAe;AAAM;AAElF,SAAgB,IAAuB;CACrC,IAAM,EAAE,UAAO,EAA0B,GACnC,EAAE,kBAAe,EAAgB,GACjC,EAAE,GAAG,MAAc,EAAQ,GAC3B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,CAAG;EAC3B,OAAO,MAAU,IAAM,IAAW;CACpC,GACA,CAAC,CAAS,CACZ,GAEM,IAAQ,EAAc,CAAE,GACxB,IAAY,EAAuB,GACnC,IAAU,EAAqB,GAC/B,IAAW,EAAgB,GAC3B,IAAO,EAAM,MACb,IAAc,IAAO,EAAuB,EAAK,MAAM,IAAI,IAE3D,CAAC,GAAa,KAAkB,EAA6B,IAAI,GACjE,CAAC,GAAa,KAAkB,EAAwB,IAAI,GAC5D,CAAC,GAAoB,KAAyB,EAAS,EAAK,GAE5D,IAAU,GACb,MACC,EAAe,aAAe,QAAQ,EAAI,UAAU,kCAAkC,GACxF,CAAC,CACH,GAEM,IAAkB,GACrB,MAAmC;EAC7B,MACL,EAAe,IAAI,GACnB,EAAU,OAAO;GAAE;GAAI,QAAQ;EAAU,GAAG,EAAE,WAAQ,CAAC;CACzD,GACA;EAAC;EAAI;EAAW;CAAO,CACzB,GAEM,IAAgB,QAAkB;EACjC,MACL,EAAe,IAAI,GACnB,EAAQ,OAAO,GAAI;GACjB,iBAAiB,EAAsB,EAAK;GAC5C;EACF,CAAC;CACH,GAAG;EAAC;EAAI;EAAS;CAAO,CAAC,GAEnB,IAAkB,GACrB,MAAsB;EACjB,CAAC,KAAM,CAAC,KAAQ,CAAC,EAA2B,EAAK,QAAQ,CAAI,MACjE,EAAe,IAAI,GACnB,EAAe,CAAI,GACnB,EAAS,OACP;GAAE,cAAc;GAAI;EAAK,GACzB;GACE,iBAAiB;IAEf,AADA,EAAe,IAAI,GACnB,EAAW,YAAY;GACzB;GACA,UAAU,MAAQ;IAEhB,AADA,EAAe,IAAI,GACnB,EAAQ,CAAG;GACb;EACF,CACF;CACF,GACA;EAAC;EAAI;EAAM;EAAU;EAAY;CAAO,CAC1C;CAEA,OACE,kBAAC,GAAD;EACE,OAAO,GAAM,QAAQ,EAAE,wCAAwC,YAAY;EAC3E,aAAa,GAAM,SAAS;EAC5B,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,cAAc;IACxC,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAC/B,EAAE,4BAA4B,MAAM,CAC/B;OACP,KACC,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,gBAAgB,EAAK,GAAG,MAAM;IACxD,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,GAC5B,EAAE,4BAA4B,MAAM,CAC/B;KAEP;;YAvBT,CA0BE,kBAAC,GAAD;GACE,WAAW,EAAM;GACjB,SAAS,EAAM;GACf,SAAS,CAAC,EAAM,aAAa,CAAC;GAC9B,OAAO,EAAM;GACb,eAAe,KAAK,EAAM,QAAQ;GAClC,YAAY,EAAE,qCAAqC,sBAAsB;aAExE,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,KACC,kBAAC,KAAD;MACE,MAAK;MACL,aAAU;MACV,WAAU;gBAET;KACA,CAAA;KAIL,kBAAC,GAAD;MAAW,WAAU;gBAArB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAK,OAAS,CAAA,GACnC,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAiB,EAAK;QACnB,CAAA,CACH;;OAEL,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,mCAAmC,QAAQ;SAC5C,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBAAwC,EAAK,UAAU;SAAQ,CAAA,CAC1E,EAAA,CAAA;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,wCAAwC,aAAa;SACtD,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK,eAAe;SACnB,CAAA,CACD,EAAA,CAAA;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,0CAA0C,WAAW;SACtD,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK;SACJ,CAAA,CACD,EAAA,CAAA;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,uCAAuC,aAAa;SACrD,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAK,eAAe,EAAW,EAAK,YAAY,IAAI;SACnD,CAAA,CACD,EAAA,CAAA;QACH;;OAEH,EAAK,aACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAK;OACL,CAAA;OAIL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,sCAAsC,YAAY;QACnD,CAAA,GACJ,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAe,KAAK,MACnB,kBAAC,UAAD;UAEE,MAAK;UACL,UAAU,EAAU,aAAa,EAAK,WAAW;UACjD,eAAe,EAAgB,CAAC;UAChC,WAAU;oBAET,EAAuB;SAClB,GAPD,CAOC,CACT,GACA,EAAK,WAAW,cACf,kBAAC,UAAD;UACE,MAAK;UACL,UAAU,EAAQ;UAClB,eAAe,EAAsB,EAAI;UACzC,WAAU;oBAJZ,CAME,kBAAC,GAAD,EAAK,WAAU,cAAe,CAAA,GAC7B,EAAE,oCAAoC,SAAS,CAC1C;WAEP;UACF;;OAGL,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,MAAD;UAAI,WAAU;oBAAd,CACE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAC/B,EAAE,sCAAsC,kBAAkB,CACzD;;SACH,CAAC,KACA,kBAAC,KAAD;UACE,IAAG;UACH,WAAU;oBAET,EACC,6CACA,gGACF;SACC,CAAA;SAEL,kBAAC,KAAD;UAAG,IAAG;UAA6B,WAAU;oBAC1C,EACC,qDACA,0HACF;SACC,CAAA;SACH,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAW,KAAK,MAAM;WACrB,IAAM,IAAU,EAAS,aAAa,MAAgB,GAChD,IAAa,EAA+B,CAAC;WACnD,OACE,kBAAC,UAAD;YAEE,MAAK;YACL,UAAU,EAAS,aAAa,CAAC,KAAe,CAAC;YACjD,oBACG,IAEG,IACE,KAAA,IACA,kCAHF;YAKN,OACG,IAKI,IAKC,KAAA,IAJA,EACE,gDACA,uDACF,IARF,EACE,uDACA,iFACF;YAQN,eAAe,EAAgB,CAAC;YAChC,WAAU;sBAzBZ;aA2BG,IACC,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,IAE3C,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;aAE5B,EAAe;aACf,CAAC,KACA,kBAAC,QAAD;cAAM,WAAU;wBACb,EAAE,yCAAyC,eAAe;aACvD,CAAA;YAEF;cArCD,CAqCC;UAEZ,CAAC;SACE,CAAA;QACF;;MACI;;KAGV,EAAK,UAAU,QACd,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAE,mCAAmC,eAAe;MACnD,CAAA,GACJ,kBAAC,OAAD;OAAK,WAAU;iBACZ,KAAK,UAAU,EAAK,QAAQ,MAAM,CAAC;MACjC,CAAA,CACI;;KAIb,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAE,gDAAgD,sBAAsB;MACvE,CAAA,GACJ,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,2BAA2B,EAAK,IAAI;OAC9D,WAAU;iBAET,EAAE,+BAA+B,UAAU;MACtC,CAAA,CACL;SACJ,EAAK,aAAa,EAAK,UAAU,SAAS,IACzC,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAK,UAAU,MAAM,GAAG,EAAE,EAAE,KAAK,MAChC,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAW,cAAc,EAAE,IAAI;OAC9C,WAAU;iBAJZ;QAME,kBAAC,GAAD,EAAQ,WAAU,uCAAwC,CAAA;QAC1D,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAkD,EAAE;SAAQ,CAAA,GACzE,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,EAAE,cACF,EAAE,SAAS,MAAM,EAAE,WAAW,EAC9B;WACA;;QACL,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAY,EAAE,WAAW;QACtB,CAAA;QACN,kBAAC,GAAD;SAAa,QAAQ,EAAE;SAAO,OAAO,EAAoB,EAAE;QAAS,CAAA;OAC9D;SAjBD,EAAE,EAiBD,CACT;KACE,CAAA,IAEL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,wCACA,0DACF;KACC,CAAA,CAEE,EAAA,CAAA;IACN;;EAEE,CAAA,GAEX,kBAAC,GAAD;GACE,MAAM;GACN,cAAc;GACd,OAAO,EAAE,yCAAyC,0BAA0B;GAC5E,aAAa,EACX,0CACA,gHACF;GACA,cAAc,EAAE,oCAAoC,SAAS;GAC7D,aAAa,EAAE,8BAA8B,QAAQ;GACrD,WAAW;GACX,WAAW,EAAQ;GACnB,MAAK;EACN,CAAA,CACS;;AAEhB"}
|
|
1
|
+
{"version":3,"file":"ConnectionDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionDetailPage.tsx"],"sourcesContent":["/**\n * Connection Detail Page\n * @module adaptercloud/pages/connections\n *\n * One connection (`adaConnection`) — its adapter, health, config, and the\n * resources it has discovered. Supports the validated health transition\n * (`adaSetConnectionStatus`), disable (`adaDisableConnection`), and starting a\n * sync run (`adaStartSyncRun`) against it.\n */\n\nimport { useCallback, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Ban, Pencil, Play, RefreshCw, 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 { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport {\n useConnection,\n useDisableConnection,\n useSetConnectionStatus,\n useStartSyncRun,\n useSyncRuns,\n} from '../../hooks/useAdapterCloudApi';\nimport {\n authMethodLabels,\n connectionStatusLabels,\n resourceStateLabels,\n syncKindLabels,\n syncStatusLabels,\n} from '../../utils/domain';\nimport { formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { formatMoney } from '../../utils/domain';\nimport type { AdaConnectionStatus, AdaSyncKind, AdaSyncStatus } from '../../types';\nimport {\n canStartConnectionSync,\n canStartConnectionSyncKind,\n isConnectionSyncKindExecutable,\n} from './connectionSync';\n\nconst TRANSITIONABLE: AdaConnectionStatus[] = ['CONNECTED', 'DEGRADED', 'ERROR', 'PENDING'];\nconst SYNC_KINDS: AdaSyncKind[] = ['DISCOVERY', 'RECONCILE', 'COST_INGEST', 'FULL'];\n\n/** How many of this connection's most recent runs the detail panel shows. */\nconst RECENT_RUN_COUNT = 5;\n\n/** Counters are meaningless until a run has produced results. */\nfunction isRunPending(status: AdaSyncStatus): boolean {\n return status === 'QUEUED' || status === 'RUNNING';\n}\n\nexport function ConnectionDetailPage() {\n const { id } = useParams<{ id: string }>();\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 query = useConnection(id);\n const setStatus = useSetConnectionStatus();\n const disable = useDisableConnection();\n const startRun = useStartSyncRun();\n const conn = query.data;\n const syncEnabled = conn ? canStartConnectionSync(conn.status) : false;\n\n // The operations log scoped to this connection — `adaListSyncRuns` filters\n // server-side by `connectionId`, so this is the real run history, not a\n // client-side slice of the estate-wide list.\n const recentRuns = useSyncRuns(\n { connectionId: id, pagination: { limit: RECENT_RUN_COUNT, offset: 0 } },\n { enabled: Boolean(id) }\n );\n const runItems = recentRuns.data?.items ?? [];\n\n const [runningKind, setRunningKind] = useState<AdaSyncKind | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmDisableOpen, setConfirmDisableOpen] = useState(false);\n\n const onError = useCallback(\n (err: unknown) =>\n setActionError(err instanceof Error ? err.message : 'Action failed. Please try again.'),\n []\n );\n\n const handleSetStatus = useCallback(\n (newStatus: AdaConnectionStatus) => {\n if (!id) return;\n setActionError(null);\n setStatus.mutate({ id, status: newStatus }, { onError });\n },\n [id, setStatus, onError]\n );\n\n const handleDisable = useCallback(() => {\n if (!id) return;\n setActionError(null);\n disable.mutate(id, {\n onSuccess: () => setConfirmDisableOpen(false),\n onError,\n });\n }, [id, disable, onError]);\n\n const handleStartKind = useCallback(\n (kind: AdaSyncKind) => {\n if (!id || !conn || !canStartConnectionSyncKind(conn.status, kind)) return;\n setActionError(null);\n setRunningKind(kind);\n startRun.mutate(\n { connectionId: id, kind },\n {\n onSuccess: () => {\n setRunningKind(null);\n navigateTo('/sync-runs');\n },\n onError: (err) => {\n setRunningKind(null);\n onError(err);\n },\n }\n );\n },\n [id, conn, startRun, navigateTo, onError]\n );\n\n return (\n <PageLayout\n title={conn?.name ?? t('adaptercloud.connections.detailTitle', 'Connection')}\n description={conn?.adapter?.name}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\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.back', 'Back')}\n </button>\n {conn && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/connections/${conn.id}/edit`)}\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 <Pencil className=\"h-4 w-4\" />\n {t('adaptercloud.common.edit', 'Edit')}\n </button>\n )}\n </div>\n }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !conn}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.connections.notFound', 'Connection not found')}\n >\n {conn && (\n <div className=\"space-y-6\">\n {actionError && (\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 {actionError}\n </p>\n )}\n\n {/* Health + actions */}\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <StatusBadge status={conn.status} />\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {authMethodLabels[conn.authMethod]}\n </span>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-4\">\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.adapter', 'Adapter')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {conn.adapter ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/adapters/${conn.adapterId}`)}\n className=\"max-w-full truncate text-left text-accent-blue hover:underline\"\n >\n {conn.adapter.name}\n {conn.adapter.version ? ` v${conn.adapter.version}` : ''}\n </button>\n ) : (\n '—'\n )}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.region', 'Region')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">{conn.region ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.environment', 'Environment')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {conn.environment ?? '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.resourceCount', 'Resources')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {conn.resourceCount}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.lastSynced', 'Last synced')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {conn.lastSyncedAt ? formatDate(conn.lastSyncedAt) : '—'}\n </dd>\n </div>\n </dl>\n\n {/* Binding references — the target this connection points at and\n the secret-store reference used to authenticate against it.\n Both are opaque references stored on the connection, never a\n credential value. */}\n {(conn.targetRef || conn.secretRef) && (\n <dl className=\"mt-4 space-y-3 border-t border-border-subtle pt-4 text-sm\">\n {conn.targetRef && (\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.targetRef', 'Target ref')}\n </dt>\n <dd className=\"mt-1 break-all rounded-lg bg-bg-sunken px-3 py-1.5 font-mono text-xs text-text-primary\">\n {conn.targetRef}\n </dd>\n </div>\n )}\n {conn.secretRef && (\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.connections.secretRef', 'Secret ref')}\n </dt>\n <dd className=\"mt-1 break-all rounded-lg bg-bg-sunken px-3 py-1.5 font-mono text-xs text-text-primary\">\n {conn.secretRef}\n </dd>\n </div>\n )}\n </dl>\n )}\n\n {conn.tags.length > 0 && (\n <div className=\"mt-4\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.common.tags', 'Tags')}\n </h3>\n <div className=\"flex flex-wrap gap-1.5\">\n {conn.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {conn.lastError && (\n <p className=\"mt-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {conn.lastError}\n </p>\n )}\n\n {/* Health transitions */}\n <div className=\"mt-5 border-t border-border-subtle pt-5\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.connections.setHealth', 'Set health')}\n </h3>\n <div className=\"flex flex-wrap gap-2\">\n {TRANSITIONABLE.map((s) => (\n <button\n key={s}\n type=\"button\"\n disabled={setStatus.isPending || conn.status === s}\n onClick={() => handleSetStatus(s)}\n 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\"\n >\n {connectionStatusLabels[s]}\n </button>\n ))}\n {conn.status !== 'DISABLED' && (\n <button\n type=\"button\"\n disabled={disable.isPending}\n onClick={() => setConfirmDisableOpen(true)}\n 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\"\n >\n <Ban className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.connections.disable', 'Disable')}\n </button>\n )}\n </div>\n </div>\n\n {/* Start sync run */}\n <div className=\"mt-5 border-t border-border-subtle pt-5\">\n <h3 className=\"mb-3 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <RefreshCw className=\"h-4 w-4\" />\n {t('adaptercloud.connections.startSync', 'Start a sync run')}\n </h3>\n {!syncEnabled && (\n <p\n id=\"disabled-connection-sync-help\"\n className=\"mb-3 text-sm text-text-secondary\"\n >\n {t(\n 'adaptercloud.connections.disabledSyncHelp',\n 'Sync runs are unavailable while this connection is disabled. Set its health to re-enable sync.'\n )}\n </p>\n )}\n <p id=\"unsupported-sync-kind-help\" className=\"mb-3 text-sm text-text-secondary\">\n {t(\n 'adaptercloud.connections.syncModeAvailabilityHelp',\n 'Discovery is executable today. Reconcile, Cost Ingest, and Full need distinct backend workers and are not available yet.'\n )}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {SYNC_KINDS.map((k) => {\n const pending = startRun.isPending && runningKind === k;\n const executable = isConnectionSyncKindExecutable(k);\n return (\n <button\n key={k}\n type=\"button\"\n disabled={startRun.isPending || !syncEnabled || !executable}\n aria-describedby={\n !executable\n ? 'unsupported-sync-kind-help'\n : syncEnabled\n ? undefined\n : 'disabled-connection-sync-help'\n }\n title={\n !executable\n ? t(\n 'adaptercloud.connections.unsupportedSyncModeTooltip',\n 'This sync mode is not available yet because it needs a distinct backend worker.'\n )\n : !syncEnabled\n ? t(\n 'adaptercloud.connections.disabledSyncTooltip',\n 'Re-enable this connection before starting a sync run.'\n )\n : undefined\n }\n onClick={() => handleStartKind(k)}\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 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {pending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Play className=\"h-4 w-4\" />\n )}\n {syncKindLabels[k]}\n {!executable && (\n <span className=\"rounded bg-bg-sunken px-1.5 py-0.5 text-[10px] font-normal text-text-secondary\">\n {t('adaptercloud.connections.notAvailable', 'Not available')}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n </GlassCard>\n\n {/* Recent sync runs against this connection */}\n <section>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {t('adaptercloud.connections.recentRuns', 'Recent Sync Runs')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/sync-runs?connectionId=${conn.id}`)}\n className=\"text-sm font-medium text-accent-blue hover:underline\"\n >\n {t('adaptercloud.common.viewAll', 'View all')}\n </button>\n </div>\n <DataState\n isLoading={recentRuns.isLoading}\n isError={recentRuns.isError}\n isEmpty={runItems.length === 0}\n error={recentRuns.error}\n onRetry={() => void recentRuns.refetch()}\n emptyTitle={t('adaptercloud.connections.noRuns', 'No sync runs yet')}\n emptyDescription={t(\n 'adaptercloud.connections.noRunsDescription',\n 'Start a sync run above to keep this connection’s inventory current.'\n )}\n emptyIcon={<RefreshCw className=\"h-10 w-10\" />}\n >\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {runItems.map((run) => (\n <div\n key={run.id}\n className=\"flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3\"\n >\n <span className=\"min-w-24 text-sm font-medium text-text-primary\">\n {syncKindLabels[run.kind]}\n </span>\n <StatusBadge status={run.status} label={syncStatusLabels[run.status]} />\n <span className=\"text-xs text-text-secondary\">\n {t('adaptercloud.syncRuns.discovered', 'Discovered')}{' '}\n <span className=\"tabular-nums text-text-primary\">\n {isRunPending(run.status) ? '—' : run.discoveredCount}\n </span>\n </span>\n <span className=\"text-xs text-text-secondary\">\n {t('adaptercloud.syncRuns.drift', 'Drift')}{' '}\n <span className=\"tabular-nums text-text-primary\">\n {isRunPending(run.status) ? '—' : run.driftCount}\n </span>\n </span>\n <span className=\"ml-auto text-xs text-text-secondary\">\n {run.finishedAt\n ? formatRelativeTime(run.finishedAt)\n : t('adaptercloud.syncRuns.notFinished', 'Not finished')}\n </span>\n </div>\n ))}\n </div>\n </DataState>\n </section>\n\n {/* Config */}\n {conn.config != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.connections.config', 'Configuration')}\n </h3>\n <pre className=\"overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(conn.config, null, 2)}\n </pre>\n </GlassCard>\n )}\n\n {/* Resources */}\n <section>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {t('adaptercloud.connections.discoveredResources', 'Discovered Resources')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources?connectionId=${conn.id}`)}\n className=\"text-sm font-medium text-accent-blue hover:underline\"\n >\n {t('adaptercloud.common.viewAll', 'View all')}\n </button>\n </div>\n {conn.resources && conn.resources.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {conn.resources.slice(0, 10).map((r) => (\n <button\n key={r.id}\n type=\"button\"\n onClick={() => navigateTo(`/resources/${r.id}`)}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50\"\n >\n <Server className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{r.name}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {r.resourceType}\n {r.region ? ` · ${r.region}` : ''}\n </p>\n </div>\n <span className=\"hidden text-xs text-text-secondary sm:inline\">\n {formatMoney(r.monthlyCost)}\n </span>\n <StatusBadge status={r.state} label={resourceStateLabels[r.state]} />\n </button>\n ))}\n </div>\n ) : (\n <p className=\"rounded-xl border border-border-subtle bg-bg-surface px-4 py-6 text-center text-sm text-text-secondary\">\n {t(\n 'adaptercloud.connections.noResources',\n 'No resources discovered yet. Start a discovery sync run.'\n )}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={confirmDisableOpen}\n onOpenChange={setConfirmDisableOpen}\n title={t('adaptercloud.connections.disableTitle', 'Disable this connection?')}\n description={t(\n 'adaptercloud.connections.disablePrompt',\n 'Disabling stops discovery and sync runs against this target. You can re-enable it later by setting its health.'\n )}\n confirmLabel={t('adaptercloud.connections.disable', 'Disable')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={handleDisable}\n isPending={disable.isPending}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionsListPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/connections/ConnectionsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoDH,wBAAgB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"ConnectionsListPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/connections/ConnectionsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoDH,wBAAgB,mBAAmB,gCAoPlC"}
|
|
@@ -37,7 +37,7 @@ function N() {
|
|
|
37
37
|
}), L = o({
|
|
38
38
|
status: "PUBLISHED",
|
|
39
39
|
pagination: { limit: 100 }
|
|
40
|
-
}).data?.items ?? [], R = u(() => I.data?.items ?? [], [I.data?.items]), z = I.data?.pageInfo.hasMore ?? !1, B = u(() => {
|
|
40
|
+
}).data?.items ?? [], R = u(() => I.data?.items ?? [], [I.data?.items]), z = I.data?.pageInfo.hasMore ?? !1, B = I.data?.pageInfo.totalCount, V = u(() => {
|
|
41
41
|
let e = T.trim().toLowerCase();
|
|
42
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
43
|
}, [R, T]);
|
|
@@ -113,12 +113,12 @@ function N() {
|
|
|
113
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",
|
|
114
114
|
children: v("adaptercloud.connections.new", "New connection")
|
|
115
115
|
}),
|
|
116
|
-
children: [
|
|
116
|
+
children: [V.length === 0 ? /* @__PURE__ */ O("p", {
|
|
117
117
|
className: "rounded-xl border border-border-subtle bg-bg-surface px-4 py-10 text-center text-sm text-text-secondary",
|
|
118
118
|
children: v("adaptercloud.connections.noSearchMatch", "No connections match your search.")
|
|
119
119
|
}) : /* @__PURE__ */ O("div", {
|
|
120
120
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
121
|
-
children:
|
|
121
|
+
children: V.map((e) => /* @__PURE__ */ k(A, {
|
|
122
122
|
role: "button",
|
|
123
123
|
tabIndex: 0,
|
|
124
124
|
onClick: () => p(`/connections/${e.id}`),
|
|
@@ -205,13 +205,14 @@ function N() {
|
|
|
205
205
|
children: [/* @__PURE__ */ O(h, { className: "h-4 w-4" }), v("adaptercloud.common.prev", "Previous")]
|
|
206
206
|
}),
|
|
207
207
|
/* @__PURE__ */ k("span", {
|
|
208
|
-
className: "text-sm text-text-secondary",
|
|
208
|
+
className: "text-sm tabular-nums text-text-secondary",
|
|
209
209
|
children: [
|
|
210
210
|
v("adaptercloud.common.showing", "Showing"),
|
|
211
211
|
" ",
|
|
212
212
|
P + 1,
|
|
213
213
|
"–",
|
|
214
|
-
P + R.length
|
|
214
|
+
P + R.length,
|
|
215
|
+
B != null && ` ${v("adaptercloud.common.of", "of")} ${B}`
|
|
215
216
|
]
|
|
216
217
|
}),
|
|
217
218
|
/* @__PURE__ */ k("button", {
|
|
@@ -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 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 = useMemo(() => query.data?.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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"}
|
|
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 = useMemo(() => query.data?.items ?? [], [query.data?.items]);\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n // Estate-wide total for the current filter. Nullable on older deployed\n // schemas — the footer then falls back to the bare offset range.\n const totalCount = query.data?.pageInfo.totalCount;\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 tabular-nums text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n {totalCount != null && ` ${t('adaptercloud.common.of', 'of')} ${totalCount}`}\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CostsPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"CostsPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,wBAAgB,SAAS,gCAgUxB"}
|