@burdenoff/microfe-adaptercloud 2026.720.2 → 2026.802.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 (25) hide show
  1. package/dist/adaptercloud/AdapterCloudRoutes.js +1 -1
  2. package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
  3. package/dist/adaptercloud/context/AdapterCloudContext.js +1 -1
  4. package/dist/adaptercloud/context/AdapterCloudContext.js.map +1 -1
  5. package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +1 -1
  6. package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
  7. package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +1 -1
  8. package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
  9. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +1 -1
  10. package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
  11. package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +1 -1
  12. package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -1
  13. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +1 -1
  14. package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
  15. package/dist/adaptercloud/pages/policies/PolicyFormPage.js +1 -1
  16. package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
  17. package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +1 -1
  18. package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
  19. package/dist/adaptercloud/pages/resources/ResourcesListPage.js +1 -1
  20. package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
  21. package/dist/adaptercloud/pages/violations/ViolationsListPage.js +1 -1
  22. package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
  23. package/dist/adaptercloud/types/index.d.ts.map +1 -1
  24. package/dist/types/assistant.d.ts.map +1 -1
  25. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionFormPage.tsx"],"sourcesContent":["/**\n * Connection Create / Edit Form Page\n * @module adaptercloud/pages/connections\n *\n * Creates a connection (`adaCreateConnection`) against a chosen adapter, or\n * edits an existing one (`adaUpdateConnection`). Edit mode pre-fills from\n * `adaConnection`; the adapter cannot be changed after creation.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport {\n useAdapters,\n useConnection,\n useCreateConnection,\n useUpdateConnection,\n} from '../../hooks/useAdapterCloudApi';\nimport { authMethodLabels } from '../../utils/domain';\nimport type { AdaAuthMethod } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\ninterface FormState {\n adapterId: string;\n name: string;\n authMethod: AdaAuthMethod;\n environment: string;\n region: string;\n secretRef: string;\n targetRef: string;\n tags: string;\n}\n\nconst EMPTY: FormState = {\n adapterId: '',\n name: '',\n authMethod: 'NONE',\n environment: '',\n region: '',\n secretRef: '',\n targetRef: '',\n tags: '',\n};\n\nexport function ConnectionFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const adapters = useAdapters({ status: 'PUBLISHED', pagination: { limit: 100 } });\n const existing = useConnection(isEdit ? id : undefined);\n const create = useCreateConnection();\n const update = useUpdateConnection();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const c = existing.data;\n if (isEdit && c) {\n setForm({\n adapterId: c.adapterId,\n name: c.name,\n authMethod: c.authMethod,\n environment: c.environment ?? '',\n region: c.region ?? '',\n secretRef: c.secretRef ?? '',\n targetRef: c.targetRef ?? '',\n tags: c.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError(t('adaptercloud.connections.form.nameRequired', 'Name is required.'));\n return;\n }\n if (!isEdit && !form.adapterId) {\n setError(t('adaptercloud.connections.form.adapterRequired', 'Select an adapter.'));\n return;\n }\n\n const common = {\n name: form.name.trim(),\n authMethod: form.authMethod,\n environment: form.environment.trim() || undefined,\n region: form.region.trim() || undefined,\n secretRef: form.secretRef.trim() || undefined,\n targetRef: form.targetRef.trim() || undefined,\n tags: parseList(form.tags),\n };\n\n const onErr = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save connection.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (c) => navigateTo(`/connections/${c.id}`), onError: onErr }\n );\n } else {\n create.mutate(\n { ...common, adapterId: form.adapterId },\n { onSuccess: (c) => navigateTo(`/connections/${c.id}`), onError: onErr }\n );\n }\n },\n [create, update, form, id, isEdit, navigateTo, t]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.connections.editTitle', 'Edit Connection')\n : t('adaptercloud.connections.newTitle', 'New Connection')\n }\n description={t(\n 'adaptercloud.connections.formSubtitle',\n 'Point an adapter at a real, authenticated target.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/connections/${id}` : '/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.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.connections.notFound', 'Connection not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-conn-adapter\" className={labelClass}>\n {t('adaptercloud.connections.form.adapter', 'Adapter')} {isEdit ? '' : '*'}\n </label>\n <select\n id=\"ada-conn-adapter\"\n value={form.adapterId}\n disabled={isEdit}\n onChange={(e) => setField('adapterId', e.target.value)}\n className={`${fieldClass} disabled:cursor-not-allowed disabled:opacity-60`}\n >\n <option value=\"\">\n {adapters.isLoading\n ? t('adaptercloud.common.loading', 'Loading…')\n : t('adaptercloud.connections.form.selectAdapter', 'Select an adapter')}\n </option>\n {(adapters.data?.items ?? []).map((a) => (\n <option key={a.id} value={a.id}>\n {a.name} ({a.slug})\n </option>\n ))}\n </select>\n {!isEdit && adapters.data && adapters.data.items.length === 0 && (\n <p className=\"mt-1 text-xs text-status-warning-text\">\n {t(\n 'adaptercloud.connections.form.noPublishedAdapters',\n 'No published adapters. Publish an adapter first.'\n )}\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-name\" className={labelClass}>\n {t('adaptercloud.connections.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-conn-name\"\n type=\"text\"\n value={form.name}\n onChange={(e) => setField('name', e.target.value)}\n className={fieldClass}\n placeholder=\"prod-account-us-east-1\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-conn-auth\" className={labelClass}>\n {t('adaptercloud.connections.form.authMethod', 'Auth Method')}\n </label>\n <select\n id=\"ada-conn-auth\"\n value={form.authMethod}\n onChange={(e) => setField('authMethod', e.target.value as AdaAuthMethod)}\n className={fieldClass}\n >\n {(Object.keys(authMethodLabels) as AdaAuthMethod[]).map((m) => (\n <option key={m} value={m}>\n {authMethodLabels[m]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-conn-region\" className={labelClass}>\n {t('adaptercloud.connections.form.region', 'Region')}\n </label>\n <input\n id=\"ada-conn-region\"\n type=\"text\"\n value={form.region}\n onChange={(e) => setField('region', e.target.value)}\n className={fieldClass}\n placeholder=\"us-east-1\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-conn-env\" className={labelClass}>\n {t('adaptercloud.connections.form.environment', 'Environment')}\n </label>\n <input\n id=\"ada-conn-env\"\n type=\"text\"\n value={form.environment}\n onChange={(e) => setField('environment', e.target.value)}\n className={fieldClass}\n placeholder=\"production\"\n />\n </div>\n <div>\n <label htmlFor=\"ada-conn-target\" className={labelClass}>\n {t('adaptercloud.connections.form.targetRef', 'Target Reference')}\n </label>\n <input\n id=\"ada-conn-target\"\n type=\"text\"\n value={form.targetRef}\n onChange={(e) => setField('targetRef', e.target.value)}\n className={fieldClass}\n placeholder=\"arn:aws:... or cluster URL\"\n />\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-secret\" className={labelClass}>\n {t('adaptercloud.connections.form.secretRef', 'Secret Reference')}\n </label>\n <input\n id=\"ada-conn-secret\"\n type=\"text\"\n value={form.secretRef}\n onChange={(e) => setField('secretRef', e.target.value)}\n className={fieldClass}\n placeholder=\"kv://adaptercloud/aws-prod\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'adaptercloud.connections.form.secretHint',\n 'A reference to the credential in the platform secret store — never the secret itself.'\n )}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-conn-tags\"\n type=\"text\"\n value={form.tags}\n onChange={(e) => setField('tags', e.target.value)}\n className={fieldClass}\n placeholder=\"production, finops\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.connections.create', 'Create connection')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConnectionFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/connections/ConnectionFormPage.tsx"],"sourcesContent":["/**\n * Connection Create / Edit Form Page\n * @module adaptercloud/pages/connections\n *\n * Creates a connection (`adaCreateConnection`) against a chosen adapter, or\n * edits an existing one (`adaUpdateConnection`). Edit mode pre-fills from\n * `adaConnection`; the adapter cannot be changed after creation.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport {\n useAdapters,\n useConnection,\n useCreateConnection,\n useUpdateConnection,\n} from '../../hooks/useAdapterCloudApi';\nimport { authMethodLabels } from '../../utils/domain';\nimport type { AdaAuthMethod } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\ninterface FormState {\n adapterId: string;\n name: string;\n authMethod: AdaAuthMethod;\n environment: string;\n region: string;\n secretRef: string;\n targetRef: string;\n tags: string;\n}\n\nconst EMPTY: FormState = {\n adapterId: '',\n name: '',\n authMethod: 'NONE',\n environment: '',\n region: '',\n secretRef: '',\n targetRef: '',\n tags: '',\n};\n\nexport function ConnectionFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const adapters = useAdapters({ status: 'PUBLISHED', pagination: { limit: 100 } });\n const existing = useConnection(isEdit ? id : undefined);\n const create = useCreateConnection();\n const update = useUpdateConnection();\n\n const [form, setForm] = useState<FormState>(EMPTY);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const c = existing.data;\n if (isEdit && c) {\n setForm({\n adapterId: c.adapterId,\n name: c.name,\n authMethod: c.authMethod,\n environment: c.environment ?? '',\n region: c.region ?? '',\n secretRef: c.secretRef ?? '',\n targetRef: c.targetRef ?? '',\n tags: c.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const setField = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError(t('adaptercloud.connections.form.nameRequired', 'Name is required.'));\n return;\n }\n if (!isEdit && !form.adapterId) {\n setError(t('adaptercloud.connections.form.adapterRequired', 'Select an adapter.'));\n return;\n }\n\n const common = {\n name: form.name.trim(),\n authMethod: form.authMethod,\n environment: form.environment.trim() || undefined,\n region: form.region.trim() || undefined,\n secretRef: form.secretRef.trim() || undefined,\n targetRef: form.targetRef.trim() || undefined,\n tags: parseList(form.tags),\n };\n\n const onErr = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save connection.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (c) => navigateTo(`/connections/${c.id}`), onError: onErr }\n );\n } else {\n create.mutate(\n { ...common, adapterId: form.adapterId },\n { onSuccess: (c) => navigateTo(`/connections/${c.id}`), onError: onErr }\n );\n }\n },\n [create, update, form, id, isEdit, navigateTo, t]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.connections.editTitle', 'Edit Connection')\n : t('adaptercloud.connections.newTitle', 'New Connection')\n }\n description={t(\n 'adaptercloud.connections.formSubtitle',\n 'Point an adapter at a real, authenticated target.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/connections/${id}` : '/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.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.connections.notFound', 'Connection not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-conn-adapter\" className={labelClass}>\n {t('adaptercloud.connections.form.adapter', 'Adapter')} {isEdit ? '' : '*'}\n </label>\n <select\n id=\"ada-conn-adapter\"\n value={form.adapterId}\n disabled={isEdit}\n onChange={(e) => setField('adapterId', e.target.value)}\n className={`${fieldClass} disabled:cursor-not-allowed disabled:opacity-60`}\n >\n <option value=\"\">\n {adapters.isLoading\n ? t('adaptercloud.common.loading', 'Loading…')\n : t('adaptercloud.connections.form.selectAdapter', 'Select an adapter')}\n </option>\n {(adapters.data?.items ?? []).map((a) => (\n <option key={a.id} value={a.id}>\n {a.name} ({a.slug})\n </option>\n ))}\n </select>\n {!isEdit && adapters.data && adapters.data.items.length === 0 && (\n <p className=\"mt-1 text-xs text-status-warning-text\">\n {t(\n 'adaptercloud.connections.form.noPublishedAdapters',\n 'No published adapters. Publish an adapter first.'\n )}\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-name\" className={labelClass}>\n {t('adaptercloud.connections.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-conn-name\"\n type=\"text\"\n value={form.name}\n onChange={(e) => setField('name', e.target.value)}\n className={fieldClass}\n placeholder=\"prod-account-us-east-1\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-conn-auth\" className={labelClass}>\n {t('adaptercloud.connections.form.authMethod', 'Auth Method')}\n </label>\n <select\n id=\"ada-conn-auth\"\n value={form.authMethod}\n onChange={(e) => setField('authMethod', e.target.value as AdaAuthMethod)}\n className={fieldClass}\n >\n {(Object.keys(authMethodLabels) as AdaAuthMethod[]).map((m) => (\n <option key={m} value={m}>\n {authMethodLabels[m]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-conn-region\" className={labelClass}>\n {t('adaptercloud.connections.form.region', 'Region')}\n </label>\n <input\n id=\"ada-conn-region\"\n type=\"text\"\n value={form.region}\n onChange={(e) => setField('region', e.target.value)}\n className={fieldClass}\n placeholder=\"us-east-1\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"ada-conn-env\" className={labelClass}>\n {t('adaptercloud.connections.form.environment', 'Environment')}\n </label>\n <input\n id=\"ada-conn-env\"\n type=\"text\"\n value={form.environment}\n onChange={(e) => setField('environment', e.target.value)}\n className={fieldClass}\n placeholder=\"production\"\n />\n </div>\n <div>\n <label htmlFor=\"ada-conn-target\" className={labelClass}>\n {t('adaptercloud.connections.form.targetRef', 'Target Reference')}\n </label>\n <input\n id=\"ada-conn-target\"\n type=\"text\"\n value={form.targetRef}\n onChange={(e) => setField('targetRef', e.target.value)}\n className={fieldClass}\n placeholder=\"arn:aws:... or cluster URL\"\n />\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-secret\" className={labelClass}>\n {t('adaptercloud.connections.form.secretRef', 'Secret Reference')}\n </label>\n <input\n id=\"ada-conn-secret\"\n type=\"text\"\n value={form.secretRef}\n onChange={(e) => setField('secretRef', e.target.value)}\n className={fieldClass}\n placeholder=\"kv://adaptercloud/aws-prod\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'adaptercloud.connections.form.secretHint',\n 'A reference to the credential in the platform secret store — never the secret itself.'\n )}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-conn-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-conn-tags\"\n type=\"text\"\n value={form.tags}\n onChange={(e) => setField('tags', e.target.value)}\n className={fieldClass}\n placeholder=\"production, finops\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.connections.create', 'Create connection')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -11,7 +11,7 @@ import { ConfirmDialog as f } from "../../components/ConfirmDialog.js";
11
11
  import { useCallback as p, useState as m } from "react";
12
12
  import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
13
  import { AlertCircle as g, Archive as _, ArrowLeft as v, Pencil as y } from "lucide-react";
14
- import { useParams as b } from "react-router-dom";
14
+ import { useParams as b } from "react-router";
15
15
  import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
16
16
  import { GlassCard as w } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/adaptercloud/pages/policies/PolicyDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"PolicyDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyDetailPage.tsx"],"sourcesContent":["/**\n * Policy Detail Page\n * @module adaptercloud/pages/policies\n *\n * One governance policy (`adaPolicy`) — its kind, enforcement, severity, JSON\n * expression, applies-to selector, and the violations it has produced. Supports\n * archiving via `adaArchivePolicy`.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Archive, AlertCircle, Pencil } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicy, useArchivePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels } from '../../utils/domain';\nimport { formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\nexport function PolicyDetailPage() {\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 = usePolicy(id);\n const archive = useArchivePolicy();\n const policy = query.data;\n\n const [confirmArchiveOpen, setConfirmArchiveOpen] = useState(false);\n\n const handleArchive = useCallback(() => {\n if (!id) return;\n archive.mutate(id, {\n onSuccess: () => {\n setConfirmArchiveOpen(false);\n navigateTo('/policies');\n },\n });\n }, [archive, id, navigateTo]);\n\n return (\n <PageLayout\n title={policy?.name ?? t('adaptercloud.policies.detailTitle', 'Policy')}\n description={policy ? policyKindLabels[policy.kind] : undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies')}\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 {policy && (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/policies/${policy.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 <button\n type=\"button\"\n onClick={() => setConfirmArchiveOpen(true)}\n disabled={archive.isPending}\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-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n <Archive className=\"h-4 w-4\" />\n {t('adaptercloud.common.archive', 'Archive')}\n </button>\n </>\n )}\n </div>\n }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !policy}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.policies.notFound', 'Policy not found')}\n >\n {policy && (\n <div className=\"space-y-6\">\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <SeverityBadge severity={policy.severity} />\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 {policyEnforcementLabels[policy.enforcement]}\n </span>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',\n policy.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {policy.isActive\n ? t('adaptercloud.policies.active', 'Active')\n : t('adaptercloud.policies.inactive', 'Inactive')}\n </span>\n </div>\n\n {policy.description && (\n <p className=\"mb-4 text-sm text-text-secondary\">{policy.description}</p>\n )}\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-3\">\n <div>\n <dt className=\"text-text-secondary\">{t('adaptercloud.policies.kind', 'Kind')}</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {policyKindLabels[policy.kind]}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.created', 'Created')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(policy.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(policy.updatedAt)}\n </dd>\n </div>\n </dl>\n\n {policy.appliesTo.length > 0 && (\n <div className=\"mt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.policies.appliesTo', 'Applies To')}\n </h3>\n <div className=\"flex flex-wrap gap-1.5\">\n {policy.appliesTo.map((sel) => (\n <span\n key={sel}\n className=\"inline-flex items-center rounded-full bg-accent-blue-subtle px-2.5 py-0.5 font-mono text-xs text-accent-blue\"\n >\n {sel}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {policy.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 {policy.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 </GlassCard>\n\n {/* Expression */}\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.policies.expression', 'Expression')}\n </h3>\n <pre className=\"overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(policy.expression, null, 2)}\n </pre>\n </GlassCard>\n\n {/* Violations */}\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.policies.violations', 'Violations')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/violations?policyId=${policy.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 {policy.violations && policy.violations.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {policy.violations.slice(0, 10).map((v) => (\n <div\n key={v.id}\n className=\"flex w-full items-start gap-3 px-4 py-3 transition-colors hover:bg-accent/50\"\n >\n <AlertCircle className=\"mt-0.5 h-4 w-4 shrink-0 text-status-error-text\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {v.message}\n </p>\n <p className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-text-secondary\">\n <span>{formatRelativeTime(v.detectedAt)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources/${v.resourceId}`)}\n className=\"font-medium text-accent-blue hover:underline\"\n >\n {t('adaptercloud.violations.viewResource', 'View resource')}\n </button>\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <SeverityBadge severity={v.severity} />\n <StatusBadge status={v.status} />\n </div>\n </div>\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.policies.noViolations',\n 'No violations recorded for this policy.'\n )}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={confirmArchiveOpen}\n onOpenChange={setConfirmArchiveOpen}\n title={t('adaptercloud.policies.archiveTitle', 'Archive this policy?')}\n description={t(\n 'adaptercloud.policies.archivePrompt',\n 'The policy will be archived and removed from every governance view. This cannot be undone from the UI.'\n )}\n confirmLabel={t('adaptercloud.common.archive', 'Archive')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={handleArchive}\n isPending={archive.isPending}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PolicyDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyDetailPage.tsx"],"sourcesContent":["/**\n * Policy Detail Page\n * @module adaptercloud/pages/policies\n *\n * One governance policy (`adaPolicy`) — its kind, enforcement, severity, JSON\n * expression, applies-to selector, and the violations it has produced. Supports\n * archiving via `adaArchivePolicy`.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Archive, AlertCircle, Pencil } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicy, useArchivePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels } from '../../utils/domain';\nimport { formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\nexport function PolicyDetailPage() {\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 = usePolicy(id);\n const archive = useArchivePolicy();\n const policy = query.data;\n\n const [confirmArchiveOpen, setConfirmArchiveOpen] = useState(false);\n\n const handleArchive = useCallback(() => {\n if (!id) return;\n archive.mutate(id, {\n onSuccess: () => {\n setConfirmArchiveOpen(false);\n navigateTo('/policies');\n },\n });\n }, [archive, id, navigateTo]);\n\n return (\n <PageLayout\n title={policy?.name ?? t('adaptercloud.policies.detailTitle', 'Policy')}\n description={policy ? policyKindLabels[policy.kind] : undefined}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies')}\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 {policy && (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/policies/${policy.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 <button\n type=\"button\"\n onClick={() => setConfirmArchiveOpen(true)}\n disabled={archive.isPending}\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-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n <Archive className=\"h-4 w-4\" />\n {t('adaptercloud.common.archive', 'Archive')}\n </button>\n </>\n )}\n </div>\n }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !policy}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.policies.notFound', 'Policy not found')}\n >\n {policy && (\n <div className=\"space-y-6\">\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <SeverityBadge severity={policy.severity} />\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 {policyEnforcementLabels[policy.enforcement]}\n </span>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',\n policy.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n {policy.isActive\n ? t('adaptercloud.policies.active', 'Active')\n : t('adaptercloud.policies.inactive', 'Inactive')}\n </span>\n </div>\n\n {policy.description && (\n <p className=\"mb-4 text-sm text-text-secondary\">{policy.description}</p>\n )}\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-3\">\n <div>\n <dt className=\"text-text-secondary\">{t('adaptercloud.policies.kind', 'Kind')}</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {policyKindLabels[policy.kind]}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.created', 'Created')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(policy.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(policy.updatedAt)}\n </dd>\n </div>\n </dl>\n\n {policy.appliesTo.length > 0 && (\n <div className=\"mt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.policies.appliesTo', 'Applies To')}\n </h3>\n <div className=\"flex flex-wrap gap-1.5\">\n {policy.appliesTo.map((sel) => (\n <span\n key={sel}\n className=\"inline-flex items-center rounded-full bg-accent-blue-subtle px-2.5 py-0.5 font-mono text-xs text-accent-blue\"\n >\n {sel}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {policy.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 {policy.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 </GlassCard>\n\n {/* Expression */}\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.policies.expression', 'Expression')}\n </h3>\n <pre className=\"overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(policy.expression, null, 2)}\n </pre>\n </GlassCard>\n\n {/* Violations */}\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.policies.violations', 'Violations')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/violations?policyId=${policy.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 {policy.violations && policy.violations.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {policy.violations.slice(0, 10).map((v) => (\n <div\n key={v.id}\n className=\"flex w-full items-start gap-3 px-4 py-3 transition-colors hover:bg-accent/50\"\n >\n <AlertCircle className=\"mt-0.5 h-4 w-4 shrink-0 text-status-error-text\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {v.message}\n </p>\n <p className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-text-secondary\">\n <span>{formatRelativeTime(v.detectedAt)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources/${v.resourceId}`)}\n className=\"font-medium text-accent-blue hover:underline\"\n >\n {t('adaptercloud.violations.viewResource', 'View resource')}\n </button>\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <SeverityBadge severity={v.severity} />\n <StatusBadge status={v.status} />\n </div>\n </div>\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.policies.noViolations',\n 'No violations recorded for this policy.'\n )}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={confirmArchiveOpen}\n onOpenChange={setConfirmArchiveOpen}\n title={t('adaptercloud.policies.archiveTitle', 'Archive this policy?')}\n description={t(\n 'adaptercloud.policies.archivePrompt',\n 'The policy will be archived and removed from every governance view. This cannot be undone from the UI.'\n )}\n confirmLabel={t('adaptercloud.common.archive', 'Archive')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={handleArchive}\n isPending={archive.isPending}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { useCreatePolicy as o, usePolicy as s, useUpdatePolicy as c } from "../.
6
6
  import { useCallback as l, useEffect as u, useState as d } from "react";
7
7
  import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
8
  import { ArrowLeft as p, Loader2 as m, Save as h } from "lucide-react";
9
- import { useParams as g } from "react-router-dom";
9
+ import { useParams as g } from "react-router";
10
10
  import { jsx as _, jsxs as v } from "react/jsx-runtime";
11
11
  import { GlassCard as y } from "@burdenoff/fe-libs/ui";
12
12
  //#region src/adaptercloud/pages/policies/PolicyFormPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"PolicyFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyFormPage.tsx"],"sourcesContent":["/**\n * Policy Create / Edit Form Page\n * @module adaptercloud/pages/policies\n *\n * Authors a new governance policy (`adaCreatePolicy`) or edits an existing one\n * (`adaUpdatePolicy`). The presence of a route `:id` switches to edit mode and\n * pre-fills from `adaPolicy` (including the JSON expression). The expression is a\n * JSON document validated client-side before submit.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicy, useCreatePolicy, useUpdatePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels, severityLabels } from '../../utils/domain';\nimport type { AdaPolicyEnforcement, AdaPolicyKind, AdaSeverity } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\nconst DEFAULT_EXPRESSION = `{\n \"match\": { \"resourceType\": \"*\" },\n \"require\": { \"tags\": [\"owner\", \"env\"] }\n}`;\n\nexport function PolicyFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const existing = usePolicy(isEdit ? id : undefined);\n const create = useCreatePolicy();\n const update = useUpdatePolicy();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<AdaPolicyKind>('GUARDRAIL');\n const [enforcement, setEnforcement] = useState<AdaPolicyEnforcement>('WARN');\n const [severity, setSeverity] = useState<AdaSeverity>('MEDIUM');\n const [appliesTo, setAppliesTo] = useState('');\n const [tags, setTags] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [expression, setExpression] = useState(DEFAULT_EXPRESSION);\n const [error, setError] = useState<string | null>(null);\n\n // Pre-fill in edit mode from the loaded policy.\n useEffect(() => {\n const p = existing.data;\n if (isEdit && p) {\n setName(p.name);\n setDescription(p.description ?? '');\n setKind(p.kind);\n setEnforcement(p.enforcement);\n setSeverity(p.severity);\n setAppliesTo(p.appliesTo.join(', '));\n setTags(p.tags.join(', '));\n setIsActive(p.isActive);\n setExpression(JSON.stringify(p.expression, null, 2));\n }\n }, [isEdit, existing.data]);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!name.trim()) {\n setError(t('adaptercloud.policies.form.nameRequired', 'Name is required.'));\n return;\n }\n\n let parsedExpression: unknown;\n try {\n parsedExpression = JSON.parse(expression);\n } catch {\n setError(\n t('adaptercloud.policies.form.invalidExpression', 'Expression must be valid JSON.')\n );\n return;\n }\n\n const common = {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo: parseList(appliesTo),\n tags: parseList(tags),\n expression: parsedExpression,\n };\n\n const onError = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save policy.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (p) => navigateTo(`/policies/${p.id}`), onError }\n );\n } else {\n create.mutate(common, {\n onSuccess: (p) => navigateTo(`/policies/${p.id}`),\n onError,\n });\n }\n },\n [\n create,\n update,\n id,\n isEdit,\n name,\n description,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo,\n tags,\n expression,\n navigateTo,\n t,\n ]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.policies.editTitle', 'Edit Policy')\n : t('adaptercloud.policies.newTitle', 'New Policy')\n }\n description={t(\n 'adaptercloud.policies.formSubtitle',\n 'Put a governance rule into the system instead of a wiki.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/policies/${id}` : '/policies')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.policies.notFound', 'Policy not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-pol-name\" className={labelClass}>\n {t('adaptercloud.policies.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-pol-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={fieldClass}\n placeholder=\"Require owner + env tags\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-desc\" className={labelClass}>\n {t('adaptercloud.policies.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-pol-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.policies.form.descriptionPlaceholder',\n 'What this policy enforces and why.'\n )}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ada-pol-kind\" className={labelClass}>\n {t('adaptercloud.policies.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-pol-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as AdaPolicyKind)}\n className={fieldClass}\n >\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-enf\" className={labelClass}>\n {t('adaptercloud.policies.form.enforcement', 'Enforcement')}\n </label>\n <select\n id=\"ada-pol-enf\"\n value={enforcement}\n onChange={(e) => setEnforcement(e.target.value as AdaPolicyEnforcement)}\n className={fieldClass}\n >\n {(Object.keys(policyEnforcementLabels) as AdaPolicyEnforcement[]).map((en) => (\n <option key={en} value={en}>\n {policyEnforcementLabels[en]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-sev\" className={labelClass}>\n {t('adaptercloud.policies.form.severity', 'Severity')}\n </label>\n <select\n id=\"ada-pol-sev\"\n value={severity}\n onChange={(e) => setSeverity(e.target.value as AdaSeverity)}\n className={fieldClass}\n >\n {(Object.keys(severityLabels) as AdaSeverity[]).map((s) => (\n <option key={s} value={s}>\n {severityLabels[s]}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-applies\" className={labelClass}>\n {t('adaptercloud.policies.form.appliesTo', 'Applies To')}\n </label>\n <input\n id=\"ada-pol-applies\"\n type=\"text\"\n value={appliesTo}\n onChange={(e) => setAppliesTo(e.target.value)}\n className={fieldClass}\n placeholder=\"aws:ec2:instance, aws:s3:bucket\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-expr\" className={labelClass}>\n {t('adaptercloud.policies.form.expression', 'Expression (JSON)')} *\n </label>\n <textarea\n id=\"ada-pol-expr\"\n value={expression}\n onChange={(e) => setExpression(e.target.value)}\n rows={8}\n spellCheck={false}\n className={`${fieldClass} font-mono`}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-pol-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={fieldClass}\n placeholder=\"tagging, governance\"\n />\n </div>\n\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t('adaptercloud.policies.form.activeImmediately', 'Active immediately')}\n </label>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.policies.create', 'Create policy')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PolicyFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyFormPage.tsx"],"sourcesContent":["/**\n * Policy Create / Edit Form Page\n * @module adaptercloud/pages/policies\n *\n * Authors a new governance policy (`adaCreatePolicy`) or edits an existing one\n * (`adaUpdatePolicy`). The presence of a route `:id` switches to edit mode and\n * pre-fills from `adaPolicy` (including the JSON expression). The expression is a\n * JSON document validated client-side before submit.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicy, useCreatePolicy, useUpdatePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels, severityLabels } from '../../utils/domain';\nimport type { AdaPolicyEnforcement, AdaPolicyKind, AdaSeverity } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\nconst DEFAULT_EXPRESSION = `{\n \"match\": { \"resourceType\": \"*\" },\n \"require\": { \"tags\": [\"owner\", \"env\"] }\n}`;\n\nexport function PolicyFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const existing = usePolicy(isEdit ? id : undefined);\n const create = useCreatePolicy();\n const update = useUpdatePolicy();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<AdaPolicyKind>('GUARDRAIL');\n const [enforcement, setEnforcement] = useState<AdaPolicyEnforcement>('WARN');\n const [severity, setSeverity] = useState<AdaSeverity>('MEDIUM');\n const [appliesTo, setAppliesTo] = useState('');\n const [tags, setTags] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [expression, setExpression] = useState(DEFAULT_EXPRESSION);\n const [error, setError] = useState<string | null>(null);\n\n // Pre-fill in edit mode from the loaded policy.\n useEffect(() => {\n const p = existing.data;\n if (isEdit && p) {\n setName(p.name);\n setDescription(p.description ?? '');\n setKind(p.kind);\n setEnforcement(p.enforcement);\n setSeverity(p.severity);\n setAppliesTo(p.appliesTo.join(', '));\n setTags(p.tags.join(', '));\n setIsActive(p.isActive);\n setExpression(JSON.stringify(p.expression, null, 2));\n }\n }, [isEdit, existing.data]);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!name.trim()) {\n setError(t('adaptercloud.policies.form.nameRequired', 'Name is required.'));\n return;\n }\n\n let parsedExpression: unknown;\n try {\n parsedExpression = JSON.parse(expression);\n } catch {\n setError(\n t('adaptercloud.policies.form.invalidExpression', 'Expression must be valid JSON.')\n );\n return;\n }\n\n const common = {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo: parseList(appliesTo),\n tags: parseList(tags),\n expression: parsedExpression,\n };\n\n const onError = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save policy.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (p) => navigateTo(`/policies/${p.id}`), onError }\n );\n } else {\n create.mutate(common, {\n onSuccess: (p) => navigateTo(`/policies/${p.id}`),\n onError,\n });\n }\n },\n [\n create,\n update,\n id,\n isEdit,\n name,\n description,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo,\n tags,\n expression,\n navigateTo,\n t,\n ]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.policies.editTitle', 'Edit Policy')\n : t('adaptercloud.policies.newTitle', 'New Policy')\n }\n description={t(\n 'adaptercloud.policies.formSubtitle',\n 'Put a governance rule into the system instead of a wiki.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/policies/${id}` : '/policies')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.policies.notFound', 'Policy not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-pol-name\" className={labelClass}>\n {t('adaptercloud.policies.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-pol-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={fieldClass}\n placeholder=\"Require owner + env tags\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-desc\" className={labelClass}>\n {t('adaptercloud.policies.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-pol-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.policies.form.descriptionPlaceholder',\n 'What this policy enforces and why.'\n )}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ada-pol-kind\" className={labelClass}>\n {t('adaptercloud.policies.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-pol-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as AdaPolicyKind)}\n className={fieldClass}\n >\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-enf\" className={labelClass}>\n {t('adaptercloud.policies.form.enforcement', 'Enforcement')}\n </label>\n <select\n id=\"ada-pol-enf\"\n value={enforcement}\n onChange={(e) => setEnforcement(e.target.value as AdaPolicyEnforcement)}\n className={fieldClass}\n >\n {(Object.keys(policyEnforcementLabels) as AdaPolicyEnforcement[]).map((en) => (\n <option key={en} value={en}>\n {policyEnforcementLabels[en]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-sev\" className={labelClass}>\n {t('adaptercloud.policies.form.severity', 'Severity')}\n </label>\n <select\n id=\"ada-pol-sev\"\n value={severity}\n onChange={(e) => setSeverity(e.target.value as AdaSeverity)}\n className={fieldClass}\n >\n {(Object.keys(severityLabels) as AdaSeverity[]).map((s) => (\n <option key={s} value={s}>\n {severityLabels[s]}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-applies\" className={labelClass}>\n {t('adaptercloud.policies.form.appliesTo', 'Applies To')}\n </label>\n <input\n id=\"ada-pol-applies\"\n type=\"text\"\n value={appliesTo}\n onChange={(e) => setAppliesTo(e.target.value)}\n className={fieldClass}\n placeholder=\"aws:ec2:instance, aws:s3:bucket\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-expr\" className={labelClass}>\n {t('adaptercloud.policies.form.expression', 'Expression (JSON)')} *\n </label>\n <textarea\n id=\"ada-pol-expr\"\n value={expression}\n onChange={(e) => setExpression(e.target.value)}\n rows={8}\n spellCheck={false}\n className={`${fieldClass} font-mono`}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-pol-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={fieldClass}\n placeholder=\"tagging, governance\"\n />\n </div>\n\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t('adaptercloud.policies.form.activeImmediately', 'Active immediately')}\n </label>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.policies.create', 'Create policy')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import "../../components/viz/index.js";
12
12
  import { useCallback as m, useMemo as h, useState as g } from "react";
13
13
  import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
14
  import { ArrowLeft as v, Cable as y, GitCompareArrows as b, Loader2 as x, Wrench as S } from "lucide-react";
15
- import { useParams as C } from "react-router-dom";
15
+ import { useParams as C } from "react-router";
16
16
  import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
17
17
  import { GlassCard as D } from "@burdenoff/fe-libs/ui";
18
18
  //#region src/adaptercloud/pages/resources/ResourceDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/resources/ResourceDetailPage.tsx"],"sourcesContent":["/**\n * Resource Detail Page\n * @module adaptercloud/pages/resources\n *\n * One discovered resource (`adaResource`) — its connection, lifecycle state,\n * cost, provider tags / attributes, dependency edges, and any drift records that\n * reference it (`adaListDriftRecords`).\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, GitCompareArrows, Cable, Wrench, Loader2 } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { TopologyGraph, type TopologyEdge, type TopologyNode } from '../../components/viz';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useResource, useDriftRecords, useRemediateDrift } from '../../hooks/useAdapterCloudApi';\nimport { resourceStateLabels, driftStatusLabels, formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaDriftRecord } from '../../types';\n\nexport function ResourceDetailPage() {\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 = useResource(id);\n const drift = useDriftRecords({ resourceId: id });\n const remediate = useRemediateDrift();\n const resource = query.data;\n\n // Dependency topology: upstream `dependsOn` nodes on the left, this resource\n // (the focused node) on the right, edges pointing into it — mirrors the mock.\n const { topoNodes, topoEdges } = useMemo(() => {\n const deps = resource?.dependsOn ?? [];\n if (!resource || deps.length === 0) {\n return { topoNodes: [] as TopologyNode[], topoEdges: [] as TopologyEdge[] };\n }\n const nodes: TopologyNode[] = [\n ...deps.map((dep) => ({\n id: dep.id,\n label: dep.name,\n group: 'deps',\n type: dep.resourceType,\n state: dep.state,\n })),\n {\n id: resource.id,\n label: resource.name,\n group: 'focus',\n type: resource.resourceType,\n state: resource.state,\n },\n ];\n const edges: TopologyEdge[] = deps.map((dep) => ({\n id: `${dep.id}->${resource.id}`,\n from: dep.id,\n to: resource.id,\n kind: 'DEPENDS_ON',\n }));\n return { topoNodes: nodes, topoEdges: edges };\n }, [resource]);\n\n const [confirmTarget, setConfirmTarget] = useState<AdaDriftRecord | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Per-id pending so only the acted drift row spins/disables.\n const pendingId = remediate.isPending ? remediate.variables : undefined;\n\n const handleRemediate = useCallback(\n (driftId: string) => {\n setActionError(null);\n remediate.mutate(driftId, {\n onSuccess: () => setConfirmTarget(null),\n onError: (err) =>\n setActionError(err instanceof Error ? err.message : 'Failed to remediate drift.'),\n });\n },\n [remediate]\n );\n\n return (\n <PageLayout\n title={resource?.name ?? t('adaptercloud.resources.detailTitle', 'Resource')}\n description={resource?.resourceType}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/resources')}\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 }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !resource}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.resources.notFound', 'Resource not found')}\n >\n {resource && (\n <div className=\"space-y-6\">\n {/* Summary */}\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <StatusBadge status={resource.state} label={resourceStateLabels[resource.state]} />\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 {resource.resourceType}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatMoney(resource.monthlyCost)}\n <span className=\"ml-1 text-xs font-normal text-text-secondary\">\n {t('adaptercloud.resources.perMonth', '/mo')}\n </span>\n </span>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-3\">\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.externalId', 'External ID')}\n </dt>\n <dd className=\"mt-0.5 break-all font-mono text-xs text-text-primary\">\n {resource.externalId}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.region', 'Region')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">{resource.region ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.lastSeen', 'Last Seen')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {resource.lastSeenAt ? formatDate(resource.lastSeenAt) : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(resource.updatedAt)}\n </dd>\n </div>\n </dl>\n\n {resource.connection && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/connections/${resource.connection?.id}`)}\n className=\"mt-4 inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm text-text-primary transition-colors hover:bg-accent\"\n >\n <Cable className=\"h-4 w-4 text-text-secondary\" />\n {resource.connection.name}\n </button>\n )}\n\n {resource.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 {resource.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 </GlassCard>\n\n {/* Provider tags + attributes */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {resource.providerTags != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.resources.providerTags', 'Provider Tags')}\n </h3>\n <pre className=\"max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(resource.providerTags, null, 2)}\n </pre>\n </GlassCard>\n )}\n {resource.attributes != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.resources.attributes', 'Attributes')}\n </h3>\n <pre className=\"max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(resource.attributes, null, 2)}\n </pre>\n </GlassCard>\n )}\n </div>\n\n {/* Dependencies (topology) */}\n <section>\n <h3 className=\"mb-3 text-base font-semibold text-text-primary\">\n {t('adaptercloud.resources.dependencies', 'Dependencies (topology)')}\n </h3>\n {resource.dependsOn && resource.dependsOn.length > 0 ? (\n <>\n <TopologyGraph\n nodes={topoNodes}\n edges={topoEdges}\n selectedId={resource.id}\n onSelect={(nodeId) => {\n if (nodeId !== resource.id) navigateTo(`/resources/${nodeId}`);\n }}\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {t(\n 'adaptercloud.resources.dependenciesHint',\n 'Upstream resources this one depends on. Select a node to open it.'\n )}\n </p>\n </>\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('adaptercloud.resources.noDependencies', 'No dependency edges recorded.')}\n </p>\n )}\n </section>\n\n {/* Drift */}\n <section>\n <h3 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <GitCompareArrows className=\"h-4 w-4\" />\n {t('adaptercloud.resources.drift', 'Drift')}\n </h3>\n {actionError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mb-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {actionError}\n </p>\n )}\n {drift.data && drift.data.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {drift.data.map((d) => {\n const canRemediate = d.status === 'DETECTED' || d.status === 'ACKNOWLEDGED';\n return (\n <div key={d.id} className=\"flex items-start gap-3 px-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-mono text-sm text-text-primary\">\n {d.attributePath}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {driftStatusLabels[d.status]} · {formatDate(d.detectedAt)}\n </p>\n </div>\n <SeverityBadge severity={d.severity} />\n {canRemediate && (\n <button\n type=\"button\"\n disabled={pendingId === d.id}\n onClick={() => {\n setActionError(null);\n setConfirmTarget(d);\n }}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n {pendingId === d.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Wrench className=\"h-3.5 w-3.5\" />\n )}\n {t('adaptercloud.drift.remediate', 'Remediate')}\n </button>\n )}\n </div>\n );\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('adaptercloud.resources.noDrift', 'No drift detected for this resource.')}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={Boolean(confirmTarget)}\n onOpenChange={(open) => {\n if (!open) setConfirmTarget(null);\n }}\n title={t('adaptercloud.drift.remediateTitle', 'Mark this drift remediated?')}\n description={t(\n 'adaptercloud.drift.remediatePrompt',\n 'This records the divergence as remediated. Confirm only if the observed state now matches the desired state.'\n )}\n confirmLabel={t('adaptercloud.drift.remediate', 'Remediate')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={() => confirmTarget && handleRemediate(confirmTarget.id)}\n isPending={Boolean(confirmTarget) && pendingId === confirmTarget?.id}\n tone=\"primary\"\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0BA,SAAgB,IAAqB;CACnC,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,EAAY,CAAE,GACtB,IAAQ,EAAgB,EAAE,YAAY,EAAG,CAAC,GAC1C,IAAY,EAAkB,GAC9B,IAAW,EAAM,MAIjB,EAAE,cAAW,iBAAc,QAAc;EAC7C,IAAM,IAAO,GAAU,aAAa,CAAC;EA0BrC,OAzBI,CAAC,KAAY,EAAK,WAAW,IACxB;GAAE,WAAW,CAAC;GAAqB,WAAW,CAAC;EAAoB,IAwBrE;GAAE,WAAW,CArBlB,GAAG,EAAK,KAAK,OAAS;IACpB,IAAI,EAAI;IACR,OAAO,EAAI;IACX,OAAO;IACP,MAAM,EAAI;IACV,OAAO,EAAI;GACb,EAAE,GACF;IACE,IAAI,EAAS;IACb,OAAO,EAAS;IAChB,OAAO;IACP,MAAM,EAAS;IACf,OAAO,EAAS;GAClB,CAQkB;GAAO,WANG,EAAK,KAAK,OAAS;IAC/C,IAAI,GAAG,EAAI,GAAG,IAAI,EAAS;IAC3B,MAAM,EAAI;IACV,IAAI,EAAS;IACb,MAAM;GACR,EACsC;EAAM;CAC9C,GAAG,CAAC,CAAQ,CAAC,GAEP,CAAC,GAAe,KAAoB,EAAgC,IAAI,GACxE,CAAC,GAAa,KAAkB,EAAwB,IAAI,GAG5D,IAAY,EAAU,YAAY,EAAU,YAAY,KAAA,GAExD,IAAkB,GACrB,MAAoB;EAEnB,AADA,EAAe,IAAI,GACnB,EAAU,OAAO,GAAS;GACxB,iBAAiB,EAAiB,IAAI;GACtC,UAAU,MACR,EAAe,aAAe,QAAQ,EAAI,UAAU,4BAA4B;EACpF,CAAC;CACH,GACA,CAAC,CAAS,CACZ;CAEA,OACE,kBAAC,GAAD;EACE,OAAO,GAAU,QAAQ,EAAE,sCAAsC,UAAU;EAC3E,aAAa,GAAU;EACvB,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,YAAY;GACtC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAC/B,EAAE,4BAA4B,MAAM,CAC/B;;YAXZ,CAcE,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,mCAAmC,oBAAoB;aAEpE,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,GAAD;MAAW,WAAU;gBAArB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAa,QAAQ,EAAS;UAAO,OAAO,EAAoB,EAAS;SAAS,CAAA;SAClF,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAS;SACN,CAAA;SACN,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACG,EAAY,EAAS,WAAW,GACjC,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAE,mCAAmC,KAAK;UACvC,CAAA,CACF;;QACH;;OAEL,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAE,qCAAqC,aAAa;UACnD,CAAA,GACJ,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAS;UACR,CAAA,CACD;;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,iCAAiC,QAAQ;SAC1C,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBAAwC,EAAS,UAAU;SAAQ,CAAA,CAC9E,EAAA,CAAA;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,mCAAmC,WAAW;SAC/C,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAS,aAAa,EAAW,EAAS,UAAU,IAAI;SACvD,CAAA,CACD,EAAA,CAAA;SACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,+BAA+B,SAAS;SACzC,CAAA,GACJ,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAW,EAAS,SAAS;SAC5B,CAAA,CACD,EAAA,CAAA;QACH;;OAEH,EAAS,cACR,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,gBAAgB,EAAS,YAAY,IAAI;QACnE,WAAU;kBAHZ,CAKE,kBAAC,GAAD,EAAO,WAAU,8BAA+B,CAAA,GAC/C,EAAS,WAAW,IACf;;OAGT,EAAS,KAAK,SAAS,KACtB,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,4BAA4B,MAAM;QACnC,CAAA,GACJ,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAS,KAAK,KAAK,MAClB,kBAAC,QAAD;UAEE,WAAU;oBAET;SACG,GAJC,CAID,CACP;QACE,CAAA,CACF;;MAEE;;KAGX,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAS,gBAAgB,QACxB,kBAAC,GAAD;OAAW,WAAU;iBAArB,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,uCAAuC,eAAe;OACvD,CAAA,GACJ,kBAAC,OAAD;QAAK,WAAU;kBACZ,KAAK,UAAU,EAAS,cAAc,MAAM,CAAC;OAC3C,CAAA,CACI;UAEZ,EAAS,cAAc,QACtB,kBAAC,GAAD;OAAW,WAAU;iBAArB,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,qCAAqC,YAAY;OAClD,CAAA,GACJ,kBAAC,OAAD;QAAK,WAAU;kBACZ,KAAK,UAAU,EAAS,YAAY,MAAM,CAAC;OACzC,CAAA,CACI;QAEV;;KAGL,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAE,uCAAuC,yBAAyB;KACjE,CAAA,GACH,EAAS,aAAa,EAAS,UAAU,SAAS,IACjD,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;MACE,OAAO;MACP,OAAO;MACP,YAAY,EAAS;MACrB,WAAW,MAAW;OACpB,AAAI,MAAW,EAAS,MAAI,EAAW,cAAc,GAAQ;MAC/D;KACD,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,2CACA,mEACF;KACC,CAAA,CACH,EAAA,CAAA,IAEF,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,yCAAyC,+BAA+B;KAC1E,CAAA,CAEE,EAAA,CAAA;KAGT,kBAAC,WAAD,EAAA,UAAA;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,GAAD,EAAkB,WAAU,UAAW,CAAA,GACtC,EAAE,gCAAgC,OAAO,CACxC;;MACH,KACC,kBAAC,KAAD;OACE,MAAK;OACL,aAAU;OACV,WAAU;iBAET;MACA,CAAA;MAEJ,EAAM,QAAQ,EAAM,KAAK,SAAS,IACjC,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAM,KAAK,KAAK,MAAM;QACrB,IAAM,IAAe,EAAE,WAAW,cAAc,EAAE,WAAW;QAC7D,OACE,kBAAC,OAAD;SAAgB,WAAU;mBAA1B;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAE;WACF,CAAA,GACH,kBAAC,KAAD;YAAG,WAAU;sBAAb;aACG,EAAkB,EAAE;aAAQ;aAAI,EAAW,EAAE,UAAU;YACvD;aACA;;UACL,kBAAC,GAAD,EAAe,UAAU,EAAE,SAAW,CAAA;UACrC,KACC,kBAAC,UAAD;WACE,MAAK;WACL,UAAU,MAAc,EAAE;WAC1B,eAAe;YAEb,AADA,EAAe,IAAI,GACnB,EAAiB,CAAC;WACpB;WACA,WAAU;qBAPZ,CASG,MAAc,EAAE,KACf,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA,GAElC,EAAE,gCAAgC,WAAW,CACxC;;SAEP;WA5BK,EAAE,EA4BP;OAET,CAAC;MACE,CAAA,IAEL,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAE,kCAAkC,sCAAsC;MAC1E,CAAA;KAEE,EAAA,CAAA;IACN;;EAEE,CAAA,GAEX,kBAAC,GAAD;GACE,MAAM,EAAQ;GACd,eAAe,MAAS;IACtB,AAAK,KAAM,EAAiB,IAAI;GAClC;GACA,OAAO,EAAE,qCAAqC,6BAA6B;GAC3E,aAAa,EACX,sCACA,8GACF;GACA,cAAc,EAAE,gCAAgC,WAAW;GAC3D,aAAa,EAAE,8BAA8B,QAAQ;GACrD,iBAAiB,KAAiB,EAAgB,EAAc,EAAE;GAClE,WAAW,EAAQ,KAAkB,MAAc,GAAe;GAClE,MAAK;EACN,CAAA,CACS;;AAEhB"}
1
+ {"version":3,"file":"ResourceDetailPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/resources/ResourceDetailPage.tsx"],"sourcesContent":["/**\n * Resource Detail Page\n * @module adaptercloud/pages/resources\n *\n * One discovered resource (`adaResource`) — its connection, lifecycle state,\n * cost, provider tags / attributes, dependency edges, and any drift records that\n * reference it (`adaListDriftRecords`).\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, GitCompareArrows, Cable, Wrench, Loader2 } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { TopologyGraph, type TopologyEdge, type TopologyNode } from '../../components/viz';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useResource, useDriftRecords, useRemediateDrift } from '../../hooks/useAdapterCloudApi';\nimport { resourceStateLabels, driftStatusLabels, formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaDriftRecord } from '../../types';\n\nexport function ResourceDetailPage() {\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 = useResource(id);\n const drift = useDriftRecords({ resourceId: id });\n const remediate = useRemediateDrift();\n const resource = query.data;\n\n // Dependency topology: upstream `dependsOn` nodes on the left, this resource\n // (the focused node) on the right, edges pointing into it — mirrors the mock.\n const { topoNodes, topoEdges } = useMemo(() => {\n const deps = resource?.dependsOn ?? [];\n if (!resource || deps.length === 0) {\n return { topoNodes: [] as TopologyNode[], topoEdges: [] as TopologyEdge[] };\n }\n const nodes: TopologyNode[] = [\n ...deps.map((dep) => ({\n id: dep.id,\n label: dep.name,\n group: 'deps',\n type: dep.resourceType,\n state: dep.state,\n })),\n {\n id: resource.id,\n label: resource.name,\n group: 'focus',\n type: resource.resourceType,\n state: resource.state,\n },\n ];\n const edges: TopologyEdge[] = deps.map((dep) => ({\n id: `${dep.id}->${resource.id}`,\n from: dep.id,\n to: resource.id,\n kind: 'DEPENDS_ON',\n }));\n return { topoNodes: nodes, topoEdges: edges };\n }, [resource]);\n\n const [confirmTarget, setConfirmTarget] = useState<AdaDriftRecord | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Per-id pending so only the acted drift row spins/disables.\n const pendingId = remediate.isPending ? remediate.variables : undefined;\n\n const handleRemediate = useCallback(\n (driftId: string) => {\n setActionError(null);\n remediate.mutate(driftId, {\n onSuccess: () => setConfirmTarget(null),\n onError: (err) =>\n setActionError(err instanceof Error ? err.message : 'Failed to remediate drift.'),\n });\n },\n [remediate]\n );\n\n return (\n <PageLayout\n title={resource?.name ?? t('adaptercloud.resources.detailTitle', 'Resource')}\n description={resource?.resourceType}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/resources')}\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 }\n >\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={!query.isLoading && !resource}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.resources.notFound', 'Resource not found')}\n >\n {resource && (\n <div className=\"space-y-6\">\n {/* Summary */}\n <GlassCard className=\"p-6\">\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <StatusBadge status={resource.state} label={resourceStateLabels[resource.state]} />\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 {resource.resourceType}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {formatMoney(resource.monthlyCost)}\n <span className=\"ml-1 text-xs font-normal text-text-secondary\">\n {t('adaptercloud.resources.perMonth', '/mo')}\n </span>\n </span>\n </div>\n\n <dl className=\"grid grid-cols-2 gap-4 text-sm sm:grid-cols-3\">\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.externalId', 'External ID')}\n </dt>\n <dd className=\"mt-0.5 break-all font-mono text-xs text-text-primary\">\n {resource.externalId}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.region', 'Region')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">{resource.region ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.resources.lastSeen', 'Last Seen')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {resource.lastSeenAt ? formatDate(resource.lastSeenAt) : '—'}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')}\n </dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDate(resource.updatedAt)}\n </dd>\n </div>\n </dl>\n\n {resource.connection && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/connections/${resource.connection?.id}`)}\n className=\"mt-4 inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm text-text-primary transition-colors hover:bg-accent\"\n >\n <Cable className=\"h-4 w-4 text-text-secondary\" />\n {resource.connection.name}\n </button>\n )}\n\n {resource.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 {resource.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 </GlassCard>\n\n {/* Provider tags + attributes */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {resource.providerTags != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.resources.providerTags', 'Provider Tags')}\n </h3>\n <pre className=\"max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(resource.providerTags, null, 2)}\n </pre>\n </GlassCard>\n )}\n {resource.attributes != null && (\n <GlassCard className=\"p-6\">\n <h3 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {t('adaptercloud.resources.attributes', 'Attributes')}\n </h3>\n <pre className=\"max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary\">\n {JSON.stringify(resource.attributes, null, 2)}\n </pre>\n </GlassCard>\n )}\n </div>\n\n {/* Dependencies (topology) */}\n <section>\n <h3 className=\"mb-3 text-base font-semibold text-text-primary\">\n {t('adaptercloud.resources.dependencies', 'Dependencies (topology)')}\n </h3>\n {resource.dependsOn && resource.dependsOn.length > 0 ? (\n <>\n <TopologyGraph\n nodes={topoNodes}\n edges={topoEdges}\n selectedId={resource.id}\n onSelect={(nodeId) => {\n if (nodeId !== resource.id) navigateTo(`/resources/${nodeId}`);\n }}\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {t(\n 'adaptercloud.resources.dependenciesHint',\n 'Upstream resources this one depends on. Select a node to open it.'\n )}\n </p>\n </>\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('adaptercloud.resources.noDependencies', 'No dependency edges recorded.')}\n </p>\n )}\n </section>\n\n {/* Drift */}\n <section>\n <h3 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <GitCompareArrows className=\"h-4 w-4\" />\n {t('adaptercloud.resources.drift', 'Drift')}\n </h3>\n {actionError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mb-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {actionError}\n </p>\n )}\n {drift.data && drift.data.length > 0 ? (\n <div className=\"divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface\">\n {drift.data.map((d) => {\n const canRemediate = d.status === 'DETECTED' || d.status === 'ACKNOWLEDGED';\n return (\n <div key={d.id} className=\"flex items-start gap-3 px-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-mono text-sm text-text-primary\">\n {d.attributePath}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {driftStatusLabels[d.status]} · {formatDate(d.detectedAt)}\n </p>\n </div>\n <SeverityBadge severity={d.severity} />\n {canRemediate && (\n <button\n type=\"button\"\n disabled={pendingId === d.id}\n onClick={() => {\n setActionError(null);\n setConfirmTarget(d);\n }}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n {pendingId === d.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Wrench className=\"h-3.5 w-3.5\" />\n )}\n {t('adaptercloud.drift.remediate', 'Remediate')}\n </button>\n )}\n </div>\n );\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('adaptercloud.resources.noDrift', 'No drift detected for this resource.')}\n </p>\n )}\n </section>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={Boolean(confirmTarget)}\n onOpenChange={(open) => {\n if (!open) setConfirmTarget(null);\n }}\n title={t('adaptercloud.drift.remediateTitle', 'Mark this drift remediated?')}\n description={t(\n 'adaptercloud.drift.remediatePrompt',\n 'This records the divergence as remediated. Confirm only if the observed state now matches the desired state.'\n )}\n confirmLabel={t('adaptercloud.drift.remediate', 'Remediate')}\n cancelLabel={t('adaptercloud.common.cancel', 'Cancel')}\n onConfirm={() => confirmTarget && handleRemediate(confirmTarget.id)}\n isPending={Boolean(confirmTarget) && pendingId === confirmTarget?.id}\n tone=\"primary\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -8,7 +8,7 @@ import { formatRelativeTime as s } from "../../utils/formatters.js";
8
8
  import { useCallback as c, useState as l } from "react";
9
9
  import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
10
  import { ChevronLeft as d, ChevronRight as f, Search as p, Server as m, X as h } from "lucide-react";
11
- import { useSearchParams as g } from "react-router-dom";
11
+ import { useSearchParams as g } from "react-router";
12
12
  import { jsx as _, jsxs as v } from "react/jsx-runtime";
13
13
  import { ResponsiveTable as y } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/adaptercloud/pages/resources/ResourcesListPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"ResourcesListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/resources/ResourcesListPage.tsx"],"sourcesContent":["/**\n * Resources List Page\n * @module adaptercloud/pages/resources\n *\n * The unified resource inventory across the workspace (`adaListResources`).\n * Supports filtering by lifecycle state, resource type (free text) and, via the\n * `?connectionId=` URL param, by connection. Offset pagination.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Server, ChevronLeft, ChevronRight, Search, X } from 'lucide-react';\nimport { ResponsiveTable, type ResponsiveTableColumn } 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 { useResources } from '../../hooks/useAdapterCloudApi';\nimport { resourceStateLabels, formatMoney } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaResource, AdaResourceState } from '../../types';\n\nconst PAGE_SIZE = 25;\n\nexport function ResourcesListPage() {\n const { navigateTo } = useAdapterCloud();\n const [searchParams] = useSearchParams();\n const connectionId = searchParams.get('connectionId') ?? undefined;\n\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 [state, setState] = useState<AdaResourceState | ''>('');\n const [typeInput, setTypeInput] = useState('');\n const [appliedType, setAppliedType] = useState('');\n const [offset, setOffset] = useState(0);\n\n const query = useResources({\n connectionId,\n state: state || undefined,\n resourceType: appliedType || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const onSearch = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setAppliedType(typeInput.trim());\n setOffset(0);\n },\n [typeInput]\n );\n\n const clearType = useCallback(() => {\n setTypeInput('');\n setAppliedType('');\n setOffset(0);\n }, []);\n\n // Clearing the connection scope must return to a clean page 1 of the full\n // inventory — reset offset AND the applied filters, then drop the URL param.\n const clearScope = useCallback(() => {\n setState('');\n setTypeInput('');\n setAppliedType('');\n setOffset(0);\n navigateTo('/resources');\n }, [navigateTo]);\n\n const columns: ResponsiveTableColumn<AdaResource>[] = [\n {\n key: 'name',\n header: t('adaptercloud.resources.name', 'Name'),\n priority: 'primary',\n cell: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{r.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{r.externalId}</p>\n </div>\n ),\n },\n {\n key: 'type',\n header: t('adaptercloud.resources.type', 'Type'),\n priority: 'primary',\n cell: (r) => <span className=\"font-mono text-xs text-text-primary\">{r.resourceType}</span>,\n },\n {\n key: 'connection',\n header: t('adaptercloud.resources.connection', 'Connection'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"truncate text-sm text-text-secondary\">{r.connection?.name ?? '—'}</span>\n ),\n },\n {\n key: 'region',\n header: t('adaptercloud.resources.region', 'Region'),\n priority: 'secondary',\n cell: (r) => <span className=\"text-sm text-text-secondary\">{r.region ?? '—'}</span>,\n },\n {\n key: 'cost',\n header: t('adaptercloud.resources.monthlyCost', 'Monthly Cost'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"text-sm tabular-nums text-text-primary\">{formatMoney(r.monthlyCost)}</span>\n ),\n },\n {\n key: 'state',\n header: t('adaptercloud.resources.state', 'State'),\n priority: 'primary',\n cell: (r) => <StatusBadge status={r.state} label={resourceStateLabels[r.state]} />,\n },\n {\n key: 'seen',\n header: t('adaptercloud.resources.lastSeen', 'Last Seen'),\n priority: 'hidden-on-mobile',\n cell: (r) => (\n <span className=\"text-xs text-text-secondary\">\n {r.lastSeenAt ? formatRelativeTime(r.lastSeenAt) : '—'}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.resources.title', 'Resources')}\n description={t(\n 'adaptercloud.resources.subtitle',\n 'The unified inventory of everything discovered across every connection.'\n )}\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <select\n value={state}\n onChange={(e) => {\n setState(e.target.value as AdaResourceState | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.resources.filterByState', 'Filter by lifecycle state')}\n className=\"w-full 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)] sm:w-auto\"\n >\n <option value=\"\">{t('adaptercloud.resources.allStates', 'All states')}</option>\n {(Object.keys(resourceStateLabels) as AdaResourceState[]).map((s) => (\n <option key={s} value={s}>\n {resourceStateLabels[s]}\n </option>\n ))}\n </select>\n <form\n onSubmit={onSearch}\n role=\"search\"\n className=\"flex w-full items-center gap-2 sm:w-auto\"\n >\n <div className=\"relative w-full sm:w-56\">\n <input\n type=\"text\"\n value={typeInput}\n onChange={(e) => setTypeInput(e.target.value)}\n aria-label={t('adaptercloud.resources.filterType', 'Filter by resource type')}\n placeholder={t('adaptercloud.resources.filterTypePlaceholder', 'Filter by type…')}\n 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)]\"\n />\n {appliedType && (\n <button\n type=\"button\"\n onClick={clearType}\n aria-label={t('adaptercloud.resources.clearTypeFilter', 'Clear type filter')}\n 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)]\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n aria-label={t('adaptercloud.resources.applyTypeFilter', 'Apply type filter')}\n 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)]\"\n >\n <Search className=\"h-4 w-4\" />\n </button>\n </form>\n {connectionId && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-accent-blue-subtle px-2.5 py-1 text-xs text-accent-blue\">\n {t('adaptercloud.resources.scopedToConnection', 'Scoped to connection')}\n <button\n type=\"button\"\n onClick={clearScope}\n aria-label={t('adaptercloud.resources.clearScope', 'Clear connection scope')}\n className=\"inline-flex items-center rounded p-0.5 font-semibold transition-colors hover:text-accent-blue-hover focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </span>\n )}\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.resources.emptyTitle', 'No resources found')}\n emptyDescription={t(\n 'adaptercloud.resources.emptyDescription',\n 'Run a discovery sync against a connection to populate the inventory.'\n )}\n emptyIcon={<Server className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\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.resources.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <ResponsiveTable\n data={items}\n columns={columns}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`/resources/${r.id}`)}\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":"ResourcesListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/resources/ResourcesListPage.tsx"],"sourcesContent":["/**\n * Resources List Page\n * @module adaptercloud/pages/resources\n *\n * The unified resource inventory across the workspace (`adaListResources`).\n * Supports filtering by lifecycle state, resource type (free text) and, via the\n * `?connectionId=` URL param, by connection. Offset pagination.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Server, ChevronLeft, ChevronRight, Search, X } from 'lucide-react';\nimport { ResponsiveTable, type ResponsiveTableColumn } 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 { useResources } from '../../hooks/useAdapterCloudApi';\nimport { resourceStateLabels, formatMoney } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport type { AdaResource, AdaResourceState } from '../../types';\n\nconst PAGE_SIZE = 25;\n\nexport function ResourcesListPage() {\n const { navigateTo } = useAdapterCloud();\n const [searchParams] = useSearchParams();\n const connectionId = searchParams.get('connectionId') ?? undefined;\n\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 [state, setState] = useState<AdaResourceState | ''>('');\n const [typeInput, setTypeInput] = useState('');\n const [appliedType, setAppliedType] = useState('');\n const [offset, setOffset] = useState(0);\n\n const query = useResources({\n connectionId,\n state: state || undefined,\n resourceType: appliedType || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const onSearch = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setAppliedType(typeInput.trim());\n setOffset(0);\n },\n [typeInput]\n );\n\n const clearType = useCallback(() => {\n setTypeInput('');\n setAppliedType('');\n setOffset(0);\n }, []);\n\n // Clearing the connection scope must return to a clean page 1 of the full\n // inventory — reset offset AND the applied filters, then drop the URL param.\n const clearScope = useCallback(() => {\n setState('');\n setTypeInput('');\n setAppliedType('');\n setOffset(0);\n navigateTo('/resources');\n }, [navigateTo]);\n\n const columns: ResponsiveTableColumn<AdaResource>[] = [\n {\n key: 'name',\n header: t('adaptercloud.resources.name', 'Name'),\n priority: 'primary',\n cell: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{r.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{r.externalId}</p>\n </div>\n ),\n },\n {\n key: 'type',\n header: t('adaptercloud.resources.type', 'Type'),\n priority: 'primary',\n cell: (r) => <span className=\"font-mono text-xs text-text-primary\">{r.resourceType}</span>,\n },\n {\n key: 'connection',\n header: t('adaptercloud.resources.connection', 'Connection'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"truncate text-sm text-text-secondary\">{r.connection?.name ?? '—'}</span>\n ),\n },\n {\n key: 'region',\n header: t('adaptercloud.resources.region', 'Region'),\n priority: 'secondary',\n cell: (r) => <span className=\"text-sm text-text-secondary\">{r.region ?? '—'}</span>,\n },\n {\n key: 'cost',\n header: t('adaptercloud.resources.monthlyCost', 'Monthly Cost'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"text-sm tabular-nums text-text-primary\">{formatMoney(r.monthlyCost)}</span>\n ),\n },\n {\n key: 'state',\n header: t('adaptercloud.resources.state', 'State'),\n priority: 'primary',\n cell: (r) => <StatusBadge status={r.state} label={resourceStateLabels[r.state]} />,\n },\n {\n key: 'seen',\n header: t('adaptercloud.resources.lastSeen', 'Last Seen'),\n priority: 'hidden-on-mobile',\n cell: (r) => (\n <span className=\"text-xs text-text-secondary\">\n {r.lastSeenAt ? formatRelativeTime(r.lastSeenAt) : '—'}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.resources.title', 'Resources')}\n description={t(\n 'adaptercloud.resources.subtitle',\n 'The unified inventory of everything discovered across every connection.'\n )}\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <select\n value={state}\n onChange={(e) => {\n setState(e.target.value as AdaResourceState | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.resources.filterByState', 'Filter by lifecycle state')}\n className=\"w-full 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)] sm:w-auto\"\n >\n <option value=\"\">{t('adaptercloud.resources.allStates', 'All states')}</option>\n {(Object.keys(resourceStateLabels) as AdaResourceState[]).map((s) => (\n <option key={s} value={s}>\n {resourceStateLabels[s]}\n </option>\n ))}\n </select>\n <form\n onSubmit={onSearch}\n role=\"search\"\n className=\"flex w-full items-center gap-2 sm:w-auto\"\n >\n <div className=\"relative w-full sm:w-56\">\n <input\n type=\"text\"\n value={typeInput}\n onChange={(e) => setTypeInput(e.target.value)}\n aria-label={t('adaptercloud.resources.filterType', 'Filter by resource type')}\n placeholder={t('adaptercloud.resources.filterTypePlaceholder', 'Filter by type…')}\n 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)]\"\n />\n {appliedType && (\n <button\n type=\"button\"\n onClick={clearType}\n aria-label={t('adaptercloud.resources.clearTypeFilter', 'Clear type filter')}\n 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)]\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n aria-label={t('adaptercloud.resources.applyTypeFilter', 'Apply type filter')}\n 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)]\"\n >\n <Search className=\"h-4 w-4\" />\n </button>\n </form>\n {connectionId && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-accent-blue-subtle px-2.5 py-1 text-xs text-accent-blue\">\n {t('adaptercloud.resources.scopedToConnection', 'Scoped to connection')}\n <button\n type=\"button\"\n onClick={clearScope}\n aria-label={t('adaptercloud.resources.clearScope', 'Clear connection scope')}\n className=\"inline-flex items-center rounded p-0.5 font-semibold transition-colors hover:text-accent-blue-hover focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </span>\n )}\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.resources.emptyTitle', 'No resources found')}\n emptyDescription={t(\n 'adaptercloud.resources.emptyDescription',\n 'Run a discovery sync against a connection to populate the inventory.'\n )}\n emptyIcon={<Server className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\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.resources.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <ResponsiveTable\n data={items}\n columns={columns}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`/resources/${r.id}`)}\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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"}
@@ -10,7 +10,7 @@ import { formatDate as c } from "../../utils/formatters.js";
10
10
  import { useCallback as l, useState as u } from "react";
11
11
  import { useI18n as ne } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
12
  import { Check as re, CheckCheck as d, ChevronDown as f, ChevronLeft as p, ChevronRight as m, FileText as h, Loader2 as g, ShieldAlert as _ } from "lucide-react";
13
- import { useSearchParams as v } from "react-router-dom";
13
+ import { useSearchParams as v } from "react-router";
14
14
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
15
15
  import { GlassCard as x, ResponsiveDialog as S, ResponsiveDialogContent as C, ResponsiveDialogFooter as w, ResponsiveDialogHeader as T, ResponsiveDialogTitle as E } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/adaptercloud/pages/violations/ViolationsListPage.tsx