@burdenoff/microfe-adaptercloud 2026.801.1 → 2026.802.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adaptercloud/components/viz/StackedBar.js +35 -1
- package/dist/adaptercloud/components/viz/StackedBar.js.map +1 -0
- package/dist/adaptercloud/pages/OverviewPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +178 -163
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +136 -139
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +9 -9
- package/dist/adaptercloud/pages/policies/PolicyFormPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js +34 -31
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +149 -128
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/topology/TopologyPage.js +4 -4
- package/dist/adaptercloud/pages/topology/TopologyPage.js.map +1 -1
- package/dist/adaptercloud/types/index.d.ts.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
8
|
-
import {
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
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
|
|
20
|
-
let { id:
|
|
21
|
-
let n =
|
|
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
|
-
}, [
|
|
24
|
-
let e =
|
|
25
|
-
|
|
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:
|
|
37
|
-
label:
|
|
46
|
+
id: R.id,
|
|
47
|
+
label: R.name,
|
|
38
48
|
group: "focus",
|
|
39
|
-
type:
|
|
40
|
-
state:
|
|
49
|
+
type: R.resourceType,
|
|
50
|
+
state: R.state
|
|
41
51
|
}],
|
|
42
52
|
topoEdges: e.map((e) => ({
|
|
43
|
-
id: `${e.id}->${
|
|
53
|
+
id: `${e.id}->${R.id}`,
|
|
44
54
|
from: e.id,
|
|
45
|
-
to:
|
|
55
|
+
to: R.id,
|
|
46
56
|
kind: "DEPENDS_ON"
|
|
47
57
|
}))
|
|
48
58
|
};
|
|
49
|
-
}, [
|
|
50
|
-
|
|
51
|
-
onSuccess: () =>
|
|
52
|
-
onError: (e) =>
|
|
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
|
-
}, [
|
|
55
|
-
return /* @__PURE__ */
|
|
56
|
-
title:
|
|
57
|
-
description:
|
|
58
|
-
actions: /* @__PURE__ */
|
|
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: () =>
|
|
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__ */
|
|
72
|
+
children: [/* @__PURE__ */ D(b, { className: "h-4 w-4" }), N("adaptercloud.common.back", "Back")]
|
|
63
73
|
}),
|
|
64
|
-
children: [/* @__PURE__ */
|
|
65
|
-
isLoading:
|
|
66
|
-
isError:
|
|
67
|
-
isEmpty: !
|
|
68
|
-
error:
|
|
69
|
-
onRetry: () => void
|
|
70
|
-
emptyTitle:
|
|
71
|
-
children:
|
|
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__ */
|
|
84
|
+
/* @__PURE__ */ O(k, {
|
|
75
85
|
className: "p-6",
|
|
76
86
|
children: [
|
|
77
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ O("div", {
|
|
78
88
|
className: "mb-4 flex flex-wrap items-center gap-3",
|
|
79
89
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
-
status:
|
|
82
|
-
label: i[
|
|
90
|
+
/* @__PURE__ */ D(a, {
|
|
91
|
+
status: R.state,
|
|
92
|
+
label: i[R.state]
|
|
83
93
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
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:
|
|
96
|
+
children: R.resourceType
|
|
87
97
|
}),
|
|
88
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ O("span", {
|
|
89
99
|
className: "text-sm font-semibold tabular-nums text-text-primary",
|
|
90
|
-
children: [r(
|
|
100
|
+
children: [r(R.monthlyCost), /* @__PURE__ */ D("span", {
|
|
91
101
|
className: "ml-1 text-xs font-normal text-text-secondary",
|
|
92
|
-
children:
|
|
102
|
+
children: N("adaptercloud.resources.perMonth", "/mo")
|
|
93
103
|
})]
|
|
94
104
|
})
|
|
95
105
|
]
|
|
96
106
|
}),
|
|
97
|
-
/* @__PURE__ */
|
|
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__ */
|
|
121
|
+
/* @__PURE__ */ O("div", {
|
|
101
122
|
className: "col-span-2 sm:col-span-3",
|
|
102
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ D("dt", {
|
|
103
124
|
className: "text-text-secondary",
|
|
104
|
-
children:
|
|
105
|
-
}), /* @__PURE__ */
|
|
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:
|
|
128
|
+
children: R.externalId
|
|
108
129
|
})]
|
|
109
130
|
}),
|
|
110
|
-
/* @__PURE__ */
|
|
131
|
+
/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
|
|
111
132
|
className: "text-text-secondary",
|
|
112
|
-
children:
|
|
113
|
-
}), /* @__PURE__ */
|
|
133
|
+
children: N("adaptercloud.resources.region", "Region")
|
|
134
|
+
}), /* @__PURE__ */ D("dd", {
|
|
114
135
|
className: "mt-0.5 font-medium text-text-primary",
|
|
115
|
-
children:
|
|
136
|
+
children: R.region ?? "—"
|
|
116
137
|
})] }),
|
|
117
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
|
|
118
139
|
className: "text-text-secondary",
|
|
119
|
-
children:
|
|
120
|
-
}), /* @__PURE__ */
|
|
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:
|
|
143
|
+
children: R.lastSeenAt ? m(R.lastSeenAt) : "—"
|
|
123
144
|
})] }),
|
|
124
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("dt", {
|
|
125
146
|
className: "text-text-secondary",
|
|
126
|
-
children:
|
|
127
|
-
}), /* @__PURE__ */
|
|
147
|
+
children: N("adaptercloud.common.updated", "Updated")
|
|
148
|
+
}), /* @__PURE__ */ D("dd", {
|
|
128
149
|
className: "mt-0.5 font-medium text-text-primary",
|
|
129
|
-
children:
|
|
150
|
+
children: m(R.updatedAt)
|
|
130
151
|
})] })
|
|
131
152
|
]
|
|
132
153
|
}),
|
|
133
|
-
|
|
154
|
+
R.connection && /* @__PURE__ */ O("button", {
|
|
134
155
|
type: "button",
|
|
135
|
-
onClick: () =>
|
|
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__ */
|
|
158
|
+
children: [/* @__PURE__ */ D(x, { className: "h-4 w-4 text-text-secondary" }), R.connection.name]
|
|
138
159
|
}),
|
|
139
|
-
|
|
160
|
+
R.tags.length > 0 && /* @__PURE__ */ O("div", {
|
|
140
161
|
className: "mt-4",
|
|
141
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ D("h3", {
|
|
142
163
|
className: "mb-2 text-sm font-semibold text-text-primary",
|
|
143
|
-
children:
|
|
144
|
-
}), /* @__PURE__ */
|
|
164
|
+
children: N("adaptercloud.common.tags", "Tags")
|
|
165
|
+
}), /* @__PURE__ */ D("div", {
|
|
145
166
|
className: "flex flex-wrap gap-1.5",
|
|
146
|
-
children:
|
|
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__ */
|
|
175
|
+
/* @__PURE__ */ O("div", {
|
|
155
176
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
156
|
-
children: [
|
|
177
|
+
children: [R.providerTags != null && /* @__PURE__ */ O(k, {
|
|
157
178
|
className: "p-6",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
179
|
+
children: [/* @__PURE__ */ D("h3", {
|
|
159
180
|
className: "mb-3 text-sm font-semibold text-text-primary",
|
|
160
|
-
children:
|
|
161
|
-
}), /* @__PURE__ */
|
|
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(
|
|
184
|
+
children: JSON.stringify(R.providerTags, null, 2)
|
|
164
185
|
})]
|
|
165
|
-
}),
|
|
186
|
+
}), R.attributes != null && /* @__PURE__ */ O(k, {
|
|
166
187
|
className: "p-6",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ D("h3", {
|
|
168
189
|
className: "mb-3 text-sm font-semibold text-text-primary",
|
|
169
|
-
children:
|
|
170
|
-
}), /* @__PURE__ */
|
|
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(
|
|
193
|
+
children: JSON.stringify(R.attributes, null, 2)
|
|
173
194
|
})]
|
|
174
195
|
})]
|
|
175
196
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ O("section", { children: [/* @__PURE__ */ D("h3", {
|
|
177
198
|
className: "mb-3 text-base font-semibold text-text-primary",
|
|
178
|
-
children:
|
|
179
|
-
}),
|
|
180
|
-
nodes:
|
|
181
|
-
edges:
|
|
182
|
-
selectedId:
|
|
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 !==
|
|
205
|
+
e !== R.id && j(`/resources/${e}`);
|
|
185
206
|
}
|
|
186
|
-
}), /* @__PURE__ */
|
|
207
|
+
}), /* @__PURE__ */ D("p", {
|
|
187
208
|
className: "mt-2 text-xs text-text-secondary",
|
|
188
|
-
children:
|
|
189
|
-
})] }) : /* @__PURE__ */
|
|
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:
|
|
212
|
+
children: N("adaptercloud.resources.noDependencies", "No dependency edges recorded.")
|
|
192
213
|
})] }),
|
|
193
|
-
/* @__PURE__ */
|
|
194
|
-
/* @__PURE__ */
|
|
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__ */
|
|
217
|
+
children: [/* @__PURE__ */ D(S, { className: "h-4 w-4" }), N("adaptercloud.resources.drift", "Drift")]
|
|
197
218
|
}),
|
|
198
|
-
|
|
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:
|
|
223
|
+
children: W
|
|
203
224
|
}),
|
|
204
|
-
|
|
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:
|
|
227
|
+
children: F.data.map((e) => {
|
|
207
228
|
let t = e.status === "DETECTED" || e.status === "ACKNOWLEDGED";
|
|
208
|
-
return /* @__PURE__ */
|
|
229
|
+
return /* @__PURE__ */ O("div", {
|
|
209
230
|
className: "flex items-start gap-3 px-4 py-3",
|
|
210
231
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
232
|
+
/* @__PURE__ */ O("div", {
|
|
212
233
|
className: "min-w-0 flex-1",
|
|
213
|
-
children: [/* @__PURE__ */
|
|
234
|
+
children: [/* @__PURE__ */ D("p", {
|
|
214
235
|
className: "truncate font-mono text-sm text-text-primary",
|
|
215
236
|
children: e.attributePath
|
|
216
|
-
}), /* @__PURE__ */
|
|
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
|
-
|
|
242
|
+
m(e.detectedAt)
|
|
222
243
|
]
|
|
223
244
|
})]
|
|
224
245
|
}),
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
t && /* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ D(o, { severity: e.severity }),
|
|
247
|
+
t && /* @__PURE__ */ O("button", {
|
|
227
248
|
type: "button",
|
|
228
|
-
disabled:
|
|
249
|
+
disabled: K === e.id,
|
|
229
250
|
onClick: () => {
|
|
230
|
-
|
|
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: [
|
|
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__ */
|
|
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:
|
|
261
|
+
children: N("adaptercloud.resources.noDrift", "No drift detected for this resource.")
|
|
241
262
|
})
|
|
242
263
|
] })
|
|
243
264
|
]
|
|
244
265
|
})
|
|
245
|
-
}), /* @__PURE__ */
|
|
246
|
-
open: !!
|
|
266
|
+
}), /* @__PURE__ */ D(h, {
|
|
267
|
+
open: !!H,
|
|
247
268
|
onOpenChange: (e) => {
|
|
248
|
-
e ||
|
|
269
|
+
e || U(null);
|
|
249
270
|
},
|
|
250
|
-
title:
|
|
251
|
-
description:
|
|
252
|
-
confirmLabel:
|
|
253
|
-
cancelLabel:
|
|
254
|
-
onConfirm: () =>
|
|
255
|
-
isPending: !!
|
|
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 {
|
|
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":";;;;;;;;;;;;;;;;;;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 {\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":";;;;;;;;;;;;;;;;;;;AAgCA,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,IAAc,EAAe,EAAE,YAAY,EAAG,CAAC,GAC/C,IAAY,EAAkB,GAC9B,IAAW,EAAM,MAIjB,IAAmB,QAAc;EACrC,IAAM,IAAU,EAAY,QAAQ,CAAC;EACrC,IAAI,EAAQ,WAAW,GAAG,OAAO,CAAC;EAClC,IAAM,oBAAU,IAAI,IAAoB;EACxC,KAAK,IAAM,KAAK,GAAS;GACvB,IAAM,IAAQ,EAAE,YAAY,MAAM,GAAG,CAAC;GACtC,EAAQ,IAAI,IAAQ,EAAQ,IAAI,CAAK,KAAK,KAAK,EAAE,MAAM;EACzD;EACA,OAAO,MAAM,KAAK,EAAQ,QAAQ,CAAC,EAChC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,MAAM,GAAG,EACT,KAAK,GAAG,OAAY,CAAM;CAC/B,GAAG,CAAC,EAAY,IAAI,CAAC,GAIf,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;;OAEJ,EAAiB,SAAS,KACzB,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,uCAAuC,eAAe;QACvD,CAAA,GACJ,kBAAC,GAAD;SACE,QAAQ;SACR,WAAU;SACV,WAAW,EACT,2CACA,sCACF;QACD,CAAA,CACE;;OAGP,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"}
|
|
@@ -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 {
|
|
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 =
|
|
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(
|
|
212
|
+
W.length > 0 ? /* @__PURE__ */ y(o, {
|
|
213
213
|
nodes: W,
|
|
214
214
|
edges: K,
|
|
215
215
|
selectedId: N,
|