@burdenoff/microfe-adaptercloud 2026.706.1 → 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 +22 -14
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.js +27 -0
- package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -0
- package/dist/adaptercloud/components/DataState.js +1 -1
- package/dist/adaptercloud/components/DataState.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 +71 -34
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +159 -156
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +179 -113
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +263 -179
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +2 -0
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +141 -124
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +2 -0
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -93
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +183 -109
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +172 -71
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +179 -96
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +109 -91
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js +211 -184
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +183 -127
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +121 -84
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +236 -153
- 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 +192 -131
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,215 +1,242 @@
|
|
|
1
1
|
import { useAdapterCloud as e } from "../../context/AdapterCloudContext.js";
|
|
2
2
|
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
3
|
import { policyEnforcementLabels as n, policyKindLabels as r, severityLabels as i } from "../../utils/domain.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { DataState as a } from "../../components/DataState.js";
|
|
5
|
+
import { useCreatePolicy as o, usePolicy as s, useUpdatePolicy as c } from "../../hooks/useAdapterCloudApi.js";
|
|
6
|
+
import { useCallback as l, useEffect as u, useState as d } from "react";
|
|
7
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
8
|
+
import { ArrowLeft as p, Loader2 as m, Save as h } from "lucide-react";
|
|
9
|
+
import { useParams as g } from "react-router-dom";
|
|
10
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as y } from "@burdenoff/fe-libs/ui";
|
|
10
12
|
//#region src/adaptercloud/pages/policies/PolicyFormPage.tsx
|
|
11
|
-
var
|
|
12
|
-
function
|
|
13
|
-
let { navigateTo:
|
|
14
|
-
let n =
|
|
13
|
+
var b = (e) => e.split(",").map((e) => e.trim()).filter(Boolean), x = "{\n \"match\": { \"resourceType\": \"*\" },\n \"require\": { \"tags\": [\"owner\", \"env\"] }\n}";
|
|
14
|
+
function S() {
|
|
15
|
+
let { id: S } = g(), C = !!S, { navigateTo: w } = e(), { t: T } = f(), E = l((e, t) => {
|
|
16
|
+
let n = T(e);
|
|
15
17
|
return n === e ? t : n;
|
|
16
|
-
}, [
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
}, [T]), D = s(C ? S : void 0), O = o(), k = c(), [A, j] = d(""), [M, N] = d(""), [P, F] = d("GUARDRAIL"), [I, L] = d("WARN"), [R, z] = d("MEDIUM"), [B, V] = d(""), [H, U] = d(""), [W, G] = d(!0), [K, q] = d(x), [J, Y] = d(null);
|
|
19
|
+
u(() => {
|
|
20
|
+
let e = D.data;
|
|
21
|
+
C && e && (j(e.name), N(e.description ?? ""), F(e.kind), L(e.enforcement), z(e.severity), V(e.appliesTo.join(", ")), U(e.tags.join(", ")), G(e.isActive), q(JSON.stringify(e.expression, null, 2)));
|
|
22
|
+
}, [C, D.data]);
|
|
23
|
+
let X = O.isPending || k.isPending, Z = l((e) => {
|
|
24
|
+
if (e.preventDefault(), Y(null), !A.trim()) {
|
|
25
|
+
Y(E("adaptercloud.policies.form.nameRequired", "Name is required."));
|
|
19
26
|
return;
|
|
20
27
|
}
|
|
21
28
|
let t;
|
|
22
29
|
try {
|
|
23
|
-
t = JSON.parse(
|
|
30
|
+
t = JSON.parse(K);
|
|
24
31
|
} catch {
|
|
25
|
-
|
|
32
|
+
Y(E("adaptercloud.policies.form.invalidExpression", "Expression must be valid JSON."));
|
|
26
33
|
return;
|
|
27
34
|
}
|
|
28
|
-
|
|
29
|
-
name:
|
|
30
|
-
description:
|
|
31
|
-
kind:
|
|
32
|
-
enforcement:
|
|
33
|
-
severity:
|
|
34
|
-
isActive:
|
|
35
|
-
appliesTo:
|
|
36
|
-
tags:
|
|
35
|
+
let n = {
|
|
36
|
+
name: A.trim(),
|
|
37
|
+
description: M.trim() || void 0,
|
|
38
|
+
kind: P,
|
|
39
|
+
enforcement: I,
|
|
40
|
+
severity: R,
|
|
41
|
+
isActive: W,
|
|
42
|
+
appliesTo: b(B),
|
|
43
|
+
tags: b(H),
|
|
37
44
|
expression: t
|
|
45
|
+
}, r = (e) => Y(e instanceof Error ? e.message : "Failed to save policy.");
|
|
46
|
+
C && S ? k.mutate({
|
|
47
|
+
id: S,
|
|
48
|
+
input: n
|
|
38
49
|
}, {
|
|
39
|
-
onSuccess: (e) =>
|
|
40
|
-
onError:
|
|
50
|
+
onSuccess: (e) => w(`/policies/${e.id}`),
|
|
51
|
+
onError: r
|
|
52
|
+
}) : O.mutate(n, {
|
|
53
|
+
onSuccess: (e) => w(`/policies/${e.id}`),
|
|
54
|
+
onError: r
|
|
41
55
|
});
|
|
42
56
|
}, [
|
|
43
|
-
|
|
44
|
-
x,
|
|
45
|
-
C,
|
|
46
|
-
T,
|
|
47
|
-
D,
|
|
57
|
+
O,
|
|
48
58
|
k,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
S,
|
|
60
|
+
C,
|
|
61
|
+
A,
|
|
62
|
+
M,
|
|
63
|
+
P,
|
|
64
|
+
I,
|
|
65
|
+
R,
|
|
66
|
+
W,
|
|
67
|
+
B,
|
|
68
|
+
H,
|
|
69
|
+
K,
|
|
70
|
+
w,
|
|
71
|
+
E
|
|
72
|
+
]), Q = "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]", $ = "mb-1.5 block text-sm font-medium text-text-primary";
|
|
73
|
+
return /* @__PURE__ */ _(t, {
|
|
74
|
+
title: C ? E("adaptercloud.policies.editTitle", "Edit Policy") : E("adaptercloud.policies.newTitle", "New Policy"),
|
|
75
|
+
description: E("adaptercloud.policies.formSubtitle", "Put a governance rule into the system instead of a wiki."),
|
|
76
|
+
actions: /* @__PURE__ */ v("button", {
|
|
60
77
|
type: "button",
|
|
61
|
-
onClick: () =>
|
|
78
|
+
onClick: () => w(C && S ? `/policies/${S}` : "/policies"),
|
|
62
79
|
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",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), E("adaptercloud.common.cancel", "Cancel")]
|
|
64
81
|
}),
|
|
65
|
-
children: /* @__PURE__ */
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
82
|
+
children: /* @__PURE__ */ _(a, {
|
|
83
|
+
isLoading: C && D.isLoading,
|
|
84
|
+
isError: C && D.isError,
|
|
85
|
+
isEmpty: C && !D.isLoading && !D.data,
|
|
86
|
+
error: D.error,
|
|
87
|
+
onRetry: () => void D.refetch(),
|
|
88
|
+
emptyTitle: E("adaptercloud.policies.notFound", "Policy not found"),
|
|
89
|
+
children: /* @__PURE__ */ _("form", {
|
|
90
|
+
onSubmit: Z,
|
|
91
|
+
className: "max-w-2xl",
|
|
92
|
+
children: /* @__PURE__ */ v(y, {
|
|
93
|
+
className: "space-y-5 p-6",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
96
|
+
htmlFor: "ada-pol-name",
|
|
97
|
+
className: $,
|
|
98
|
+
children: [E("adaptercloud.policies.form.name", "Name"), " *"]
|
|
99
|
+
}), /* @__PURE__ */ _("input", {
|
|
100
|
+
id: "ada-pol-name",
|
|
101
|
+
type: "text",
|
|
102
|
+
value: A,
|
|
103
|
+
onChange: (e) => j(e.target.value),
|
|
104
|
+
className: Q,
|
|
105
|
+
placeholder: "Require owner + env tags"
|
|
106
|
+
})] }),
|
|
107
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
108
|
+
htmlFor: "ada-pol-desc",
|
|
109
|
+
className: $,
|
|
110
|
+
children: E("adaptercloud.policies.form.description", "Description")
|
|
111
|
+
}), /* @__PURE__ */ _("textarea", {
|
|
112
|
+
id: "ada-pol-desc",
|
|
113
|
+
value: M,
|
|
114
|
+
onChange: (e) => N(e.target.value),
|
|
115
|
+
rows: 2,
|
|
116
|
+
className: Q,
|
|
117
|
+
placeholder: E("adaptercloud.policies.form.descriptionPlaceholder", "What this policy enforces and why.")
|
|
118
|
+
})] }),
|
|
119
|
+
/* @__PURE__ */ v("div", {
|
|
120
|
+
className: "grid grid-cols-1 gap-5 sm:grid-cols-3",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
123
|
+
htmlFor: "ada-pol-kind",
|
|
124
|
+
className: $,
|
|
125
|
+
children: E("adaptercloud.policies.form.kind", "Kind")
|
|
126
|
+
}), /* @__PURE__ */ _("select", {
|
|
127
|
+
id: "ada-pol-kind",
|
|
128
|
+
value: P,
|
|
129
|
+
onChange: (e) => F(e.target.value),
|
|
130
|
+
className: Q,
|
|
131
|
+
children: Object.keys(r).map((e) => /* @__PURE__ */ _("option", {
|
|
132
|
+
value: e,
|
|
133
|
+
children: r[e]
|
|
134
|
+
}, e))
|
|
135
|
+
})] }),
|
|
136
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
137
|
+
htmlFor: "ada-pol-enf",
|
|
138
|
+
className: $,
|
|
139
|
+
children: E("adaptercloud.policies.form.enforcement", "Enforcement")
|
|
140
|
+
}), /* @__PURE__ */ _("select", {
|
|
141
|
+
id: "ada-pol-enf",
|
|
142
|
+
value: I,
|
|
143
|
+
onChange: (e) => L(e.target.value),
|
|
144
|
+
className: Q,
|
|
145
|
+
children: Object.keys(n).map((e) => /* @__PURE__ */ _("option", {
|
|
146
|
+
value: e,
|
|
147
|
+
children: n[e]
|
|
148
|
+
}, e))
|
|
149
|
+
})] }),
|
|
150
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
151
|
+
htmlFor: "ada-pol-sev",
|
|
152
|
+
className: $,
|
|
153
|
+
children: E("adaptercloud.policies.form.severity", "Severity")
|
|
154
|
+
}), /* @__PURE__ */ _("select", {
|
|
155
|
+
id: "ada-pol-sev",
|
|
156
|
+
value: R,
|
|
157
|
+
onChange: (e) => z(e.target.value),
|
|
158
|
+
className: Q,
|
|
159
|
+
children: Object.keys(i).map((e) => /* @__PURE__ */ _("option", {
|
|
160
|
+
value: e,
|
|
161
|
+
children: i[e]
|
|
162
|
+
}, e))
|
|
163
|
+
})] })
|
|
164
|
+
]
|
|
147
165
|
}),
|
|
148
|
-
/* @__PURE__ */
|
|
149
|
-
|
|
166
|
+
/* @__PURE__ */ v("div", { children: [
|
|
167
|
+
/* @__PURE__ */ _("label", {
|
|
168
|
+
htmlFor: "ada-pol-applies",
|
|
169
|
+
className: $,
|
|
170
|
+
children: E("adaptercloud.policies.form.appliesTo", "Applies To")
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ _("input", {
|
|
173
|
+
id: "ada-pol-applies",
|
|
174
|
+
type: "text",
|
|
175
|
+
value: B,
|
|
176
|
+
onChange: (e) => V(e.target.value),
|
|
177
|
+
className: Q,
|
|
178
|
+
placeholder: "aws:ec2:instance, aws:s3:bucket"
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ _("p", {
|
|
181
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
182
|
+
children: E("adaptercloud.common.commaSeparated", "Comma-separated.")
|
|
183
|
+
})
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("label", {
|
|
186
|
+
htmlFor: "ada-pol-expr",
|
|
187
|
+
className: $,
|
|
188
|
+
children: [E("adaptercloud.policies.form.expression", "Expression (JSON)"), " *"]
|
|
189
|
+
}), /* @__PURE__ */ _("textarea", {
|
|
190
|
+
id: "ada-pol-expr",
|
|
191
|
+
value: K,
|
|
192
|
+
onChange: (e) => q(e.target.value),
|
|
193
|
+
rows: 8,
|
|
194
|
+
spellCheck: !1,
|
|
195
|
+
className: `${Q} font-mono`
|
|
196
|
+
})] }),
|
|
197
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
198
|
+
htmlFor: "ada-pol-tags",
|
|
199
|
+
className: $,
|
|
200
|
+
children: E("adaptercloud.common.tags", "Tags")
|
|
201
|
+
}), /* @__PURE__ */ _("input", {
|
|
202
|
+
id: "ada-pol-tags",
|
|
150
203
|
type: "text",
|
|
151
|
-
value:
|
|
152
|
-
onChange: (e) =>
|
|
153
|
-
className:
|
|
154
|
-
placeholder: "
|
|
204
|
+
value: H,
|
|
205
|
+
onChange: (e) => U(e.target.value),
|
|
206
|
+
className: Q,
|
|
207
|
+
placeholder: "tagging, governance"
|
|
208
|
+
})] }),
|
|
209
|
+
/* @__PURE__ */ v("label", {
|
|
210
|
+
className: "flex items-center gap-2 text-sm text-text-primary",
|
|
211
|
+
children: [/* @__PURE__ */ _("input", {
|
|
212
|
+
type: "checkbox",
|
|
213
|
+
checked: W,
|
|
214
|
+
onChange: (e) => G(e.target.checked),
|
|
215
|
+
className: "h-4 w-4 rounded border-border-subtle"
|
|
216
|
+
}), E("adaptercloud.policies.form.activeImmediately", "Active immediately")]
|
|
155
217
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
spellCheck: !1,
|
|
171
|
-
className: `${H} font-mono`
|
|
172
|
-
})] }),
|
|
173
|
-
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
|
|
174
|
-
htmlFor: "ada-pol-tags",
|
|
175
|
-
className: U,
|
|
176
|
-
children: y("adaptercloud.common.tags", "Tags")
|
|
177
|
-
}), /* @__PURE__ */ f("input", {
|
|
178
|
-
id: "ada-pol-tags",
|
|
179
|
-
type: "text",
|
|
180
|
-
value: N,
|
|
181
|
-
onChange: (e) => P(e.target.value),
|
|
182
|
-
className: H,
|
|
183
|
-
placeholder: "tagging, governance"
|
|
184
|
-
})] }),
|
|
185
|
-
/* @__PURE__ */ p("label", {
|
|
186
|
-
className: "flex items-center gap-2 text-sm text-text-primary",
|
|
187
|
-
children: [/* @__PURE__ */ f("input", {
|
|
188
|
-
type: "checkbox",
|
|
189
|
-
checked: F,
|
|
190
|
-
onChange: (e) => I(e.target.checked),
|
|
191
|
-
className: "h-4 w-4 rounded border-border-subtle"
|
|
192
|
-
}), y("adaptercloud.policies.form.activeImmediately", "Active immediately")]
|
|
193
|
-
}),
|
|
194
|
-
z && /* @__PURE__ */ f("p", {
|
|
195
|
-
className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
196
|
-
children: z
|
|
197
|
-
}),
|
|
198
|
-
/* @__PURE__ */ f("div", {
|
|
199
|
-
className: "flex justify-end gap-3 pt-2",
|
|
200
|
-
children: /* @__PURE__ */ p("button", {
|
|
201
|
-
type: "submit",
|
|
202
|
-
disabled: b.isPending,
|
|
203
|
-
className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50",
|
|
204
|
-
children: [b.isPending ? /* @__PURE__ */ f(u, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ f(d, { className: "h-4 w-4" }), y("adaptercloud.policies.create", "Create policy")]
|
|
218
|
+
J && /* @__PURE__ */ _("p", {
|
|
219
|
+
role: "alert",
|
|
220
|
+
"aria-live": "polite",
|
|
221
|
+
className: "rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
222
|
+
children: J
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ _("div", {
|
|
225
|
+
className: "flex justify-end gap-3 pt-2",
|
|
226
|
+
children: /* @__PURE__ */ v("button", {
|
|
227
|
+
type: "submit",
|
|
228
|
+
disabled: X,
|
|
229
|
+
className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50",
|
|
230
|
+
children: [X ? /* @__PURE__ */ _(m, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ _(h, { className: "h-4 w-4" }), C ? E("adaptercloud.common.saveChanges", "Save changes") : E("adaptercloud.policies.create", "Create policy")]
|
|
231
|
+
})
|
|
205
232
|
})
|
|
206
|
-
|
|
207
|
-
|
|
233
|
+
]
|
|
234
|
+
})
|
|
208
235
|
})
|
|
209
236
|
})
|
|
210
237
|
});
|
|
211
238
|
}
|
|
212
239
|
//#endregion
|
|
213
|
-
export {
|
|
240
|
+
export { S as PolicyFormPage };
|
|
214
241
|
|
|
215
242
|
//# sourceMappingURL=PolicyFormPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PolicyFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyFormPage.tsx"],"sourcesContent":["/**\n * Policy Create Form Page\n * @module adaptercloud/pages/policies\n *\n * Authors a new governance policy (`adaCreatePolicy`). The expression is a JSON\n * document validated client-side before submit.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useCreatePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels, severityLabels } from '../../utils/domain';\nimport type { AdaPolicyEnforcement, AdaPolicyKind, AdaSeverity } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\nconst DEFAULT_EXPRESSION = `{\n \"match\": { \"resourceType\": \"*\" },\n \"require\": { \"tags\": [\"owner\", \"env\"] }\n}`;\n\nexport function PolicyFormPage() {\n const { 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 create = useCreatePolicy();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<AdaPolicyKind>('GUARDRAIL');\n const [enforcement, setEnforcement] = useState<AdaPolicyEnforcement>('WARN');\n const [severity, setSeverity] = useState<AdaSeverity>('MEDIUM');\n const [appliesTo, setAppliesTo] = useState('');\n const [tags, setTags] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [expression, setExpression] = useState(DEFAULT_EXPRESSION);\n const [error, setError] = useState<string | null>(null);\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!name.trim()) {\n setError(t('adaptercloud.policies.form.nameRequired', 'Name is required.'));\n return;\n }\n\n let parsedExpression: unknown;\n try {\n parsedExpression = JSON.parse(expression);\n } catch {\n setError(\n t('adaptercloud.policies.form.invalidExpression', 'Expression must be valid JSON.')\n );\n return;\n }\n\n create.mutate(\n {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo: parseList(appliesTo),\n tags: parseList(tags),\n expression: parsedExpression,\n },\n {\n onSuccess: (p) => navigateTo(`/policies/${p.id}`),\n onError: (err) =>\n setError(err instanceof Error ? err.message : 'Failed to create policy.'),\n }\n );\n },\n [\n create,\n name,\n description,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo,\n tags,\n expression,\n navigateTo,\n t,\n ]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={t('adaptercloud.policies.newTitle', 'New Policy')}\n description={t(\n 'adaptercloud.policies.formSubtitle',\n 'Put a governance rule into the system instead of a wiki.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-pol-name\" className={labelClass}>\n {t('adaptercloud.policies.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-pol-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={fieldClass}\n placeholder=\"Require owner + env tags\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-desc\" className={labelClass}>\n {t('adaptercloud.policies.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-pol-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.policies.form.descriptionPlaceholder',\n 'What this policy enforces and why.'\n )}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ada-pol-kind\" className={labelClass}>\n {t('adaptercloud.policies.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-pol-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as AdaPolicyKind)}\n className={fieldClass}\n >\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-enf\" className={labelClass}>\n {t('adaptercloud.policies.form.enforcement', 'Enforcement')}\n </label>\n <select\n id=\"ada-pol-enf\"\n value={enforcement}\n onChange={(e) => setEnforcement(e.target.value as AdaPolicyEnforcement)}\n className={fieldClass}\n >\n {(Object.keys(policyEnforcementLabels) as AdaPolicyEnforcement[]).map((en) => (\n <option key={en} value={en}>\n {policyEnforcementLabels[en]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-sev\" className={labelClass}>\n {t('adaptercloud.policies.form.severity', 'Severity')}\n </label>\n <select\n id=\"ada-pol-sev\"\n value={severity}\n onChange={(e) => setSeverity(e.target.value as AdaSeverity)}\n className={fieldClass}\n >\n {(Object.keys(severityLabels) as AdaSeverity[]).map((s) => (\n <option key={s} value={s}>\n {severityLabels[s]}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-applies\" className={labelClass}>\n {t('adaptercloud.policies.form.appliesTo', 'Applies To')}\n </label>\n <input\n id=\"ada-pol-applies\"\n type=\"text\"\n value={appliesTo}\n onChange={(e) => setAppliesTo(e.target.value)}\n className={fieldClass}\n placeholder=\"aws:ec2:instance, aws:s3:bucket\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-expr\" className={labelClass}>\n {t('adaptercloud.policies.form.expression', 'Expression (JSON)')} *\n </label>\n <textarea\n id=\"ada-pol-expr\"\n value={expression}\n onChange={(e) => setExpression(e.target.value)}\n rows={8}\n spellCheck={false}\n className={`${fieldClass} font-mono`}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-pol-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={fieldClass}\n placeholder=\"tagging, governance\"\n />\n </div>\n\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t('adaptercloud.policies.form.activeImmediately', 'Active immediately')}\n </label>\n\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {t('adaptercloud.policies.create', 'Create policy')}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PolicyFormPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PolicyFormPage.tsx"],"sourcesContent":["/**\n * Policy Create / Edit Form Page\n * @module adaptercloud/pages/policies\n *\n * Authors a new governance policy (`adaCreatePolicy`) or edits an existing one\n * (`adaUpdatePolicy`). The presence of a route `:id` switches to edit mode and\n * pre-fills from `adaPolicy` (including the JSON expression). The expression is a\n * JSON document validated client-side before submit.\n */\n\nimport { useCallback, useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicy, useCreatePolicy, useUpdatePolicy } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels, severityLabels } from '../../utils/domain';\nimport type { AdaPolicyEnforcement, AdaPolicyKind, AdaSeverity } from '../../types';\n\nconst parseList = (value: string): string[] =>\n value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\nconst DEFAULT_EXPRESSION = `{\n \"match\": { \"resourceType\": \"*\" },\n \"require\": { \"tags\": [\"owner\", \"env\"] }\n}`;\n\nexport function PolicyFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const existing = usePolicy(isEdit ? id : undefined);\n const create = useCreatePolicy();\n const update = useUpdatePolicy();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<AdaPolicyKind>('GUARDRAIL');\n const [enforcement, setEnforcement] = useState<AdaPolicyEnforcement>('WARN');\n const [severity, setSeverity] = useState<AdaSeverity>('MEDIUM');\n const [appliesTo, setAppliesTo] = useState('');\n const [tags, setTags] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [expression, setExpression] = useState(DEFAULT_EXPRESSION);\n const [error, setError] = useState<string | null>(null);\n\n // Pre-fill in edit mode from the loaded policy.\n useEffect(() => {\n const p = existing.data;\n if (isEdit && p) {\n setName(p.name);\n setDescription(p.description ?? '');\n setKind(p.kind);\n setEnforcement(p.enforcement);\n setSeverity(p.severity);\n setAppliesTo(p.appliesTo.join(', '));\n setTags(p.tags.join(', '));\n setIsActive(p.isActive);\n setExpression(JSON.stringify(p.expression, null, 2));\n }\n }, [isEdit, existing.data]);\n\n const isPending = create.isPending || update.isPending;\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!name.trim()) {\n setError(t('adaptercloud.policies.form.nameRequired', 'Name is required.'));\n return;\n }\n\n let parsedExpression: unknown;\n try {\n parsedExpression = JSON.parse(expression);\n } catch {\n setError(\n t('adaptercloud.policies.form.invalidExpression', 'Expression must be valid JSON.')\n );\n return;\n }\n\n const common = {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo: parseList(appliesTo),\n tags: parseList(tags),\n expression: parsedExpression,\n };\n\n const onError = (err: unknown) =>\n setError(err instanceof Error ? err.message : 'Failed to save policy.');\n\n if (isEdit && id) {\n update.mutate(\n { id, input: common },\n { onSuccess: (p) => navigateTo(`/policies/${p.id}`), onError }\n );\n } else {\n create.mutate(common, {\n onSuccess: (p) => navigateTo(`/policies/${p.id}`),\n onError,\n });\n }\n },\n [\n create,\n update,\n id,\n isEdit,\n name,\n description,\n kind,\n enforcement,\n severity,\n isActive,\n appliesTo,\n tags,\n expression,\n navigateTo,\n t,\n ]\n );\n\n const fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n const labelClass = 'mb-1.5 block text-sm font-medium text-text-primary';\n\n return (\n <PageLayout\n title={\n isEdit\n ? t('adaptercloud.policies.editTitle', 'Edit Policy')\n : t('adaptercloud.policies.newTitle', 'New Policy')\n }\n description={t(\n 'adaptercloud.policies.formSubtitle',\n 'Put a governance rule into the system instead of a wiki.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/policies/${id}` : '/policies')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n }\n >\n <DataState\n isLoading={isEdit && existing.isLoading}\n isError={isEdit && existing.isError}\n isEmpty={isEdit && !existing.isLoading && !existing.data}\n error={existing.error}\n onRetry={() => void existing.refetch()}\n emptyTitle={t('adaptercloud.policies.notFound', 'Policy not found')}\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl\">\n <GlassCard className=\"space-y-5 p-6\">\n <div>\n <label htmlFor=\"ada-pol-name\" className={labelClass}>\n {t('adaptercloud.policies.form.name', 'Name')} *\n </label>\n <input\n id=\"ada-pol-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={fieldClass}\n placeholder=\"Require owner + env tags\"\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-desc\" className={labelClass}>\n {t('adaptercloud.policies.form.description', 'Description')}\n </label>\n <textarea\n id=\"ada-pol-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n placeholder={t(\n 'adaptercloud.policies.form.descriptionPlaceholder',\n 'What this policy enforces and why.'\n )}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ada-pol-kind\" className={labelClass}>\n {t('adaptercloud.policies.form.kind', 'Kind')}\n </label>\n <select\n id=\"ada-pol-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as AdaPolicyKind)}\n className={fieldClass}\n >\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-enf\" className={labelClass}>\n {t('adaptercloud.policies.form.enforcement', 'Enforcement')}\n </label>\n <select\n id=\"ada-pol-enf\"\n value={enforcement}\n onChange={(e) => setEnforcement(e.target.value as AdaPolicyEnforcement)}\n className={fieldClass}\n >\n {(Object.keys(policyEnforcementLabels) as AdaPolicyEnforcement[]).map((en) => (\n <option key={en} value={en}>\n {policyEnforcementLabels[en]}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ada-pol-sev\" className={labelClass}>\n {t('adaptercloud.policies.form.severity', 'Severity')}\n </label>\n <select\n id=\"ada-pol-sev\"\n value={severity}\n onChange={(e) => setSeverity(e.target.value as AdaSeverity)}\n className={fieldClass}\n >\n {(Object.keys(severityLabels) as AdaSeverity[]).map((s) => (\n <option key={s} value={s}>\n {severityLabels[s]}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-applies\" className={labelClass}>\n {t('adaptercloud.policies.form.appliesTo', 'Applies To')}\n </label>\n <input\n id=\"ada-pol-applies\"\n type=\"text\"\n value={appliesTo}\n onChange={(e) => setAppliesTo(e.target.value)}\n className={fieldClass}\n placeholder=\"aws:ec2:instance, aws:s3:bucket\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.common.commaSeparated', 'Comma-separated.')}\n </p>\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-expr\" className={labelClass}>\n {t('adaptercloud.policies.form.expression', 'Expression (JSON)')} *\n </label>\n <textarea\n id=\"ada-pol-expr\"\n value={expression}\n onChange={(e) => setExpression(e.target.value)}\n rows={8}\n spellCheck={false}\n className={`${fieldClass} font-mono`}\n />\n </div>\n\n <div>\n <label htmlFor=\"ada-pol-tags\" className={labelClass}>\n {t('adaptercloud.common.tags', 'Tags')}\n </label>\n <input\n id=\"ada-pol-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={fieldClass}\n placeholder=\"tagging, governance\"\n />\n </div>\n\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t('adaptercloud.policies.form.activeImmediately', 'Active immediately')}\n </label>\n\n {error && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {error}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isPending}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n {isEdit\n ? t('adaptercloud.common.saveChanges', 'Save changes')\n : t('adaptercloud.policies.create', 'Create policy')}\n </button>\n </div>\n </GlassCard>\n </form>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|