@burdenoff/microfe-adaptercloud 2026.802.1 → 2026.805.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.
@@ -4,25 +4,35 @@ import { driftStatusLabels as n, formatMoney as r, resourceStateLabels as i } fr
4
4
  import { StatusBadge as a } from "../../components/StatusBadge.js";
5
5
  import { SeverityBadge as o } from "../../components/SeverityBadge.js";
6
6
  import { DataState as s } from "../../components/DataState.js";
7
- import { useDriftRecords as c, useRemediateDrift as l, useResource as u } from "../../hooks/useAdapterCloudApi.js";
8
- import { formatDate as d } from "../../utils/formatters.js";
9
- import { ConfirmDialog as f } from "../../components/ConfirmDialog.js";
10
- import { TopologyGraph as p } from "../../components/viz/TopologyGraph.js";
7
+ import { Sparkline as c } from "../../components/viz/Sparkline.js";
8
+ import { TopologyGraph as l } from "../../components/viz/TopologyGraph.js";
11
9
  import "../../components/viz/index.js";
12
- import { useCallback as m, useMemo as h, useState as g } from "react";
13
- import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
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";
16
- import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
17
- import { GlassCard as D } from "@burdenoff/fe-libs/ui";
10
+ import { useCostRecords as u, useDriftRecords as d, useRemediateDrift as f, useResource as p } from "../../hooks/useAdapterCloudApi.js";
11
+ import { formatDate as m } from "../../utils/formatters.js";
12
+ import { ConfirmDialog as h } from "../../components/ConfirmDialog.js";
13
+ import { useCallback as g, useMemo as _, useState as v } from "react";
14
+ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
15
+ import { ArrowLeft as b, Cable as x, GitCompareArrows as S, Loader2 as C, Wrench as w } from "lucide-react";
16
+ import { useParams as T } from "react-router";
17
+ import { Fragment as E, jsx as D, jsxs as O } from "react/jsx-runtime";
18
+ import { GlassCard as k } from "@burdenoff/fe-libs/ui";
18
19
  //#region src/adaptercloud/pages/resources/ResourceDetailPage.tsx
19
- function O() {
20
- let { id: O } = C(), { navigateTo: k } = e(), { t: A } = _(), j = m((e, t) => {
21
- let n = A(e);
20
+ function A() {
21
+ let { id: A } = T(), { navigateTo: j } = e(), { t: M } = y(), N = g((e, t) => {
22
+ let n = M(e);
22
23
  return n === e ? t : n;
23
- }, [A]), M = u(O), N = c({ resourceId: O }), P = l(), F = M.data, { topoNodes: I, topoEdges: L } = h(() => {
24
- let e = F?.dependsOn ?? [];
25
- return !F || e.length === 0 ? {
24
+ }, [M]), P = p(A), F = d({ resourceId: A }), I = u({ resourceId: A }), L = f(), R = P.data, z = _(() => {
25
+ let e = I.data ?? [];
26
+ if (e.length === 0) return [];
27
+ let t = /* @__PURE__ */ new Map();
28
+ for (let n of e) {
29
+ let e = n.periodStart.slice(0, 7);
30
+ t.set(e, (t.get(e) ?? 0) + n.amount);
31
+ }
32
+ return Array.from(t.entries()).sort(([e], [t]) => e.localeCompare(t)).slice(-12).map(([, e]) => e);
33
+ }, [I.data]), { topoNodes: B, topoEdges: V } = _(() => {
34
+ let e = R?.dependsOn ?? [];
35
+ return !R || e.length === 0 ? {
26
36
  topoNodes: [],
27
37
  topoEdges: []
28
38
  } : {
@@ -33,117 +43,128 @@ function O() {
33
43
  type: e.resourceType,
34
44
  state: e.state
35
45
  })), {
36
- id: F.id,
37
- label: F.name,
46
+ id: R.id,
47
+ label: R.name,
38
48
  group: "focus",
39
- type: F.resourceType,
40
- state: F.state
49
+ type: R.resourceType,
50
+ state: R.state
41
51
  }],
42
52
  topoEdges: e.map((e) => ({
43
- id: `${e.id}->${F.id}`,
53
+ id: `${e.id}->${R.id}`,
44
54
  from: e.id,
45
- to: F.id,
55
+ to: R.id,
46
56
  kind: "DEPENDS_ON"
47
57
  }))
48
58
  };
49
- }, [F]), [R, z] = g(null), [B, V] = g(null), H = P.isPending ? P.variables : void 0, U = m((e) => {
50
- V(null), P.mutate(e, {
51
- onSuccess: () => z(null),
52
- onError: (e) => V(e instanceof Error ? e.message : "Failed to remediate drift.")
59
+ }, [R]), [H, U] = v(null), [W, G] = v(null), K = L.isPending ? L.variables : void 0, q = g((e) => {
60
+ G(null), L.mutate(e, {
61
+ onSuccess: () => U(null),
62
+ onError: (e) => G(e instanceof Error ? e.message : "Failed to remediate drift.")
53
63
  });
54
- }, [P]);
55
- return /* @__PURE__ */ E(t, {
56
- title: F?.name ?? j("adaptercloud.resources.detailTitle", "Resource"),
57
- description: F?.resourceType,
58
- actions: /* @__PURE__ */ E("button", {
64
+ }, [L]);
65
+ return /* @__PURE__ */ O(t, {
66
+ title: R?.name ?? N("adaptercloud.resources.detailTitle", "Resource"),
67
+ description: R?.resourceType,
68
+ actions: /* @__PURE__ */ O("button", {
59
69
  type: "button",
60
- onClick: () => k("/resources"),
70
+ onClick: () => j("/resources"),
61
71
  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",
62
- children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), j("adaptercloud.common.back", "Back")]
72
+ children: [/* @__PURE__ */ D(b, { className: "h-4 w-4" }), N("adaptercloud.common.back", "Back")]
63
73
  }),
64
- children: [/* @__PURE__ */ T(s, {
65
- isLoading: M.isLoading,
66
- isError: M.isError,
67
- isEmpty: !M.isLoading && !F,
68
- error: M.error,
69
- onRetry: () => void M.refetch(),
70
- emptyTitle: j("adaptercloud.resources.notFound", "Resource not found"),
71
- children: F && /* @__PURE__ */ E("div", {
74
+ children: [/* @__PURE__ */ D(s, {
75
+ isLoading: P.isLoading,
76
+ isError: P.isError,
77
+ isEmpty: !P.isLoading && !R,
78
+ error: P.error,
79
+ onRetry: () => void P.refetch(),
80
+ emptyTitle: N("adaptercloud.resources.notFound", "Resource not found"),
81
+ children: R && /* @__PURE__ */ O("div", {
72
82
  className: "space-y-6",
73
83
  children: [
74
- /* @__PURE__ */ E(D, {
84
+ /* @__PURE__ */ O(k, {
75
85
  className: "p-6",
76
86
  children: [
77
- /* @__PURE__ */ E("div", {
87
+ /* @__PURE__ */ O("div", {
78
88
  className: "mb-4 flex flex-wrap items-center gap-3",
79
89
  children: [
80
- /* @__PURE__ */ T(a, {
81
- status: F.state,
82
- label: i[F.state]
90
+ /* @__PURE__ */ D(a, {
91
+ status: R.state,
92
+ label: i[R.state]
83
93
  }),
84
- /* @__PURE__ */ T("span", {
94
+ /* @__PURE__ */ D("span", {
85
95
  className: "inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
86
- children: F.resourceType
96
+ children: R.resourceType
87
97
  }),
88
- /* @__PURE__ */ E("span", {
98
+ /* @__PURE__ */ O("span", {
89
99
  className: "text-sm font-semibold tabular-nums text-text-primary",
90
- children: [r(F.monthlyCost), /* @__PURE__ */ T("span", {
100
+ children: [r(R.monthlyCost), /* @__PURE__ */ D("span", {
91
101
  className: "ml-1 text-xs font-normal text-text-secondary",
92
- children: j("adaptercloud.resources.perMonth", "/mo")
102
+ children: N("adaptercloud.resources.perMonth", "/mo")
93
103
  })]
94
104
  })
95
105
  ]
96
106
  }),
97
- /* @__PURE__ */ E("dl", {
107
+ z.length > 1 && /* @__PURE__ */ O("div", {
108
+ className: "mt-4",
109
+ children: [/* @__PURE__ */ D("h3", {
110
+ className: "mb-2 text-xs font-medium text-text-secondary",
111
+ children: N("adaptercloud.resources.monthlyTrend", "Monthly trend")
112
+ }), /* @__PURE__ */ D(c, {
113
+ points: z,
114
+ className: "text-accent-blue h-10 w-full max-w-xs",
115
+ ariaLabel: N("adaptercloud.resources.monthlyTrendAria", "Monthly cost trend for this resource")
116
+ })]
117
+ }),
118
+ /* @__PURE__ */ O("dl", {
98
119
  className: "grid grid-cols-2 gap-4 text-sm sm:grid-cols-3",
99
120
  children: [
100
- /* @__PURE__ */ E("div", {
121
+ /* @__PURE__ */ O("div", {
101
122
  className: "col-span-2 sm:col-span-3",
102
- children: [/* @__PURE__ */ T("dt", {
123
+ children: [/* @__PURE__ */ D("dt", {
103
124
  className: "text-text-secondary",
104
- children: j("adaptercloud.resources.externalId", "External ID")
105
- }), /* @__PURE__ */ T("dd", {
125
+ children: N("adaptercloud.resources.externalId", "External ID")
126
+ }), /* @__PURE__ */ D("dd", {
106
127
  className: "mt-0.5 break-all font-mono text-xs text-text-primary",
107
- children: F.externalId
128
+ children: R.externalId
108
129
  })]
109
130
  }),
110
- /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
131
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
111
132
  className: "text-text-secondary",
112
- children: j("adaptercloud.resources.region", "Region")
113
- }), /* @__PURE__ */ T("dd", {
133
+ children: N("adaptercloud.resources.region", "Region")
134
+ }), /* @__PURE__ */ D("dd", {
114
135
  className: "mt-0.5 font-medium text-text-primary",
115
- children: F.region ?? "—"
136
+ children: R.region ?? "—"
116
137
  })] }),
117
- /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
138
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
118
139
  className: "text-text-secondary",
119
- children: j("adaptercloud.resources.lastSeen", "Last Seen")
120
- }), /* @__PURE__ */ T("dd", {
140
+ children: N("adaptercloud.resources.lastSeen", "Last Seen")
141
+ }), /* @__PURE__ */ D("dd", {
121
142
  className: "mt-0.5 font-medium text-text-primary",
122
- children: F.lastSeenAt ? d(F.lastSeenAt) : "—"
143
+ children: R.lastSeenAt ? m(R.lastSeenAt) : "—"
123
144
  })] }),
124
- /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("dt", {
145
+ /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
125
146
  className: "text-text-secondary",
126
- children: j("adaptercloud.common.updated", "Updated")
127
- }), /* @__PURE__ */ T("dd", {
147
+ children: N("adaptercloud.common.updated", "Updated")
148
+ }), /* @__PURE__ */ D("dd", {
128
149
  className: "mt-0.5 font-medium text-text-primary",
129
- children: d(F.updatedAt)
150
+ children: m(R.updatedAt)
130
151
  })] })
131
152
  ]
132
153
  }),
133
- F.connection && /* @__PURE__ */ E("button", {
154
+ R.connection && /* @__PURE__ */ O("button", {
134
155
  type: "button",
135
- onClick: () => k(`/connections/${F.connection?.id}`),
156
+ onClick: () => j(`/connections/${R.connection?.id}`),
136
157
  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",
137
- children: [/* @__PURE__ */ T(y, { className: "h-4 w-4 text-text-secondary" }), F.connection.name]
158
+ children: [/* @__PURE__ */ D(x, { className: "h-4 w-4 text-text-secondary" }), R.connection.name]
138
159
  }),
139
- F.tags.length > 0 && /* @__PURE__ */ E("div", {
160
+ R.tags.length > 0 && /* @__PURE__ */ O("div", {
140
161
  className: "mt-4",
141
- children: [/* @__PURE__ */ T("h3", {
162
+ children: [/* @__PURE__ */ D("h3", {
142
163
  className: "mb-2 text-sm font-semibold text-text-primary",
143
- children: j("adaptercloud.common.tags", "Tags")
144
- }), /* @__PURE__ */ T("div", {
164
+ children: N("adaptercloud.common.tags", "Tags")
165
+ }), /* @__PURE__ */ D("div", {
145
166
  className: "flex flex-wrap gap-1.5",
146
- children: F.tags.map((e) => /* @__PURE__ */ T("span", {
167
+ children: R.tags.map((e) => /* @__PURE__ */ D("span", {
147
168
  className: "inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary",
148
169
  children: e
149
170
  }, e))
@@ -151,113 +172,113 @@ function O() {
151
172
  })
152
173
  ]
153
174
  }),
154
- /* @__PURE__ */ E("div", {
175
+ /* @__PURE__ */ O("div", {
155
176
  className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
156
- children: [F.providerTags != null && /* @__PURE__ */ E(D, {
177
+ children: [R.providerTags != null && /* @__PURE__ */ O(k, {
157
178
  className: "p-6",
158
- children: [/* @__PURE__ */ T("h3", {
179
+ children: [/* @__PURE__ */ D("h3", {
159
180
  className: "mb-3 text-sm font-semibold text-text-primary",
160
- children: j("adaptercloud.resources.providerTags", "Provider Tags")
161
- }), /* @__PURE__ */ T("pre", {
181
+ children: N("adaptercloud.resources.providerTags", "Provider Tags")
182
+ }), /* @__PURE__ */ D("pre", {
162
183
  className: "max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary",
163
- children: JSON.stringify(F.providerTags, null, 2)
184
+ children: JSON.stringify(R.providerTags, null, 2)
164
185
  })]
165
- }), F.attributes != null && /* @__PURE__ */ E(D, {
186
+ }), R.attributes != null && /* @__PURE__ */ O(k, {
166
187
  className: "p-6",
167
- children: [/* @__PURE__ */ T("h3", {
188
+ children: [/* @__PURE__ */ D("h3", {
168
189
  className: "mb-3 text-sm font-semibold text-text-primary",
169
- children: j("adaptercloud.resources.attributes", "Attributes")
170
- }), /* @__PURE__ */ T("pre", {
190
+ children: N("adaptercloud.resources.attributes", "Attributes")
191
+ }), /* @__PURE__ */ D("pre", {
171
192
  className: "max-h-72 overflow-auto rounded-lg bg-bg-sunken p-4 text-xs text-text-secondary",
172
- children: JSON.stringify(F.attributes, null, 2)
193
+ children: JSON.stringify(R.attributes, null, 2)
173
194
  })]
174
195
  })]
175
196
  }),
176
- /* @__PURE__ */ E("section", { children: [/* @__PURE__ */ T("h3", {
197
+ /* @__PURE__ */ O("section", { children: [/* @__PURE__ */ D("h3", {
177
198
  className: "mb-3 text-base font-semibold text-text-primary",
178
- children: j("adaptercloud.resources.dependencies", "Dependencies (topology)")
179
- }), F.dependsOn && F.dependsOn.length > 0 ? /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T(p, {
180
- nodes: I,
181
- edges: L,
182
- selectedId: F.id,
199
+ children: N("adaptercloud.resources.dependencies", "Dependencies (topology)")
200
+ }), R.dependsOn && R.dependsOn.length > 0 ? /* @__PURE__ */ O(E, { children: [/* @__PURE__ */ D(l, {
201
+ nodes: B,
202
+ edges: V,
203
+ selectedId: R.id,
183
204
  onSelect: (e) => {
184
- e !== F.id && k(`/resources/${e}`);
205
+ e !== R.id && j(`/resources/${e}`);
185
206
  }
186
- }), /* @__PURE__ */ T("p", {
207
+ }), /* @__PURE__ */ D("p", {
187
208
  className: "mt-2 text-xs text-text-secondary",
188
- children: j("adaptercloud.resources.dependenciesHint", "Upstream resources this one depends on. Select a node to open it.")
189
- })] }) : /* @__PURE__ */ T("p", {
209
+ children: N("adaptercloud.resources.dependenciesHint", "Upstream resources this one depends on. Select a node to open it.")
210
+ })] }) : /* @__PURE__ */ D("p", {
190
211
  className: "rounded-xl border border-border-subtle bg-bg-surface px-4 py-6 text-center text-sm text-text-secondary",
191
- children: j("adaptercloud.resources.noDependencies", "No dependency edges recorded.")
212
+ children: N("adaptercloud.resources.noDependencies", "No dependency edges recorded.")
192
213
  })] }),
193
- /* @__PURE__ */ E("section", { children: [
194
- /* @__PURE__ */ E("h3", {
214
+ /* @__PURE__ */ O("section", { children: [
215
+ /* @__PURE__ */ O("h3", {
195
216
  className: "mb-3 flex items-center gap-2 text-base font-semibold text-text-primary",
196
- children: [/* @__PURE__ */ T(b, { className: "h-4 w-4" }), j("adaptercloud.resources.drift", "Drift")]
217
+ children: [/* @__PURE__ */ D(S, { className: "h-4 w-4" }), N("adaptercloud.resources.drift", "Drift")]
197
218
  }),
198
- B && /* @__PURE__ */ T("p", {
219
+ W && /* @__PURE__ */ D("p", {
199
220
  role: "alert",
200
221
  "aria-live": "polite",
201
222
  className: "mb-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
202
- children: B
223
+ children: W
203
224
  }),
204
- N.data && N.data.length > 0 ? /* @__PURE__ */ T("div", {
225
+ F.data && F.data.length > 0 ? /* @__PURE__ */ D("div", {
205
226
  className: "divide-y divide-border rounded-xl border border-border-subtle bg-bg-surface",
206
- children: N.data.map((e) => {
227
+ children: F.data.map((e) => {
207
228
  let t = e.status === "DETECTED" || e.status === "ACKNOWLEDGED";
208
- return /* @__PURE__ */ E("div", {
229
+ return /* @__PURE__ */ O("div", {
209
230
  className: "flex items-start gap-3 px-4 py-3",
210
231
  children: [
211
- /* @__PURE__ */ E("div", {
232
+ /* @__PURE__ */ O("div", {
212
233
  className: "min-w-0 flex-1",
213
- children: [/* @__PURE__ */ T("p", {
234
+ children: [/* @__PURE__ */ D("p", {
214
235
  className: "truncate font-mono text-sm text-text-primary",
215
236
  children: e.attributePath
216
- }), /* @__PURE__ */ E("p", {
237
+ }), /* @__PURE__ */ O("p", {
217
238
  className: "mt-0.5 text-xs text-text-secondary",
218
239
  children: [
219
240
  n[e.status],
220
241
  " · ",
221
- d(e.detectedAt)
242
+ m(e.detectedAt)
222
243
  ]
223
244
  })]
224
245
  }),
225
- /* @__PURE__ */ T(o, { severity: e.severity }),
226
- t && /* @__PURE__ */ E("button", {
246
+ /* @__PURE__ */ D(o, { severity: e.severity }),
247
+ t && /* @__PURE__ */ O("button", {
227
248
  type: "button",
228
- disabled: H === e.id,
249
+ disabled: K === e.id,
229
250
  onClick: () => {
230
- V(null), z(e);
251
+ G(null), U(e);
231
252
  },
232
253
  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",
233
- children: [H === e.id ? /* @__PURE__ */ T(x, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ T(S, { className: "h-3.5 w-3.5" }), j("adaptercloud.drift.remediate", "Remediate")]
254
+ children: [K === e.id ? /* @__PURE__ */ D(C, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ D(w, { className: "h-3.5 w-3.5" }), N("adaptercloud.drift.remediate", "Remediate")]
234
255
  })
235
256
  ]
236
257
  }, e.id);
237
258
  })
238
- }) : /* @__PURE__ */ T("p", {
259
+ }) : /* @__PURE__ */ D("p", {
239
260
  className: "rounded-xl border border-border-subtle bg-bg-surface px-4 py-6 text-center text-sm text-text-secondary",
240
- children: j("adaptercloud.resources.noDrift", "No drift detected for this resource.")
261
+ children: N("adaptercloud.resources.noDrift", "No drift detected for this resource.")
241
262
  })
242
263
  ] })
243
264
  ]
244
265
  })
245
- }), /* @__PURE__ */ T(f, {
246
- open: !!R,
266
+ }), /* @__PURE__ */ D(h, {
267
+ open: !!H,
247
268
  onOpenChange: (e) => {
248
- e || z(null);
269
+ e || U(null);
249
270
  },
250
- title: j("adaptercloud.drift.remediateTitle", "Mark this drift remediated?"),
251
- description: j("adaptercloud.drift.remediatePrompt", "This records the divergence as remediated. Confirm only if the observed state now matches the desired state."),
252
- confirmLabel: j("adaptercloud.drift.remediate", "Remediate"),
253
- cancelLabel: j("adaptercloud.common.cancel", "Cancel"),
254
- onConfirm: () => R && U(R.id),
255
- isPending: !!R && H === R?.id,
271
+ title: N("adaptercloud.drift.remediateTitle", "Mark this drift remediated?"),
272
+ description: N("adaptercloud.drift.remediatePrompt", "This records the divergence as remediated. Confirm only if the observed state now matches the desired state."),
273
+ confirmLabel: N("adaptercloud.drift.remediate", "Remediate"),
274
+ cancelLabel: N("adaptercloud.common.cancel", "Cancel"),
275
+ onConfirm: () => H && q(H.id),
276
+ isPending: !!H && K === H?.id,
256
277
  tone: "primary"
257
278
  })]
258
279
  });
259
280
  }
260
281
  //#endregion
261
- export { O as ResourceDetailPage };
282
+ export { A as ResourceDetailPage };
262
283
 
263
284
  //# sourceMappingURL=ResourceDetailPage.js.map
@@ -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';\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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"}
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 {\n useResource,\n useDriftRecords,\n useRemediateDrift,\n useCostRecords,\n} from '../../hooks/useAdapterCloudApi';\nimport { resourceStateLabels, driftStatusLabels, formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport { Sparkline } from '../../components/viz';\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 costRecords = useCostRecords({ resourceId: id });\n const remediate = useRemediateDrift();\n const resource = query.data;\n\n // Monthly cost trend for the sparkline: aggregate cost-record amounts by\n // period-start month and take the last 12 months in chronological order.\n const monthlyCostTrend = useMemo(() => {\n const records = costRecords.data ?? [];\n if (records.length === 0) return [];\n const byMonth = new Map<string, number>();\n for (const r of records) {\n const month = r.periodStart.slice(0, 7); // YYYY-MM\n byMonth.set(month, (byMonth.get(month) ?? 0) + r.amount);\n }\n return Array.from(byMonth.entries())\n .sort(([a], [b]) => a.localeCompare(b))\n .slice(-12)\n .map(([, amount]) => amount);\n }, [costRecords.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 {monthlyCostTrend.length > 1 && (\n <div className=\"mt-4\">\n <h3 className=\"mb-2 text-xs font-medium text-text-secondary\">\n {t('adaptercloud.resources.monthlyTrend', 'Monthly trend')}\n </h3>\n <Sparkline\n points={monthlyCostTrend}\n className=\"text-accent-blue h-10 w-full max-w-xs\"\n ariaLabel={t(\n 'adaptercloud.resources.monthlyTrendAria',\n 'Monthly cost trend for this resource'\n )}\n />\n </div>\n )}\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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"}
@@ -3,9 +3,9 @@ import { PageLayout as t } from "../../components/PageLayout.js";
3
3
  import { humanizeEnum as n, resourceStateLabels as r } from "../../utils/domain.js";
4
4
  import { StatusBadge as i } from "../../components/StatusBadge.js";
5
5
  import { DataState as a } from "../../components/DataState.js";
6
- import { useResources as o, useTopology as s } from "../../hooks/useAdapterCloudApi.js";
7
- import { TopologyGraph as ee } from "../../components/viz/TopologyGraph.js";
6
+ import { TopologyGraph as o } from "../../components/viz/TopologyGraph.js";
8
7
  import "../../components/viz/index.js";
8
+ import { useResources as s, useTopology as ee } from "../../hooks/useAdapterCloudApi.js";
9
9
  import { useCallback as c, useMemo as l, useState as u } from "react";
10
10
  import { useI18n as te } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
11
  import { ArrowRight as d, Boxes as f, GitBranch as p, Layers as m, RefreshCw as h, Search as g, Waypoints as _, X as v } from "lucide-react";
@@ -40,7 +40,7 @@ function w() {
40
40
  let { navigateTo: w } = e(), { t: T } = te(), E = c((e, t) => {
41
41
  let n = T(e);
42
42
  return n === e ? t : n;
43
- }, [T]), D = o({ pagination: { limit: 100 } }), O = s(), [k, A] = u(""), [j, M] = u(""), [N, P] = u(null), F = l(() => D.data?.items ?? [], [D.data]), I = l(() => O.data ?? [], [O.data]), L = l(() => F.map((e) => ({
43
+ }, [T]), D = s({ pagination: { limit: 100 } }), O = ee(), [k, A] = u(""), [j, M] = u(""), [N, P] = u(null), F = l(() => D.data?.items ?? [], [D.data]), I = l(() => O.data ?? [], [O.data]), L = l(() => F.map((e) => ({
44
44
  id: e.id,
45
45
  label: e.name,
46
46
  type: e.resourceType,
@@ -209,7 +209,7 @@ function w() {
209
209
  className: "rounded-lg border border-border-subtle bg-bg-sunken/50 px-4 py-3 text-sm text-text-secondary",
210
210
  children: E("adaptercloud.topology.noEdges", "No dependency edges recorded yet — nodes are shown grouped by zone. Add dependencies from a resource to connect the graph.")
211
211
  }),
212
- W.length > 0 ? /* @__PURE__ */ y(ee, {
212
+ W.length > 0 ? /* @__PURE__ */ y(o, {
213
213
  nodes: W,
214
214
  edges: K,
215
215
  selectedId: N,