@veltrixsecops/app-sdk 3.2.0 → 3.5.0
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/README.md +1 -1
- package/dist/byol/index.cjs +3802 -0
- package/dist/byol/index.cjs.map +1 -0
- package/dist/byol/index.d.cts +578 -0
- package/dist/byol/index.d.ts +578 -0
- package/dist/byol/index.js +2587 -0
- package/dist/byol/index.js.map +1 -0
- package/dist/chunk-3WK4EVQ3.js +842 -0
- package/dist/chunk-3WK4EVQ3.js.map +1 -0
- package/dist/{chunk-EOOEHZGC.js → chunk-HIL6RIDG.js} +107 -3
- package/dist/chunk-HIL6RIDG.js.map +1 -0
- package/dist/{chunk-VWFTOFTI.js → chunk-Y6XBNZGR.js} +8 -2
- package/dist/chunk-Y6XBNZGR.js.map +1 -0
- package/dist/client/index.cjs +112 -2
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +118 -6
- package/dist/client/index.d.ts +118 -6
- package/dist/client/index.js +13 -1
- package/dist/connections/index.cjs +1787 -0
- package/dist/connections/index.cjs.map +1 -0
- package/dist/connections/index.d.cts +78 -0
- package/dist/connections/index.d.ts +78 -0
- package/dist/connections/index.js +754 -0
- package/dist/connections/index.js.map +1 -0
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/opentofu/index.cjs +138 -0
- package/dist/opentofu/index.cjs.map +1 -0
- package/dist/opentofu/index.d.cts +131 -0
- package/dist/opentofu/index.d.ts +131 -0
- package/dist/opentofu/index.js +109 -0
- package/dist/opentofu/index.js.map +1 -0
- package/dist/pipeline/index.cjs +7 -0
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +9 -2
- package/dist/pipeline/index.d.ts +9 -2
- package/dist/pipeline/index.js +3 -1
- package/dist/pipeline-beeT8dje.d.cts +408 -0
- package/dist/pipeline-beeT8dje.d.ts +408 -0
- package/dist/ui/index.cjs +136 -0
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +109 -1
- package/dist/ui/index.d.ts +109 -1
- package/dist/ui/index.js +35 -677
- package/dist/ui/index.js.map +1 -1
- package/dist/{use-app-context-OQlcmq7t.d.cts → use-app-context-Byv_fam0.d.cts} +206 -4
- package/dist/{use-app-context-OQlcmq7t.d.ts → use-app-context-Byv_fam0.d.ts} +206 -4
- package/opentofu/README.md +114 -0
- package/opentofu/modules/aws/main.tf +1008 -0
- package/opentofu/modules/aws/outputs.tf +90 -0
- package/opentofu/modules/aws/variables.tf +437 -0
- package/opentofu/modules/aws/versions.tf +23 -0
- package/opentofu/modules/azure/main.tf +920 -0
- package/opentofu/modules/azure/outputs.tf +72 -0
- package/opentofu/modules/azure/variables.tf +498 -0
- package/opentofu/modules/azure/versions.tf +27 -0
- package/opentofu/modules/gcp/main.tf +645 -0
- package/opentofu/modules/gcp/outputs.tf +73 -0
- package/opentofu/modules/gcp/variables.tf +428 -0
- package/opentofu/modules/gcp/versions.tf +27 -0
- package/opentofu/modules/hetzner/main.tf +432 -0
- package/opentofu/modules/hetzner/outputs.tf +72 -0
- package/opentofu/modules/hetzner/variables.tf +423 -0
- package/opentofu/modules/hetzner/versions.tf +26 -0
- package/package.json +33 -1
- package/dist/chunk-EOOEHZGC.js.map +0 -1
- package/dist/chunk-VWFTOFTI.js.map +0 -1
- package/dist/pipeline-A-gSsPRR.d.cts +0 -189
- package/dist/pipeline-A-gSsPRR.d.ts +0 -189
|
@@ -0,0 +1,2587 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
Badge,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
CardBody,
|
|
7
|
+
CardHeader,
|
|
8
|
+
DataTable,
|
|
9
|
+
EmptyState,
|
|
10
|
+
FilterBar,
|
|
11
|
+
FormDialog,
|
|
12
|
+
Input,
|
|
13
|
+
Modal,
|
|
14
|
+
Pagination,
|
|
15
|
+
Select,
|
|
16
|
+
SortSelect,
|
|
17
|
+
Spinner,
|
|
18
|
+
StatsCard,
|
|
19
|
+
useConfirmDialog
|
|
20
|
+
} from "../chunk-3WK4EVQ3.js";
|
|
21
|
+
import {
|
|
22
|
+
authFetch
|
|
23
|
+
} from "../chunk-HIL6RIDG.js";
|
|
24
|
+
|
|
25
|
+
// src/byol/ByolInfrastructureManager.tsx
|
|
26
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
|
|
27
|
+
|
|
28
|
+
// src/byol/types.ts
|
|
29
|
+
var SINGLE_SITE_PLACEMENT = { mode: "single" };
|
|
30
|
+
var MIN_HEAVY_FORWARDERS = 1;
|
|
31
|
+
var SELF_HOSTED = "self-hosted";
|
|
32
|
+
var SELF_HOSTED_LABEL = "Self-Hosted";
|
|
33
|
+
var DEFAULT_DEPLOYMENT_TYPES = [
|
|
34
|
+
{ value: "single", label: "Single instance" },
|
|
35
|
+
{ value: "distributed", label: "Distributed" }
|
|
36
|
+
];
|
|
37
|
+
var NETWORK_MODE_OPTIONS = [
|
|
38
|
+
{ value: "dedicated", label: "Dedicated \u2014 your cloud (BYOC)" },
|
|
39
|
+
{ value: "existing", label: "Existing network \u2014 your cloud (BYOC)" },
|
|
40
|
+
{ value: "shared", label: "Veltrix-hosted (shared)" }
|
|
41
|
+
];
|
|
42
|
+
var DNS_MODE_OPTIONS = [
|
|
43
|
+
{ value: "managed", label: "Managed" },
|
|
44
|
+
{ value: "delegated", label: "Delegated" },
|
|
45
|
+
{ value: "private-only", label: "Private only" }
|
|
46
|
+
];
|
|
47
|
+
var BYOC_NETWORK_MODES = /* @__PURE__ */ new Set(["dedicated", "existing"]);
|
|
48
|
+
var CONTROL_PLANE_LAYOUT_OPTIONS = [
|
|
49
|
+
{ value: "dedicated", label: "Dedicated", description: "5 servers \u2014 one management role each (highest isolation)." },
|
|
50
|
+
{ value: "consolidated", label: "Consolidated", description: "~3 servers \u2014 cluster manager and SH deployer isolated, rest combined." },
|
|
51
|
+
{ value: "single", label: "Single node", description: "1 server running every management role (lowest cost, non-HA)." }
|
|
52
|
+
];
|
|
53
|
+
var PLACEMENT_GRANULARITY_OPTIONS = [
|
|
54
|
+
{ value: "az", label: "Availability zones (same region)" },
|
|
55
|
+
{ value: "region", label: "Regions (multi-region)" }
|
|
56
|
+
];
|
|
57
|
+
function editFormState(row) {
|
|
58
|
+
const providerId = row.cloudProviderId ? row.cloudProviderId : row.hosting_type === SELF_HOSTED_LABEL ? SELF_HOSTED : "";
|
|
59
|
+
return {
|
|
60
|
+
name: row.name ?? "",
|
|
61
|
+
deploymentType: row.deploymentType ?? "single",
|
|
62
|
+
environmentType: row.environmentType ?? "",
|
|
63
|
+
providerId,
|
|
64
|
+
region: row.region ?? "",
|
|
65
|
+
indexerCount: String(row.indexerCount ?? 1),
|
|
66
|
+
searchHeadCount: String(row.searchHeadCount ?? 1),
|
|
67
|
+
networkMode: row.networkMode ?? "shared",
|
|
68
|
+
dnsMode: row.dnsMode ?? "managed",
|
|
69
|
+
cloudAccountConnectionId: row.cloudAccountConnectionId ?? "",
|
|
70
|
+
controlPlaneLayout: row.controlPlaneLayout ?? "dedicated",
|
|
71
|
+
heavyForwarderCount: String(row.heavyForwarderCount ?? 1),
|
|
72
|
+
instanceType: row.instanceType ?? "",
|
|
73
|
+
indexerPlacement: row.indexerPlacement ?? { mode: "single" },
|
|
74
|
+
searchHeadPlacement: row.searchHeadPlacement ?? { mode: "single" },
|
|
75
|
+
versionId: row.versionId ?? ""
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
var BLANK_FORM = {
|
|
79
|
+
name: "",
|
|
80
|
+
deploymentType: "single",
|
|
81
|
+
environmentType: "",
|
|
82
|
+
providerId: "",
|
|
83
|
+
region: "",
|
|
84
|
+
indexerCount: "1",
|
|
85
|
+
searchHeadCount: "1",
|
|
86
|
+
// New cloud infra defaults to 'dedicated' — OpenTofu creates its own VPC and the
|
|
87
|
+
// deployment depends on nothing pre-existing. 'shared' attaches to a Veltrix-managed
|
|
88
|
+
// base network that must be provisioned by a separate platform tofu stack; until that
|
|
89
|
+
// stack exists, defaulting to 'shared' produces "no matching VPC" at apply time.
|
|
90
|
+
networkMode: "dedicated",
|
|
91
|
+
dnsMode: "managed",
|
|
92
|
+
cloudAccountConnectionId: "",
|
|
93
|
+
controlPlaneLayout: "dedicated",
|
|
94
|
+
heavyForwarderCount: "1",
|
|
95
|
+
instanceType: "",
|
|
96
|
+
indexerPlacement: { mode: "single" },
|
|
97
|
+
searchHeadPlacement: { mode: "single" },
|
|
98
|
+
versionId: ""
|
|
99
|
+
};
|
|
100
|
+
var INSTANCE_TYPE_EXAMPLES = {
|
|
101
|
+
aws: "t2.medium",
|
|
102
|
+
azure: "Standard_B2s",
|
|
103
|
+
gcp: "e2-medium",
|
|
104
|
+
hetzner: "cx22"
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/byol/status.ts
|
|
108
|
+
var VARIANTS = {
|
|
109
|
+
not_started: "default",
|
|
110
|
+
provisioning: "info",
|
|
111
|
+
running: "success",
|
|
112
|
+
active: "success",
|
|
113
|
+
degraded: "warning",
|
|
114
|
+
stopped: "warning",
|
|
115
|
+
failed: "danger",
|
|
116
|
+
error: "danger",
|
|
117
|
+
destroying: "info"
|
|
118
|
+
};
|
|
119
|
+
var LABELS = {
|
|
120
|
+
not_started: "Not Started",
|
|
121
|
+
provisioning: "Provisioning",
|
|
122
|
+
running: "Running",
|
|
123
|
+
active: "Running",
|
|
124
|
+
degraded: "Degraded",
|
|
125
|
+
stopped: "Stopped",
|
|
126
|
+
failed: "Failed",
|
|
127
|
+
error: "Failed",
|
|
128
|
+
destroying: "Destroying"
|
|
129
|
+
};
|
|
130
|
+
function statusVariant(status) {
|
|
131
|
+
return status && VARIANTS[status] || "default";
|
|
132
|
+
}
|
|
133
|
+
function statusLabel(status) {
|
|
134
|
+
if (!status) return "Unknown";
|
|
135
|
+
return LABELS[status] || status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
136
|
+
}
|
|
137
|
+
function isRunning(status) {
|
|
138
|
+
return status === "running" || status === "active";
|
|
139
|
+
}
|
|
140
|
+
function isNotStarted(status) {
|
|
141
|
+
return !status || status === "not_started";
|
|
142
|
+
}
|
|
143
|
+
var RESOURCE_VARIANTS = {
|
|
144
|
+
ready: "success",
|
|
145
|
+
provisioning: "info",
|
|
146
|
+
attention: "warning",
|
|
147
|
+
failed: "danger",
|
|
148
|
+
not_started: "default"
|
|
149
|
+
};
|
|
150
|
+
var RESOURCE_LABELS = {
|
|
151
|
+
ready: "Ready",
|
|
152
|
+
provisioning: "Provisioning",
|
|
153
|
+
attention: "Attention",
|
|
154
|
+
failed: "Failed",
|
|
155
|
+
not_started: "Not started"
|
|
156
|
+
};
|
|
157
|
+
function resourceStatusVariant(status) {
|
|
158
|
+
return status && RESOURCE_VARIANTS[status] || "default";
|
|
159
|
+
}
|
|
160
|
+
function resourceStatusLabel(status) {
|
|
161
|
+
if (!status) return "Not started";
|
|
162
|
+
return RESOURCE_LABELS[status] || status.replace(/_/g, " ");
|
|
163
|
+
}
|
|
164
|
+
var STEP_VARIANTS = {
|
|
165
|
+
done: "success",
|
|
166
|
+
running: "info",
|
|
167
|
+
failed: "danger",
|
|
168
|
+
pending: "default"
|
|
169
|
+
};
|
|
170
|
+
function stepStatusVariant(status) {
|
|
171
|
+
return status && STEP_VARIANTS[status] || "default";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/byol/detail/shared.tsx
|
|
175
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
176
|
+
var rgbToken = (name, fallback) => `rgb(var(${name}, ${fallback}))`;
|
|
177
|
+
var tokens = {
|
|
178
|
+
border: rgbToken("--color-border", "229 231 235"),
|
|
179
|
+
borderStrong: rgbToken("--color-border-strong", "209 213 219"),
|
|
180
|
+
surface: rgbToken("--color-surface-raised", "255 255 255"),
|
|
181
|
+
// cards / panels / active nav
|
|
182
|
+
surface2: rgbToken("--color-surface", "249 250 251"),
|
|
183
|
+
// page surface — sidebar / insets
|
|
184
|
+
text: rgbToken("--color-content-primary", "17 24 39"),
|
|
185
|
+
muted: rgbToken("--color-content-secondary", "75 85 99"),
|
|
186
|
+
faint: rgbToken("--color-content-tertiary", "156 163 175"),
|
|
187
|
+
primary: "var(--veltrix-app-primary, #FF6600)",
|
|
188
|
+
danger: rgbToken("--color-danger", "220 38 38"),
|
|
189
|
+
success: rgbToken("--color-success", "22 163 74"),
|
|
190
|
+
info: rgbToken("--color-info", "14 116 144"),
|
|
191
|
+
warning: rgbToken("--color-warning", "161 98 7")
|
|
192
|
+
};
|
|
193
|
+
var StatusPill = ({ status }) => /* @__PURE__ */ jsx(Badge, { variant: statusVariant(status), children: statusLabel(status) });
|
|
194
|
+
var ResourcePill = ({ status }) => /* @__PURE__ */ jsx(Badge, { variant: resourceStatusVariant(status), size: "sm", children: resourceStatusLabel(status) });
|
|
195
|
+
var Meta = ({ label, children }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
196
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, textTransform: "uppercase", letterSpacing: ".05em", color: tokens.faint }, children: label }),
|
|
197
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: tokens.text, marginTop: 2 }, children })
|
|
198
|
+
] });
|
|
199
|
+
function barColor(status) {
|
|
200
|
+
switch (status) {
|
|
201
|
+
case "ready":
|
|
202
|
+
return tokens.success;
|
|
203
|
+
case "provisioning":
|
|
204
|
+
return tokens.info;
|
|
205
|
+
case "attention":
|
|
206
|
+
return tokens.warning;
|
|
207
|
+
case "failed":
|
|
208
|
+
return tokens.danger;
|
|
209
|
+
default:
|
|
210
|
+
return tokens.borderStrong;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
var ProgressMeter = ({ resources }) => {
|
|
214
|
+
const total = resources.length;
|
|
215
|
+
const ready = resources.filter((r) => r.status === "ready").length;
|
|
216
|
+
const counts = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const r of resources) counts.set(r.status, (counts.get(r.status) ?? 0) + 1);
|
|
218
|
+
const order = ["ready", "provisioning", "attention", "failed", "not_started"];
|
|
219
|
+
return /* @__PURE__ */ jsxs("div", { style: { width: 300, maxWidth: "48vw" }, children: [
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: 12, color: tokens.muted, marginBottom: 6 }, children: [
|
|
221
|
+
/* @__PURE__ */ jsx("span", { children: "Deployment progress" }),
|
|
222
|
+
/* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: total === 0 ? "Not deployed" : `${ready} / ${total} resources ready` })
|
|
223
|
+
] }),
|
|
224
|
+
/* @__PURE__ */ jsx("div", { style: { height: 8, borderRadius: 999, background: tokens.surface2, overflow: "hidden", display: "flex" }, children: total === 0 ? null : order.map((s2) => {
|
|
225
|
+
const n = counts.get(s2) ?? 0;
|
|
226
|
+
if (!n) return null;
|
|
227
|
+
return /* @__PURE__ */ jsx("div", { style: { width: `${n / total * 100}%`, background: barColor(s2), height: "100%" } }, s2);
|
|
228
|
+
}) })
|
|
229
|
+
] });
|
|
230
|
+
};
|
|
231
|
+
var Panel = ({
|
|
232
|
+
title,
|
|
233
|
+
actions,
|
|
234
|
+
children
|
|
235
|
+
}) => /* @__PURE__ */ jsxs("div", { style: { border: `1px solid ${tokens.border}`, borderRadius: 12, background: tokens.surface, overflow: "hidden" }, children: [
|
|
236
|
+
(title || actions) && /* @__PURE__ */ jsxs(
|
|
237
|
+
"div",
|
|
238
|
+
{
|
|
239
|
+
style: {
|
|
240
|
+
display: "flex",
|
|
241
|
+
alignItems: "center",
|
|
242
|
+
justifyContent: "space-between",
|
|
243
|
+
gap: 12,
|
|
244
|
+
padding: "12px 16px",
|
|
245
|
+
borderBottom: `1px solid ${tokens.border}`
|
|
246
|
+
},
|
|
247
|
+
children: [
|
|
248
|
+
title ? /* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 14, fontWeight: 600, color: tokens.text }, children: title }) : /* @__PURE__ */ jsx("span", {}),
|
|
249
|
+
actions
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
/* @__PURE__ */ jsx("div", { style: { padding: 16 }, children })
|
|
254
|
+
] });
|
|
255
|
+
|
|
256
|
+
// src/byol/api.ts
|
|
257
|
+
async function errorText(res) {
|
|
258
|
+
return res.json().then((b) => b?.error || `HTTP ${res.status}`).catch(() => `HTTP ${res.status}`);
|
|
259
|
+
}
|
|
260
|
+
function formatDate(value) {
|
|
261
|
+
if (!value) return "\u2014";
|
|
262
|
+
const date = new Date(value);
|
|
263
|
+
return Number.isNaN(date.getTime()) ? "\u2014" : date.toLocaleDateString();
|
|
264
|
+
}
|
|
265
|
+
function formatDateTime(value) {
|
|
266
|
+
if (!value) return "\u2014";
|
|
267
|
+
const date = new Date(value);
|
|
268
|
+
return Number.isNaN(date.getTime()) ? "\u2014" : date.toLocaleString();
|
|
269
|
+
}
|
|
270
|
+
async function json(res) {
|
|
271
|
+
if (!res.ok) throw new Error(await errorText(res));
|
|
272
|
+
return res.json();
|
|
273
|
+
}
|
|
274
|
+
function getInfra(apiBase, id) {
|
|
275
|
+
return authFetch(`${apiBase}/${id}`).then((r) => json(r));
|
|
276
|
+
}
|
|
277
|
+
function getResources(apiBase, id) {
|
|
278
|
+
return authFetch(`${apiBase}/${id}/resources`).then((r) => json(r));
|
|
279
|
+
}
|
|
280
|
+
function getDeployments(apiBase, id) {
|
|
281
|
+
return authFetch(`${apiBase}/${id}/deployments`).then((r) => json(r));
|
|
282
|
+
}
|
|
283
|
+
function getPlan(apiBase, id) {
|
|
284
|
+
return authFetch(`${apiBase}/${id}/plan`).then((r) => json(r));
|
|
285
|
+
}
|
|
286
|
+
function deployInfra(apiBase, id) {
|
|
287
|
+
return authFetch(`${apiBase}/${id}/deploy`, { method: "POST" }).then(
|
|
288
|
+
(r) => json(r)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function destroyInfra(apiBase, id) {
|
|
292
|
+
return authFetch(`${apiBase}/${id}/destroy`, { method: "POST" }).then(
|
|
293
|
+
(r) => json(r)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
function lifecycleInfra(apiBase, id, action) {
|
|
297
|
+
return authFetch(`${apiBase}/${id}/${action}`, { method: "POST" }).then((r) => json(r));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/byol/ByolInfrastructureDetail.tsx
|
|
301
|
+
import { useCallback, useEffect, useMemo as useMemo2, useRef, useState } from "react";
|
|
302
|
+
|
|
303
|
+
// src/byol/placement.ts
|
|
304
|
+
function allocateNodesBySite(totalNodes, sites) {
|
|
305
|
+
const n = Math.max(0, Math.floor(totalNodes));
|
|
306
|
+
const list = sites ?? [];
|
|
307
|
+
if (list.length === 0) return [];
|
|
308
|
+
const base = list.map(() => 0);
|
|
309
|
+
let assigned = 0;
|
|
310
|
+
for (let i = 0; i < list.length && assigned < n; i++) {
|
|
311
|
+
base[i] = 1;
|
|
312
|
+
assigned++;
|
|
313
|
+
}
|
|
314
|
+
const remaining = n - assigned;
|
|
315
|
+
if (remaining > 0) {
|
|
316
|
+
const totalPercent = list.reduce((sum, s2) => sum + Math.max(0, s2.percent), 0) || 1;
|
|
317
|
+
const ideal = list.map((s2) => Math.max(0, s2.percent) / totalPercent * remaining);
|
|
318
|
+
const floors = ideal.map((v) => Math.floor(v));
|
|
319
|
+
let leftover = remaining - floors.reduce((a, b) => a + b, 0);
|
|
320
|
+
const order = list.map((_, i) => ({ i, frac: ideal[i] - floors[i] })).sort((a, b) => b.frac - a.frac || a.i - b.i);
|
|
321
|
+
for (let k = 0; k < order.length && leftover > 0; k++) {
|
|
322
|
+
floors[order[k].i]++;
|
|
323
|
+
leftover--;
|
|
324
|
+
}
|
|
325
|
+
for (let i = 0; i < list.length; i++) base[i] += floors[i];
|
|
326
|
+
}
|
|
327
|
+
return list.map((s2, i) => ({ site: s2.site, count: base[i], percent: s2.percent }));
|
|
328
|
+
}
|
|
329
|
+
function validatePlacement(placement, totalNodes) {
|
|
330
|
+
if (!placement || placement.mode === "single") return null;
|
|
331
|
+
const sites = placement.sites ?? [];
|
|
332
|
+
if (sites.length < 2) return "Multi-site placement needs at least two sites.";
|
|
333
|
+
const seen = /* @__PURE__ */ new Set();
|
|
334
|
+
for (const s2 of sites) {
|
|
335
|
+
if (!s2.site || !s2.site.trim()) return "Every site must be selected.";
|
|
336
|
+
if (seen.has(s2.site)) return `Site "${s2.site}" is listed more than once.`;
|
|
337
|
+
seen.add(s2.site);
|
|
338
|
+
if (!(s2.percent > 0)) return `Site "${s2.site}" must have a percent greater than zero.`;
|
|
339
|
+
}
|
|
340
|
+
const sumPercent = sites.reduce((sum, s2) => sum + s2.percent, 0);
|
|
341
|
+
if (Math.round(sumPercent) !== 100) return `Site percentages must total 100 (currently ${Math.round(sumPercent)}).`;
|
|
342
|
+
if (sites.length > Math.floor(totalNodes)) {
|
|
343
|
+
return `Too many sites (${sites.length}) for ${totalNodes} node${totalNodes === 1 ? "" : "s"} \u2014 each site needs at least one node.`;
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
function effectivePlacement(placement, eligible) {
|
|
348
|
+
if (!eligible || !placement || placement.mode !== "multi-site") return { mode: "single" };
|
|
349
|
+
if (!placement.sites || placement.sites.length < 2) return { mode: "single" };
|
|
350
|
+
return placement;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/byol/topology.ts
|
|
354
|
+
var CONTROL_PLANE_ROLE_META = {
|
|
355
|
+
"license-manager": { kind: "license-manager", name: "License Manager", role: "Serves BYOL license pool" },
|
|
356
|
+
"cluster-manager": { kind: "cluster-manager", name: "Cluster Manager", role: "Indexer cluster coordinator" },
|
|
357
|
+
"sh-deployer": { kind: "sh-deployer", name: "SH Deployer", role: "Search head cluster deployer" },
|
|
358
|
+
"deployment-server": { kind: "deployment-server", name: "Deployment Server", role: "Forwarder app distribution" },
|
|
359
|
+
"monitoring-console": { kind: "monitoring-console", name: "Monitoring Console", role: "Fleet health & DMC" }
|
|
360
|
+
};
|
|
361
|
+
function controlPlaneGroups(layout) {
|
|
362
|
+
switch (layout) {
|
|
363
|
+
case "single":
|
|
364
|
+
return [["license-manager", "cluster-manager", "sh-deployer", "deployment-server", "monitoring-console"]];
|
|
365
|
+
case "consolidated":
|
|
366
|
+
return [["cluster-manager"], ["sh-deployer"], ["license-manager", "deployment-server", "monitoring-console"]];
|
|
367
|
+
case "dedicated":
|
|
368
|
+
default:
|
|
369
|
+
return [["license-manager"], ["cluster-manager"], ["sh-deployer"], ["deployment-server"], ["monitoring-console"]];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function buildControlPlane(layout, region) {
|
|
373
|
+
return controlPlaneGroups(layout).map((roles) => {
|
|
374
|
+
if (roles.length === 1) {
|
|
375
|
+
const meta = CONTROL_PLANE_ROLE_META[roles[0]];
|
|
376
|
+
return { planKey: `control-plane/${roles[0]}`, tier: "control-plane", kind: meta.kind, name: meta.name, role: meta.role, region, roles: [roles[0]] };
|
|
377
|
+
}
|
|
378
|
+
const label = roles.map((r) => CONTROL_PLANE_ROLE_META[r].name).join(" \xB7 ");
|
|
379
|
+
return {
|
|
380
|
+
planKey: "control-plane/management",
|
|
381
|
+
tier: "control-plane",
|
|
382
|
+
kind: "management-node",
|
|
383
|
+
name: roles.length >= 5 ? "Management node (all roles)" : "Management node",
|
|
384
|
+
role: label,
|
|
385
|
+
region,
|
|
386
|
+
roles: [...roles]
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function assignNodeSites(count, placement, primaryRegion, legacyRegions) {
|
|
391
|
+
const eff = effectivePlacement(placement ?? void 0, true);
|
|
392
|
+
if (eff.mode === "multi-site" && eff.sites && eff.sites.length >= 2) {
|
|
393
|
+
const granularity = eff.granularity ?? "az";
|
|
394
|
+
const out2 = [];
|
|
395
|
+
for (const alloc of allocateNodesBySite(count, eff.sites)) {
|
|
396
|
+
for (let k = 0; k < alloc.count; k++) {
|
|
397
|
+
out2.push(granularity === "az" ? { region: primaryRegion, zone: alloc.site } : { region: alloc.site, zone: null });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return out2;
|
|
401
|
+
}
|
|
402
|
+
const out = [];
|
|
403
|
+
for (let i = 0; i < count; i++) out.push({ region: pickRegion(legacyRegions, i, primaryRegion), zone: null });
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
var TIER_LABELS = {
|
|
407
|
+
foundation: "Foundation",
|
|
408
|
+
"control-plane": "Control plane",
|
|
409
|
+
data: "Data tier \u2014 indexer cluster",
|
|
410
|
+
search: "Search tier \u2014 search head cluster",
|
|
411
|
+
ingest: "Ingest & access"
|
|
412
|
+
};
|
|
413
|
+
var TIER_ORDER = ["foundation", "control-plane", "data", "search", "ingest"];
|
|
414
|
+
var DISTRIBUTED = "distributed";
|
|
415
|
+
function pickRegion(regions, index, fallback) {
|
|
416
|
+
if (regions && regions.length > 0) return regions[index % regions.length];
|
|
417
|
+
return fallback;
|
|
418
|
+
}
|
|
419
|
+
function buildByolResourcePlan(input) {
|
|
420
|
+
const distributed = (input.deploymentType ?? "single") === DISTRIBUTED;
|
|
421
|
+
const primaryRegion = input.region ?? null;
|
|
422
|
+
const isCloud = input.isCloud ?? false;
|
|
423
|
+
const items = [];
|
|
424
|
+
items.push({ planKey: "foundation/network", tier: "foundation", kind: "network", name: "Network", role: "VPC \xB7 subnets \xB7 security groups", region: primaryRegion });
|
|
425
|
+
if (distributed && isCloud) {
|
|
426
|
+
items.push({ planKey: "foundation/load-balancer", tier: "foundation", kind: "load-balancer", name: "Load balancer", role: "Search + HEC ingress", region: primaryRegion });
|
|
427
|
+
items.push({ planKey: "foundation/dns", tier: "foundation", kind: "dns", name: "DNS", role: "Public + private records", region: "global" });
|
|
428
|
+
}
|
|
429
|
+
items.push({ planKey: "foundation/tls", tier: "foundation", kind: "tls", name: "TLS certificates", role: "Web + inter-node (S2S)", region: null });
|
|
430
|
+
items.push({ planKey: "foundation/storage", tier: "foundation", kind: "storage", name: "Storage", role: distributed ? "SmartStore + hot/warm volumes" : "Index volumes", region: primaryRegion });
|
|
431
|
+
items.push({ planKey: "foundation/secrets", tier: "foundation", kind: "secrets", name: "Secrets", role: "Admin password \xB7 pass4SymmKey", region: null });
|
|
432
|
+
items.push({ planKey: "foundation/license-file", tier: "foundation", kind: "license-file", name: "BYOL license file", role: "Uploaded & validated", region: null });
|
|
433
|
+
if (!distributed) {
|
|
434
|
+
items.push({ planKey: "data/standalone", tier: "data", kind: "standalone", name: "Splunk instance", role: "All-in-one (indexer + search head + web)", region: primaryRegion });
|
|
435
|
+
items.push({ planKey: "ingest/hec", tier: "ingest", kind: "hec", name: "HTTP Event Collector", role: "Token endpoint", region: primaryRegion });
|
|
436
|
+
return items.map((it, i) => stampOrder(it, i));
|
|
437
|
+
}
|
|
438
|
+
items.push(...buildControlPlane(input.controlPlaneLayout ?? "dedicated", primaryRegion));
|
|
439
|
+
const indexerCount = Math.max(1, input.indexerCount ?? 1);
|
|
440
|
+
const indexerSites = assignNodeSites(indexerCount, input.indexerPlacement, primaryRegion, input.indexerRegions);
|
|
441
|
+
for (let i = 0; i < indexerCount; i++) {
|
|
442
|
+
items.push({
|
|
443
|
+
planKey: `data/indexer-${i + 1}`,
|
|
444
|
+
tier: "data",
|
|
445
|
+
kind: "indexer",
|
|
446
|
+
name: `Indexer peer ${i + 1}`,
|
|
447
|
+
role: "Cluster peer node",
|
|
448
|
+
region: indexerSites[i].region,
|
|
449
|
+
zone: indexerSites[i].zone
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
const searchHeadCount = Math.max(1, input.searchHeadCount ?? 1);
|
|
453
|
+
const searchHeadSites = assignNodeSites(searchHeadCount, input.searchHeadPlacement, primaryRegion, input.searchHeadRegions);
|
|
454
|
+
for (let i = 0; i < searchHeadCount; i++) {
|
|
455
|
+
items.push({
|
|
456
|
+
planKey: `search/search-head-${i + 1}`,
|
|
457
|
+
tier: "search",
|
|
458
|
+
kind: "search-head",
|
|
459
|
+
name: `Search head ${i + 1}`,
|
|
460
|
+
role: i === 0 ? "SHC captain candidate" : "SHC member",
|
|
461
|
+
region: searchHeadSites[i].region,
|
|
462
|
+
zone: searchHeadSites[i].zone
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
items.push({ planKey: "ingest/hec", tier: "ingest", kind: "hec", name: "HTTP Event Collector", role: "Token endpoint via LB", region: primaryRegion });
|
|
466
|
+
const heavyForwarderCount = Math.max(1, input.heavyForwarderCount ?? 1);
|
|
467
|
+
for (let i = 0; i < heavyForwarderCount; i++) {
|
|
468
|
+
items.push({
|
|
469
|
+
planKey: `ingest/heavy-forwarder-${i + 1}`,
|
|
470
|
+
tier: "ingest",
|
|
471
|
+
kind: "heavy-forwarder",
|
|
472
|
+
name: `Heavy Forwarder ${i + 1}`,
|
|
473
|
+
role: "Ingest routing / props",
|
|
474
|
+
region: primaryRegion
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return items.map((it, i) => stampOrder(it, i));
|
|
478
|
+
}
|
|
479
|
+
function stampOrder(item, index) {
|
|
480
|
+
return { ...item, sortOrder: index };
|
|
481
|
+
}
|
|
482
|
+
var DEPLOYMENT_STEPS = [
|
|
483
|
+
{ key: "plan", title: "Plan created", detail: "Resources planned from topology; desired state recorded." },
|
|
484
|
+
{ key: "foundation", title: "Provision foundation", detail: "Network, load balancer, DNS, TLS, storage and secrets." },
|
|
485
|
+
{ key: "license", title: "License Manager online \xB7 license applied", detail: "BYOL license validated and pool published." },
|
|
486
|
+
{ key: "cluster", title: "Cluster Manager online", detail: "Indexer cluster policy (RF/SF) published." },
|
|
487
|
+
{ key: "indexers", title: "Indexer peers joining cluster", detail: "Indexer peers boot and register with the cluster manager." },
|
|
488
|
+
{ key: "search-heads", title: "Search head cluster forming", detail: "Deployer pushes the SHC bundle; captain election." },
|
|
489
|
+
{ key: "services", title: "Deployment server & monitoring console", detail: "Ancillary control-plane services come online." },
|
|
490
|
+
{ key: "post-config", title: "Post-deploy configuration", detail: "Apply indexes, HEC tokens and Splunk apps." },
|
|
491
|
+
{ key: "health", title: "End-to-end health check", detail: "Verify search, ingest and replication end to end." }
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
// src/byol/detail/OverviewTab.tsx
|
|
495
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
496
|
+
var OverviewTab = ({ infra, resources }) => {
|
|
497
|
+
const distributed = infra.deploymentType === "distributed";
|
|
498
|
+
const regionCount = new Set(
|
|
499
|
+
resources.map((r) => r.region).filter((r) => Boolean(r) && r !== "global")
|
|
500
|
+
).size;
|
|
501
|
+
const readyCount = resources.filter((r) => r.status === "ready").length;
|
|
502
|
+
return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 20 }, children: [
|
|
503
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 12 }, children: [
|
|
504
|
+
/* @__PURE__ */ jsx2(StatsCard, { label: "Indexer peers", value: infra.indexerCount ?? "\u2014" }),
|
|
505
|
+
/* @__PURE__ */ jsx2(StatsCard, { label: "Search heads", value: infra.searchHeadCount ?? "\u2014" }),
|
|
506
|
+
/* @__PURE__ */ jsx2(StatsCard, { label: "Regions", value: regionCount || (infra.region ? 1 : "\u2014") }),
|
|
507
|
+
/* @__PURE__ */ jsx2(
|
|
508
|
+
StatsCard,
|
|
509
|
+
{
|
|
510
|
+
label: resources.length ? "Resources ready" : "Resources planned",
|
|
511
|
+
value: resources.length ? `${readyCount} / ${resources.length}` : "\u2014"
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
] }),
|
|
515
|
+
/* @__PURE__ */ jsx2(Panel, { title: "What gets deployed", children: /* @__PURE__ */ jsxs2("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.6, color: tokens.muted }, children: [
|
|
516
|
+
distributed ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
517
|
+
"A distributed BYOL environment provisions a full Splunk topology: a foundation tier (network, load balancing, DNS, TLS, storage/SmartStore, secrets and your license file), a control plane (license manager, cluster manager, deployment server, monitoring console and search-head deployer), an",
|
|
518
|
+
" ",
|
|
519
|
+
infra.indexerCount,
|
|
520
|
+
"-peer indexer cluster, a ",
|
|
521
|
+
infra.searchHeadCount,
|
|
522
|
+
"-member search-head cluster, and the ingest path (HEC + heavy forwarders)."
|
|
523
|
+
] }) : /* @__PURE__ */ jsx2(Fragment, { children: "A single-instance BYOL environment provisions the foundation (network, TLS, storage, secrets and your license file) plus one all-in-one Splunk node (indexer + search head + web) and an HTTP Event Collector endpoint." }),
|
|
524
|
+
" ",
|
|
525
|
+
"See the ",
|
|
526
|
+
/* @__PURE__ */ jsx2("strong", { children: "Resources" }),
|
|
527
|
+
" tab for the live plan and ",
|
|
528
|
+
/* @__PURE__ */ jsx2("strong", { children: "Activity" }),
|
|
529
|
+
" for the run log."
|
|
530
|
+
] }) })
|
|
531
|
+
] });
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/byol/detail/ResourcesTab.tsx
|
|
535
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
536
|
+
var STRIPE = {
|
|
537
|
+
ready: tokens.success,
|
|
538
|
+
provisioning: tokens.info,
|
|
539
|
+
attention: tokens.warning,
|
|
540
|
+
failed: tokens.danger,
|
|
541
|
+
not_started: tokens.borderStrong
|
|
542
|
+
};
|
|
543
|
+
var LEGEND = [
|
|
544
|
+
{ status: "ready", label: "Ready" },
|
|
545
|
+
{ status: "provisioning", label: "Provisioning" },
|
|
546
|
+
{ status: "attention", label: "Attention" },
|
|
547
|
+
{ status: "failed", label: "Failed" },
|
|
548
|
+
{ status: "not_started", label: "Not started" }
|
|
549
|
+
];
|
|
550
|
+
var ResourceCard = ({ resource }) => /* @__PURE__ */ jsxs3(
|
|
551
|
+
"div",
|
|
552
|
+
{
|
|
553
|
+
style: {
|
|
554
|
+
position: "relative",
|
|
555
|
+
border: `1px solid ${tokens.border}`,
|
|
556
|
+
borderRadius: 8,
|
|
557
|
+
background: tokens.surface,
|
|
558
|
+
padding: "12px 13px",
|
|
559
|
+
display: "flex",
|
|
560
|
+
flexDirection: "column",
|
|
561
|
+
gap: 8,
|
|
562
|
+
overflow: "hidden"
|
|
563
|
+
},
|
|
564
|
+
children: [
|
|
565
|
+
/* @__PURE__ */ jsx3("span", { style: { position: "absolute", left: 0, top: 0, bottom: 0, width: 3, background: STRIPE[resource.status] ?? STRIPE.not_started } }),
|
|
566
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }, children: [
|
|
567
|
+
/* @__PURE__ */ jsx3("span", { style: { fontWeight: 600, fontSize: 13, color: tokens.text }, children: resource.name }),
|
|
568
|
+
/* @__PURE__ */ jsx3(ResourcePill, { status: resource.status })
|
|
569
|
+
] }),
|
|
570
|
+
resource.role ? /* @__PURE__ */ jsx3("div", { style: { fontSize: 11, color: tokens.faint }, children: resource.role }) : null,
|
|
571
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, fontSize: 11, color: tokens.muted }, children: [
|
|
572
|
+
/* @__PURE__ */ jsx3("span", { children: resource.zone ? `${resource.region ?? ""} \xB7 ${resource.zone}` : resource.region || "\u2014" }),
|
|
573
|
+
/* @__PURE__ */ jsx3("span", { style: { fontFamily: "var(--font-mono, ui-monospace, monospace)", color: tokens.faint }, children: resource.externalRef || "\u2014" })
|
|
574
|
+
] })
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
var ResourcesTab = ({ resources, derived }) => {
|
|
579
|
+
if (resources.length === 0) {
|
|
580
|
+
return /* @__PURE__ */ jsx3(
|
|
581
|
+
EmptyState,
|
|
582
|
+
{
|
|
583
|
+
title: "No resources planned",
|
|
584
|
+
description: "This infrastructure has no derivable topology yet. Check the deployment type and node counts in Settings."
|
|
585
|
+
}
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: 18 }, children: [
|
|
589
|
+
derived ? /* @__PURE__ */ jsxs3(
|
|
590
|
+
"div",
|
|
591
|
+
{
|
|
592
|
+
style: {
|
|
593
|
+
fontSize: 13,
|
|
594
|
+
color: tokens.muted,
|
|
595
|
+
background: tokens.surface2,
|
|
596
|
+
border: `1px solid ${tokens.border}`,
|
|
597
|
+
borderRadius: 8,
|
|
598
|
+
padding: "10px 14px"
|
|
599
|
+
},
|
|
600
|
+
children: [
|
|
601
|
+
"This is the plan derived from the topology \u2014 the resources that ",
|
|
602
|
+
/* @__PURE__ */ jsx3("strong", { children: "will" }),
|
|
603
|
+
" be provisioned. Deploy the environment to begin creating them."
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
) : null,
|
|
607
|
+
/* @__PURE__ */ jsx3("div", { style: { display: "flex", flexWrap: "wrap", gap: 14, fontSize: 12, color: tokens.muted }, children: LEGEND.map((l) => /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
|
|
608
|
+
/* @__PURE__ */ jsx3("span", { style: { width: 8, height: 8, borderRadius: "50%", background: STRIPE[l.status] } }),
|
|
609
|
+
l.label
|
|
610
|
+
] }, l.status)) }),
|
|
611
|
+
TIER_ORDER.map((tier) => {
|
|
612
|
+
const inTier = resources.filter((r) => r.tier === tier);
|
|
613
|
+
if (inTier.length === 0) return null;
|
|
614
|
+
const ready = inTier.filter((r) => r.status === "ready").length;
|
|
615
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
616
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 9 }, children: [
|
|
617
|
+
/* @__PURE__ */ jsx3(
|
|
618
|
+
"h3",
|
|
619
|
+
{
|
|
620
|
+
style: {
|
|
621
|
+
margin: 0,
|
|
622
|
+
fontSize: 12,
|
|
623
|
+
letterSpacing: ".08em",
|
|
624
|
+
textTransform: "uppercase",
|
|
625
|
+
color: tokens.primary
|
|
626
|
+
},
|
|
627
|
+
children: TIER_LABELS[tier]
|
|
628
|
+
}
|
|
629
|
+
),
|
|
630
|
+
/* @__PURE__ */ jsxs3("span", { style: { fontSize: 12, color: tokens.faint, fontVariantNumeric: "tabular-nums" }, children: [
|
|
631
|
+
inTier.length,
|
|
632
|
+
" ",
|
|
633
|
+
inTier.length === 1 ? "resource" : "resources",
|
|
634
|
+
!derived ? ` \xB7 ${ready} ready` : ""
|
|
635
|
+
] })
|
|
636
|
+
] }),
|
|
637
|
+
/* @__PURE__ */ jsx3("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(232px, 1fr))", gap: 10 }, children: inTier.map((r) => /* @__PURE__ */ jsx3(ResourceCard, { resource: r }, r.planKey)) })
|
|
638
|
+
] }, tier);
|
|
639
|
+
})
|
|
640
|
+
] });
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// src/byol/detail/ActivityTab.tsx
|
|
644
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
645
|
+
var NODE_COLOR = {
|
|
646
|
+
done: tokens.success,
|
|
647
|
+
running: tokens.info,
|
|
648
|
+
failed: tokens.danger,
|
|
649
|
+
pending: tokens.borderStrong
|
|
650
|
+
};
|
|
651
|
+
var StepNode = ({ step, last }) => /* @__PURE__ */ jsxs4("div", { style: { position: "relative", paddingBottom: last ? 0 : 20 }, children: [
|
|
652
|
+
/* @__PURE__ */ jsx4(
|
|
653
|
+
"span",
|
|
654
|
+
{
|
|
655
|
+
style: {
|
|
656
|
+
position: "absolute",
|
|
657
|
+
left: -24,
|
|
658
|
+
top: 2,
|
|
659
|
+
width: 16,
|
|
660
|
+
height: 16,
|
|
661
|
+
borderRadius: "50%",
|
|
662
|
+
background: NODE_COLOR[step.status] ?? NODE_COLOR.pending,
|
|
663
|
+
border: `3px solid ${tokens.surface}`,
|
|
664
|
+
boxShadow: `0 0 0 1px ${tokens.border}`
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
),
|
|
668
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [
|
|
669
|
+
/* @__PURE__ */ jsx4("span", { style: { fontWeight: 600, fontSize: 14, color: tokens.text }, children: step.title }),
|
|
670
|
+
/* @__PURE__ */ jsx4(Badge, { variant: stepStatusVariant(step.status), size: "sm", children: step.status }),
|
|
671
|
+
/* @__PURE__ */ jsx4("span", { style: { fontSize: 12, color: tokens.faint }, children: step.completedAt ? formatDateTime(step.completedAt) : step.startedAt ? formatDateTime(step.startedAt) : "" })
|
|
672
|
+
] }),
|
|
673
|
+
step.detail ? /* @__PURE__ */ jsx4("div", { style: { fontSize: 13, color: tokens.muted, marginTop: 3 }, children: step.detail }) : null,
|
|
674
|
+
step.logs ? /* @__PURE__ */ jsx4(
|
|
675
|
+
"pre",
|
|
676
|
+
{
|
|
677
|
+
style: {
|
|
678
|
+
marginTop: 8,
|
|
679
|
+
background: tokens.surface2,
|
|
680
|
+
border: `1px solid ${tokens.border}`,
|
|
681
|
+
borderRadius: 8,
|
|
682
|
+
padding: "9px 12px",
|
|
683
|
+
fontFamily: "var(--font-mono, ui-monospace, monospace)",
|
|
684
|
+
fontSize: 11.5,
|
|
685
|
+
color: tokens.muted,
|
|
686
|
+
whiteSpace: "pre-wrap",
|
|
687
|
+
overflowX: "auto"
|
|
688
|
+
},
|
|
689
|
+
children: step.logs
|
|
690
|
+
}
|
|
691
|
+
) : null
|
|
692
|
+
] });
|
|
693
|
+
var RunTimeline = ({ run }) => /* @__PURE__ */ jsxs4("div", { children: [
|
|
694
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 14, flexWrap: "wrap" }, children: [
|
|
695
|
+
/* @__PURE__ */ jsxs4("h3", { style: { margin: 0, fontSize: 14, fontWeight: 600, color: tokens.text, textTransform: "capitalize" }, children: [
|
|
696
|
+
run.action,
|
|
697
|
+
" run"
|
|
698
|
+
] }),
|
|
699
|
+
/* @__PURE__ */ jsx4(Badge, { variant: run.status === "succeeded" ? "success" : run.status === "failed" ? "danger" : "info", size: "sm", children: run.status }),
|
|
700
|
+
/* @__PURE__ */ jsxs4("span", { style: { fontSize: 12, color: tokens.faint }, children: [
|
|
701
|
+
"started ",
|
|
702
|
+
formatDateTime(run.startedAt)
|
|
703
|
+
] })
|
|
704
|
+
] }),
|
|
705
|
+
/* @__PURE__ */ jsxs4("div", { style: { position: "relative", paddingLeft: 26 }, children: [
|
|
706
|
+
/* @__PURE__ */ jsx4("span", { style: { position: "absolute", left: 8, top: 4, bottom: 4, width: 2, background: tokens.border } }),
|
|
707
|
+
run.steps.map((s2, i) => /* @__PURE__ */ jsx4(StepNode, { step: s2, last: i === run.steps.length - 1 }, s2.id || s2.key))
|
|
708
|
+
] })
|
|
709
|
+
] });
|
|
710
|
+
var ActivityTab = ({ deployments }) => {
|
|
711
|
+
if (deployments.length === 0) {
|
|
712
|
+
return /* @__PURE__ */ jsx4(
|
|
713
|
+
EmptyState,
|
|
714
|
+
{
|
|
715
|
+
title: "No deployment activity yet",
|
|
716
|
+
description: "When you deploy this environment, each provisioning step will stream here."
|
|
717
|
+
}
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const [latest, ...previous] = deployments;
|
|
721
|
+
return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 28 }, children: [
|
|
722
|
+
/* @__PURE__ */ jsx4(RunTimeline, { run: latest }),
|
|
723
|
+
previous.length > 0 ? /* @__PURE__ */ jsxs4("div", { children: [
|
|
724
|
+
/* @__PURE__ */ jsx4("h4", { style: { margin: "0 0 10px", fontSize: 12, letterSpacing: ".06em", textTransform: "uppercase", color: tokens.faint }, children: "Earlier runs" }),
|
|
725
|
+
/* @__PURE__ */ jsx4("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: previous.map((run) => /* @__PURE__ */ jsxs4(
|
|
726
|
+
"div",
|
|
727
|
+
{
|
|
728
|
+
style: {
|
|
729
|
+
display: "flex",
|
|
730
|
+
alignItems: "center",
|
|
731
|
+
justifyContent: "space-between",
|
|
732
|
+
gap: 10,
|
|
733
|
+
padding: "10px 14px",
|
|
734
|
+
border: `1px solid ${tokens.border}`,
|
|
735
|
+
borderRadius: 8,
|
|
736
|
+
background: tokens.surface,
|
|
737
|
+
fontSize: 13
|
|
738
|
+
},
|
|
739
|
+
children: [
|
|
740
|
+
/* @__PURE__ */ jsx4("span", { style: { textTransform: "capitalize", color: tokens.text }, children: run.action }),
|
|
741
|
+
/* @__PURE__ */ jsx4("span", { style: { color: tokens.faint }, children: formatDateTime(run.startedAt) }),
|
|
742
|
+
/* @__PURE__ */ jsx4(Badge, { variant: run.status === "succeeded" ? "success" : run.status === "failed" ? "danger" : "info", size: "sm", children: run.status })
|
|
743
|
+
]
|
|
744
|
+
},
|
|
745
|
+
run.id
|
|
746
|
+
)) })
|
|
747
|
+
] }) : null
|
|
748
|
+
] });
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// src/byol/detail/AccessTab.tsx
|
|
752
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
753
|
+
var mono = "var(--font-mono, ui-monospace, monospace)";
|
|
754
|
+
function asHost(ref) {
|
|
755
|
+
if (!ref) return null;
|
|
756
|
+
const v = ref.trim();
|
|
757
|
+
if (/^https?:\/\//i.test(v)) {
|
|
758
|
+
try {
|
|
759
|
+
return new URL(v).host;
|
|
760
|
+
} catch {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const HOSTNAME = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i;
|
|
765
|
+
return HOSTNAME.test(v) ? v : null;
|
|
766
|
+
}
|
|
767
|
+
var Row = ({ label, value }) => /* @__PURE__ */ jsxs5(
|
|
768
|
+
"div",
|
|
769
|
+
{
|
|
770
|
+
style: {
|
|
771
|
+
display: "grid",
|
|
772
|
+
gridTemplateColumns: "180px 1fr",
|
|
773
|
+
gap: 16,
|
|
774
|
+
alignItems: "center",
|
|
775
|
+
padding: "11px 0",
|
|
776
|
+
borderBottom: `1px solid ${tokens.border}`,
|
|
777
|
+
fontSize: 13
|
|
778
|
+
},
|
|
779
|
+
children: [
|
|
780
|
+
/* @__PURE__ */ jsx5("span", { style: { color: tokens.muted }, children: label }),
|
|
781
|
+
/* @__PURE__ */ jsx5("span", { style: { fontFamily: mono, fontSize: 12.5, color: tokens.text, overflowWrap: "anywhere" }, children: value })
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
var AccessTab = ({ infra, resources }) => {
|
|
786
|
+
if (!isRunning(infra.status)) {
|
|
787
|
+
return /* @__PURE__ */ jsx5(
|
|
788
|
+
EmptyState,
|
|
789
|
+
{
|
|
790
|
+
title: "Endpoints appear once the environment is running",
|
|
791
|
+
description: "Deploy the environment; when the search tier is up, its Splunk Web, management and HEC endpoints show here."
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
const refOf = (planKey) => resources.find((r) => r.planKey === planKey)?.externalRef ?? null;
|
|
796
|
+
const dnsHost = asHost(refOf("foundation/dns"));
|
|
797
|
+
const albHost = asHost(refOf("ingest/hec"));
|
|
798
|
+
const webHost = dnsHost ?? albHost;
|
|
799
|
+
const hecHost = albHost ?? dnsHost;
|
|
800
|
+
const indexerTargets = resources.filter((r) => r.kind === "indexer").map((r) => asHost(r.externalRef)).filter((h) => Boolean(h)).map((h) => `${h}:9997`);
|
|
801
|
+
const endpoints = [];
|
|
802
|
+
if (webHost) {
|
|
803
|
+
endpoints.push({ label: "Splunk Web (search)", value: `https://${webHost}` });
|
|
804
|
+
endpoints.push({ label: "Management API", value: `https://${webHost}:8089` });
|
|
805
|
+
}
|
|
806
|
+
if (hecHost) {
|
|
807
|
+
endpoints.push({ label: "HTTP Event Collector", value: `https://${hecHost}:8088/services/collector` });
|
|
808
|
+
}
|
|
809
|
+
if (endpoints.length === 0) {
|
|
810
|
+
return /* @__PURE__ */ jsx5(Panel, { title: "Endpoints", children: /* @__PURE__ */ jsxs5("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.6, color: tokens.muted }, children: [
|
|
811
|
+
"The environment is running, but the provisioner has not reported a reachable endpoint yet. Splunk Web, the management API and the HTTP Event Collector URL appear here as soon as the load balancer and DNS name are published. Check the ",
|
|
812
|
+
/* @__PURE__ */ jsx5("strong", { children: "Resources" }),
|
|
813
|
+
" tab for each component\u2019s external reference."
|
|
814
|
+
] }) });
|
|
815
|
+
}
|
|
816
|
+
const tcpoutGroup = infra.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "veltrix_splunk";
|
|
817
|
+
return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
818
|
+
/* @__PURE__ */ jsx5(Panel, { title: "Endpoints", children: endpoints.map((e) => /* @__PURE__ */ jsx5(Row, { label: e.label, value: e.value }, e.label)) }),
|
|
819
|
+
/* @__PURE__ */ jsx5(Panel, { title: "Point a forwarder at this environment", children: indexerTargets.length > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
820
|
+
/* @__PURE__ */ jsxs5("p", { style: { margin: "0 0 12px", fontSize: 13, color: tokens.muted }, children: [
|
|
821
|
+
"Add this to a universal forwarder\u2019s ",
|
|
822
|
+
/* @__PURE__ */ jsx5("span", { style: { fontFamily: mono }, children: "outputs.conf" }),
|
|
823
|
+
":"
|
|
824
|
+
] }),
|
|
825
|
+
/* @__PURE__ */ jsx5(
|
|
826
|
+
"pre",
|
|
827
|
+
{
|
|
828
|
+
style: {
|
|
829
|
+
margin: 0,
|
|
830
|
+
background: tokens.surface2,
|
|
831
|
+
border: `1px solid ${tokens.border}`,
|
|
832
|
+
borderRadius: 8,
|
|
833
|
+
padding: "13px 15px",
|
|
834
|
+
fontFamily: mono,
|
|
835
|
+
fontSize: 12,
|
|
836
|
+
color: tokens.text,
|
|
837
|
+
overflowX: "auto"
|
|
838
|
+
},
|
|
839
|
+
children: `[tcpout]
|
|
840
|
+
defaultGroup = ${tcpoutGroup}
|
|
841
|
+
|
|
842
|
+
[tcpout:${tcpoutGroup}]
|
|
843
|
+
server = ${indexerTargets.join(", ")}
|
|
844
|
+
sslVerifyServerCert = true`
|
|
845
|
+
}
|
|
846
|
+
)
|
|
847
|
+
] }) : /* @__PURE__ */ jsx5("p", { style: { margin: 0, fontSize: 13, color: tokens.muted }, children: "Forwarder targets appear here once the indexer peers report their addresses." }) })
|
|
848
|
+
] });
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/byol/detail/ConfigurationTab.tsx
|
|
852
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
853
|
+
var ConfigurationTab = ({ links, configBase }) => {
|
|
854
|
+
if (!links || links.length === 0) {
|
|
855
|
+
return /* @__PURE__ */ jsx6(
|
|
856
|
+
EmptyState,
|
|
857
|
+
{
|
|
858
|
+
title: "No configuration surfaces linked",
|
|
859
|
+
description: "This app has not surfaced any configuration links for its BYOL environments."
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
const resolve = (link) => {
|
|
864
|
+
if (configBase && link.configTypeId) return `${configBase}/${link.configTypeId}`;
|
|
865
|
+
return link.href;
|
|
866
|
+
};
|
|
867
|
+
return /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", gap: 14 }, children: [
|
|
868
|
+
/* @__PURE__ */ jsx6("p", { style: { margin: 0, fontSize: 13, color: tokens.muted }, children: "Once the environment is running, configure it as code. These open the app\u2019s configuration surfaces." }),
|
|
869
|
+
/* @__PURE__ */ jsx6("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 12 }, children: links.map((link) => {
|
|
870
|
+
const url = resolve(link);
|
|
871
|
+
const inner = /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
872
|
+
/* @__PURE__ */ jsxs6("h4", { style: { margin: "0 0 4px", fontSize: 13, display: "flex", justifyContent: "space-between", color: tokens.text }, children: [
|
|
873
|
+
link.title,
|
|
874
|
+
/* @__PURE__ */ jsx6("span", { style: { color: tokens.faint }, "aria-hidden": true, children: "\u2197" })
|
|
875
|
+
] }),
|
|
876
|
+
/* @__PURE__ */ jsx6("p", { style: { margin: 0, fontSize: 12, color: tokens.muted }, children: link.description })
|
|
877
|
+
] });
|
|
878
|
+
const cardStyle = {
|
|
879
|
+
border: `1px solid ${tokens.border}`,
|
|
880
|
+
borderRadius: 8,
|
|
881
|
+
padding: 14,
|
|
882
|
+
background: tokens.surface,
|
|
883
|
+
textDecoration: "none",
|
|
884
|
+
color: "inherit",
|
|
885
|
+
display: "block",
|
|
886
|
+
cursor: url ? "pointer" : "default",
|
|
887
|
+
opacity: url ? 1 : 0.7
|
|
888
|
+
};
|
|
889
|
+
return url ? /* @__PURE__ */ jsx6("a", { href: url, style: cardStyle, children: inner }, link.key) : /* @__PURE__ */ jsx6("div", { style: cardStyle, title: "This link is unavailable here", children: inner }, link.key);
|
|
890
|
+
}) })
|
|
891
|
+
] });
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// src/byol/detail/SettingsTab.tsx
|
|
895
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
896
|
+
var Row2 = ({ label, value }) => /* @__PURE__ */ jsxs7(
|
|
897
|
+
"div",
|
|
898
|
+
{
|
|
899
|
+
style: {
|
|
900
|
+
display: "grid",
|
|
901
|
+
gridTemplateColumns: "200px 1fr",
|
|
902
|
+
gap: 16,
|
|
903
|
+
padding: "11px 0",
|
|
904
|
+
borderBottom: `1px solid ${tokens.border}`,
|
|
905
|
+
fontSize: 13
|
|
906
|
+
},
|
|
907
|
+
children: [
|
|
908
|
+
/* @__PURE__ */ jsx7("span", { style: { color: tokens.muted }, children: label }),
|
|
909
|
+
/* @__PURE__ */ jsx7("span", { style: { color: tokens.text, fontWeight: 600 }, children: value })
|
|
910
|
+
]
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
var SettingsTab = ({ infra, busy, onEdit, onDestroy, onDelete }) => {
|
|
914
|
+
const provider = infra.hosting_type || (infra.cloudProviderId ? "Cloud" : SELF_HOSTED_LABEL);
|
|
915
|
+
const neverDeployed = isNotStarted(infra.status);
|
|
916
|
+
return /* @__PURE__ */ jsxs7("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
917
|
+
/* @__PURE__ */ jsxs7(
|
|
918
|
+
Panel,
|
|
919
|
+
{
|
|
920
|
+
title: "Topology",
|
|
921
|
+
actions: /* @__PURE__ */ jsx7(Button, { variant: "ghost", size: "sm", onClick: onEdit, disabled: busy, children: "Edit" }),
|
|
922
|
+
children: [
|
|
923
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Name", value: infra.name }),
|
|
924
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Deployment type", value: infra.deploymentType ?? "\u2014" }),
|
|
925
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Indexers / Search heads", value: `${infra.indexerCount ?? "\u2014"} / ${infra.searchHeadCount ?? "\u2014"}` }),
|
|
926
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Provider", value: provider }),
|
|
927
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Region", value: infra.region || "\u2014" }),
|
|
928
|
+
/* @__PURE__ */ jsx7(Row2, { label: "Environment", value: infra.environmentType || "\u2014" })
|
|
929
|
+
]
|
|
930
|
+
}
|
|
931
|
+
),
|
|
932
|
+
/* @__PURE__ */ jsxs7("div", { style: { border: `1px solid ${tokens.danger}`, borderRadius: 12, padding: "16px 18px" }, children: [
|
|
933
|
+
/* @__PURE__ */ jsx7("h3", { style: { margin: "0 0 4px", fontSize: 14, color: tokens.danger }, children: "Danger zone" }),
|
|
934
|
+
/* @__PURE__ */ jsx7("p", { style: { margin: "0 0 12px", fontSize: 13, color: tokens.muted }, children: neverDeployed ? "This environment has not been deployed. Deleting removes the record and its plan." : "Destroy tears down every resource in the plan \u2014 instances, storage, network. This cannot be undone." }),
|
|
935
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
|
|
936
|
+
!neverDeployed ? /* @__PURE__ */ jsx7(Button, { variant: "ghost", size: "sm", onClick: onDestroy, disabled: busy, children: "Destroy infrastructure" }) : null,
|
|
937
|
+
/* @__PURE__ */ jsx7(Button, { variant: "danger", size: "sm", onClick: onDelete, disabled: busy, children: "Delete record" })
|
|
938
|
+
] })
|
|
939
|
+
] })
|
|
940
|
+
] });
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// src/byol/detail/ByolPlanModal.tsx
|
|
944
|
+
import React from "react";
|
|
945
|
+
|
|
946
|
+
// src/byol/diffPlan.ts
|
|
947
|
+
var nz = (v) => v == null ? null : v;
|
|
948
|
+
function differs(current, desired) {
|
|
949
|
+
return current.tier !== desired.tier || current.kind !== desired.kind || current.name !== desired.name || nz(current.role) !== nz(desired.role) || nz(current.region) !== nz(desired.region);
|
|
950
|
+
}
|
|
951
|
+
function needsReplan(status) {
|
|
952
|
+
return status === "failed" || status === "attention";
|
|
953
|
+
}
|
|
954
|
+
function diffPlan(current, desired) {
|
|
955
|
+
const currentByKey = new Map(current.map((c) => [c.planKey, c]));
|
|
956
|
+
const desiredKeys = new Set(desired.map((d) => d.planKey));
|
|
957
|
+
const add = [];
|
|
958
|
+
const change = [];
|
|
959
|
+
const noop = [];
|
|
960
|
+
for (const item of desired) {
|
|
961
|
+
const existing = currentByKey.get(item.planKey);
|
|
962
|
+
if (!existing) add.push(item);
|
|
963
|
+
else if (differs(existing, item) || needsReplan(existing.status)) change.push(item);
|
|
964
|
+
else noop.push(item);
|
|
965
|
+
}
|
|
966
|
+
const destroy = current.filter((c) => !desiredKeys.has(c.planKey));
|
|
967
|
+
return { add, change, destroy, noop };
|
|
968
|
+
}
|
|
969
|
+
function tierRank(tier) {
|
|
970
|
+
const idx = TIER_ORDER.indexOf(tier);
|
|
971
|
+
return idx === -1 ? TIER_ORDER.length : idx;
|
|
972
|
+
}
|
|
973
|
+
function buildByolPlan(current, desired) {
|
|
974
|
+
const diff = diffPlan(current, desired);
|
|
975
|
+
const summary = {
|
|
976
|
+
add: diff.add.length,
|
|
977
|
+
change: diff.change.length,
|
|
978
|
+
destroy: diff.destroy.length,
|
|
979
|
+
noop: diff.noop.length
|
|
980
|
+
};
|
|
981
|
+
const action = /* @__PURE__ */ new Map();
|
|
982
|
+
for (const d of diff.add) action.set(d.planKey, "add");
|
|
983
|
+
for (const d of diff.change) action.set(d.planKey, "change");
|
|
984
|
+
for (const d of diff.noop) action.set(d.planKey, "noop");
|
|
985
|
+
const lines = [];
|
|
986
|
+
desired.forEach((d, i) => {
|
|
987
|
+
lines.push({
|
|
988
|
+
tier: d.tier,
|
|
989
|
+
rank: d.sortOrder ?? i,
|
|
990
|
+
planKey: d.planKey,
|
|
991
|
+
action: action.get(d.planKey) ?? "noop",
|
|
992
|
+
name: d.name,
|
|
993
|
+
role: nz(d.role),
|
|
994
|
+
region: nz(d.region),
|
|
995
|
+
kind: d.kind
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
const tail = desired.length;
|
|
999
|
+
diff.destroy.forEach((c, i) => {
|
|
1000
|
+
lines.push({
|
|
1001
|
+
tier: c.tier,
|
|
1002
|
+
rank: tail + i,
|
|
1003
|
+
planKey: c.planKey,
|
|
1004
|
+
action: "destroy",
|
|
1005
|
+
name: c.name,
|
|
1006
|
+
role: nz(c.role),
|
|
1007
|
+
region: nz(c.region),
|
|
1008
|
+
kind: c.kind
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
lines.sort((a, b) => tierRank(a.tier) - tierRank(b.tier) || a.rank - b.rank);
|
|
1012
|
+
const groups = [];
|
|
1013
|
+
const byTier = /* @__PURE__ */ new Map();
|
|
1014
|
+
for (const line of lines) {
|
|
1015
|
+
let group = byTier.get(line.tier);
|
|
1016
|
+
if (!group) {
|
|
1017
|
+
group = { tier: line.tier, items: [] };
|
|
1018
|
+
byTier.set(line.tier, group);
|
|
1019
|
+
groups.push(group);
|
|
1020
|
+
}
|
|
1021
|
+
group.items.push({
|
|
1022
|
+
planKey: line.planKey,
|
|
1023
|
+
action: line.action,
|
|
1024
|
+
name: line.name,
|
|
1025
|
+
role: line.role,
|
|
1026
|
+
region: line.region,
|
|
1027
|
+
kind: line.kind
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
return { summary, groups };
|
|
1031
|
+
}
|
|
1032
|
+
function planHasChanges(summary) {
|
|
1033
|
+
return summary.add + summary.change + summary.destroy > 0;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/byol/detail/planLines.tsx
|
|
1037
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1038
|
+
var ACTION_META = {
|
|
1039
|
+
add: { glyph: "+", color: tokens.success, label: "add" },
|
|
1040
|
+
change: { glyph: "~", color: tokens.warning, label: "change" },
|
|
1041
|
+
destroy: { glyph: "\u2212", color: tokens.danger, label: "destroy" },
|
|
1042
|
+
noop: { glyph: "\xB7", color: tokens.faint, label: "no change" }
|
|
1043
|
+
};
|
|
1044
|
+
var tierLabel = (tier) => TIER_LABELS[tier] ?? tier.replace(/-/g, " ");
|
|
1045
|
+
var PlanLine = ({ item }) => {
|
|
1046
|
+
const meta = ACTION_META[item.action];
|
|
1047
|
+
return /* @__PURE__ */ jsxs8(
|
|
1048
|
+
"div",
|
|
1049
|
+
{
|
|
1050
|
+
style: {
|
|
1051
|
+
position: "relative",
|
|
1052
|
+
border: `1px solid ${tokens.border}`,
|
|
1053
|
+
borderRadius: 8,
|
|
1054
|
+
background: tokens.surface,
|
|
1055
|
+
padding: "10px 12px 10px 14px",
|
|
1056
|
+
display: "flex",
|
|
1057
|
+
alignItems: "center",
|
|
1058
|
+
gap: 10,
|
|
1059
|
+
overflow: "hidden"
|
|
1060
|
+
},
|
|
1061
|
+
children: [
|
|
1062
|
+
/* @__PURE__ */ jsx8("span", { style: { position: "absolute", left: 0, top: 0, bottom: 0, width: 3, background: meta.color } }),
|
|
1063
|
+
/* @__PURE__ */ jsx8(
|
|
1064
|
+
"span",
|
|
1065
|
+
{
|
|
1066
|
+
"aria-hidden": true,
|
|
1067
|
+
title: meta.label,
|
|
1068
|
+
style: {
|
|
1069
|
+
flex: "none",
|
|
1070
|
+
width: 20,
|
|
1071
|
+
height: 20,
|
|
1072
|
+
borderRadius: 5,
|
|
1073
|
+
display: "inline-flex",
|
|
1074
|
+
alignItems: "center",
|
|
1075
|
+
justifyContent: "center",
|
|
1076
|
+
fontWeight: 700,
|
|
1077
|
+
fontSize: 14,
|
|
1078
|
+
lineHeight: 1,
|
|
1079
|
+
color: meta.color,
|
|
1080
|
+
background: tokens.surface2
|
|
1081
|
+
},
|
|
1082
|
+
children: meta.glyph
|
|
1083
|
+
}
|
|
1084
|
+
),
|
|
1085
|
+
/* @__PURE__ */ jsxs8("div", { style: { minWidth: 0, flex: 1 }, children: [
|
|
1086
|
+
/* @__PURE__ */ jsx8("div", { style: { fontWeight: 600, fontSize: 13, color: tokens.text, textDecoration: item.action === "destroy" ? "line-through" : "none" }, children: item.name }),
|
|
1087
|
+
item.role ? /* @__PURE__ */ jsx8("div", { style: { fontSize: 11, color: tokens.faint }, children: item.role }) : null
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsx8("span", { style: { fontSize: 11, color: tokens.muted, flex: "none" }, children: item.region || "\u2014" })
|
|
1090
|
+
]
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
};
|
|
1094
|
+
var PlanGroup = ({ tier, items }) => /* @__PURE__ */ jsxs8("div", { children: [
|
|
1095
|
+
/* @__PURE__ */ jsx8(
|
|
1096
|
+
"h3",
|
|
1097
|
+
{
|
|
1098
|
+
style: {
|
|
1099
|
+
margin: "0 0 8px",
|
|
1100
|
+
fontSize: 12,
|
|
1101
|
+
letterSpacing: ".08em",
|
|
1102
|
+
textTransform: "uppercase",
|
|
1103
|
+
color: tokens.primary
|
|
1104
|
+
},
|
|
1105
|
+
children: tierLabel(tier)
|
|
1106
|
+
}
|
|
1107
|
+
),
|
|
1108
|
+
/* @__PURE__ */ jsx8("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: items.map((item) => /* @__PURE__ */ jsx8(PlanLine, { item }, item.planKey)) })
|
|
1109
|
+
] });
|
|
1110
|
+
|
|
1111
|
+
// src/byol/detail/ByolPlanModal.tsx
|
|
1112
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1113
|
+
var InfoPanel = ({ title, children }) => /* @__PURE__ */ jsxs9("div", { style: { border: `1px solid ${tokens.border}`, borderRadius: 8, background: tokens.surface2, overflow: "hidden" }, children: [
|
|
1114
|
+
/* @__PURE__ */ jsx9(
|
|
1115
|
+
"div",
|
|
1116
|
+
{
|
|
1117
|
+
style: {
|
|
1118
|
+
padding: "8px 12px",
|
|
1119
|
+
borderBottom: `1px solid ${tokens.border}`,
|
|
1120
|
+
fontSize: 11,
|
|
1121
|
+
letterSpacing: ".08em",
|
|
1122
|
+
textTransform: "uppercase",
|
|
1123
|
+
color: tokens.primary,
|
|
1124
|
+
fontWeight: 700
|
|
1125
|
+
},
|
|
1126
|
+
children: title
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "10px 12px" }, children })
|
|
1130
|
+
] });
|
|
1131
|
+
var NetworkPanel = ({ plan }) => {
|
|
1132
|
+
if (!plan.network) {
|
|
1133
|
+
if (!plan.networkUnavailable) return null;
|
|
1134
|
+
return /* @__PURE__ */ jsx9(InfoPanel, { title: "Network", children: /* @__PURE__ */ jsx9("div", { style: { fontSize: 13, color: tokens.muted }, children: "Subnet allocation preview is temporarily unavailable \u2014 the CIDR is reserved when you apply." }) });
|
|
1135
|
+
}
|
|
1136
|
+
return /* @__PURE__ */ jsx9(InfoPanel, { title: "Network", children: /* @__PURE__ */ jsxs9("div", { style: { fontSize: 13, color: tokens.text }, children: [
|
|
1137
|
+
"Subnet",
|
|
1138
|
+
" ",
|
|
1139
|
+
/* @__PURE__ */ jsx9("code", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", color: tokens.primary }, children: plan.network.subnetCidr }),
|
|
1140
|
+
" ",
|
|
1141
|
+
"will be allocated in",
|
|
1142
|
+
" ",
|
|
1143
|
+
/* @__PURE__ */ jsx9("code", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", color: tokens.text }, children: plan.network.networkRef }),
|
|
1144
|
+
"."
|
|
1145
|
+
] }) });
|
|
1146
|
+
};
|
|
1147
|
+
var TagsPanel = ({ plan }) => {
|
|
1148
|
+
const entries = plan.tags ? Object.entries(plan.tags) : [];
|
|
1149
|
+
if (entries.length === 0) return null;
|
|
1150
|
+
return /* @__PURE__ */ jsx9(InfoPanel, { title: "Tags applied to every resource", children: /* @__PURE__ */ jsx9("div", { style: { display: "grid", gridTemplateColumns: "auto 1fr", gap: "4px 12px", fontSize: 12 }, children: entries.map(([key, value]) => /* @__PURE__ */ jsxs9(React.Fragment, { children: [
|
|
1151
|
+
/* @__PURE__ */ jsx9(
|
|
1152
|
+
"span",
|
|
1153
|
+
{
|
|
1154
|
+
style: {
|
|
1155
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
1156
|
+
color: tokens.muted,
|
|
1157
|
+
whiteSpace: "nowrap"
|
|
1158
|
+
},
|
|
1159
|
+
children: key
|
|
1160
|
+
}
|
|
1161
|
+
),
|
|
1162
|
+
/* @__PURE__ */ jsx9("span", { style: { color: tokens.text, wordBreak: "break-all" }, children: value })
|
|
1163
|
+
] }, key)) }) });
|
|
1164
|
+
};
|
|
1165
|
+
var SummaryChips = ({ plan }) => {
|
|
1166
|
+
const { add, change, destroy } = plan.summary;
|
|
1167
|
+
return /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }, children: [
|
|
1168
|
+
/* @__PURE__ */ jsxs9(Badge, { variant: "success", size: "sm", children: [
|
|
1169
|
+
"+",
|
|
1170
|
+
add,
|
|
1171
|
+
" to add"
|
|
1172
|
+
] }),
|
|
1173
|
+
/* @__PURE__ */ jsxs9(Badge, { variant: "warning", size: "sm", children: [
|
|
1174
|
+
"~",
|
|
1175
|
+
change,
|
|
1176
|
+
" to change"
|
|
1177
|
+
] }),
|
|
1178
|
+
/* @__PURE__ */ jsxs9(Badge, { variant: "danger", size: "sm", children: [
|
|
1179
|
+
"\u2212",
|
|
1180
|
+
destroy,
|
|
1181
|
+
" to destroy"
|
|
1182
|
+
] }),
|
|
1183
|
+
plan.summary.noop > 0 ? /* @__PURE__ */ jsxs9("span", { style: { fontSize: 12, color: tokens.faint }, children: [
|
|
1184
|
+
plan.summary.noop,
|
|
1185
|
+
" unchanged"
|
|
1186
|
+
] }) : null
|
|
1187
|
+
] });
|
|
1188
|
+
};
|
|
1189
|
+
var PlanBody = ({ plan, loading, error, allowApplyWithoutChanges }) => {
|
|
1190
|
+
if (loading) {
|
|
1191
|
+
return /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "24px 0", color: tokens.muted, fontSize: 13 }, children: [
|
|
1192
|
+
/* @__PURE__ */ jsx9(Spinner, { size: "sm" }),
|
|
1193
|
+
" Computing plan\u2026"
|
|
1194
|
+
] });
|
|
1195
|
+
}
|
|
1196
|
+
if (error) {
|
|
1197
|
+
return /* @__PURE__ */ jsx9("div", { style: { padding: "12px 0", color: tokens.danger, fontSize: 13 }, children: error });
|
|
1198
|
+
}
|
|
1199
|
+
if (!plan) return null;
|
|
1200
|
+
return /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
1201
|
+
/* @__PURE__ */ jsx9(SummaryChips, { plan }),
|
|
1202
|
+
/* @__PURE__ */ jsx9(NetworkPanel, { plan }),
|
|
1203
|
+
/* @__PURE__ */ jsx9(TagsPanel, { plan }),
|
|
1204
|
+
!planHasChanges(plan.summary) ? /* @__PURE__ */ jsx9(
|
|
1205
|
+
"div",
|
|
1206
|
+
{
|
|
1207
|
+
style: {
|
|
1208
|
+
fontSize: 13,
|
|
1209
|
+
color: tokens.muted,
|
|
1210
|
+
background: tokens.surface2,
|
|
1211
|
+
border: `1px solid ${tokens.border}`,
|
|
1212
|
+
borderRadius: 8,
|
|
1213
|
+
padding: "10px 14px"
|
|
1214
|
+
},
|
|
1215
|
+
children: allowApplyWithoutChanges ? "No plan changes \u2014 the previous run did not complete. Apply to re-run the deployment with the same plan." : "No changes. Infrastructure is up to date."
|
|
1216
|
+
}
|
|
1217
|
+
) : plan.groups.map((group) => /* @__PURE__ */ jsx9(PlanGroup, { tier: group.tier, items: group.items }, group.tier))
|
|
1218
|
+
] });
|
|
1219
|
+
};
|
|
1220
|
+
var ByolPlanModal = ({
|
|
1221
|
+
isOpen,
|
|
1222
|
+
onClose,
|
|
1223
|
+
plan,
|
|
1224
|
+
loading,
|
|
1225
|
+
error,
|
|
1226
|
+
applying,
|
|
1227
|
+
onApply,
|
|
1228
|
+
infraName,
|
|
1229
|
+
allowApplyWithoutChanges
|
|
1230
|
+
}) => {
|
|
1231
|
+
const noChanges = !plan || !planHasChanges(plan.summary);
|
|
1232
|
+
const applyDisabled = loading || applying || noChanges && !allowApplyWithoutChanges;
|
|
1233
|
+
return /* @__PURE__ */ jsx9(
|
|
1234
|
+
Modal,
|
|
1235
|
+
{
|
|
1236
|
+
isOpen,
|
|
1237
|
+
onClose,
|
|
1238
|
+
size: "lg",
|
|
1239
|
+
title: "Review deployment plan",
|
|
1240
|
+
subtitle: infraName ? `Changes that will be applied to ${infraName}.` : "Changes that will be applied to this environment.",
|
|
1241
|
+
disableBackdropClose: applying,
|
|
1242
|
+
footer: /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1243
|
+
/* @__PURE__ */ jsx9(Button, { variant: "ghost", size: "sm", onClick: onClose, disabled: applying, children: "Cancel" }),
|
|
1244
|
+
/* @__PURE__ */ jsx9(Button, { variant: "primary", size: "sm", onClick: onApply, isLoading: applying, disabled: applyDisabled, children: "Apply" })
|
|
1245
|
+
] }),
|
|
1246
|
+
children: /* @__PURE__ */ jsx9(PlanBody, { plan, loading, error, allowApplyWithoutChanges })
|
|
1247
|
+
}
|
|
1248
|
+
);
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
// src/byol/detail/DestroyPlanModal.tsx
|
|
1252
|
+
import { useMemo } from "react";
|
|
1253
|
+
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1254
|
+
var DestroyPlanModal = ({
|
|
1255
|
+
isOpen,
|
|
1256
|
+
onClose,
|
|
1257
|
+
resources,
|
|
1258
|
+
loading,
|
|
1259
|
+
error,
|
|
1260
|
+
destroying,
|
|
1261
|
+
onConfirm,
|
|
1262
|
+
infraName
|
|
1263
|
+
}) => {
|
|
1264
|
+
const plan = useMemo(() => resources ? buildByolPlan(resources, []) : null, [resources]);
|
|
1265
|
+
const destroyCount = plan?.summary.destroy ?? 0;
|
|
1266
|
+
const confirmDisabled = loading || destroying;
|
|
1267
|
+
return /* @__PURE__ */ jsx10(
|
|
1268
|
+
Modal,
|
|
1269
|
+
{
|
|
1270
|
+
isOpen,
|
|
1271
|
+
onClose,
|
|
1272
|
+
size: "lg",
|
|
1273
|
+
title: "Destroy infrastructure",
|
|
1274
|
+
subtitle: infraName ? `Every resource for "${infraName}" will be torn down.` : "Every resource for this environment will be torn down.",
|
|
1275
|
+
disableBackdropClose: destroying,
|
|
1276
|
+
footer: /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
1277
|
+
/* @__PURE__ */ jsx10(Button, { variant: "ghost", size: "sm", onClick: onClose, disabled: destroying, children: "Cancel" }),
|
|
1278
|
+
/* @__PURE__ */ jsx10(Button, { variant: "danger", size: "sm", onClick: onConfirm, isLoading: destroying, disabled: confirmDisabled, children: "Destroy" })
|
|
1279
|
+
] }),
|
|
1280
|
+
children: /* @__PURE__ */ jsxs10("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
1281
|
+
/* @__PURE__ */ jsx10(Alert, { variant: "danger", title: "This cannot be undone", children: "Destroying removes every instance, storage volume, and network resource provisioned for this environment." }),
|
|
1282
|
+
error ? /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, color: tokens.danger }, children: error }) : null,
|
|
1283
|
+
loading ? /* @__PURE__ */ jsxs10("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 0", color: tokens.muted, fontSize: 13 }, children: [
|
|
1284
|
+
/* @__PURE__ */ jsx10(Spinner, { size: "sm" }),
|
|
1285
|
+
" Loading current resources\u2026"
|
|
1286
|
+
] }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
1287
|
+
/* @__PURE__ */ jsxs10(Badge, { variant: "danger", size: "sm", children: [
|
|
1288
|
+
"\u2212",
|
|
1289
|
+
destroyCount,
|
|
1290
|
+
" to destroy"
|
|
1291
|
+
] }),
|
|
1292
|
+
plan && plan.groups.length > 0 ? /* @__PURE__ */ jsx10("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: plan.groups.map((group) => /* @__PURE__ */ jsx10(PlanGroup, { tier: group.tier, items: group.items }, group.tier)) }) : /* @__PURE__ */ jsx10(
|
|
1293
|
+
"div",
|
|
1294
|
+
{
|
|
1295
|
+
style: {
|
|
1296
|
+
fontSize: 13,
|
|
1297
|
+
color: tokens.muted,
|
|
1298
|
+
background: tokens.surface2,
|
|
1299
|
+
border: `1px solid ${tokens.border}`,
|
|
1300
|
+
borderRadius: 8,
|
|
1301
|
+
padding: "10px 14px"
|
|
1302
|
+
},
|
|
1303
|
+
children: "No resource inventory is available for this environment \u2014 it will still be destroyed."
|
|
1304
|
+
}
|
|
1305
|
+
)
|
|
1306
|
+
] })
|
|
1307
|
+
] })
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
// src/byol/detail/icons.tsx
|
|
1313
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1314
|
+
var Svg = ({ size = 16, style, children }) => /* @__PURE__ */ jsx11(
|
|
1315
|
+
"svg",
|
|
1316
|
+
{
|
|
1317
|
+
width: size,
|
|
1318
|
+
height: size,
|
|
1319
|
+
viewBox: "0 0 24 24",
|
|
1320
|
+
fill: "none",
|
|
1321
|
+
stroke: "currentColor",
|
|
1322
|
+
strokeWidth: 2,
|
|
1323
|
+
strokeLinecap: "round",
|
|
1324
|
+
strokeLinejoin: "round",
|
|
1325
|
+
"aria-hidden": true,
|
|
1326
|
+
focusable: false,
|
|
1327
|
+
style: { display: "block", flex: "none", ...style },
|
|
1328
|
+
children
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1331
|
+
var OverviewIcon = (p) => /* @__PURE__ */ jsxs11(Svg, { ...p, children: [
|
|
1332
|
+
/* @__PURE__ */ jsx11("rect", { x: "3", y: "3", width: "7", height: "9", rx: "1" }),
|
|
1333
|
+
/* @__PURE__ */ jsx11("rect", { x: "14", y: "3", width: "7", height: "5", rx: "1" }),
|
|
1334
|
+
/* @__PURE__ */ jsx11("rect", { x: "14", y: "12", width: "7", height: "9", rx: "1" }),
|
|
1335
|
+
/* @__PURE__ */ jsx11("rect", { x: "3", y: "16", width: "7", height: "5", rx: "1" })
|
|
1336
|
+
] });
|
|
1337
|
+
var ResourcesIcon = (p) => /* @__PURE__ */ jsxs11(Svg, { ...p, children: [
|
|
1338
|
+
/* @__PURE__ */ jsx11("rect", { x: "2", y: "2", width: "20", height: "8", rx: "2" }),
|
|
1339
|
+
/* @__PURE__ */ jsx11("rect", { x: "2", y: "14", width: "20", height: "8", rx: "2" }),
|
|
1340
|
+
/* @__PURE__ */ jsx11("line", { x1: "6", y1: "6", x2: "6.01", y2: "6" }),
|
|
1341
|
+
/* @__PURE__ */ jsx11("line", { x1: "6", y1: "18", x2: "6.01", y2: "18" })
|
|
1342
|
+
] });
|
|
1343
|
+
var ActivityIcon = (p) => /* @__PURE__ */ jsx11(Svg, { ...p, children: /* @__PURE__ */ jsx11("path", { d: "M22 12h-4l-3 9L9 3l-3 9H2" }) });
|
|
1344
|
+
var AccessIcon = (p) => /* @__PURE__ */ jsxs11(Svg, { ...p, children: [
|
|
1345
|
+
/* @__PURE__ */ jsx11("circle", { cx: "7.5", cy: "15.5", r: "5.5" }),
|
|
1346
|
+
/* @__PURE__ */ jsx11("path", { d: "m21 2-9.6 9.6" }),
|
|
1347
|
+
/* @__PURE__ */ jsx11("path", { d: "m15.5 7.5 3 3L22 7l-3-3" })
|
|
1348
|
+
] });
|
|
1349
|
+
var ConfigurationIcon = (p) => /* @__PURE__ */ jsxs11(Svg, { ...p, children: [
|
|
1350
|
+
/* @__PURE__ */ jsx11("line", { x1: "21", y1: "4", x2: "14", y2: "4" }),
|
|
1351
|
+
/* @__PURE__ */ jsx11("line", { x1: "10", y1: "4", x2: "3", y2: "4" }),
|
|
1352
|
+
/* @__PURE__ */ jsx11("line", { x1: "21", y1: "12", x2: "12", y2: "12" }),
|
|
1353
|
+
/* @__PURE__ */ jsx11("line", { x1: "8", y1: "12", x2: "3", y2: "12" }),
|
|
1354
|
+
/* @__PURE__ */ jsx11("line", { x1: "21", y1: "20", x2: "16", y2: "20" }),
|
|
1355
|
+
/* @__PURE__ */ jsx11("line", { x1: "12", y1: "20", x2: "3", y2: "20" }),
|
|
1356
|
+
/* @__PURE__ */ jsx11("line", { x1: "14", y1: "2", x2: "14", y2: "6" }),
|
|
1357
|
+
/* @__PURE__ */ jsx11("line", { x1: "8", y1: "10", x2: "8", y2: "14" }),
|
|
1358
|
+
/* @__PURE__ */ jsx11("line", { x1: "16", y1: "18", x2: "16", y2: "22" })
|
|
1359
|
+
] });
|
|
1360
|
+
var SettingsIcon = (p) => /* @__PURE__ */ jsxs11(Svg, { ...p, children: [
|
|
1361
|
+
/* @__PURE__ */ jsx11("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z" }),
|
|
1362
|
+
/* @__PURE__ */ jsx11("circle", { cx: "12", cy: "12", r: "3" })
|
|
1363
|
+
] });
|
|
1364
|
+
|
|
1365
|
+
// src/byol/ByolInfrastructureDetail.tsx
|
|
1366
|
+
import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1367
|
+
var PROVISIONING_POLL_MS = 4e3;
|
|
1368
|
+
var SECTIONS = [
|
|
1369
|
+
{ key: "overview", label: "Overview", Icon: OverviewIcon },
|
|
1370
|
+
{ key: "resources", label: "Resources", Icon: ResourcesIcon },
|
|
1371
|
+
{ key: "activity", label: "Activity", Icon: ActivityIcon },
|
|
1372
|
+
{ key: "access", label: "Access", Icon: AccessIcon },
|
|
1373
|
+
{ key: "config", label: "Configuration", Icon: ConfigurationIcon },
|
|
1374
|
+
{ key: "settings", label: "Settings", Icon: SettingsIcon }
|
|
1375
|
+
];
|
|
1376
|
+
function derivePlan(infra) {
|
|
1377
|
+
const plan = buildByolResourcePlan({
|
|
1378
|
+
deploymentType: infra.deploymentType,
|
|
1379
|
+
indexerCount: infra.indexerCount,
|
|
1380
|
+
searchHeadCount: infra.searchHeadCount,
|
|
1381
|
+
hostingType: infra.hosting_type,
|
|
1382
|
+
isCloud: Boolean(infra.cloudProviderId),
|
|
1383
|
+
region: infra.region ?? null,
|
|
1384
|
+
indexerRegions: (infra.indexerRegions ?? []).map((r) => r.region),
|
|
1385
|
+
searchHeadRegions: (infra.searchHeadRegions ?? []).map((r) => r.region)
|
|
1386
|
+
});
|
|
1387
|
+
return plan.map((p) => ({
|
|
1388
|
+
id: p.planKey,
|
|
1389
|
+
infrastructureId: infra.id,
|
|
1390
|
+
tier: p.tier,
|
|
1391
|
+
kind: p.kind,
|
|
1392
|
+
name: p.name,
|
|
1393
|
+
role: p.role,
|
|
1394
|
+
region: p.region,
|
|
1395
|
+
status: "not_started",
|
|
1396
|
+
externalRef: null,
|
|
1397
|
+
message: null,
|
|
1398
|
+
planKey: p.planKey,
|
|
1399
|
+
sortOrder: p.sortOrder
|
|
1400
|
+
}));
|
|
1401
|
+
}
|
|
1402
|
+
function readSectionFromUrl() {
|
|
1403
|
+
if (typeof window === "undefined") return null;
|
|
1404
|
+
const s2 = new URLSearchParams(window.location.search).get("section");
|
|
1405
|
+
return SECTIONS.some((x) => x.key === s2) ? s2 : null;
|
|
1406
|
+
}
|
|
1407
|
+
var ByolInfrastructureDetail = ({
|
|
1408
|
+
apiBase,
|
|
1409
|
+
initialInfra,
|
|
1410
|
+
configBase,
|
|
1411
|
+
configLinks,
|
|
1412
|
+
onBack,
|
|
1413
|
+
onEdit,
|
|
1414
|
+
onDeleted,
|
|
1415
|
+
onChanged,
|
|
1416
|
+
reloadSignal = 0
|
|
1417
|
+
}) => {
|
|
1418
|
+
const [infra, setInfra] = useState(initialInfra);
|
|
1419
|
+
const [resources, setResources] = useState([]);
|
|
1420
|
+
const [deployments, setDeployments] = useState([]);
|
|
1421
|
+
const [section, setSection] = useState(readSectionFromUrl() ?? "overview");
|
|
1422
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
1423
|
+
const [loading, setLoading] = useState(true);
|
|
1424
|
+
const [busy, setBusy] = useState(false);
|
|
1425
|
+
const [error, setError] = useState(null);
|
|
1426
|
+
const [planOpen, setPlanOpen] = useState(false);
|
|
1427
|
+
const [plan, setPlan] = useState(null);
|
|
1428
|
+
const [planLoading, setPlanLoading] = useState(false);
|
|
1429
|
+
const [planError, setPlanError] = useState(null);
|
|
1430
|
+
const [applying, setApplying] = useState(false);
|
|
1431
|
+
const [destroyOpen, setDestroyOpen] = useState(false);
|
|
1432
|
+
const [destroyResources, setDestroyResources] = useState(null);
|
|
1433
|
+
const [destroyLoading, setDestroyLoading] = useState(false);
|
|
1434
|
+
const [destroyError, setDestroyError] = useState(null);
|
|
1435
|
+
const [destroying, setDestroying] = useState(false);
|
|
1436
|
+
const { confirm } = useConfirmDialog();
|
|
1437
|
+
const id = initialInfra.id;
|
|
1438
|
+
const initialRef = useRef(initialInfra);
|
|
1439
|
+
initialRef.current = initialInfra;
|
|
1440
|
+
const load = useCallback(async () => {
|
|
1441
|
+
setError(null);
|
|
1442
|
+
try {
|
|
1443
|
+
const [freshInfra, res, deps] = await Promise.all([
|
|
1444
|
+
getInfra(apiBase, id).catch(() => initialRef.current),
|
|
1445
|
+
getResources(apiBase, id).catch(() => []),
|
|
1446
|
+
getDeployments(apiBase, id).catch(() => [])
|
|
1447
|
+
]);
|
|
1448
|
+
setInfra(freshInfra);
|
|
1449
|
+
setResources(res);
|
|
1450
|
+
setDeployments(deps);
|
|
1451
|
+
} catch (e) {
|
|
1452
|
+
setError(e.message);
|
|
1453
|
+
} finally {
|
|
1454
|
+
setLoading(false);
|
|
1455
|
+
}
|
|
1456
|
+
}, [apiBase, id]);
|
|
1457
|
+
useEffect(() => {
|
|
1458
|
+
void load();
|
|
1459
|
+
}, [load, reloadSignal]);
|
|
1460
|
+
useEffect(() => {
|
|
1461
|
+
if (typeof window === "undefined") return;
|
|
1462
|
+
const params = new URLSearchParams(window.location.search);
|
|
1463
|
+
params.set("infra", id);
|
|
1464
|
+
params.set("section", section);
|
|
1465
|
+
window.history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
|
1466
|
+
}, [id, section]);
|
|
1467
|
+
const refresh = useCallback(async () => {
|
|
1468
|
+
await load();
|
|
1469
|
+
onChanged();
|
|
1470
|
+
}, [load, onChanged]);
|
|
1471
|
+
const transient = infra.status === "provisioning" || infra.status === "destroying";
|
|
1472
|
+
useEffect(() => {
|
|
1473
|
+
if (!transient) return;
|
|
1474
|
+
let active = true;
|
|
1475
|
+
const timer = setInterval(() => {
|
|
1476
|
+
if (active) void refresh();
|
|
1477
|
+
}, PROVISIONING_POLL_MS);
|
|
1478
|
+
return () => {
|
|
1479
|
+
active = false;
|
|
1480
|
+
clearInterval(timer);
|
|
1481
|
+
};
|
|
1482
|
+
}, [transient, refresh]);
|
|
1483
|
+
const runAction = useCallback(
|
|
1484
|
+
async (fn) => {
|
|
1485
|
+
setBusy(true);
|
|
1486
|
+
setError(null);
|
|
1487
|
+
try {
|
|
1488
|
+
await fn();
|
|
1489
|
+
await refresh();
|
|
1490
|
+
} catch (e) {
|
|
1491
|
+
setError(e.message);
|
|
1492
|
+
} finally {
|
|
1493
|
+
setBusy(false);
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
[refresh]
|
|
1497
|
+
);
|
|
1498
|
+
const openPlan = useCallback(() => {
|
|
1499
|
+
setPlanOpen(true);
|
|
1500
|
+
setPlan(null);
|
|
1501
|
+
setPlanError(null);
|
|
1502
|
+
setPlanLoading(true);
|
|
1503
|
+
getPlan(apiBase, id).then((p) => setPlan(p)).catch((e) => setPlanError(e.message)).finally(() => setPlanLoading(false));
|
|
1504
|
+
}, [apiBase, id]);
|
|
1505
|
+
const closePlan = useCallback(() => {
|
|
1506
|
+
if (applying) return;
|
|
1507
|
+
setPlanOpen(false);
|
|
1508
|
+
}, [applying]);
|
|
1509
|
+
const onApply = useCallback(async () => {
|
|
1510
|
+
setApplying(true);
|
|
1511
|
+
setPlanError(null);
|
|
1512
|
+
try {
|
|
1513
|
+
await deployInfra(apiBase, id);
|
|
1514
|
+
setPlanOpen(false);
|
|
1515
|
+
setSection("activity");
|
|
1516
|
+
await refresh();
|
|
1517
|
+
} catch (e) {
|
|
1518
|
+
setPlanError(e.message);
|
|
1519
|
+
} finally {
|
|
1520
|
+
setApplying(false);
|
|
1521
|
+
}
|
|
1522
|
+
}, [apiBase, id, refresh]);
|
|
1523
|
+
const openDestroy = useCallback(() => {
|
|
1524
|
+
setDestroyOpen(true);
|
|
1525
|
+
setDestroyError(null);
|
|
1526
|
+
setDestroyResources(null);
|
|
1527
|
+
setDestroyLoading(true);
|
|
1528
|
+
getResources(apiBase, id).then((res) => setDestroyResources(res)).catch(() => setDestroyResources(resources)).finally(() => setDestroyLoading(false));
|
|
1529
|
+
}, [apiBase, id, resources]);
|
|
1530
|
+
const closeDestroy = useCallback(() => {
|
|
1531
|
+
if (destroying) return;
|
|
1532
|
+
setDestroyOpen(false);
|
|
1533
|
+
}, [destroying]);
|
|
1534
|
+
const confirmDestroy = useCallback(async () => {
|
|
1535
|
+
setDestroying(true);
|
|
1536
|
+
setDestroyError(null);
|
|
1537
|
+
try {
|
|
1538
|
+
await destroyInfra(apiBase, id);
|
|
1539
|
+
setDestroyOpen(false);
|
|
1540
|
+
setSection("activity");
|
|
1541
|
+
await refresh();
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
setDestroyError(e.message);
|
|
1544
|
+
} finally {
|
|
1545
|
+
setDestroying(false);
|
|
1546
|
+
}
|
|
1547
|
+
}, [apiBase, id, refresh]);
|
|
1548
|
+
const onLifecycle = (action) => runAction(() => lifecycleInfra(apiBase, id, action));
|
|
1549
|
+
const onDelete = async () => {
|
|
1550
|
+
const ok = await confirm({
|
|
1551
|
+
title: "Delete infrastructure record",
|
|
1552
|
+
message: `Delete "${infra.name}"? This cannot be undone.`,
|
|
1553
|
+
confirmText: "Delete",
|
|
1554
|
+
cancelText: "Cancel",
|
|
1555
|
+
variant: "danger"
|
|
1556
|
+
});
|
|
1557
|
+
if (!ok) return;
|
|
1558
|
+
setBusy(true);
|
|
1559
|
+
setError(null);
|
|
1560
|
+
try {
|
|
1561
|
+
const res = await import("../client/index.js").then(({ authFetch: authFetch2 }) => authFetch2(`${apiBase}/${id}`, { method: "DELETE" }));
|
|
1562
|
+
if (!res.ok && res.status !== 204) throw new Error(await errorText(res));
|
|
1563
|
+
onDeleted();
|
|
1564
|
+
} catch (e) {
|
|
1565
|
+
setError(e.message);
|
|
1566
|
+
setBusy(false);
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
const status = infra.status;
|
|
1570
|
+
const notStarted = isNotStarted(status);
|
|
1571
|
+
const failed = status === "failed" || status === "error";
|
|
1572
|
+
const provisioning = status === "provisioning" || status === "destroying";
|
|
1573
|
+
const running = isRunning(status);
|
|
1574
|
+
const deprovisioned = status === "deprovisioned" || status === "decommissioned";
|
|
1575
|
+
const latestRunAction = deployments[0]?.action;
|
|
1576
|
+
const failedDestroy = failed && latestRunAction === "destroy";
|
|
1577
|
+
const derived = resources.length === 0;
|
|
1578
|
+
const displayResources = useMemo2(
|
|
1579
|
+
() => derived ? derivePlan(infra) : resources,
|
|
1580
|
+
[derived, infra, resources]
|
|
1581
|
+
);
|
|
1582
|
+
const provider = infra.hosting_type || (infra.cloudProviderId ? "Cloud" : SELF_HOSTED_LABEL);
|
|
1583
|
+
const primaryAction = (() => {
|
|
1584
|
+
if (notStarted) return /* @__PURE__ */ jsx12(Button, { variant: "primary", size: "sm", onClick: openPlan, disabled: busy, children: "Deploy environment" });
|
|
1585
|
+
if (deprovisioned) return /* @__PURE__ */ jsx12(Button, { variant: "primary", size: "sm", onClick: openPlan, disabled: busy, children: "Re-provision" });
|
|
1586
|
+
if (failedDestroy) return /* @__PURE__ */ jsx12(Button, { variant: "danger", size: "sm", onClick: openDestroy, disabled: busy, children: "Retry Destroy" });
|
|
1587
|
+
if (failed) return /* @__PURE__ */ jsx12(Button, { variant: "primary", size: "sm", onClick: openPlan, disabled: busy, children: "Retry deployment" });
|
|
1588
|
+
if (provisioning) return /* @__PURE__ */ jsx12(Button, { variant: "primary", size: "sm", onClick: () => setSection("activity"), children: "View progress" });
|
|
1589
|
+
return null;
|
|
1590
|
+
})();
|
|
1591
|
+
const content = (() => {
|
|
1592
|
+
switch (section) {
|
|
1593
|
+
case "overview":
|
|
1594
|
+
return /* @__PURE__ */ jsx12(OverviewTab, { infra, resources: displayResources });
|
|
1595
|
+
case "resources":
|
|
1596
|
+
return /* @__PURE__ */ jsx12(ResourcesTab, { resources: displayResources, derived });
|
|
1597
|
+
case "activity":
|
|
1598
|
+
return /* @__PURE__ */ jsx12(ActivityTab, { deployments });
|
|
1599
|
+
case "access":
|
|
1600
|
+
return /* @__PURE__ */ jsx12(AccessTab, { infra, resources });
|
|
1601
|
+
case "config":
|
|
1602
|
+
return /* @__PURE__ */ jsx12(ConfigurationTab, { links: configLinks ?? [], configBase });
|
|
1603
|
+
case "settings":
|
|
1604
|
+
return /* @__PURE__ */ jsx12(
|
|
1605
|
+
SettingsTab,
|
|
1606
|
+
{
|
|
1607
|
+
infra,
|
|
1608
|
+
busy,
|
|
1609
|
+
onEdit: () => onEdit(infra),
|
|
1610
|
+
onDestroy: openDestroy,
|
|
1611
|
+
onDelete
|
|
1612
|
+
}
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
})();
|
|
1616
|
+
return /* @__PURE__ */ jsxs12("div", { children: [
|
|
1617
|
+
/* @__PURE__ */ jsx12(
|
|
1618
|
+
"button",
|
|
1619
|
+
{
|
|
1620
|
+
onClick: onBack,
|
|
1621
|
+
style: {
|
|
1622
|
+
display: "inline-flex",
|
|
1623
|
+
alignItems: "center",
|
|
1624
|
+
gap: 7,
|
|
1625
|
+
color: tokens.muted,
|
|
1626
|
+
fontSize: 13,
|
|
1627
|
+
background: "none",
|
|
1628
|
+
border: "none",
|
|
1629
|
+
cursor: "pointer",
|
|
1630
|
+
padding: 0,
|
|
1631
|
+
marginBottom: 12
|
|
1632
|
+
},
|
|
1633
|
+
children: "\u2039 Back to infrastructure"
|
|
1634
|
+
}
|
|
1635
|
+
),
|
|
1636
|
+
/* @__PURE__ */ jsxs12("div", { style: { border: `1px solid ${tokens.border}`, borderRadius: 12, background: tokens.surface, overflow: "hidden" }, children: [
|
|
1637
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexWrap: "wrap", gap: "18px 24px", alignItems: "flex-start", justifyContent: "space-between", padding: 20 }, children: [
|
|
1638
|
+
/* @__PURE__ */ jsxs12("div", { style: { minWidth: 260, flex: 1 }, children: [
|
|
1639
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
|
|
1640
|
+
/* @__PURE__ */ jsx12("h2", { style: { margin: 0, fontSize: 21, letterSpacing: "-.01em", color: tokens.text }, children: infra.name }),
|
|
1641
|
+
/* @__PURE__ */ jsx12(StatusPill, { status }),
|
|
1642
|
+
loading ? /* @__PURE__ */ jsx12(Spinner, { size: "sm" }) : null
|
|
1643
|
+
] }),
|
|
1644
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexWrap: "wrap", gap: "12px 20px", marginTop: 12 }, children: [
|
|
1645
|
+
/* @__PURE__ */ jsx12(Meta, { label: "Environment", children: infra.environmentType || "\u2014" }),
|
|
1646
|
+
/* @__PURE__ */ jsx12(Meta, { label: "Deployment", children: infra.deploymentType ?? "\u2014" }),
|
|
1647
|
+
/* @__PURE__ */ jsx12(Meta, { label: "Provider", children: provider }),
|
|
1648
|
+
/* @__PURE__ */ jsx12(Meta, { label: "Region", children: infra.region || "\u2014" }),
|
|
1649
|
+
/* @__PURE__ */ jsx12(Meta, { label: "Updated", children: formatDate(infra.updatedAt) })
|
|
1650
|
+
] }),
|
|
1651
|
+
/* @__PURE__ */ jsx12("div", { style: { marginTop: 14 }, children: /* @__PURE__ */ jsx12(ProgressMeter, { resources: displayResources }) })
|
|
1652
|
+
] }),
|
|
1653
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 10 }, children: [
|
|
1654
|
+
primaryAction,
|
|
1655
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }, children: [
|
|
1656
|
+
running ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
|
|
1657
|
+
/* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", onClick: () => onLifecycle("stop"), disabled: busy, children: "Stop" }),
|
|
1658
|
+
/* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", onClick: () => onLifecycle("restart"), disabled: busy, children: "Restart" })
|
|
1659
|
+
] }) : null,
|
|
1660
|
+
/* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", onClick: () => onEdit(infra), disabled: busy, children: "Edit topology" }),
|
|
1661
|
+
/* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", onClick: () => void refresh(), disabled: busy, children: "Refresh" })
|
|
1662
|
+
] })
|
|
1663
|
+
] })
|
|
1664
|
+
] }),
|
|
1665
|
+
error ? /* @__PURE__ */ jsx12("div", { style: { padding: "0 20px 12px", color: tokens.danger, fontSize: 13 }, children: error }) : null,
|
|
1666
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", borderTop: `1px solid ${tokens.border}`, alignItems: "stretch" }, children: [
|
|
1667
|
+
/* @__PURE__ */ jsxs12(
|
|
1668
|
+
"aside",
|
|
1669
|
+
{
|
|
1670
|
+
style: {
|
|
1671
|
+
flex: `0 0 ${collapsed ? 56 : 220}px`,
|
|
1672
|
+
width: collapsed ? 56 : 220,
|
|
1673
|
+
borderRight: `1px solid ${tokens.border}`,
|
|
1674
|
+
background: tokens.surface2,
|
|
1675
|
+
padding: "14px 12px",
|
|
1676
|
+
display: "flex",
|
|
1677
|
+
flexDirection: "column",
|
|
1678
|
+
gap: 2,
|
|
1679
|
+
transition: "flex-basis .12s, width .12s"
|
|
1680
|
+
},
|
|
1681
|
+
children: [
|
|
1682
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", alignItems: "center", justifyContent: collapsed ? "center" : "space-between", padding: "2px 6px 10px" }, children: [
|
|
1683
|
+
collapsed ? null : /* @__PURE__ */ jsx12("span", { style: { fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase", color: tokens.faint, fontWeight: 700 }, children: "This infrastructure" }),
|
|
1684
|
+
/* @__PURE__ */ jsx12(
|
|
1685
|
+
"button",
|
|
1686
|
+
{
|
|
1687
|
+
onClick: () => setCollapsed((c) => !c),
|
|
1688
|
+
title: collapsed ? "Expand" : "Collapse",
|
|
1689
|
+
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1690
|
+
style: {
|
|
1691
|
+
border: `1px solid ${tokens.border}`,
|
|
1692
|
+
background: tokens.surface,
|
|
1693
|
+
color: tokens.muted,
|
|
1694
|
+
borderRadius: 6,
|
|
1695
|
+
width: 24,
|
|
1696
|
+
height: 24,
|
|
1697
|
+
cursor: "pointer",
|
|
1698
|
+
lineHeight: 1
|
|
1699
|
+
},
|
|
1700
|
+
children: collapsed ? "\xBB" : "\xAB"
|
|
1701
|
+
}
|
|
1702
|
+
)
|
|
1703
|
+
] }),
|
|
1704
|
+
SECTIONS.map((s2) => {
|
|
1705
|
+
const active = s2.key === section;
|
|
1706
|
+
const { Icon } = s2;
|
|
1707
|
+
return /* @__PURE__ */ jsxs12(
|
|
1708
|
+
"button",
|
|
1709
|
+
{
|
|
1710
|
+
onClick: () => setSection(s2.key),
|
|
1711
|
+
title: s2.label,
|
|
1712
|
+
style: {
|
|
1713
|
+
position: "relative",
|
|
1714
|
+
display: "flex",
|
|
1715
|
+
alignItems: "center",
|
|
1716
|
+
justifyContent: collapsed ? "center" : "flex-start",
|
|
1717
|
+
gap: 11,
|
|
1718
|
+
padding: collapsed ? "9px 0" : "9px 10px",
|
|
1719
|
+
borderRadius: 8,
|
|
1720
|
+
border: "none",
|
|
1721
|
+
background: active ? tokens.surface : "transparent",
|
|
1722
|
+
color: active ? tokens.primary : tokens.muted,
|
|
1723
|
+
fontWeight: 600,
|
|
1724
|
+
fontSize: 13,
|
|
1725
|
+
cursor: "pointer",
|
|
1726
|
+
textAlign: "left",
|
|
1727
|
+
width: "100%"
|
|
1728
|
+
},
|
|
1729
|
+
children: [
|
|
1730
|
+
active ? /* @__PURE__ */ jsx12("span", { style: { position: "absolute", left: 0, top: 8, bottom: 8, width: 3, borderRadius: "0 3px 3px 0", background: tokens.primary } }) : null,
|
|
1731
|
+
/* @__PURE__ */ jsx12("span", { style: { width: 18, display: "inline-flex", alignItems: "center", justifyContent: "center", flex: "none" }, children: /* @__PURE__ */ jsx12(Icon, { size: 16 }) }),
|
|
1732
|
+
collapsed ? null : /* @__PURE__ */ jsx12("span", { style: { flex: 1 }, children: s2.label }),
|
|
1733
|
+
!collapsed && s2.key === "resources" && displayResources.length > 0 ? /* @__PURE__ */ jsx12("span", { style: { fontSize: 11, fontWeight: 600, color: tokens.faint }, children: displayResources.length }) : null,
|
|
1734
|
+
!collapsed && s2.key === "activity" && provisioning ? /* @__PURE__ */ jsx12("span", { style: { width: 7, height: 7, borderRadius: "50%", background: tokens.info } }) : null
|
|
1735
|
+
]
|
|
1736
|
+
},
|
|
1737
|
+
s2.key
|
|
1738
|
+
);
|
|
1739
|
+
})
|
|
1740
|
+
]
|
|
1741
|
+
}
|
|
1742
|
+
),
|
|
1743
|
+
/* @__PURE__ */ jsx12("div", { style: { flex: 1, minWidth: 0, padding: "22px 24px" }, children: content })
|
|
1744
|
+
] })
|
|
1745
|
+
] }),
|
|
1746
|
+
/* @__PURE__ */ jsx12(
|
|
1747
|
+
ByolPlanModal,
|
|
1748
|
+
{
|
|
1749
|
+
isOpen: planOpen,
|
|
1750
|
+
onClose: closePlan,
|
|
1751
|
+
plan,
|
|
1752
|
+
loading: planLoading,
|
|
1753
|
+
error: planError,
|
|
1754
|
+
applying,
|
|
1755
|
+
onApply,
|
|
1756
|
+
infraName: infra.name,
|
|
1757
|
+
allowApplyWithoutChanges: infra.status === "failed" || infra.status === "error" || infra.status === "not_started"
|
|
1758
|
+
}
|
|
1759
|
+
),
|
|
1760
|
+
/* @__PURE__ */ jsx12(
|
|
1761
|
+
DestroyPlanModal,
|
|
1762
|
+
{
|
|
1763
|
+
isOpen: destroyOpen,
|
|
1764
|
+
onClose: closeDestroy,
|
|
1765
|
+
resources: destroyResources,
|
|
1766
|
+
loading: destroyLoading,
|
|
1767
|
+
error: destroyError,
|
|
1768
|
+
destroying,
|
|
1769
|
+
onConfirm: confirmDestroy,
|
|
1770
|
+
infraName: infra.name
|
|
1771
|
+
}
|
|
1772
|
+
)
|
|
1773
|
+
] });
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
// src/byol/ClusterPlacementField.tsx
|
|
1777
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1778
|
+
var MODE_OPTIONS = [
|
|
1779
|
+
{ value: "single", label: "Single site \u2014 all nodes in the main region" },
|
|
1780
|
+
{ value: "multi-site", label: "Multi-site \u2014 spread nodes by percent" }
|
|
1781
|
+
];
|
|
1782
|
+
var AWS_AZ_LETTERS = ["a", "b", "c", "d", "e", "f"];
|
|
1783
|
+
var GCP_AZ_LETTERS = ["a", "b", "c", "d", "f"];
|
|
1784
|
+
var AZURE_ZONES = ["1", "2", "3"];
|
|
1785
|
+
function azOptionsFor(providerCode, region) {
|
|
1786
|
+
const code = (providerCode || "aws").toLowerCase();
|
|
1787
|
+
const base = region || "region";
|
|
1788
|
+
if (code === "azure") return AZURE_ZONES.map((z) => ({ value: z, label: `Zone ${z}` }));
|
|
1789
|
+
if (code === "gcp") return GCP_AZ_LETTERS.map((l) => ({ value: `${base}-${l}`, label: `${base}-${l}` }));
|
|
1790
|
+
return AWS_AZ_LETTERS.map((l) => ({ value: `${base}${l}`, label: `${base}${l}` }));
|
|
1791
|
+
}
|
|
1792
|
+
function evenPercents(count) {
|
|
1793
|
+
if (count <= 0) return [];
|
|
1794
|
+
const base = Math.floor(100 / count);
|
|
1795
|
+
const remainder = 100 - base * count;
|
|
1796
|
+
return Array.from({ length: count }, (_, i) => base + (i < remainder ? 1 : 0));
|
|
1797
|
+
}
|
|
1798
|
+
function defaultSites(granularity, primaryRegion, providerCode, regionOptions) {
|
|
1799
|
+
const [p0, p1] = evenPercents(2);
|
|
1800
|
+
if (granularity === "az") {
|
|
1801
|
+
const az = azOptionsFor(providerCode, primaryRegion);
|
|
1802
|
+
return [
|
|
1803
|
+
{ site: az[0]?.value ?? `${primaryRegion}a`, percent: p0 },
|
|
1804
|
+
{ site: az[1]?.value ?? `${primaryRegion}b`, percent: p1 }
|
|
1805
|
+
];
|
|
1806
|
+
}
|
|
1807
|
+
const regions = regionOptions.map((o) => o.value).filter(Boolean);
|
|
1808
|
+
const first = primaryRegion || regions[0] || "";
|
|
1809
|
+
const second = regions.find((r) => r !== first) ?? "";
|
|
1810
|
+
return [
|
|
1811
|
+
{ site: first, percent: p0 },
|
|
1812
|
+
{ site: second, percent: p1 }
|
|
1813
|
+
];
|
|
1814
|
+
}
|
|
1815
|
+
var ClusterPlacementField = ({
|
|
1816
|
+
label,
|
|
1817
|
+
placement,
|
|
1818
|
+
nodeCount,
|
|
1819
|
+
primaryRegion,
|
|
1820
|
+
providerCode,
|
|
1821
|
+
regionOptions,
|
|
1822
|
+
onChange
|
|
1823
|
+
}) => {
|
|
1824
|
+
const multi = placement.mode === "multi-site";
|
|
1825
|
+
const granularity = placement.granularity ?? "az";
|
|
1826
|
+
const sites = placement.sites ?? [];
|
|
1827
|
+
const siteOptions = granularity === "az" ? azOptionsFor(providerCode, primaryRegion) : regionOptions;
|
|
1828
|
+
const emit = (next) => onChange({ mode: "multi-site", granularity, sites: next });
|
|
1829
|
+
const setMode = (mode) => {
|
|
1830
|
+
if (mode !== "multi-site") return onChange({ mode: "single" });
|
|
1831
|
+
onChange({
|
|
1832
|
+
mode: "multi-site",
|
|
1833
|
+
granularity,
|
|
1834
|
+
sites: sites.length >= 2 ? sites : defaultSites(granularity, primaryRegion, providerCode, regionOptions)
|
|
1835
|
+
});
|
|
1836
|
+
};
|
|
1837
|
+
const setGranularity = (value) => {
|
|
1838
|
+
const g = value;
|
|
1839
|
+
onChange({ mode: "multi-site", granularity: g, sites: defaultSites(g, primaryRegion, providerCode, regionOptions) });
|
|
1840
|
+
};
|
|
1841
|
+
const updateSite = (i, patch) => emit(sites.map((s2, idx) => idx === i ? { ...s2, ...patch } : s2));
|
|
1842
|
+
const addSite = () => {
|
|
1843
|
+
const used = new Set(sites.map((s2) => s2.site));
|
|
1844
|
+
const nextSite = siteOptions.map((o) => o.value).find((v) => !used.has(v)) ?? "";
|
|
1845
|
+
emit([...sites, { site: nextSite, percent: 0 }]);
|
|
1846
|
+
};
|
|
1847
|
+
const removeSite = (i) => emit(sites.filter((_, idx) => idx !== i));
|
|
1848
|
+
const distributeEvenly = () => {
|
|
1849
|
+
const percents = evenPercents(sites.length);
|
|
1850
|
+
emit(sites.map((s2, i) => ({ ...s2, percent: percents[i] })));
|
|
1851
|
+
};
|
|
1852
|
+
const allocation = multi ? allocateNodesBySite(nodeCount, sites) : [];
|
|
1853
|
+
const validationError = multi ? validatePlacement(placement, nodeCount) : null;
|
|
1854
|
+
return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
1855
|
+
/* @__PURE__ */ jsx13(Select, { label, value: placement.mode, onChange: setMode, options: MODE_OPTIONS }),
|
|
1856
|
+
multi ? /* @__PURE__ */ jsxs13(
|
|
1857
|
+
"div",
|
|
1858
|
+
{
|
|
1859
|
+
style: {
|
|
1860
|
+
display: "flex",
|
|
1861
|
+
flexDirection: "column",
|
|
1862
|
+
gap: 10,
|
|
1863
|
+
padding: 12,
|
|
1864
|
+
border: `1px solid ${tokens.border}`,
|
|
1865
|
+
borderRadius: 8,
|
|
1866
|
+
background: tokens.surface2
|
|
1867
|
+
},
|
|
1868
|
+
children: [
|
|
1869
|
+
/* @__PURE__ */ jsx13(
|
|
1870
|
+
Select,
|
|
1871
|
+
{
|
|
1872
|
+
label: "Spread across",
|
|
1873
|
+
value: granularity,
|
|
1874
|
+
onChange: setGranularity,
|
|
1875
|
+
options: [...PLACEMENT_GRANULARITY_OPTIONS]
|
|
1876
|
+
}
|
|
1877
|
+
),
|
|
1878
|
+
/* @__PURE__ */ jsx13("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: sites.map((site, i) => /* @__PURE__ */ jsxs13("div", { style: { display: "grid", gridTemplateColumns: "1fr 92px auto auto", gap: 8, alignItems: "end" }, children: [
|
|
1879
|
+
/* @__PURE__ */ jsx13(
|
|
1880
|
+
Select,
|
|
1881
|
+
{
|
|
1882
|
+
label: i === 0 ? granularity === "az" ? "Zone" : "Region" : void 0,
|
|
1883
|
+
value: site.site,
|
|
1884
|
+
onChange: (value) => updateSite(i, { site: value }),
|
|
1885
|
+
options: siteOptions,
|
|
1886
|
+
placeholder: "Select\u2026"
|
|
1887
|
+
}
|
|
1888
|
+
),
|
|
1889
|
+
/* @__PURE__ */ jsx13(
|
|
1890
|
+
Input,
|
|
1891
|
+
{
|
|
1892
|
+
label: i === 0 ? "Share %" : void 0,
|
|
1893
|
+
type: "number",
|
|
1894
|
+
min: 0,
|
|
1895
|
+
max: 100,
|
|
1896
|
+
value: String(site.percent),
|
|
1897
|
+
onChange: (e) => updateSite(i, { percent: Number(e.target.value) || 0 }),
|
|
1898
|
+
fullWidth: true
|
|
1899
|
+
}
|
|
1900
|
+
),
|
|
1901
|
+
/* @__PURE__ */ jsxs13("span", { style: { fontSize: 12, color: tokens.muted, paddingBottom: 10, whiteSpace: "nowrap" }, children: [
|
|
1902
|
+
"\u2192 ",
|
|
1903
|
+
allocation[i]?.count ?? 0,
|
|
1904
|
+
" node",
|
|
1905
|
+
(allocation[i]?.count ?? 0) === 1 ? "" : "s"
|
|
1906
|
+
] }),
|
|
1907
|
+
/* @__PURE__ */ jsx13(
|
|
1908
|
+
Button,
|
|
1909
|
+
{
|
|
1910
|
+
variant: "ghost",
|
|
1911
|
+
size: "sm",
|
|
1912
|
+
onClick: () => removeSite(i),
|
|
1913
|
+
disabled: sites.length <= 2,
|
|
1914
|
+
"aria-label": `Remove site ${i + 1}`,
|
|
1915
|
+
style: { marginBottom: 4 },
|
|
1916
|
+
children: "\u2715"
|
|
1917
|
+
}
|
|
1918
|
+
)
|
|
1919
|
+
] }, i)) }),
|
|
1920
|
+
/* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 8 }, children: [
|
|
1921
|
+
/* @__PURE__ */ jsx13(Button, { variant: "secondary", size: "sm", onClick: addSite, disabled: sites.length >= siteOptions.length, children: "Add site" }),
|
|
1922
|
+
/* @__PURE__ */ jsx13(Button, { variant: "ghost", size: "sm", onClick: distributeEvenly, children: "Distribute evenly" })
|
|
1923
|
+
] }),
|
|
1924
|
+
validationError ? /* @__PURE__ */ jsx13("p", { style: { margin: 0, fontSize: 12, color: tokens.danger }, children: validationError }) : /* @__PURE__ */ jsxs13("p", { style: { margin: 0, fontSize: 12, color: tokens.muted }, children: [
|
|
1925
|
+
sites.length,
|
|
1926
|
+
" sites \xB7 ",
|
|
1927
|
+
allocation.reduce((n, a) => n + a.count, 0),
|
|
1928
|
+
" of ",
|
|
1929
|
+
nodeCount,
|
|
1930
|
+
" nodes placed."
|
|
1931
|
+
] })
|
|
1932
|
+
]
|
|
1933
|
+
}
|
|
1934
|
+
) : null
|
|
1935
|
+
] });
|
|
1936
|
+
};
|
|
1937
|
+
|
|
1938
|
+
// src/byol/ByolInfrastructureManager.tsx
|
|
1939
|
+
import { Fragment as Fragment7, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1940
|
+
var ByolInfrastructureManager = ({
|
|
1941
|
+
apiBase,
|
|
1942
|
+
title = "BYOL Infrastructure",
|
|
1943
|
+
deploymentTypes = DEFAULT_DEPLOYMENT_TYPES,
|
|
1944
|
+
configBase,
|
|
1945
|
+
configLinks,
|
|
1946
|
+
versionOptions = [],
|
|
1947
|
+
defaultVersionId
|
|
1948
|
+
}) => {
|
|
1949
|
+
const [infrastructure, setInfrastructure] = useState2([]);
|
|
1950
|
+
const [tags, setTags] = useState2([]);
|
|
1951
|
+
const [cloudProviders, setCloudProviders] = useState2([]);
|
|
1952
|
+
const [regions, setRegions] = useState2([]);
|
|
1953
|
+
const [cloudAccounts, setCloudAccounts] = useState2([]);
|
|
1954
|
+
const [error, setError] = useState2(null);
|
|
1955
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
1956
|
+
const [selectedId, setSelectedId] = useState2(() => {
|
|
1957
|
+
if (typeof window === "undefined") return null;
|
|
1958
|
+
return new URLSearchParams(window.location.search).get("infra");
|
|
1959
|
+
});
|
|
1960
|
+
const [reloadSignal, setReloadSignal] = useState2(0);
|
|
1961
|
+
const [search, setSearch] = useState2("");
|
|
1962
|
+
const [environmentFilter, setEnvironmentFilter] = useState2(null);
|
|
1963
|
+
const [deploymentFilter, setDeploymentFilter] = useState2(null);
|
|
1964
|
+
const [statusFilter, setStatusFilter] = useState2(null);
|
|
1965
|
+
const [sortField, setSortField] = useState2("name");
|
|
1966
|
+
const [sortDir, setSortDir] = useState2("asc");
|
|
1967
|
+
const [page, setPage] = useState2(1);
|
|
1968
|
+
const [pageSize, setPageSize] = useState2(10);
|
|
1969
|
+
const [dialogOpen, setDialogOpen] = useState2(false);
|
|
1970
|
+
const [editing, setEditing] = useState2(null);
|
|
1971
|
+
const [form, setForm] = useState2(BLANK_FORM);
|
|
1972
|
+
const [submitting, setSubmitting] = useState2(false);
|
|
1973
|
+
const [formError, setFormError] = useState2(null);
|
|
1974
|
+
const load = useCallback2(() => {
|
|
1975
|
+
setIsLoading(true);
|
|
1976
|
+
setError(null);
|
|
1977
|
+
return authFetch(apiBase).then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`))).then((data) => setInfrastructure(Array.isArray(data) ? data : [])).catch((e) => setError(e.message)).finally(() => setIsLoading(false));
|
|
1978
|
+
}, [apiBase]);
|
|
1979
|
+
useEffect2(() => {
|
|
1980
|
+
void load();
|
|
1981
|
+
}, [load]);
|
|
1982
|
+
useEffect2(() => {
|
|
1983
|
+
authFetch("/api/tags").then((res) => res.ok ? res.json() : []).then((data) => setTags(Array.isArray(data) ? data : [])).catch(() => setTags([]));
|
|
1984
|
+
authFetch("/api/cloud-providers").then((res) => res.ok ? res.json() : []).then(
|
|
1985
|
+
(data) => setCloudProviders(Array.isArray(data) ? data.filter((c) => c.isActive !== false) : [])
|
|
1986
|
+
).catch(() => setCloudProviders([]));
|
|
1987
|
+
authFetch("/api/cloud-accounts").then((res) => res.ok ? res.json() : []).then((data) => setCloudAccounts(Array.isArray(data) ? data : [])).catch(() => setCloudAccounts([]));
|
|
1988
|
+
}, []);
|
|
1989
|
+
const isCloud = form.providerId !== "" && form.providerId !== SELF_HOSTED;
|
|
1990
|
+
const showRegion = isCloud && form.deploymentType === "distributed";
|
|
1991
|
+
const isByoc = BYOC_NETWORK_MODES.has(form.networkMode);
|
|
1992
|
+
const showCloudAccount = isCloud;
|
|
1993
|
+
const cloudAccountRequired = isByoc && isCloud;
|
|
1994
|
+
useEffect2(() => {
|
|
1995
|
+
if (!isCloud) {
|
|
1996
|
+
setRegions([]);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
let cancelled = false;
|
|
2000
|
+
authFetch(`/api/cloud-providers/${form.providerId}/regions`).then((res) => res.ok ? res.json() : []).then((data) => {
|
|
2001
|
+
if (!cancelled) setRegions(Array.isArray(data) ? data.filter((r) => r.isActive !== false) : []);
|
|
2002
|
+
}).catch(() => {
|
|
2003
|
+
if (!cancelled) setRegions([]);
|
|
2004
|
+
});
|
|
2005
|
+
return () => {
|
|
2006
|
+
cancelled = true;
|
|
2007
|
+
};
|
|
2008
|
+
}, [isCloud, form.providerId]);
|
|
2009
|
+
const openCreate = () => {
|
|
2010
|
+
setEditing(null);
|
|
2011
|
+
setForm({ ...BLANK_FORM, versionId: defaultVersionId ?? "" });
|
|
2012
|
+
setFormError(null);
|
|
2013
|
+
setDialogOpen(true);
|
|
2014
|
+
};
|
|
2015
|
+
const openEdit = (row) => {
|
|
2016
|
+
setEditing(row);
|
|
2017
|
+
setForm(editFormState(row));
|
|
2018
|
+
setFormError(null);
|
|
2019
|
+
setDialogOpen(true);
|
|
2020
|
+
};
|
|
2021
|
+
const closeDialog = useCallback2(() => {
|
|
2022
|
+
if (submitting) return;
|
|
2023
|
+
setDialogOpen(false);
|
|
2024
|
+
}, [submitting]);
|
|
2025
|
+
const setField = (key, value) => setForm((prev) => ({ ...prev, [key]: value }));
|
|
2026
|
+
const handleProviderChange = (value) => setForm((prev) => ({ ...prev, providerId: value, cloudAccountConnectionId: "" }));
|
|
2027
|
+
const handleSubmit = async () => {
|
|
2028
|
+
if (!form.name.trim()) {
|
|
2029
|
+
setFormError("Name is required");
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const indexerCount = Number(form.indexerCount);
|
|
2033
|
+
const searchHeadCount = Number(form.searchHeadCount);
|
|
2034
|
+
if (form.deploymentType === "distributed") {
|
|
2035
|
+
if (indexerCount < 3) {
|
|
2036
|
+
setFormError("Distributed deployments require at least 3 indexers");
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
if (searchHeadCount < 2) {
|
|
2040
|
+
setFormError("Distributed deployments require at least 2 search heads");
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
if (showRegion) {
|
|
2044
|
+
const indexerErr = validatePlacement(form.indexerPlacement, indexerCount);
|
|
2045
|
+
if (indexerErr) {
|
|
2046
|
+
setFormError(`Indexer placement: ${indexerErr}`);
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
const searchErr = validatePlacement(form.searchHeadPlacement, searchHeadCount);
|
|
2050
|
+
if (searchErr) {
|
|
2051
|
+
setFormError(`Search head placement: ${searchErr}`);
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
if (cloudAccountRequired && !form.cloudAccountConnectionId) {
|
|
2057
|
+
setFormError("Select a verified cloud account for a BYOC deployment target");
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
const selfHosted = form.providerId === SELF_HOSTED;
|
|
2061
|
+
const selectedCloud = cloudProviders.find((c) => c.id === form.providerId);
|
|
2062
|
+
const distributed = form.deploymentType === "distributed";
|
|
2063
|
+
const normalizePlacement = (p) => distributed && showRegion && p?.mode === "multi-site" ? p : { mode: "single" };
|
|
2064
|
+
setSubmitting(true);
|
|
2065
|
+
setFormError(null);
|
|
2066
|
+
const payload = {
|
|
2067
|
+
name: form.name.trim(),
|
|
2068
|
+
deploymentType: form.deploymentType,
|
|
2069
|
+
environmentType: form.environmentType,
|
|
2070
|
+
hosting_type: selfHosted ? SELF_HOSTED_LABEL : selectedCloud?.name ?? "",
|
|
2071
|
+
cloudProviderId: selfHosted ? void 0 : form.providerId || void 0,
|
|
2072
|
+
region: showRegion ? form.region : "",
|
|
2073
|
+
indexerCount,
|
|
2074
|
+
searchHeadCount,
|
|
2075
|
+
networkMode: form.networkMode,
|
|
2076
|
+
dnsMode: form.dnsMode,
|
|
2077
|
+
cloudAccountConnectionId: showCloudAccount ? form.cloudAccountConnectionId : void 0,
|
|
2078
|
+
// Topology authoring — only meaningful for distributed deployments.
|
|
2079
|
+
controlPlaneLayout: distributed ? form.controlPlaneLayout : "dedicated",
|
|
2080
|
+
heavyForwarderCount: distributed ? Math.max(1, Number(form.heavyForwarderCount) || 1) : 1,
|
|
2081
|
+
// Compute size override; empty → the cloud default (t2.medium-class). Only
|
|
2082
|
+
// meaningful for a cloud deployment.
|
|
2083
|
+
instanceType: !selfHosted ? form.instanceType.trim() || void 0 : void 0,
|
|
2084
|
+
indexerPlacement: normalizePlacement(form.indexerPlacement),
|
|
2085
|
+
searchHeadPlacement: normalizePlacement(form.searchHeadPlacement),
|
|
2086
|
+
versionId: form.versionId || void 0
|
|
2087
|
+
};
|
|
2088
|
+
try {
|
|
2089
|
+
const res = await authFetch(editing ? `${apiBase}/${editing.id}` : apiBase, {
|
|
2090
|
+
method: editing ? "PUT" : "POST",
|
|
2091
|
+
headers: { "Content-Type": "application/json" },
|
|
2092
|
+
body: JSON.stringify(payload)
|
|
2093
|
+
});
|
|
2094
|
+
if (!res.ok) throw new Error(await errorText(res));
|
|
2095
|
+
setDialogOpen(false);
|
|
2096
|
+
await load();
|
|
2097
|
+
if (editing) setReloadSignal((n) => n + 1);
|
|
2098
|
+
} catch (e) {
|
|
2099
|
+
setFormError(e.message);
|
|
2100
|
+
} finally {
|
|
2101
|
+
setSubmitting(false);
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
const openDetail = (id) => {
|
|
2105
|
+
setSelectedId(id);
|
|
2106
|
+
if (typeof window !== "undefined" && id === null) {
|
|
2107
|
+
const params = new URLSearchParams(window.location.search);
|
|
2108
|
+
params.delete("infra");
|
|
2109
|
+
params.delete("section");
|
|
2110
|
+
const qs = params.toString();
|
|
2111
|
+
window.history.replaceState(null, "", qs ? `${window.location.pathname}?${qs}` : window.location.pathname);
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
const columns = [
|
|
2115
|
+
{ key: "name", header: "Name", render: (row) => /* @__PURE__ */ jsx14("strong", { children: row.name }) },
|
|
2116
|
+
{ key: "deploymentType", header: "Deployment", render: (row) => row.deploymentType ?? "\u2014" },
|
|
2117
|
+
{ key: "environmentType", header: "Environment", render: (row) => row.environmentType ?? "\u2014" },
|
|
2118
|
+
{ key: "hosting_type", header: "Provider", render: (row) => row.hosting_type ?? "\u2014" },
|
|
2119
|
+
{ key: "region", header: "Region", render: (row) => row.region || "\u2014" },
|
|
2120
|
+
{ key: "indexerCount", header: "Indexers", align: "right", render: (row) => row.indexerCount ?? "\u2014" },
|
|
2121
|
+
{ key: "searchHeadCount", header: "Search heads", align: "right", render: (row) => row.searchHeadCount ?? "\u2014" },
|
|
2122
|
+
{ key: "status", header: "Status", render: (row) => /* @__PURE__ */ jsx14(StatusPill, { status: row.status }) },
|
|
2123
|
+
{ key: "updatedAt", header: "Updated", render: (row) => formatDate(row.updatedAt) },
|
|
2124
|
+
{ key: "chevron", header: "", align: "right", width: "32px", render: () => /* @__PURE__ */ jsx14("span", { "aria-hidden": true, style: { color: tokens.faint }, children: "\u203A" }) }
|
|
2125
|
+
];
|
|
2126
|
+
const environmentOptions = useMemo3(() => tags.map((t) => ({ value: t.name, label: t.name })), [tags]);
|
|
2127
|
+
const providerOptions = useMemo3(
|
|
2128
|
+
() => [
|
|
2129
|
+
{ value: SELF_HOSTED, label: SELF_HOSTED_LABEL },
|
|
2130
|
+
...cloudProviders.map((c) => ({ value: c.id, label: c.name }))
|
|
2131
|
+
],
|
|
2132
|
+
[cloudProviders]
|
|
2133
|
+
);
|
|
2134
|
+
const regionOptions = useMemo3(
|
|
2135
|
+
() => regions.map((r) => ({ value: r.code, label: `${r.name} (${r.code})` })),
|
|
2136
|
+
[regions]
|
|
2137
|
+
);
|
|
2138
|
+
const selectedProvider = useMemo3(
|
|
2139
|
+
() => cloudProviders.find((c) => c.id === form.providerId),
|
|
2140
|
+
[cloudProviders, form.providerId]
|
|
2141
|
+
);
|
|
2142
|
+
const verifiedCloudAccounts = useMemo3(
|
|
2143
|
+
() => cloudAccounts.filter(
|
|
2144
|
+
(a) => a.status === "VERIFIED" && (!selectedProvider?.code || a.provider === selectedProvider.code)
|
|
2145
|
+
),
|
|
2146
|
+
[cloudAccounts, selectedProvider]
|
|
2147
|
+
);
|
|
2148
|
+
const cloudAccountOptions = useMemo3(
|
|
2149
|
+
() => verifiedCloudAccounts.map((a) => ({
|
|
2150
|
+
value: a.id,
|
|
2151
|
+
label: `${a.name} (${a.provider}${a.scope === "platform" ? " \xB7 Veltrix-managed" : ""})`
|
|
2152
|
+
})),
|
|
2153
|
+
[verifiedCloudAccounts]
|
|
2154
|
+
);
|
|
2155
|
+
const statusFilterOptions = useMemo3(() => {
|
|
2156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2157
|
+
infrastructure.forEach((i) => seen.add(i.status ?? "unknown"));
|
|
2158
|
+
return Array.from(seen, (value) => ({ value, label: value }));
|
|
2159
|
+
}, [infrastructure]);
|
|
2160
|
+
const sortOptions = [
|
|
2161
|
+
{ value: "name", label: "Name" },
|
|
2162
|
+
{ value: "indexerCount", label: "Indexers" },
|
|
2163
|
+
{ value: "searchHeadCount", label: "Search heads" },
|
|
2164
|
+
{ value: "status", label: "Status" },
|
|
2165
|
+
{ value: "updatedAt", label: "Updated" }
|
|
2166
|
+
];
|
|
2167
|
+
const filters = [
|
|
2168
|
+
{ key: "environment", label: "Environment", options: environmentOptions, value: environmentFilter, onChange: setEnvironmentFilter, alwaysVisible: true },
|
|
2169
|
+
{ key: "deployment", label: "Deployment", options: deploymentTypes, value: deploymentFilter, onChange: setDeploymentFilter },
|
|
2170
|
+
{ key: "status", label: "Status", options: statusFilterOptions, value: statusFilter, onChange: setStatusFilter }
|
|
2171
|
+
];
|
|
2172
|
+
const filteredSorted = useMemo3(() => {
|
|
2173
|
+
const term = search.trim().toLowerCase();
|
|
2174
|
+
const rows = infrastructure.filter((row) => {
|
|
2175
|
+
if (term) {
|
|
2176
|
+
const haystack = `${row.name ?? ""} ${row.hosting_type ?? ""} ${row.region ?? ""}`.toLowerCase();
|
|
2177
|
+
if (!haystack.includes(term)) return false;
|
|
2178
|
+
}
|
|
2179
|
+
if (environmentFilter && row.environmentType !== environmentFilter) return false;
|
|
2180
|
+
if (deploymentFilter && row.deploymentType !== deploymentFilter) return false;
|
|
2181
|
+
if (statusFilter && (row.status ?? "unknown") !== statusFilter) return false;
|
|
2182
|
+
return true;
|
|
2183
|
+
});
|
|
2184
|
+
const dir = sortDir === "asc" ? 1 : -1;
|
|
2185
|
+
return [...rows].sort((a, b) => {
|
|
2186
|
+
switch (sortField) {
|
|
2187
|
+
case "indexerCount":
|
|
2188
|
+
return ((a.indexerCount ?? 0) - (b.indexerCount ?? 0)) * dir;
|
|
2189
|
+
case "searchHeadCount":
|
|
2190
|
+
return ((a.searchHeadCount ?? 0) - (b.searchHeadCount ?? 0)) * dir;
|
|
2191
|
+
case "status":
|
|
2192
|
+
return (a.status ?? "").localeCompare(b.status ?? "") * dir;
|
|
2193
|
+
case "updatedAt":
|
|
2194
|
+
return (new Date(a.updatedAt ?? 0).getTime() - new Date(b.updatedAt ?? 0).getTime()) * dir;
|
|
2195
|
+
case "name":
|
|
2196
|
+
default:
|
|
2197
|
+
return (a.name ?? "").localeCompare(b.name ?? "") * dir;
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
}, [infrastructure, search, environmentFilter, deploymentFilter, statusFilter, sortField, sortDir]);
|
|
2201
|
+
const pageRows = useMemo3(
|
|
2202
|
+
() => filteredSorted.slice((page - 1) * pageSize, page * pageSize),
|
|
2203
|
+
[filteredSorted, page, pageSize]
|
|
2204
|
+
);
|
|
2205
|
+
useEffect2(() => {
|
|
2206
|
+
setPage(1);
|
|
2207
|
+
}, [search, environmentFilter, deploymentFilter, statusFilter, sortField, sortDir]);
|
|
2208
|
+
const selected = selectedId ? infrastructure.find((i) => i.id === selectedId) ?? null : null;
|
|
2209
|
+
if (selectedId && selected) {
|
|
2210
|
+
return /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
2211
|
+
/* @__PURE__ */ jsx14(
|
|
2212
|
+
ByolInfrastructureDetail,
|
|
2213
|
+
{
|
|
2214
|
+
apiBase,
|
|
2215
|
+
initialInfra: selected,
|
|
2216
|
+
configBase,
|
|
2217
|
+
configLinks,
|
|
2218
|
+
reloadSignal,
|
|
2219
|
+
onBack: () => openDetail(null),
|
|
2220
|
+
onEdit: openEdit,
|
|
2221
|
+
onDeleted: () => {
|
|
2222
|
+
openDetail(null);
|
|
2223
|
+
void load();
|
|
2224
|
+
},
|
|
2225
|
+
onChanged: () => void load()
|
|
2226
|
+
}
|
|
2227
|
+
),
|
|
2228
|
+
/* @__PURE__ */ jsx14(
|
|
2229
|
+
FormDialog,
|
|
2230
|
+
{
|
|
2231
|
+
isOpen: dialogOpen,
|
|
2232
|
+
onClose: closeDialog,
|
|
2233
|
+
title: editing ? `Edit "${editing.name}"` : "New BYOL infrastructure",
|
|
2234
|
+
description: "Define the deployment topology for a Bring-Your-Own-License Splunk environment.",
|
|
2235
|
+
onSubmit: handleSubmit,
|
|
2236
|
+
submitText: editing ? "Save changes" : "Create infrastructure",
|
|
2237
|
+
isSubmitting: submitting,
|
|
2238
|
+
submitDisabled: !form.name.trim() || cloudAccountRequired && !form.cloudAccountConnectionId,
|
|
2239
|
+
error: formError,
|
|
2240
|
+
size: "md",
|
|
2241
|
+
children: /* @__PURE__ */ jsx14(
|
|
2242
|
+
FormBody,
|
|
2243
|
+
{
|
|
2244
|
+
form,
|
|
2245
|
+
setField,
|
|
2246
|
+
onProviderChange: handleProviderChange,
|
|
2247
|
+
deploymentTypes,
|
|
2248
|
+
environmentOptions,
|
|
2249
|
+
providerOptions,
|
|
2250
|
+
regionOptions,
|
|
2251
|
+
showRegion,
|
|
2252
|
+
showCloudAccount,
|
|
2253
|
+
cloudAccountRequired,
|
|
2254
|
+
cloudAccountOptions,
|
|
2255
|
+
selectedProviderName: selectedProvider?.name,
|
|
2256
|
+
providerCode: selectedProvider?.code,
|
|
2257
|
+
versionOptions
|
|
2258
|
+
}
|
|
2259
|
+
)
|
|
2260
|
+
}
|
|
2261
|
+
)
|
|
2262
|
+
] });
|
|
2263
|
+
}
|
|
2264
|
+
return /* @__PURE__ */ jsxs14(Card, { variant: "bordered", children: [
|
|
2265
|
+
/* @__PURE__ */ jsx14(
|
|
2266
|
+
CardHeader,
|
|
2267
|
+
{
|
|
2268
|
+
actions: /* @__PURE__ */ jsxs14("div", { style: { display: "flex", gap: 8 }, children: [
|
|
2269
|
+
/* @__PURE__ */ jsx14(Button, { variant: "secondary", size: "sm", onClick: () => void load(), isLoading, children: "Refresh" }),
|
|
2270
|
+
/* @__PURE__ */ jsx14(Button, { variant: "primary", size: "sm", onClick: openCreate, children: "New infrastructure" })
|
|
2271
|
+
] }),
|
|
2272
|
+
children: /* @__PURE__ */ jsx14("h2", { style: { margin: 0, fontSize: 16, fontWeight: 600 }, children: title })
|
|
2273
|
+
}
|
|
2274
|
+
),
|
|
2275
|
+
/* @__PURE__ */ jsxs14(CardBody, { children: [
|
|
2276
|
+
error ? /* @__PURE__ */ jsxs14("p", { style: { color: tokens.danger }, children: [
|
|
2277
|
+
"Failed to load BYOL infrastructure: ",
|
|
2278
|
+
error
|
|
2279
|
+
] }) : null,
|
|
2280
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", justifyContent: "space-between", marginBottom: 12 }, children: [
|
|
2281
|
+
/* @__PURE__ */ jsx14(
|
|
2282
|
+
FilterBar,
|
|
2283
|
+
{
|
|
2284
|
+
search: { value: search, onChange: setSearch, placeholder: "Search infrastructure\u2026" },
|
|
2285
|
+
filters
|
|
2286
|
+
}
|
|
2287
|
+
),
|
|
2288
|
+
/* @__PURE__ */ jsx14(
|
|
2289
|
+
SortSelect,
|
|
2290
|
+
{
|
|
2291
|
+
options: sortOptions,
|
|
2292
|
+
value: sortField,
|
|
2293
|
+
direction: sortDir,
|
|
2294
|
+
onChange: (field, dir) => {
|
|
2295
|
+
setSortField(field);
|
|
2296
|
+
setSortDir(dir);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
)
|
|
2300
|
+
] }),
|
|
2301
|
+
/* @__PURE__ */ jsx14(
|
|
2302
|
+
DataTable,
|
|
2303
|
+
{
|
|
2304
|
+
columns,
|
|
2305
|
+
data: pageRows,
|
|
2306
|
+
rowKey: (row) => row.id,
|
|
2307
|
+
isLoading,
|
|
2308
|
+
onRowClick: (row) => openDetail(row.id),
|
|
2309
|
+
emptyState: {
|
|
2310
|
+
title: "No BYOL infrastructure yet",
|
|
2311
|
+
description: "Create a BYOL deployment to manage its topology, resources and lifecycle here."
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
),
|
|
2315
|
+
/* @__PURE__ */ jsx14("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx14(
|
|
2316
|
+
Pagination,
|
|
2317
|
+
{
|
|
2318
|
+
page,
|
|
2319
|
+
pageSize,
|
|
2320
|
+
totalItems: filteredSorted.length,
|
|
2321
|
+
onPageChange: setPage,
|
|
2322
|
+
onPageSizeChange: setPageSize,
|
|
2323
|
+
pageSizeOptions: [10, 25, 50]
|
|
2324
|
+
}
|
|
2325
|
+
) })
|
|
2326
|
+
] }),
|
|
2327
|
+
/* @__PURE__ */ jsx14(
|
|
2328
|
+
FormDialog,
|
|
2329
|
+
{
|
|
2330
|
+
isOpen: dialogOpen,
|
|
2331
|
+
onClose: closeDialog,
|
|
2332
|
+
title: editing ? `Edit "${editing.name}"` : "New BYOL infrastructure",
|
|
2333
|
+
description: "Define the deployment topology for a Bring-Your-Own-License Splunk environment.",
|
|
2334
|
+
onSubmit: handleSubmit,
|
|
2335
|
+
submitText: editing ? "Save changes" : "Create infrastructure",
|
|
2336
|
+
isSubmitting: submitting,
|
|
2337
|
+
submitDisabled: !form.name.trim() || cloudAccountRequired && !form.cloudAccountConnectionId,
|
|
2338
|
+
error: formError,
|
|
2339
|
+
size: "md",
|
|
2340
|
+
children: /* @__PURE__ */ jsx14(
|
|
2341
|
+
FormBody,
|
|
2342
|
+
{
|
|
2343
|
+
form,
|
|
2344
|
+
setField,
|
|
2345
|
+
onProviderChange: handleProviderChange,
|
|
2346
|
+
deploymentTypes,
|
|
2347
|
+
environmentOptions,
|
|
2348
|
+
providerOptions,
|
|
2349
|
+
regionOptions,
|
|
2350
|
+
showRegion,
|
|
2351
|
+
showCloudAccount,
|
|
2352
|
+
cloudAccountRequired,
|
|
2353
|
+
cloudAccountOptions,
|
|
2354
|
+
selectedProviderName: selectedProvider?.name,
|
|
2355
|
+
versionOptions
|
|
2356
|
+
}
|
|
2357
|
+
)
|
|
2358
|
+
}
|
|
2359
|
+
)
|
|
2360
|
+
] });
|
|
2361
|
+
};
|
|
2362
|
+
var FormSection = ({
|
|
2363
|
+
title,
|
|
2364
|
+
description,
|
|
2365
|
+
children
|
|
2366
|
+
}) => /* @__PURE__ */ jsxs14("div", { children: [
|
|
2367
|
+
/* @__PURE__ */ jsx14("p", { style: { margin: 0, fontSize: 13, fontWeight: 600, color: tokens.text }, children: title }),
|
|
2368
|
+
description ? /* @__PURE__ */ jsx14("p", { style: { margin: "2px 0 0", fontSize: 12, color: tokens.muted }, children: description }) : null,
|
|
2369
|
+
/* @__PURE__ */ jsx14("div", { style: { marginTop: 10, display: "flex", flexDirection: "column", gap: 12 }, children })
|
|
2370
|
+
] });
|
|
2371
|
+
var FormBody = ({
|
|
2372
|
+
form,
|
|
2373
|
+
setField,
|
|
2374
|
+
onProviderChange,
|
|
2375
|
+
deploymentTypes,
|
|
2376
|
+
environmentOptions,
|
|
2377
|
+
providerOptions,
|
|
2378
|
+
regionOptions,
|
|
2379
|
+
showRegion,
|
|
2380
|
+
showCloudAccount,
|
|
2381
|
+
cloudAccountRequired,
|
|
2382
|
+
cloudAccountOptions,
|
|
2383
|
+
selectedProviderName,
|
|
2384
|
+
providerCode,
|
|
2385
|
+
versionOptions
|
|
2386
|
+
}) => /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
2387
|
+
/* @__PURE__ */ jsx14(Input, { label: "Name", value: form.name, onChange: (e) => setField("name", e.target.value), placeholder: "e.g. Production cluster", fullWidth: true, autoFocus: true }),
|
|
2388
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
2389
|
+
/* @__PURE__ */ jsx14(Select, { label: "Deployment type", value: form.deploymentType, onChange: (value) => setField("deploymentType", value), options: deploymentTypes }),
|
|
2390
|
+
/* @__PURE__ */ jsx14(Select, { label: "Environment", value: form.environmentType, onChange: (value) => setField("environmentType", value), options: environmentOptions })
|
|
2391
|
+
] }),
|
|
2392
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: showRegion ? "1fr 1fr" : "1fr", gap: 12 }, children: [
|
|
2393
|
+
/* @__PURE__ */ jsx14(Select, { label: "Provider", value: form.providerId, onChange: onProviderChange, options: providerOptions }),
|
|
2394
|
+
showRegion ? /* @__PURE__ */ jsx14(Select, { label: "Region", value: form.region, onChange: (value) => setField("region", value), options: regionOptions }) : null
|
|
2395
|
+
] }),
|
|
2396
|
+
/* @__PURE__ */ jsxs14(FormSection, { title: "Deployment target", children: [
|
|
2397
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
2398
|
+
/* @__PURE__ */ jsx14(
|
|
2399
|
+
Select,
|
|
2400
|
+
{
|
|
2401
|
+
label: "Network",
|
|
2402
|
+
value: form.networkMode,
|
|
2403
|
+
onChange: (value) => setField("networkMode", value),
|
|
2404
|
+
options: NETWORK_MODE_OPTIONS
|
|
2405
|
+
}
|
|
2406
|
+
),
|
|
2407
|
+
/* @__PURE__ */ jsx14(Select, { label: "DNS", value: form.dnsMode, onChange: (value) => setField("dnsMode", value), options: DNS_MODE_OPTIONS })
|
|
2408
|
+
] }),
|
|
2409
|
+
showCloudAccount ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
2410
|
+
/* @__PURE__ */ jsx14(
|
|
2411
|
+
Select,
|
|
2412
|
+
{
|
|
2413
|
+
label: cloudAccountRequired ? "Cloud account *" : "Cloud account",
|
|
2414
|
+
value: form.cloudAccountConnectionId,
|
|
2415
|
+
onChange: (value) => setField("cloudAccountConnectionId", value),
|
|
2416
|
+
options: cloudAccountOptions,
|
|
2417
|
+
placeholder: cloudAccountOptions.length ? "Select a verified cloud account\u2026" : cloudAccountRequired ? "No verified cloud accounts" : "None \u2014 use the default provisioning identity",
|
|
2418
|
+
disabled: cloudAccountOptions.length === 0,
|
|
2419
|
+
helperText: cloudAccountRequired ? "Required for a dedicated or existing-network (BYOC) deployment." : "Optional \u2014 the Veltrix-managed account this hosted deployment provisions through."
|
|
2420
|
+
}
|
|
2421
|
+
),
|
|
2422
|
+
cloudAccountRequired && cloudAccountOptions.length === 0 ? /* @__PURE__ */ jsx14(Alert, { variant: "warning", title: "No verified cloud account available", children: selectedProviderName ? `No verified ${selectedProviderName} cloud account found. Register and verify a cloud account first in Settings \u2192 Cloud Accounts.` : "Register and verify a cloud account first in Settings \u2192 Cloud Accounts." }) : null
|
|
2423
|
+
] }) : null
|
|
2424
|
+
] }),
|
|
2425
|
+
/* @__PURE__ */ jsxs14(
|
|
2426
|
+
FormSection,
|
|
2427
|
+
{
|
|
2428
|
+
title: "Topology",
|
|
2429
|
+
description: form.deploymentType === "distributed" ? "Distributed deployments need at least 3 indexers and 2 search heads." : "Number of indexer and search-head nodes to provision.",
|
|
2430
|
+
children: [
|
|
2431
|
+
versionOptions.length > 0 ? /* @__PURE__ */ jsx14(
|
|
2432
|
+
Select,
|
|
2433
|
+
{
|
|
2434
|
+
label: "Splunk version",
|
|
2435
|
+
value: form.versionId,
|
|
2436
|
+
onChange: (value) => setField("versionId", value),
|
|
2437
|
+
options: versionOptions,
|
|
2438
|
+
placeholder: "Use the app's default version",
|
|
2439
|
+
helperText: "The Splunk release installed on every node. Leave unset to use the app's default installer."
|
|
2440
|
+
}
|
|
2441
|
+
) : null,
|
|
2442
|
+
/* @__PURE__ */ jsxs14("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
2443
|
+
/* @__PURE__ */ jsx14(Input, { label: "Indexers", type: "number", min: 1, value: form.indexerCount, onChange: (e) => setField("indexerCount", e.target.value), fullWidth: true }),
|
|
2444
|
+
/* @__PURE__ */ jsx14(Input, { label: "Search heads", type: "number", min: 1, value: form.searchHeadCount, onChange: (e) => setField("searchHeadCount", e.target.value), fullWidth: true })
|
|
2445
|
+
] }),
|
|
2446
|
+
form.providerId && form.providerId !== SELF_HOSTED ? /* @__PURE__ */ jsx14(
|
|
2447
|
+
Input,
|
|
2448
|
+
{
|
|
2449
|
+
label: "Compute size (instance type)",
|
|
2450
|
+
value: form.instanceType,
|
|
2451
|
+
onChange: (e) => setField("instanceType", e.target.value),
|
|
2452
|
+
placeholder: INSTANCE_TYPE_EXAMPLES.aws,
|
|
2453
|
+
fullWidth: true,
|
|
2454
|
+
helperText: `Leave blank for the cloud default (~2 vCPU / 4 GB). Examples: AWS ${INSTANCE_TYPE_EXAMPLES.aws}, Azure ${INSTANCE_TYPE_EXAMPLES.azure}, GCP ${INSTANCE_TYPE_EXAMPLES.gcp}, Hetzner ${INSTANCE_TYPE_EXAMPLES.hetzner}. Applies to every node; you can change it here later.`
|
|
2455
|
+
}
|
|
2456
|
+
) : null,
|
|
2457
|
+
showRegion ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
2458
|
+
/* @__PURE__ */ jsx14(
|
|
2459
|
+
ClusterPlacementField,
|
|
2460
|
+
{
|
|
2461
|
+
label: "Indexer cluster placement",
|
|
2462
|
+
placement: form.indexerPlacement,
|
|
2463
|
+
nodeCount: Math.max(1, Number(form.indexerCount) || 1),
|
|
2464
|
+
primaryRegion: form.region,
|
|
2465
|
+
providerCode,
|
|
2466
|
+
regionOptions,
|
|
2467
|
+
onChange: (p) => setField("indexerPlacement", p)
|
|
2468
|
+
}
|
|
2469
|
+
),
|
|
2470
|
+
/* @__PURE__ */ jsx14(
|
|
2471
|
+
ClusterPlacementField,
|
|
2472
|
+
{
|
|
2473
|
+
label: "Search head cluster placement",
|
|
2474
|
+
placement: form.searchHeadPlacement,
|
|
2475
|
+
nodeCount: Math.max(1, Number(form.searchHeadCount) || 1),
|
|
2476
|
+
primaryRegion: form.region,
|
|
2477
|
+
providerCode,
|
|
2478
|
+
regionOptions,
|
|
2479
|
+
onChange: (p) => setField("searchHeadPlacement", p)
|
|
2480
|
+
}
|
|
2481
|
+
)
|
|
2482
|
+
] }) : null
|
|
2483
|
+
]
|
|
2484
|
+
}
|
|
2485
|
+
),
|
|
2486
|
+
form.deploymentType === "distributed" ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
|
|
2487
|
+
/* @__PURE__ */ jsx14(
|
|
2488
|
+
FormSection,
|
|
2489
|
+
{
|
|
2490
|
+
title: "Control plane",
|
|
2491
|
+
description: "How many instances the five management roles run on \u2014 fewer instances cut cost, more give isolation and HA.",
|
|
2492
|
+
children: /* @__PURE__ */ jsx14(
|
|
2493
|
+
Select,
|
|
2494
|
+
{
|
|
2495
|
+
label: "Consolidation",
|
|
2496
|
+
value: form.controlPlaneLayout,
|
|
2497
|
+
onChange: (value) => setField("controlPlaneLayout", value),
|
|
2498
|
+
options: CONTROL_PLANE_LAYOUT_OPTIONS.map((o) => ({ value: o.value, label: `${o.label} \u2014 ${o.description}` }))
|
|
2499
|
+
}
|
|
2500
|
+
)
|
|
2501
|
+
}
|
|
2502
|
+
),
|
|
2503
|
+
/* @__PURE__ */ jsx14(
|
|
2504
|
+
FormSection,
|
|
2505
|
+
{
|
|
2506
|
+
title: "Ingest",
|
|
2507
|
+
description: "Heavy forwarders for ingest routing. One is provisioned by default; add more for higher throughput.",
|
|
2508
|
+
children: /* @__PURE__ */ jsx14(
|
|
2509
|
+
Input,
|
|
2510
|
+
{
|
|
2511
|
+
label: "Heavy forwarders",
|
|
2512
|
+
type: "number",
|
|
2513
|
+
min: 1,
|
|
2514
|
+
value: form.heavyForwarderCount,
|
|
2515
|
+
onChange: (e) => setField("heavyForwarderCount", e.target.value),
|
|
2516
|
+
fullWidth: true
|
|
2517
|
+
}
|
|
2518
|
+
)
|
|
2519
|
+
}
|
|
2520
|
+
)
|
|
2521
|
+
] }) : null
|
|
2522
|
+
] });
|
|
2523
|
+
var ByolInfrastructureManager_default = ByolInfrastructureManager;
|
|
2524
|
+
|
|
2525
|
+
// src/byol/tags.ts
|
|
2526
|
+
var BYOL_TAG_KEYS = [
|
|
2527
|
+
"Veltrix:Customer",
|
|
2528
|
+
"Veltrix:Environment",
|
|
2529
|
+
"Veltrix:EnvName",
|
|
2530
|
+
"Veltrix:EnvType",
|
|
2531
|
+
"Veltrix:App",
|
|
2532
|
+
"Veltrix:ManagedBy",
|
|
2533
|
+
"CostCenter",
|
|
2534
|
+
"Owner"
|
|
2535
|
+
];
|
|
2536
|
+
var MANAGED_BY = "Veltrix";
|
|
2537
|
+
function s(value) {
|
|
2538
|
+
return String(value ?? "").trim();
|
|
2539
|
+
}
|
|
2540
|
+
function buildByolTags(input) {
|
|
2541
|
+
const customerId = s(input.customerId);
|
|
2542
|
+
const customerLabel = s(input.customerShortName) || customerId;
|
|
2543
|
+
const costCenter = s(input.costCenter) || customerLabel;
|
|
2544
|
+
const owner = s(input.owner) || customerLabel;
|
|
2545
|
+
return {
|
|
2546
|
+
"Veltrix:Customer": customerLabel,
|
|
2547
|
+
"Veltrix:Environment": s(input.infrastructureId),
|
|
2548
|
+
"Veltrix:EnvName": s(input.name),
|
|
2549
|
+
"Veltrix:EnvType": s(input.environmentType),
|
|
2550
|
+
"Veltrix:App": s(input.appId),
|
|
2551
|
+
"Veltrix:ManagedBy": MANAGED_BY,
|
|
2552
|
+
CostCenter: costCenter,
|
|
2553
|
+
Owner: owner
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
export {
|
|
2557
|
+
BYOL_TAG_KEYS,
|
|
2558
|
+
ByolInfrastructureDetail,
|
|
2559
|
+
ByolInfrastructureManager,
|
|
2560
|
+
ByolPlanModal,
|
|
2561
|
+
CONTROL_PLANE_LAYOUT_OPTIONS,
|
|
2562
|
+
DEPLOYMENT_STEPS,
|
|
2563
|
+
DestroyPlanModal,
|
|
2564
|
+
MANAGED_BY,
|
|
2565
|
+
MIN_HEAVY_FORWARDERS,
|
|
2566
|
+
PLACEMENT_GRANULARITY_OPTIONS,
|
|
2567
|
+
SINGLE_SITE_PLACEMENT,
|
|
2568
|
+
TIER_LABELS,
|
|
2569
|
+
TIER_ORDER,
|
|
2570
|
+
allocateNodesBySite,
|
|
2571
|
+
buildByolPlan,
|
|
2572
|
+
buildByolResourcePlan,
|
|
2573
|
+
buildByolTags,
|
|
2574
|
+
ByolInfrastructureManager_default as default,
|
|
2575
|
+
diffPlan,
|
|
2576
|
+
effectivePlacement,
|
|
2577
|
+
isNotStarted,
|
|
2578
|
+
isRunning,
|
|
2579
|
+
planHasChanges,
|
|
2580
|
+
resourceStatusLabel,
|
|
2581
|
+
resourceStatusVariant,
|
|
2582
|
+
statusLabel,
|
|
2583
|
+
statusVariant,
|
|
2584
|
+
stepStatusVariant,
|
|
2585
|
+
validatePlacement
|
|
2586
|
+
};
|
|
2587
|
+
//# sourceMappingURL=index.js.map
|