@burdenoff/microfe-adaptercloud 2026.825.4 → 2026.827.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.
Files changed (48) hide show
  1. package/dist/adaptercloud/components/ActiveSwitch.d.ts +17 -0
  2. package/dist/adaptercloud/components/ActiveSwitch.d.ts.map +1 -0
  3. package/dist/adaptercloud/components/ActiveSwitch.js +28 -0
  4. package/dist/adaptercloud/components/ActiveSwitch.js.map +1 -0
  5. package/dist/adaptercloud/components/index.d.ts +1 -0
  6. package/dist/adaptercloud/components/index.d.ts.map +1 -1
  7. package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts +28 -2
  8. package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts.map +1 -1
  9. package/dist/adaptercloud/hooks/useAdapterCloudApi.js +43 -18
  10. package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
  11. package/dist/adaptercloud/pages/OverviewPage.d.ts.map +1 -1
  12. package/dist/adaptercloud/pages/OverviewPage.js +4 -4
  13. package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
  14. package/dist/adaptercloud/pages/adapters/AdaptersListPage.d.ts.map +1 -1
  15. package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +6 -5
  16. package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
  17. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.d.ts.map +1 -1
  18. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +217 -104
  19. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
  20. package/dist/adaptercloud/pages/connections/ConnectionsListPage.d.ts.map +1 -1
  21. package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +6 -5
  22. package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
  23. package/dist/adaptercloud/pages/costs/CostsPage.d.ts.map +1 -1
  24. package/dist/adaptercloud/pages/costs/CostsPage.js +183 -156
  25. package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
  26. package/dist/adaptercloud/pages/drift/DriftListPage.d.ts.map +1 -1
  27. package/dist/adaptercloud/pages/drift/DriftListPage.js +108 -107
  28. package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
  29. package/dist/adaptercloud/pages/policies/PoliciesListPage.d.ts.map +1 -1
  30. package/dist/adaptercloud/pages/policies/PoliciesListPage.js +141 -131
  31. package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
  32. package/dist/adaptercloud/pages/policies/PolicyDetailPage.d.ts.map +1 -1
  33. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +171 -117
  34. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
  35. package/dist/adaptercloud/pages/resources/ResourcesListPage.d.ts.map +1 -1
  36. package/dist/adaptercloud/pages/resources/ResourcesListPage.js +85 -66
  37. package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
  38. package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.d.ts.map +1 -1
  39. package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +148 -121
  40. package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
  41. package/dist/adaptercloud/pages/violations/ViolationsListPage.d.ts.map +1 -1
  42. package/dist/adaptercloud/pages/violations/ViolationsListPage.js +145 -144
  43. package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
  44. package/dist/utils/pageContext.d.ts +0 -2
  45. package/dist/utils/pageContext.d.ts.map +1 -1
  46. package/dist/utils/pageContext.js +1 -3
  47. package/dist/utils/pageContext.js.map +1 -1
  48. package/package.json +1 -1
@@ -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: [B.length === 0 ? /* @__PURE__ */ O("p", {
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: B.map((e) => /* @__PURE__ */ k(A, {
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;AAqBH,wBAAgB,SAAS,gCAkSxB"}
1
+ {"version":3,"file":"CostsPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,wBAAgB,SAAS,gCAgUxB"}
@@ -6,26 +6,30 @@ import { DataState as i } from "../../components/DataState.js";
6
6
  import { BarChart as a } from "../../components/viz/BarChart.js";
7
7
  import { Sparkline as o } from "../../components/viz/Sparkline.js";
8
8
  import "../../components/viz/index.js";
9
- import { useCostRecords as s, useCostSummary as c } from "../../hooks/useAdapterCloudApi.js";
10
- import { formatDate as l } from "../../utils/formatters.js";
11
- import { useCallback as u, useMemo as d, useState as f } from "react";
12
- import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
- import { DollarSign as m, Receipt as h, Search as g, X as _ } from "lucide-react";
14
- import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
15
- import { EmphasisPanel as x, GlassCard as S, ResponsiveTable as C } from "@burdenoff/fe-libs/ui";
9
+ import { useConnections as s, useCostRecords as c, useCostSummary as l } from "../../hooks/useAdapterCloudApi.js";
10
+ import { formatDate as u } from "../../utils/formatters.js";
11
+ import { useCallback as d, useMemo as f, useState as p } from "react";
12
+ import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
+ import { DollarSign as h, Receipt as g, Search as _, X as v } from "lucide-react";
14
+ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
15
+ import { EmphasisPanel as S, GlassCard as C, ResponsiveTable as w } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/adaptercloud/pages/costs/CostsPage.tsx
17
- function w() {
18
- let { navigateTo: w } = e(), { t: T } = p(), E = u((e, t) => {
19
- let n = T(e);
17
+ var T = 100;
18
+ function E() {
19
+ let { navigateTo: E } = e(), { t: D } = m(), O = d((e, t) => {
20
+ let n = D(e);
20
21
  return n === e ? t : n;
21
- }, [T]), [D, O] = f(""), [k, A] = f(""), j = c(), M = s({ service: k || void 0 }), N = j.data?.currency ?? "USD", P = d(() => [...j.data?.lines ?? []].sort((e, t) => t.total - e.total), [j.data]), F = d(() => P.map((e) => ({
22
+ }, [D]), [k, A] = p(""), [j, M] = p(""), [N, P] = p(""), F = s({ pagination: { limit: T } }).data?.items ?? [], I = l(N || void 0), L = c({
23
+ connectionId: N || void 0,
24
+ service: j || void 0
25
+ }), R = I.data?.currency ?? "USD", z = f(() => [...I.data?.lines ?? []].sort((e, t) => t.total - e.total), [I.data]), B = f(() => z.map((e) => ({
22
26
  key: e.service,
23
27
  label: e.service,
24
28
  value: e.total,
25
- display: r(e.total, N)
26
- })), [P, N]), I = d(() => {
29
+ display: r(e.total, R)
30
+ })), [z, R]), V = f(() => {
27
31
  let e = /* @__PURE__ */ new Map();
28
- for (let t of M.data ?? []) {
32
+ for (let t of L.data ?? []) {
29
33
  let n = new Date(t.periodStart);
30
34
  if (Number.isNaN(n.getTime())) continue;
31
35
  let r = `${n.getUTCFullYear()}-${String(n.getUTCMonth() + 1).padStart(2, "0")}`;
@@ -36,43 +40,43 @@ function w() {
36
40
  points: t,
37
41
  deltaPct: r > 0 ? Math.round((n - r) / r * 100) : null
38
42
  };
39
- }, [M.data]), L = u((e) => {
40
- e.preventDefault(), A(D.trim());
41
- }, [D]), R = u(() => {
42
- O(""), A("");
43
- }, []), z = [
43
+ }, [L.data]), H = d((e) => {
44
+ e.preventDefault(), M(k.trim());
45
+ }, [k]), U = d(() => {
46
+ A(""), M("");
47
+ }, []), W = [
44
48
  {
45
49
  key: "service",
46
- header: E("adaptercloud.costs.service", "Service"),
50
+ header: O("adaptercloud.costs.service", "Service"),
47
51
  priority: "primary",
48
- cell: (e) => /* @__PURE__ */ y("span", {
52
+ cell: (e) => /* @__PURE__ */ b("span", {
49
53
  className: "font-medium text-text-primary",
50
54
  children: e.service
51
55
  })
52
56
  },
53
57
  {
54
58
  key: "amount",
55
- header: E("adaptercloud.costs.amount", "Amount"),
59
+ header: O("adaptercloud.costs.amount", "Amount"),
56
60
  priority: "primary",
57
- cell: (e) => /* @__PURE__ */ y("span", {
61
+ cell: (e) => /* @__PURE__ */ b("span", {
58
62
  className: "tabular-nums text-text-primary",
59
63
  children: r(e.amount, e.currency)
60
64
  })
61
65
  },
62
66
  {
63
67
  key: "period",
64
- header: E("adaptercloud.costs.period", "Period"),
68
+ header: O("adaptercloud.costs.period", "Period"),
65
69
  priority: "secondary",
66
- cell: (e) => /* @__PURE__ */ b("span", {
70
+ cell: (e) => /* @__PURE__ */ x("span", {
67
71
  className: "text-sm text-text-secondary",
68
72
  children: [
69
- l(e.periodStart, {
73
+ u(e.periodStart, {
70
74
  hour: void 0,
71
75
  minute: void 0
72
76
  }),
73
77
  " –",
74
78
  " ",
75
- l(e.periodEnd, {
79
+ u(e.periodEnd, {
76
80
  hour: void 0,
77
81
  minute: void 0
78
82
  })
@@ -81,163 +85,186 @@ function w() {
81
85
  },
82
86
  {
83
87
  key: "resource",
84
- header: E("adaptercloud.costs.resource", "Resource"),
88
+ header: O("adaptercloud.costs.resource", "Resource"),
85
89
  priority: "secondary",
86
- cell: (e) => e.resource?.name ? /* @__PURE__ */ y("button", {
90
+ cell: (e) => e.resource?.name ? /* @__PURE__ */ b("button", {
87
91
  type: "button",
88
- onClick: () => e.resourceId && w(`/resources/${e.resourceId}`),
92
+ onClick: () => e.resourceId && E(`/resources/${e.resourceId}`),
89
93
  className: "max-w-[12rem] truncate text-left text-sm text-accent-blue hover:underline",
90
94
  children: e.resource.name
91
- }) : e.resourceId ? /* @__PURE__ */ b("span", {
95
+ }) : e.resourceId ? /* @__PURE__ */ x("span", {
92
96
  className: "font-mono text-xs text-text-secondary",
93
97
  children: [e.resourceId.slice(0, 10), "…"]
94
- }) : /* @__PURE__ */ y("span", {
98
+ }) : /* @__PURE__ */ b("span", {
95
99
  className: "text-text-tertiary",
96
100
  children: "—"
97
101
  })
98
102
  },
99
103
  {
100
104
  key: "connection",
101
- header: E("adaptercloud.costs.connection", "Connection"),
105
+ header: O("adaptercloud.costs.connection", "Connection"),
102
106
  priority: "hidden-on-mobile",
103
- cell: (e) => e.connection?.name ? /* @__PURE__ */ y("span", {
107
+ cell: (e) => e.connection?.name ? /* @__PURE__ */ b("span", {
104
108
  className: "max-w-[12rem] truncate text-sm text-text-primary",
105
109
  children: e.connection.name
106
- }) : /* @__PURE__ */ y("span", {
110
+ }) : /* @__PURE__ */ b("span", {
107
111
  className: "text-text-tertiary",
108
112
  children: "—"
109
113
  })
110
114
  }
111
115
  ];
112
- return /* @__PURE__ */ b(t, {
113
- title: E("adaptercloud.costs.title", "FinOps"),
114
- description: E("adaptercloud.costs.subtitle", "Spend attributed to the same resources you operate — cost and topology share one model."),
115
- children: [/* @__PURE__ */ b(i, {
116
- isLoading: j.isLoading,
117
- isError: j.isError,
118
- error: j.error,
119
- onRetry: () => void j.refetch(),
120
- children: [/* @__PURE__ */ b(x, {
121
- className: "mb-8",
122
- children: [/* @__PURE__ */ y("div", {
123
- className: "mb-4 flex items-center justify-between",
124
- children: /* @__PURE__ */ y("h2", {
125
- className: "text-lg font-semibold text-text-primary",
126
- children: E("adaptercloud.costs.summary", "Cost Summary")
127
- })
128
- }), /* @__PURE__ */ b("div", {
129
- className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
130
- children: [/* @__PURE__ */ y(n, {
131
- featured: !0,
132
- icon: /* @__PURE__ */ y(m, { className: "h-5 w-5" }),
133
- value: j.data ? r(j.data.total, N) : "—",
134
- label: E("adaptercloud.costs.totalSpend", "Total Spend")
135
- }), P.slice(0, 3).map((e) => /* @__PURE__ */ y(n, {
136
- icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
137
- value: r(e.total, N),
138
- label: e.service
139
- }, e.service))]
116
+ return /* @__PURE__ */ x(t, {
117
+ title: O("adaptercloud.costs.title", "FinOps"),
118
+ description: O("adaptercloud.costs.subtitle", "Spend attributed to the same resources you operate — cost and topology share one model."),
119
+ children: [
120
+ /* @__PURE__ */ x("div", {
121
+ className: "mb-6 flex flex-wrap items-center gap-3",
122
+ children: [/* @__PURE__ */ b("label", {
123
+ htmlFor: "ada-cost-connection",
124
+ className: "text-sm text-text-secondary",
125
+ children: O("adaptercloud.costs.connection", "Connection")
126
+ }), /* @__PURE__ */ x("select", {
127
+ id: "ada-cost-connection",
128
+ value: N,
129
+ onChange: (e) => P(e.target.value),
130
+ 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)]",
131
+ children: [/* @__PURE__ */ b("option", {
132
+ value: "",
133
+ children: O("adaptercloud.costs.allConnections", "All connections")
134
+ }), F.map((e) => /* @__PURE__ */ b("option", {
135
+ value: e.id,
136
+ children: e.name
137
+ }, e.id))]
140
138
  })]
141
- }), P.length > 0 && /* @__PURE__ */ b("div", {
142
- className: "mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3",
143
- children: [/* @__PURE__ */ b("section", {
144
- className: "lg:col-span-2",
145
- children: [/* @__PURE__ */ y("h2", {
146
- className: "mb-4 text-lg font-semibold text-text-primary",
147
- children: E("adaptercloud.costs.byService", "Spend by Service")
148
- }), /* @__PURE__ */ y(S, {
149
- className: "p-5",
150
- children: /* @__PURE__ */ y(a, {
151
- data: F,
152
- labelWidthClassName: "w-24 sm:w-32"
139
+ }),
140
+ /* @__PURE__ */ x(i, {
141
+ isLoading: I.isLoading,
142
+ isError: I.isError,
143
+ error: I.error,
144
+ onRetry: () => void I.refetch(),
145
+ children: [/* @__PURE__ */ x(S, {
146
+ className: "mb-8",
147
+ children: [/* @__PURE__ */ b("div", {
148
+ className: "mb-4 flex items-center justify-between",
149
+ children: /* @__PURE__ */ b("h2", {
150
+ className: "text-lg font-semibold text-text-primary",
151
+ children: O("adaptercloud.costs.summary", "Cost Summary")
153
152
  })
153
+ }), /* @__PURE__ */ x("div", {
154
+ className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
155
+ children: [/* @__PURE__ */ b(n, {
156
+ featured: !0,
157
+ icon: /* @__PURE__ */ b(h, { className: "h-5 w-5" }),
158
+ value: I.data ? r(I.data.total, R) : "—",
159
+ label: O("adaptercloud.costs.totalSpend", "Total Spend")
160
+ }), z.slice(0, 3).map((e) => /* @__PURE__ */ b(n, {
161
+ icon: /* @__PURE__ */ b(g, { className: "h-5 w-5" }),
162
+ value: r(e.total, R),
163
+ label: e.service
164
+ }, e.service))]
154
165
  })]
155
- }), /* @__PURE__ */ b("section", { children: [/* @__PURE__ */ y("h2", {
156
- className: "mb-4 text-lg font-semibold text-text-primary",
157
- children: E("adaptercloud.costs.trend", "Monthly Trend")
158
- }), /* @__PURE__ */ y(S, {
159
- className: "flex h-[calc(100%-2.75rem)] flex-col justify-between p-5",
160
- children: I.points.length >= 2 ? /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ b("div", {
161
- className: "flex items-baseline gap-2",
162
- children: [/* @__PURE__ */ y("span", {
163
- className: "text-2xl font-bold tabular-nums text-text-primary",
164
- children: r(I.points[I.points.length - 1] ?? 0, N)
165
- }), I.deltaPct !== null && /* @__PURE__ */ b("span", {
166
- className: `text-xs font-semibold tabular-nums ${I.deltaPct > 0 ? "text-status-error-text" : "text-status-success-text"}`,
167
- children: [
168
- I.deltaPct > 0 ? "▲" : "▼",
169
- " ",
170
- Math.abs(I.deltaPct),
171
- "%",
172
- " ",
173
- E("adaptercloud.costs.vsPrev", "vs prev")
174
- ]
166
+ }), z.length > 0 && /* @__PURE__ */ x("div", {
167
+ className: "mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3",
168
+ children: [/* @__PURE__ */ x("section", {
169
+ className: "lg:col-span-2",
170
+ children: [/* @__PURE__ */ b("h2", {
171
+ className: "mb-4 text-lg font-semibold text-text-primary",
172
+ children: O("adaptercloud.costs.byService", "Spend by Service")
173
+ }), /* @__PURE__ */ b(C, {
174
+ className: "p-5",
175
+ children: /* @__PURE__ */ b(a, {
176
+ data: B,
177
+ labelWidthClassName: "w-24 sm:w-32"
178
+ })
175
179
  })]
176
- }), /* @__PURE__ */ y(o, {
177
- points: I.points,
178
- className: "mt-4 h-16 w-full text-accent-blue"
179
- })] }) : /* @__PURE__ */ y("p", {
180
- className: "py-6 text-center text-sm text-text-secondary",
181
- children: E("adaptercloud.costs.trendEmpty", "Ingest at least two months of cost to see a trend.")
182
- })
183
- })] })]
184
- })]
185
- }), /* @__PURE__ */ b("section", { children: [/* @__PURE__ */ b("div", {
186
- className: "mb-4 flex flex-wrap items-center justify-between gap-3",
187
- children: [/* @__PURE__ */ y("h2", {
188
- className: "text-lg font-semibold text-text-primary",
189
- children: E("adaptercloud.costs.records", "Cost Records")
190
- }), /* @__PURE__ */ b("form", {
191
- onSubmit: L,
192
- role: "search",
193
- className: "flex w-full items-center gap-2 sm:w-auto",
194
- children: [/* @__PURE__ */ b("div", {
195
- className: "relative w-full sm:w-56",
196
- children: [/* @__PURE__ */ y("input", {
197
- type: "text",
198
- value: D,
199
- onChange: (e) => O(e.target.value),
200
- "aria-label": E("adaptercloud.costs.filterService", "Filter by service"),
201
- placeholder: E("adaptercloud.costs.filterServicePlaceholder", "Filter by service…"),
202
- className: "w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-3 pr-9 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
203
- }), k && /* @__PURE__ */ y("button", {
204
- type: "button",
205
- onClick: R,
206
- "aria-label": E("adaptercloud.costs.clearServiceFilter", "Clear service filter"),
207
- className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
208
- children: /* @__PURE__ */ y(_, { className: "h-4 w-4" })
209
- })]
210
- }), /* @__PURE__ */ y("button", {
211
- type: "submit",
212
- "aria-label": E("adaptercloud.costs.applyServiceFilter", "Apply service filter"),
213
- className: "inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
214
- children: /* @__PURE__ */ y(g, { className: "h-4 w-4" })
180
+ }), /* @__PURE__ */ x("section", { children: [/* @__PURE__ */ b("h2", {
181
+ className: "mb-4 text-lg font-semibold text-text-primary",
182
+ children: O("adaptercloud.costs.trend", "Monthly Trend")
183
+ }), /* @__PURE__ */ b(C, {
184
+ className: "flex h-[calc(100%-2.75rem)] flex-col justify-between p-5",
185
+ children: V.points.length >= 2 ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ x("div", {
186
+ className: "flex items-baseline gap-2",
187
+ children: [/* @__PURE__ */ b("span", {
188
+ className: "text-2xl font-bold tabular-nums text-text-primary",
189
+ children: r(V.points[V.points.length - 1] ?? 0, R)
190
+ }), V.deltaPct !== null && /* @__PURE__ */ x("span", {
191
+ className: `text-xs font-semibold tabular-nums ${V.deltaPct > 0 ? "text-status-error-text" : "text-status-success-text"}`,
192
+ children: [
193
+ V.deltaPct > 0 ? "" : "▼",
194
+ " ",
195
+ Math.abs(V.deltaPct),
196
+ "%",
197
+ " ",
198
+ O("adaptercloud.costs.vsPrev", "vs prev")
199
+ ]
200
+ })]
201
+ }), /* @__PURE__ */ b(o, {
202
+ points: V.points,
203
+ className: "mt-4 h-16 w-full text-accent-blue"
204
+ })] }) : /* @__PURE__ */ b("p", {
205
+ className: "py-6 text-center text-sm text-text-secondary",
206
+ children: O("adaptercloud.costs.trendEmpty", "Ingest at least two months of cost to see a trend.")
207
+ })
208
+ })] })]
215
209
  })]
216
- })]
217
- }), /* @__PURE__ */ y(i, {
218
- isLoading: M.isLoading,
219
- isError: M.isError,
220
- isEmpty: (M.data ?? []).length === 0,
221
- error: M.error,
222
- onRetry: () => void M.refetch(),
223
- emptyTitle: E("adaptercloud.costs.emptyTitle", "No cost records"),
224
- emptyDescription: E("adaptercloud.costs.emptyDescription", "Run a cost-ingest sync against a connection to populate the ledger."),
225
- emptyIcon: /* @__PURE__ */ y(h, { className: "h-10 w-10" }),
226
- emptyAction: /* @__PURE__ */ y("button", {
227
- type: "button",
228
- onClick: () => w("/connections"),
229
- className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
230
- children: E("adaptercloud.costs.goToConnections", "Go to connections")
231
210
  }),
232
- children: /* @__PURE__ */ y(C, {
233
- data: M.data ?? [],
234
- columns: z,
235
- rowKey: (e) => e.id
236
- })
237
- })] })]
211
+ /* @__PURE__ */ x("section", { children: [/* @__PURE__ */ x("div", {
212
+ className: "mb-4 flex flex-wrap items-center justify-between gap-3",
213
+ children: [/* @__PURE__ */ b("h2", {
214
+ className: "text-lg font-semibold text-text-primary",
215
+ children: O("adaptercloud.costs.records", "Cost Records")
216
+ }), /* @__PURE__ */ x("form", {
217
+ onSubmit: H,
218
+ role: "search",
219
+ className: "flex w-full items-center gap-2 sm:w-auto",
220
+ children: [/* @__PURE__ */ x("div", {
221
+ className: "relative w-full sm:w-56",
222
+ children: [/* @__PURE__ */ b("input", {
223
+ type: "text",
224
+ value: k,
225
+ onChange: (e) => A(e.target.value),
226
+ "aria-label": O("adaptercloud.costs.filterService", "Filter by service"),
227
+ placeholder: O("adaptercloud.costs.filterServicePlaceholder", "Filter by service…"),
228
+ className: "w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-3 pr-9 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
229
+ }), j && /* @__PURE__ */ b("button", {
230
+ type: "button",
231
+ onClick: U,
232
+ "aria-label": O("adaptercloud.costs.clearServiceFilter", "Clear service filter"),
233
+ className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
234
+ children: /* @__PURE__ */ b(v, { className: "h-4 w-4" })
235
+ })]
236
+ }), /* @__PURE__ */ b("button", {
237
+ type: "submit",
238
+ "aria-label": O("adaptercloud.costs.applyServiceFilter", "Apply service filter"),
239
+ className: "inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
240
+ children: /* @__PURE__ */ b(_, { className: "h-4 w-4" })
241
+ })]
242
+ })]
243
+ }), /* @__PURE__ */ b(i, {
244
+ isLoading: L.isLoading,
245
+ isError: L.isError,
246
+ isEmpty: (L.data ?? []).length === 0,
247
+ error: L.error,
248
+ onRetry: () => void L.refetch(),
249
+ emptyTitle: O("adaptercloud.costs.emptyTitle", "No cost records"),
250
+ emptyDescription: O("adaptercloud.costs.emptyDescription", "Run a cost-ingest sync against a connection to populate the ledger."),
251
+ emptyIcon: /* @__PURE__ */ b(g, { className: "h-10 w-10" }),
252
+ emptyAction: /* @__PURE__ */ b("button", {
253
+ type: "button",
254
+ onClick: () => E("/connections"),
255
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
256
+ children: O("adaptercloud.costs.goToConnections", "Go to connections")
257
+ }),
258
+ children: /* @__PURE__ */ b(w, {
259
+ data: L.data ?? [],
260
+ columns: W,
261
+ rowKey: (e) => e.id
262
+ })
263
+ })] })
264
+ ]
238
265
  });
239
266
  }
240
267
  //#endregion
241
- export { w as CostsPage };
268
+ export { E as CostsPage };
242
269
 
243
270
  //# sourceMappingURL=CostsPage.js.map