@burdenoff/microfe-adaptercloud 2026.706.2 → 2026.706.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adaptercloud/AdapterCloudRoot.js +52 -44
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoutes.js +18 -14
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -1
- package/dist/adaptercloud/components/viz/BarChart.js +32 -0
- package/dist/adaptercloud/components/viz/BarChart.js.map +1 -0
- package/dist/adaptercloud/components/viz/Sparkline.js +50 -0
- package/dist/adaptercloud/components/viz/Sparkline.js.map +1 -0
- package/dist/adaptercloud/components/viz/StackedBar.js +1 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js +96 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js.map +1 -0
- package/dist/adaptercloud/components/viz/index.js +4 -0
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +22 -4
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +176 -123
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +261 -179
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -94
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +160 -111
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +163 -84
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +177 -96
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +145 -131
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +10 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +226 -165
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/topology/TopologyPage.js +397 -0
- package/dist/adaptercloud/pages/topology/TopologyPage.js.map +1 -0
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +184 -131
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,163 +4,244 @@ import { PageLayout as n } from "../../components/PageLayout.js";
|
|
|
4
4
|
import { policyEnforcementLabels as r, policyKindLabels as i } from "../../utils/domain.js";
|
|
5
5
|
import { SeverityBadge as a } from "../../components/SeverityBadge.js";
|
|
6
6
|
import { DataState as o } from "../../components/DataState.js";
|
|
7
|
-
import { usePolicies as s } from "../../hooks/useAdapterCloudApi.js";
|
|
8
|
-
import { formatRelativeTime as
|
|
9
|
-
import { useCallback as
|
|
10
|
-
import { useI18n as
|
|
11
|
-
import { ChevronLeft as
|
|
12
|
-
import { jsx as
|
|
13
|
-
import { GlassCard as
|
|
7
|
+
import { usePolicies as s, useUpdatePolicy as c } from "../../hooks/useAdapterCloudApi.js";
|
|
8
|
+
import { formatRelativeTime as l } from "../../utils/formatters.js";
|
|
9
|
+
import { useCallback as u, useState as d } from "react";
|
|
10
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { ChevronLeft as p, ChevronRight as m, Plus as h, ScrollText as g } from "lucide-react";
|
|
12
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
13
|
+
import { GlassCard as y } from "@burdenoff/fe-libs/ui";
|
|
14
14
|
//#region src/adaptercloud/pages/policies/PoliciesListPage.tsx
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
var b = 20, x = "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium", S = {
|
|
16
|
+
SECURITY: "bg-accent-blue-subtle text-accent-blue",
|
|
17
|
+
GUARDRAIL: "bg-accent-blue-subtle text-accent-blue",
|
|
18
|
+
COMPLIANCE: "bg-accent-blue-subtle text-accent-blue",
|
|
19
|
+
COST: "bg-status-success-bg-subtle text-status-success-text",
|
|
20
|
+
NAMING: "bg-bg-sunken text-text-secondary",
|
|
21
|
+
TAGGING: "bg-bg-sunken text-text-secondary"
|
|
22
|
+
}, C = {
|
|
23
|
+
BLOCK: "bg-status-error-bg-subtle text-status-error-text",
|
|
24
|
+
WARN: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
25
|
+
ADVISORY: "bg-bg-sunken text-text-secondary"
|
|
26
|
+
};
|
|
27
|
+
function w({ kind: e }) {
|
|
28
|
+
return /* @__PURE__ */ _("span", {
|
|
29
|
+
className: t(x, S[e]),
|
|
30
|
+
children: i[e]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function T({ enforcement: e }) {
|
|
34
|
+
return /* @__PURE__ */ _("span", {
|
|
35
|
+
className: t(x, C[e]),
|
|
36
|
+
children: r[e]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function E({ label: e, children: t }) {
|
|
40
|
+
return /* @__PURE__ */ v("div", {
|
|
41
|
+
className: "flex flex-col gap-1",
|
|
42
|
+
children: [/* @__PURE__ */ _("span", {
|
|
43
|
+
className: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
44
|
+
children: e
|
|
45
|
+
}), t]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function D() {
|
|
49
|
+
let { navigateTo: r } = e(), { t: x } = f(), S = u((e, t) => {
|
|
18
50
|
let n = x(e);
|
|
19
51
|
return n === e ? t : n;
|
|
20
|
-
}, [x]), [C,
|
|
52
|
+
}, [x]), [C, D] = d(""), [O, k] = d("all"), [A, j] = d(0), [M, N] = d({}), P = s({
|
|
21
53
|
kind: C || void 0,
|
|
22
|
-
isActive:
|
|
54
|
+
isActive: O === "all" ? void 0 : O === "active",
|
|
23
55
|
pagination: {
|
|
24
|
-
limit:
|
|
25
|
-
offset:
|
|
56
|
+
limit: b,
|
|
57
|
+
offset: A
|
|
26
58
|
}
|
|
27
|
-
}),
|
|
28
|
-
|
|
59
|
+
}), F = c(), I = P.data?.items ?? [], L = P.data?.pageInfo.hasMore ?? !1, R = u((e) => {
|
|
60
|
+
let t = !(M[e.id] ?? e.isActive);
|
|
61
|
+
N((n) => ({
|
|
62
|
+
...n,
|
|
63
|
+
[e.id]: t
|
|
64
|
+
})), F.mutate({
|
|
65
|
+
id: e.id,
|
|
66
|
+
input: { isActive: t }
|
|
67
|
+
}, { onSettled: () => N((t) => {
|
|
68
|
+
let n = { ...t };
|
|
69
|
+
return delete n[e.id], n;
|
|
70
|
+
}) });
|
|
71
|
+
}, [F, M]);
|
|
72
|
+
return /* @__PURE__ */ v(n, {
|
|
29
73
|
title: S("adaptercloud.policies.title", "Policies"),
|
|
30
74
|
description: S("adaptercloud.policies.subtitle", "Governance rules that evaluate the inventory — guardrail, tagging, security, cost, compliance and naming."),
|
|
31
|
-
actions: /* @__PURE__ */
|
|
75
|
+
actions: /* @__PURE__ */ v("button", {
|
|
32
76
|
type: "button",
|
|
33
|
-
onClick: () =>
|
|
77
|
+
onClick: () => r("/policies/new"),
|
|
34
78
|
className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
35
|
-
children: [/* @__PURE__ */
|
|
79
|
+
children: [/* @__PURE__ */ _(h, { className: "h-4 w-4" }), S("adaptercloud.policies.new", "New policy")]
|
|
36
80
|
}),
|
|
37
|
-
children: [/* @__PURE__ */
|
|
81
|
+
children: [/* @__PURE__ */ v("div", {
|
|
38
82
|
className: "mb-6 flex flex-wrap gap-3",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
83
|
+
children: [/* @__PURE__ */ v("select", {
|
|
40
84
|
value: C,
|
|
41
85
|
onChange: (e) => {
|
|
42
|
-
|
|
86
|
+
D(e.target.value), j(0);
|
|
43
87
|
},
|
|
44
88
|
"aria-label": S("adaptercloud.policies.filterByKind", "Filter by kind"),
|
|
45
89
|
className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
46
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ _("option", {
|
|
47
91
|
value: "",
|
|
48
92
|
children: S("adaptercloud.policies.allKinds", "All kinds")
|
|
49
|
-
}), Object.keys(i).map((e) => /* @__PURE__ */
|
|
93
|
+
}), Object.keys(i).map((e) => /* @__PURE__ */ _("option", {
|
|
50
94
|
value: e,
|
|
51
95
|
children: i[e]
|
|
52
96
|
}, e))]
|
|
53
|
-
}), /* @__PURE__ */
|
|
54
|
-
value:
|
|
97
|
+
}), /* @__PURE__ */ v("select", {
|
|
98
|
+
value: O,
|
|
55
99
|
onChange: (e) => {
|
|
56
|
-
|
|
100
|
+
k(e.target.value), j(0);
|
|
57
101
|
},
|
|
58
102
|
"aria-label": S("adaptercloud.policies.filterByActive", "Filter by active state"),
|
|
59
103
|
className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
60
104
|
children: [
|
|
61
|
-
/* @__PURE__ */
|
|
105
|
+
/* @__PURE__ */ _("option", {
|
|
62
106
|
value: "all",
|
|
63
107
|
children: S("adaptercloud.policies.allStates", "All")
|
|
64
108
|
}),
|
|
65
|
-
/* @__PURE__ */
|
|
109
|
+
/* @__PURE__ */ _("option", {
|
|
66
110
|
value: "active",
|
|
67
111
|
children: S("adaptercloud.policies.active", "Active")
|
|
68
112
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ _("option", {
|
|
70
114
|
value: "inactive",
|
|
71
115
|
children: S("adaptercloud.policies.inactive", "Inactive")
|
|
72
116
|
})
|
|
73
117
|
]
|
|
74
118
|
})]
|
|
75
|
-
}), /* @__PURE__ */
|
|
76
|
-
isLoading:
|
|
77
|
-
isError:
|
|
78
|
-
isEmpty:
|
|
79
|
-
error:
|
|
80
|
-
onRetry: () => void
|
|
119
|
+
}), /* @__PURE__ */ v(o, {
|
|
120
|
+
isLoading: P.isLoading,
|
|
121
|
+
isError: P.isError,
|
|
122
|
+
isEmpty: I.length === 0,
|
|
123
|
+
error: P.error,
|
|
124
|
+
onRetry: () => void P.refetch(),
|
|
81
125
|
emptyTitle: S("adaptercloud.policies.emptyTitle", "No policies yet"),
|
|
82
126
|
emptyDescription: S("adaptercloud.policies.emptyDescription", "Author your first governance policy to put compliance into the system."),
|
|
83
|
-
emptyIcon: /* @__PURE__ */ g
|
|
84
|
-
emptyAction: /* @__PURE__ */
|
|
127
|
+
emptyIcon: /* @__PURE__ */ _(g, { className: "h-10 w-10" }),
|
|
128
|
+
emptyAction: /* @__PURE__ */ _("button", {
|
|
85
129
|
type: "button",
|
|
86
|
-
onClick: () =>
|
|
130
|
+
onClick: () => r("/policies/new"),
|
|
87
131
|
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
88
132
|
children: S("adaptercloud.policies.new", "New policy")
|
|
89
133
|
}),
|
|
90
|
-
children: [/* @__PURE__ */
|
|
91
|
-
className: "
|
|
92
|
-
children:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
className: "
|
|
107
|
-
children:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
children: [/* @__PURE__ */ _("div", {
|
|
135
|
+
className: "space-y-3",
|
|
136
|
+
children: I.map((e) => {
|
|
137
|
+
let n = M[e.id] ?? e.isActive, i = e.id in M;
|
|
138
|
+
return /* @__PURE__ */ v(y, {
|
|
139
|
+
role: "button",
|
|
140
|
+
tabIndex: 0,
|
|
141
|
+
onClick: () => r(`/policies/${e.id}`),
|
|
142
|
+
onKeyDown: (t) => {
|
|
143
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), r(`/policies/${e.id}`));
|
|
144
|
+
},
|
|
145
|
+
className: "cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
146
|
+
children: [/* @__PURE__ */ v("div", {
|
|
147
|
+
className: "flex flex-col gap-4 lg:flex-row lg:items-center lg:gap-6",
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ v("div", {
|
|
150
|
+
className: "min-w-0 lg:flex-1",
|
|
151
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
152
|
+
className: "truncate text-base font-semibold text-text-primary",
|
|
153
|
+
children: e.name
|
|
154
|
+
}), e.description && /* @__PURE__ */ _("p", {
|
|
155
|
+
className: "mt-0.5 line-clamp-1 text-sm text-text-secondary",
|
|
156
|
+
children: e.description
|
|
157
|
+
})]
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ _(E, {
|
|
160
|
+
label: S("adaptercloud.policies.kind", "Kind"),
|
|
161
|
+
children: /* @__PURE__ */ _(w, { kind: e.kind })
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ _(E, {
|
|
164
|
+
label: S("adaptercloud.policies.enforcement", "Enforcement"),
|
|
165
|
+
children: /* @__PURE__ */ _(T, { enforcement: e.enforcement })
|
|
166
|
+
}),
|
|
167
|
+
/* @__PURE__ */ _(E, {
|
|
168
|
+
label: S("adaptercloud.policies.severity", "Severity"),
|
|
169
|
+
children: /* @__PURE__ */ _(a, { severity: e.severity })
|
|
170
|
+
}),
|
|
171
|
+
/* @__PURE__ */ _(E, {
|
|
172
|
+
label: S("adaptercloud.policies.activeLabel", "Active"),
|
|
173
|
+
children: /* @__PURE__ */ v("div", {
|
|
174
|
+
className: "flex items-center gap-2",
|
|
175
|
+
children: [/* @__PURE__ */ _("button", {
|
|
176
|
+
type: "button",
|
|
177
|
+
role: "switch",
|
|
178
|
+
"aria-checked": n,
|
|
179
|
+
"aria-label": n ? S("adaptercloud.policies.deactivate", "Deactivate policy") : S("adaptercloud.policies.activate", "Activate policy"),
|
|
180
|
+
disabled: i,
|
|
181
|
+
onClick: (t) => {
|
|
182
|
+
t.stopPropagation(), R(e);
|
|
183
|
+
},
|
|
184
|
+
className: t("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-60", n ? "border-transparent bg-action-primary-bg" : "border-border-subtle bg-bg-sunken"),
|
|
185
|
+
children: /* @__PURE__ */ _("span", {
|
|
186
|
+
"aria-hidden": "true",
|
|
187
|
+
className: t("inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform", n ? "translate-x-4 bg-action-primary-text" : "translate-x-0.5 bg-text-secondary")
|
|
188
|
+
})
|
|
189
|
+
}), /* @__PURE__ */ _("span", {
|
|
190
|
+
className: "text-xs text-text-secondary",
|
|
191
|
+
children: n ? S("adaptercloud.policies.active", "Active") : S("adaptercloud.policies.inactive", "Inactive")
|
|
192
|
+
})]
|
|
193
|
+
})
|
|
194
|
+
}),
|
|
195
|
+
e.appliesTo.length > 0 && /* @__PURE__ */ _(E, {
|
|
196
|
+
label: S("adaptercloud.policies.appliesTo", "Applies to"),
|
|
197
|
+
children: /* @__PURE__ */ v("div", {
|
|
198
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
199
|
+
children: [e.appliesTo.slice(0, 2).map((e) => /* @__PURE__ */ _("span", {
|
|
200
|
+
className: "inline-flex items-center rounded-full bg-accent-blue-subtle px-2 py-0.5 font-mono text-xs text-accent-blue",
|
|
201
|
+
children: e
|
|
202
|
+
}, e)), e.appliesTo.length > 2 && /* @__PURE__ */ v("span", {
|
|
203
|
+
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
204
|
+
children: ["+", e.appliesTo.length - 2]
|
|
205
|
+
})]
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
]
|
|
209
|
+
}), /* @__PURE__ */ v("p", {
|
|
210
|
+
className: "mt-3 text-xs text-text-secondary",
|
|
130
211
|
children: [
|
|
131
212
|
S("adaptercloud.common.updated", "Updated"),
|
|
132
213
|
" ",
|
|
133
|
-
|
|
214
|
+
l(e.updatedAt)
|
|
134
215
|
]
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
}), (
|
|
216
|
+
})]
|
|
217
|
+
}, e.id);
|
|
218
|
+
})
|
|
219
|
+
}), (A > 0 || L) && /* @__PURE__ */ v("div", {
|
|
139
220
|
className: "mt-6 flex items-center justify-between",
|
|
140
221
|
children: [
|
|
141
|
-
/* @__PURE__ */
|
|
222
|
+
/* @__PURE__ */ v("button", {
|
|
142
223
|
type: "button",
|
|
143
|
-
disabled:
|
|
144
|
-
onClick: () =>
|
|
224
|
+
disabled: A === 0,
|
|
225
|
+
onClick: () => j((e) => Math.max(0, e - b)),
|
|
145
226
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
227
|
+
children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), S("adaptercloud.common.prev", "Previous")]
|
|
147
228
|
}),
|
|
148
|
-
/* @__PURE__ */
|
|
229
|
+
/* @__PURE__ */ v("span", {
|
|
149
230
|
className: "text-sm text-text-secondary",
|
|
150
231
|
children: [
|
|
151
232
|
S("adaptercloud.common.showing", "Showing"),
|
|
152
233
|
" ",
|
|
153
|
-
|
|
234
|
+
A + 1,
|
|
154
235
|
"–",
|
|
155
|
-
|
|
236
|
+
A + I.length
|
|
156
237
|
]
|
|
157
238
|
}),
|
|
158
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ v("button", {
|
|
159
240
|
type: "button",
|
|
160
|
-
disabled: !
|
|
161
|
-
onClick: () =>
|
|
241
|
+
disabled: !L,
|
|
242
|
+
onClick: () => j((e) => e + b),
|
|
162
243
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
|
|
163
|
-
children: [S("adaptercloud.common.next", "Next"), /* @__PURE__ */
|
|
244
|
+
children: [S("adaptercloud.common.next", "Next"), /* @__PURE__ */ _(m, { className: "h-4 w-4" })]
|
|
164
245
|
})
|
|
165
246
|
]
|
|
166
247
|
})]
|
|
@@ -168,6 +249,6 @@ function b() {
|
|
|
168
249
|
});
|
|
169
250
|
}
|
|
170
251
|
//#endregion
|
|
171
|
-
export {
|
|
252
|
+
export { D as PoliciesListPage };
|
|
172
253
|
|
|
173
254
|
//# sourceMappingURL=PoliciesListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PoliciesListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PoliciesListPage.tsx"],"sourcesContent":["/**\n * Policies List Page\n * @module adaptercloud/pages/policies\n *\n * Governance rules evaluated against the inventory (`adaListPolicies`). Filters\n * by kind and active flag; offset pagination.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ScrollText, Plus, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicies } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { AdaPolicyKind } from '../../types';\n\nconst PAGE_SIZE = 20;\n\ntype ActiveFilter = 'all' | 'active' | 'inactive';\n\nexport function PoliciesListPage() {\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 [kind, setKind] = useState<AdaPolicyKind | ''>('');\n const [active, setActive] = useState<ActiveFilter>('all');\n const [offset, setOffset] = useState(0);\n\n const query = usePolicies({\n kind: kind || undefined,\n isActive: active === 'all' ? undefined : active === 'active',\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title={t('adaptercloud.policies.title', 'Policies')}\n description={t(\n 'adaptercloud.policies.subtitle',\n 'Governance rules that evaluate the inventory — guardrail, tagging, security, cost, compliance and naming.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t('adaptercloud.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={kind}\n onChange={(e) => {\n setKind(e.target.value as AdaPolicyKind | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.policies.filterByKind', 'Filter by kind')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.policies.allKinds', 'All kinds')}</option>\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n <select\n value={active}\n onChange={(e) => {\n setActive(e.target.value as ActiveFilter);\n setOffset(0);\n }}\n aria-label={t('adaptercloud.policies.filterByActive', 'Filter by active state')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"all\">{t('adaptercloud.policies.allStates', 'All')}</option>\n <option value=\"active\">{t('adaptercloud.policies.active', 'Active')}</option>\n <option value=\"inactive\">{t('adaptercloud.policies.inactive', 'Inactive')}</option>\n </select>\n </div>\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.policies.emptyTitle', 'No policies yet')}\n emptyDescription={t(\n 'adaptercloud.policies.emptyDescription',\n 'Author your first governance policy to put compliance into the system.'\n )}\n emptyIcon={<ScrollText className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((policy) => (\n <GlassCard\n key={policy.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/policies/${policy.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/policies/${policy.id}`);\n }\n }}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {policy.name}\n </h3>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {policyKindLabels[policy.kind]}\n </p>\n </div>\n <SeverityBadge severity={policy.severity} />\n </div>\n\n {policy.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">\n {policy.description}\n </p>\n )}\n\n <div className=\"mt-4 flex items-center gap-2\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {policyEnforcementLabels[policy.enforcement]}\n </span>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n policy.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n <span\n className={cn(\n 'h-1.5 w-1.5 rounded-full',\n policy.isActive ? 'bg-status-success-text' : 'bg-text-secondary'\n )}\n />\n {policy.isActive\n ? t('adaptercloud.policies.active', 'Active')\n : t('adaptercloud.policies.inactive', 'Inactive')}\n </span>\n </div>\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')} {formatRelativeTime(policy.updatedAt)}\n </p>\n </GlassCard>\n ))}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PoliciesListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PoliciesListPage.tsx"],"sourcesContent":["/**\n * Policies List Page\n * @module adaptercloud/pages/policies\n *\n * Governance rules evaluated against the inventory (`adaListPolicies`). Filters\n * by kind and active flag; offset pagination. Each row surfaces color-coded\n * KIND / ENFORCEMENT / SEVERITY badges, an applies-to chip set, and an inline\n * Active toggle wired to `adaUpdatePolicy` (optimistic, toasts on success).\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ScrollText, Plus, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicies, useUpdatePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { AdaPolicy, AdaPolicyEnforcement, AdaPolicyKind } from '../../types';\n\nconst PAGE_SIZE = 20;\n\ntype ActiveFilter = 'all' | 'active' | 'inactive';\n\n// ---------------------------------------------------------------------------\n// Color-coded governance badges (semantic tokens only), mirroring SeverityBadge.\n// ---------------------------------------------------------------------------\n\nconst badgeBase = 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium';\n\n/** Kind → semantic token. Security/Guardrail/Compliance read as governance\n * (accent-blue), Cost as spend (success/green), the rest as neutral. */\nconst policyKindBadgeClass: Record<AdaPolicyKind, string> = {\n SECURITY: 'bg-accent-blue-subtle text-accent-blue',\n GUARDRAIL: 'bg-accent-blue-subtle text-accent-blue',\n COMPLIANCE: 'bg-accent-blue-subtle text-accent-blue',\n COST: 'bg-status-success-bg-subtle text-status-success-text',\n NAMING: 'bg-bg-sunken text-text-secondary',\n TAGGING: 'bg-bg-sunken text-text-secondary',\n};\n\n/** Enforcement → semantic token. Block = error, Warn = warning, Advisory = neutral. */\nconst policyEnforcementBadgeClass: Record<AdaPolicyEnforcement, string> = {\n BLOCK: 'bg-status-error-bg-subtle text-status-error-text',\n WARN: 'bg-status-warning-bg-subtle text-status-warning-text',\n ADVISORY: 'bg-bg-sunken text-text-secondary',\n};\n\nfunction KindBadge({ kind }: { kind: AdaPolicyKind }) {\n return <span className={cn(badgeBase, policyKindBadgeClass[kind])}>{policyKindLabels[kind]}</span>;\n}\n\nfunction EnforcementBadge({ enforcement }: { enforcement: AdaPolicyEnforcement }) {\n return (\n <span className={cn(badgeBase, policyEnforcementBadgeClass[enforcement])}>\n {policyEnforcementLabels[enforcement]}\n </span>\n );\n}\n\n/** Labeled meta column (eyebrow + value) used across each policy row. */\nfunction MetaCol({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n {label}\n </span>\n {children}\n </div>\n );\n}\n\nexport function PoliciesListPage() {\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 [kind, setKind] = useState<AdaPolicyKind | ''>('');\n const [active, setActive] = useState<ActiveFilter>('all');\n const [offset, setOffset] = useState(0);\n // Local optimistic override of isActive per policy id while the mutation is in\n // flight, so the toggle flips instantly instead of waiting for the refetch.\n const [pendingActive, setPendingActive] = useState<Record<string, boolean>>({});\n\n const query = usePolicies({\n kind: kind || undefined,\n isActive: active === 'all' ? undefined : active === 'active',\n pagination: { limit: PAGE_SIZE, offset },\n });\n const update = useUpdatePolicy();\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const toggleActive = useCallback(\n (policy: AdaPolicy) => {\n const next = !(pendingActive[policy.id] ?? policy.isActive);\n setPendingActive((m) => ({ ...m, [policy.id]: next }));\n update.mutate(\n { id: policy.id, input: { isActive: next } },\n {\n onSettled: () =>\n setPendingActive((m) => {\n const copy = { ...m };\n delete copy[policy.id];\n return copy;\n }),\n }\n );\n },\n [update, pendingActive]\n );\n\n return (\n <PageLayout\n title={t('adaptercloud.policies.title', 'Policies')}\n description={t(\n 'adaptercloud.policies.subtitle',\n 'Governance rules that evaluate the inventory — guardrail, tagging, security, cost, compliance and naming.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t('adaptercloud.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={kind}\n onChange={(e) => {\n setKind(e.target.value as AdaPolicyKind | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.policies.filterByKind', 'Filter by kind')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.policies.allKinds', 'All kinds')}</option>\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n <select\n value={active}\n onChange={(e) => {\n setActive(e.target.value as ActiveFilter);\n setOffset(0);\n }}\n aria-label={t('adaptercloud.policies.filterByActive', 'Filter by active state')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"all\">{t('adaptercloud.policies.allStates', 'All')}</option>\n <option value=\"active\">{t('adaptercloud.policies.active', 'Active')}</option>\n <option value=\"inactive\">{t('adaptercloud.policies.inactive', 'Inactive')}</option>\n </select>\n </div>\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.policies.emptyTitle', 'No policies yet')}\n emptyDescription={t(\n 'adaptercloud.policies.emptyDescription',\n 'Author your first governance policy to put compliance into the system.'\n )}\n emptyIcon={<ScrollText className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"space-y-3\">\n {items.map((policy) => {\n const isActiveNow = pendingActive[policy.id] ?? policy.isActive;\n const isToggling = policy.id in pendingActive;\n return (\n <GlassCard\n key={policy.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/policies/${policy.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/policies/${policy.id}`);\n }\n }}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:gap-6\">\n <div className=\"min-w-0 lg:flex-1\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {policy.name}\n </h3>\n {policy.description && (\n <p className=\"mt-0.5 line-clamp-1 text-sm text-text-secondary\">\n {policy.description}\n </p>\n )}\n </div>\n\n <MetaCol label={t('adaptercloud.policies.kind', 'Kind')}>\n <KindBadge kind={policy.kind} />\n </MetaCol>\n\n <MetaCol label={t('adaptercloud.policies.enforcement', 'Enforcement')}>\n <EnforcementBadge enforcement={policy.enforcement} />\n </MetaCol>\n\n <MetaCol label={t('adaptercloud.policies.severity', 'Severity')}>\n <SeverityBadge severity={policy.severity} />\n </MetaCol>\n\n <MetaCol label={t('adaptercloud.policies.activeLabel', 'Active')}>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isActiveNow}\n aria-label={\n isActiveNow\n ? t('adaptercloud.policies.deactivate', 'Deactivate policy')\n : t('adaptercloud.policies.activate', 'Activate policy')\n }\n disabled={isToggling}\n onClick={(e) => {\n e.stopPropagation();\n toggleActive(policy);\n }}\n className={cn(\n 'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-60',\n isActiveNow\n ? 'border-transparent bg-action-primary-bg'\n : 'border-border-subtle bg-bg-sunken'\n )}\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n 'inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform',\n isActiveNow\n ? 'translate-x-4 bg-action-primary-text'\n : 'translate-x-0.5 bg-text-secondary'\n )}\n />\n </button>\n <span className=\"text-xs text-text-secondary\">\n {isActiveNow\n ? t('adaptercloud.policies.active', 'Active')\n : t('adaptercloud.policies.inactive', 'Inactive')}\n </span>\n </div>\n </MetaCol>\n\n {policy.appliesTo.length > 0 && (\n <MetaCol label={t('adaptercloud.policies.appliesTo', 'Applies to')}>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {policy.appliesTo.slice(0, 2).map((sel) => (\n <span\n key={sel}\n className=\"inline-flex items-center rounded-full bg-accent-blue-subtle px-2 py-0.5 font-mono text-xs text-accent-blue\"\n >\n {sel}\n </span>\n ))}\n {policy.appliesTo.length > 2 && (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n +{policy.appliesTo.length - 2}\n </span>\n )}\n </div>\n </MetaCol>\n )}\n </div>\n\n <p className=\"mt-3 text-xs text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')} {formatRelativeTime(policy.updatedAt)}\n </p>\n </GlassCard>\n );\n })}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|