@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,754 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Badge,
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
CardBody,
|
|
6
|
+
CardHeader,
|
|
7
|
+
DataTable,
|
|
8
|
+
FilterBar,
|
|
9
|
+
FormDialog,
|
|
10
|
+
Input,
|
|
11
|
+
Pagination,
|
|
12
|
+
Select,
|
|
13
|
+
SortSelect,
|
|
14
|
+
useConfirmDialog
|
|
15
|
+
} from "../chunk-3WK4EVQ3.js";
|
|
16
|
+
import {
|
|
17
|
+
addInventoryItem,
|
|
18
|
+
createCredential,
|
|
19
|
+
fetchAppOnboarding,
|
|
20
|
+
listCredentials,
|
|
21
|
+
listEnvironments,
|
|
22
|
+
listInventory,
|
|
23
|
+
removeCredential,
|
|
24
|
+
resolveTool,
|
|
25
|
+
startOnboarding,
|
|
26
|
+
testConnection,
|
|
27
|
+
updateCredential,
|
|
28
|
+
updateInventoryItem
|
|
29
|
+
} from "../chunk-HIL6RIDG.js";
|
|
30
|
+
|
|
31
|
+
// src/connections/index.tsx
|
|
32
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
33
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
34
|
+
var BLANK_FORM = {
|
|
35
|
+
name: "",
|
|
36
|
+
environmentId: "",
|
|
37
|
+
environmentType: "",
|
|
38
|
+
authType: "password",
|
|
39
|
+
username: "",
|
|
40
|
+
secret: "",
|
|
41
|
+
endpoint: ""
|
|
42
|
+
};
|
|
43
|
+
var ENV_TYPE_LABELS = {
|
|
44
|
+
production: "Production",
|
|
45
|
+
"pre-production": "Pre-production",
|
|
46
|
+
staging: "Staging",
|
|
47
|
+
qa: "QA",
|
|
48
|
+
uat: "UAT",
|
|
49
|
+
development: "Development",
|
|
50
|
+
testing: "Testing",
|
|
51
|
+
sandbox: "Sandbox",
|
|
52
|
+
dr: "DR (Disaster Recovery)",
|
|
53
|
+
demo: "Demo",
|
|
54
|
+
other: "Other"
|
|
55
|
+
};
|
|
56
|
+
var envTypeLabel = (t) => ENV_TYPE_LABELS[t] ?? t;
|
|
57
|
+
function toCredentialType(authType) {
|
|
58
|
+
return authType === "token" ? "TOKEN" : "PASSWORD";
|
|
59
|
+
}
|
|
60
|
+
function hostFromEndpoint(endpoint) {
|
|
61
|
+
const trimmed = (endpoint || "").trim();
|
|
62
|
+
if (!trimmed) return "";
|
|
63
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
64
|
+
try {
|
|
65
|
+
return new URL(withScheme).host;
|
|
66
|
+
} catch {
|
|
67
|
+
return trimmed.replace(/^https?:\/\//i, "").replace(/[/?#].*$/, "");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function portFromEndpoint(endpoint) {
|
|
71
|
+
const trimmed = (endpoint || "").trim();
|
|
72
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(withScheme);
|
|
75
|
+
if (url.port) return url.port;
|
|
76
|
+
return url.protocol === "http:" ? "80" : "443";
|
|
77
|
+
} catch {
|
|
78
|
+
return "443";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function ensureConnectionComponent(params) {
|
|
82
|
+
const hostname = hostFromEndpoint(params.endpoint);
|
|
83
|
+
if (!hostname) {
|
|
84
|
+
throw new Error("An endpoint is required to register a deploy target for this connection.");
|
|
85
|
+
}
|
|
86
|
+
const items = await listInventory();
|
|
87
|
+
const existing = items.find(
|
|
88
|
+
(c) => (c.type ?? []).includes(params.componentType) && (params.environmentId ? (c.tags ?? []).some((t) => t.id === params.environmentId) : true)
|
|
89
|
+
);
|
|
90
|
+
const input = {
|
|
91
|
+
hostname,
|
|
92
|
+
port: portFromEndpoint(params.endpoint),
|
|
93
|
+
type: [params.componentType],
|
|
94
|
+
toolId: params.toolId,
|
|
95
|
+
credentialId: params.credentialId,
|
|
96
|
+
tagIds: params.environmentId ? [params.environmentId] : []
|
|
97
|
+
};
|
|
98
|
+
if (existing) await updateInventoryItem(existing.id, input);
|
|
99
|
+
else await addInventoryItem(input);
|
|
100
|
+
}
|
|
101
|
+
function fromCredentialType(type) {
|
|
102
|
+
return type === "TOKEN" ? "token" : "password";
|
|
103
|
+
}
|
|
104
|
+
var ConnectionsManager = ({
|
|
105
|
+
appName,
|
|
106
|
+
appId,
|
|
107
|
+
title = "Connections",
|
|
108
|
+
namePlaceholder = "e.g. Production API",
|
|
109
|
+
endpointPlaceholder = "e.g. https://api.example.com",
|
|
110
|
+
endpointHelper = "API base URL / endpoint this connection reaches.",
|
|
111
|
+
usernameLabel = "Username",
|
|
112
|
+
tokenLabel = "API token",
|
|
113
|
+
tokenUsernamePlaceholder = "service account (optional)",
|
|
114
|
+
passwordUsernamePlaceholder = "e.g. svc_veltrix",
|
|
115
|
+
usernameOptionalForToken = true,
|
|
116
|
+
onboarding,
|
|
117
|
+
componentType
|
|
118
|
+
}) => {
|
|
119
|
+
const { confirm } = useConfirmDialog();
|
|
120
|
+
const authTypes = useMemo(
|
|
121
|
+
() => [
|
|
122
|
+
{ value: "password", label: "Username & password" },
|
|
123
|
+
{ value: "token", label: tokenLabel }
|
|
124
|
+
],
|
|
125
|
+
[tokenLabel]
|
|
126
|
+
);
|
|
127
|
+
const [connections, setConnections] = useState([]);
|
|
128
|
+
const [environments, setEnvironments] = useState([]);
|
|
129
|
+
const [error, setError] = useState(null);
|
|
130
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
131
|
+
const [search, setSearch] = useState("");
|
|
132
|
+
const [environmentFilter, setEnvironmentFilter] = useState(null);
|
|
133
|
+
const [authTypeFilter, setAuthTypeFilter] = useState(null);
|
|
134
|
+
const [sortField, setSortField] = useState("name");
|
|
135
|
+
const [sortDir, setSortDir] = useState("asc");
|
|
136
|
+
const [page, setPage] = useState(1);
|
|
137
|
+
const [pageSize, setPageSize] = useState(10);
|
|
138
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
139
|
+
const [editing, setEditing] = useState(null);
|
|
140
|
+
const [form, setForm] = useState(BLANK_FORM);
|
|
141
|
+
const [submitting, setSubmitting] = useState(false);
|
|
142
|
+
const [formError, setFormError] = useState(null);
|
|
143
|
+
const [tests, setTests] = useState({});
|
|
144
|
+
const [onboardOpen, setOnboardOpen] = useState(false);
|
|
145
|
+
const [onboardName, setOnboardName] = useState("");
|
|
146
|
+
const [onboardEnvironmentId, setOnboardEnvironmentId] = useState("");
|
|
147
|
+
const [onboardSettings, setOnboardSettings] = useState({});
|
|
148
|
+
const [onboardSubmitting, setOnboardSubmitting] = useState(false);
|
|
149
|
+
const [onboardError, setOnboardError] = useState(null);
|
|
150
|
+
const [notice, setNotice] = useState(null);
|
|
151
|
+
const [fetchedOnboarding, setFetchedOnboarding] = useState(null);
|
|
152
|
+
const effectiveOnboarding = onboarding ?? fetchedOnboarding;
|
|
153
|
+
const load = useCallback(async () => {
|
|
154
|
+
setIsLoading(true);
|
|
155
|
+
setError(null);
|
|
156
|
+
try {
|
|
157
|
+
const tool = await resolveTool(appName);
|
|
158
|
+
const [creds, envs] = await Promise.all([
|
|
159
|
+
tool ? listCredentials(tool.id) : Promise.resolve([]),
|
|
160
|
+
listEnvironments()
|
|
161
|
+
]);
|
|
162
|
+
setConnections(creds);
|
|
163
|
+
setEnvironments(envs);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
setError(e.message);
|
|
166
|
+
} finally {
|
|
167
|
+
setIsLoading(false);
|
|
168
|
+
}
|
|
169
|
+
}, [appName]);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
void load();
|
|
172
|
+
}, [load]);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (onboarding) return;
|
|
175
|
+
let cancelled = false;
|
|
176
|
+
void fetchAppOnboarding(appId).then((d) => {
|
|
177
|
+
if (cancelled || !d) return;
|
|
178
|
+
setFetchedOnboarding({
|
|
179
|
+
provider: d.provider,
|
|
180
|
+
label: d.label,
|
|
181
|
+
brokered: d.brokered ?? false,
|
|
182
|
+
requiredSettings: d.requiredSettings ?? []
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return () => {
|
|
186
|
+
cancelled = true;
|
|
187
|
+
};
|
|
188
|
+
}, [appId, onboarding]);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (typeof window === "undefined") return;
|
|
191
|
+
const params = new URLSearchParams(window.location.search);
|
|
192
|
+
const outcome = params.get("onboarded");
|
|
193
|
+
if (!outcome) return;
|
|
194
|
+
if (outcome === "ok") {
|
|
195
|
+
setNotice(
|
|
196
|
+
params.get("pending") === "manual" ? "Connection created. One manual step remains \u2014 see the connection to finish setup." : "Connection created successfully."
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
setError(`Onboarding failed: ${params.get("reason") || "unknown error"}`);
|
|
200
|
+
}
|
|
201
|
+
params.delete("onboarded");
|
|
202
|
+
params.delete("pending");
|
|
203
|
+
params.delete("reason");
|
|
204
|
+
params.delete("connectionId");
|
|
205
|
+
const qs = params.toString();
|
|
206
|
+
window.history.replaceState({}, "", `${window.location.pathname}${qs ? `?${qs}` : ""}`);
|
|
207
|
+
}, []);
|
|
208
|
+
const openOnboard = () => {
|
|
209
|
+
setOnboardName("");
|
|
210
|
+
setOnboardEnvironmentId("");
|
|
211
|
+
setOnboardSettings({});
|
|
212
|
+
setOnboardError(null);
|
|
213
|
+
setOnboardOpen(true);
|
|
214
|
+
};
|
|
215
|
+
const handleStartOnboarding = async () => {
|
|
216
|
+
if (!effectiveOnboarding) return;
|
|
217
|
+
const name = onboardName.trim();
|
|
218
|
+
if (!name) {
|
|
219
|
+
setOnboardError("Name is required");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!onboardEnvironmentId) {
|
|
223
|
+
setOnboardError("Environment is required");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const missing = effectiveOnboarding.requiredSettings.filter((k) => !onboardSettings[k]?.trim());
|
|
227
|
+
if (missing.length > 0) {
|
|
228
|
+
setOnboardError(`Please provide: ${missing.join(", ")}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
setOnboardSubmitting(true);
|
|
232
|
+
setOnboardError(null);
|
|
233
|
+
try {
|
|
234
|
+
const { authorizeUrl } = await startOnboarding(appId, {
|
|
235
|
+
environmentId: onboardEnvironmentId,
|
|
236
|
+
connectionName: name,
|
|
237
|
+
settings: onboardSettings
|
|
238
|
+
});
|
|
239
|
+
if (typeof window !== "undefined") window.location.assign(authorizeUrl);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
setOnboardError(e.message);
|
|
242
|
+
setOnboardSubmitting(false);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const openCreate = () => {
|
|
246
|
+
setEditing(null);
|
|
247
|
+
setForm(BLANK_FORM);
|
|
248
|
+
setFormError(null);
|
|
249
|
+
setDialogOpen(true);
|
|
250
|
+
};
|
|
251
|
+
const openEdit = (row) => {
|
|
252
|
+
setEditing(row);
|
|
253
|
+
setForm({
|
|
254
|
+
name: row.name ?? "",
|
|
255
|
+
environmentId: row.tags?.[0]?.id ?? "",
|
|
256
|
+
environmentType: row.environmentType ?? "",
|
|
257
|
+
authType: fromCredentialType(row.type),
|
|
258
|
+
username: row.username ?? "",
|
|
259
|
+
secret: "",
|
|
260
|
+
endpoint: row.endpoint ?? ""
|
|
261
|
+
});
|
|
262
|
+
setFormError(null);
|
|
263
|
+
setDialogOpen(true);
|
|
264
|
+
};
|
|
265
|
+
const closeDialog = useCallback(() => {
|
|
266
|
+
if (submitting) return;
|
|
267
|
+
setDialogOpen(false);
|
|
268
|
+
}, [submitting]);
|
|
269
|
+
const setField = (key, value) => setForm((prev) => ({ ...prev, [key]: value }));
|
|
270
|
+
const handleSubmit = async () => {
|
|
271
|
+
const name = form.name.trim();
|
|
272
|
+
if (!name) {
|
|
273
|
+
setFormError("Name is required");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!form.environmentId) {
|
|
277
|
+
setFormError("Environment is required");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
setSubmitting(true);
|
|
281
|
+
setFormError(null);
|
|
282
|
+
const secret = form.secret.trim();
|
|
283
|
+
const username = form.username.trim();
|
|
284
|
+
const endpoint = form.endpoint.trim();
|
|
285
|
+
const tagIds = [form.environmentId];
|
|
286
|
+
try {
|
|
287
|
+
let credentialId;
|
|
288
|
+
let toolId;
|
|
289
|
+
if (editing) {
|
|
290
|
+
const update = {
|
|
291
|
+
name,
|
|
292
|
+
username,
|
|
293
|
+
type: toCredentialType(form.authType),
|
|
294
|
+
endpoint,
|
|
295
|
+
environmentType: form.environmentType || null,
|
|
296
|
+
tagIds
|
|
297
|
+
};
|
|
298
|
+
if (secret) {
|
|
299
|
+
if (form.authType === "token") update.apiToken = secret;
|
|
300
|
+
else update.password = secret;
|
|
301
|
+
}
|
|
302
|
+
await updateCredential(editing.id, update);
|
|
303
|
+
credentialId = editing.id;
|
|
304
|
+
} else {
|
|
305
|
+
const tool = await resolveTool(appName);
|
|
306
|
+
if (!tool) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`No "${appName}" tool found for your organization \u2014 make sure the app is installed before adding connections.`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
toolId = tool.id;
|
|
312
|
+
const created = await createCredential({
|
|
313
|
+
name,
|
|
314
|
+
username,
|
|
315
|
+
password: form.authType === "password" ? secret : "",
|
|
316
|
+
apiToken: form.authType === "token" ? secret : void 0,
|
|
317
|
+
type: toCredentialType(form.authType),
|
|
318
|
+
endpoint,
|
|
319
|
+
environmentType: form.environmentType || null,
|
|
320
|
+
toolId: tool.id,
|
|
321
|
+
tagIds
|
|
322
|
+
});
|
|
323
|
+
credentialId = created.id;
|
|
324
|
+
}
|
|
325
|
+
if (componentType) {
|
|
326
|
+
try {
|
|
327
|
+
if (!toolId) toolId = (await resolveTool(appName))?.id;
|
|
328
|
+
await ensureConnectionComponent({
|
|
329
|
+
componentType,
|
|
330
|
+
toolId,
|
|
331
|
+
credentialId,
|
|
332
|
+
endpoint,
|
|
333
|
+
environmentId: form.environmentId
|
|
334
|
+
});
|
|
335
|
+
} catch (compErr) {
|
|
336
|
+
setNotice(
|
|
337
|
+
`Connection saved, but registering its deploy target failed: ${compErr.message} Re-save the connection to retry.`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
setDialogOpen(false);
|
|
342
|
+
await load();
|
|
343
|
+
} catch (e) {
|
|
344
|
+
setFormError(e.message);
|
|
345
|
+
} finally {
|
|
346
|
+
setSubmitting(false);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
const handleDelete = async (row) => {
|
|
350
|
+
const ok = await confirm({
|
|
351
|
+
title: "Remove connection",
|
|
352
|
+
message: `Remove the connection "${row.name}"? This cannot be undone.`,
|
|
353
|
+
confirmText: "Remove",
|
|
354
|
+
cancelText: "Cancel",
|
|
355
|
+
variant: "danger"
|
|
356
|
+
});
|
|
357
|
+
if (!ok) return;
|
|
358
|
+
try {
|
|
359
|
+
await removeCredential(row.id);
|
|
360
|
+
await load();
|
|
361
|
+
} catch (e) {
|
|
362
|
+
setError(e.message);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const handleTest = async (row) => {
|
|
366
|
+
setTests((t) => ({ ...t, [row.id]: { loading: true } }));
|
|
367
|
+
try {
|
|
368
|
+
const result = await testConnection(appId, row.id);
|
|
369
|
+
setTests((t) => ({ ...t, [row.id]: result }));
|
|
370
|
+
} catch (e) {
|
|
371
|
+
setTests((t) => ({ ...t, [row.id]: { ok: false, message: e.message } }));
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const columns = [
|
|
375
|
+
{ key: "name", header: "Name", render: (row) => /* @__PURE__ */ jsx("strong", { children: row.name }) },
|
|
376
|
+
{
|
|
377
|
+
key: "environment",
|
|
378
|
+
header: "Environment",
|
|
379
|
+
render: (row) => row.tags && row.tags.length > 0 ? /* @__PURE__ */ jsx(Badge, { variant: "info", size: "sm", children: row.tags[0].name }) : /* @__PURE__ */ jsx(Badge, { variant: "warning", size: "sm", children: "none" })
|
|
380
|
+
},
|
|
381
|
+
{ key: "username", header: usernameLabel, render: (row) => row.username || "\u2014" },
|
|
382
|
+
{
|
|
383
|
+
key: "auth",
|
|
384
|
+
header: "Auth",
|
|
385
|
+
render: (row) => /* @__PURE__ */ jsxs(Badge, { variant: row.hasSecret ? "success" : "warning", size: "sm", children: [
|
|
386
|
+
row.type === "TOKEN" ? "token" : "password",
|
|
387
|
+
row.hasSecret ? "" : " \xB7 no secret"
|
|
388
|
+
] })
|
|
389
|
+
},
|
|
390
|
+
{ key: "endpoint", header: "Endpoint", render: (row) => row.endpoint || "\u2014" },
|
|
391
|
+
{
|
|
392
|
+
key: "actions",
|
|
393
|
+
header: "",
|
|
394
|
+
align: "right",
|
|
395
|
+
render: (row) => {
|
|
396
|
+
const t = tests[row.id];
|
|
397
|
+
const loading = !!t && "loading" in t;
|
|
398
|
+
const result = t && !("loading" in t) ? t : null;
|
|
399
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end", alignItems: "center", flexWrap: "wrap" }, children: [
|
|
400
|
+
result ? /* @__PURE__ */ jsxs(
|
|
401
|
+
"span",
|
|
402
|
+
{
|
|
403
|
+
style: { display: "inline-flex", alignItems: "center", gap: 6, maxWidth: 340 },
|
|
404
|
+
title: [result.message, ...result.details ?? []].filter(Boolean).join("\n"),
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ jsx(Badge, { variant: result.ok ? "success" : "danger", size: "sm", children: result.ok ? "\u2713" : "\u2717" }),
|
|
407
|
+
/* @__PURE__ */ jsxs(
|
|
408
|
+
"span",
|
|
409
|
+
{
|
|
410
|
+
style: {
|
|
411
|
+
fontSize: 12,
|
|
412
|
+
color: result.ok ? "var(--color-success, #16a34a)" : "var(--color-danger, #dc2626)",
|
|
413
|
+
overflow: "hidden",
|
|
414
|
+
textOverflow: "ellipsis",
|
|
415
|
+
whiteSpace: "nowrap"
|
|
416
|
+
},
|
|
417
|
+
children: [
|
|
418
|
+
result.message,
|
|
419
|
+
result.latencyMs != null ? ` \xB7 ${result.latencyMs} ms` : ""
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
) : null,
|
|
426
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", isLoading: loading, onClick: () => void handleTest(row), children: "Test" }),
|
|
427
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => openEdit(row), children: "Edit" }),
|
|
428
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void handleDelete(row), children: "Remove" })
|
|
429
|
+
] });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
];
|
|
433
|
+
const isToken = form.authType === "token";
|
|
434
|
+
const secretLabel = isToken ? tokenLabel : "Password";
|
|
435
|
+
const usernameFieldLabel = isToken && usernameOptionalForToken ? `${usernameLabel} (optional)` : usernameLabel;
|
|
436
|
+
const environmentOptions = [
|
|
437
|
+
{ value: "", label: environments.length ? "\u2014 Select environment \u2014" : "\u2014 No environments \u2014" },
|
|
438
|
+
...environments.map((e) => ({ value: e.id, label: e.name }))
|
|
439
|
+
];
|
|
440
|
+
const selectedEnv = environments.find((e) => e.id === form.environmentId);
|
|
441
|
+
const selectedEnvTypes = selectedEnv?.environmentTypes ?? [];
|
|
442
|
+
const environmentTypeOptions = [
|
|
443
|
+
{ value: "", label: "Any type" },
|
|
444
|
+
...selectedEnvTypes.map((t) => ({ value: t, label: envTypeLabel(t) }))
|
|
445
|
+
];
|
|
446
|
+
const environmentFilterOptions = environments.map((e) => ({ value: e.id, label: e.name }));
|
|
447
|
+
const sortOptions = [
|
|
448
|
+
{ value: "name", label: "Name" },
|
|
449
|
+
{ value: "environment", label: "Environment" },
|
|
450
|
+
{ value: "username", label: usernameLabel }
|
|
451
|
+
];
|
|
452
|
+
const filters = [
|
|
453
|
+
{
|
|
454
|
+
key: "environment",
|
|
455
|
+
label: "Environment",
|
|
456
|
+
options: environmentFilterOptions,
|
|
457
|
+
value: environmentFilter,
|
|
458
|
+
onChange: setEnvironmentFilter,
|
|
459
|
+
alwaysVisible: true
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
key: "authType",
|
|
463
|
+
label: "Auth method",
|
|
464
|
+
options: authTypes,
|
|
465
|
+
value: authTypeFilter,
|
|
466
|
+
onChange: setAuthTypeFilter
|
|
467
|
+
}
|
|
468
|
+
];
|
|
469
|
+
const filteredSorted = useMemo(() => {
|
|
470
|
+
const term = search.trim().toLowerCase();
|
|
471
|
+
const rows = connections.filter((row) => {
|
|
472
|
+
if (term) {
|
|
473
|
+
const haystack = `${row.name ?? ""} ${row.username ?? ""} ${row.endpoint ?? ""}`.toLowerCase();
|
|
474
|
+
if (!haystack.includes(term)) return false;
|
|
475
|
+
}
|
|
476
|
+
if (environmentFilter && row.tags?.[0]?.id !== environmentFilter) return false;
|
|
477
|
+
if (authTypeFilter && fromCredentialType(row.type) !== authTypeFilter) return false;
|
|
478
|
+
return true;
|
|
479
|
+
});
|
|
480
|
+
const dir = sortDir === "asc" ? 1 : -1;
|
|
481
|
+
return [...rows].sort((a, b) => {
|
|
482
|
+
switch (sortField) {
|
|
483
|
+
case "environment":
|
|
484
|
+
return (a.tags?.[0]?.name ?? "").localeCompare(b.tags?.[0]?.name ?? "") * dir;
|
|
485
|
+
case "username":
|
|
486
|
+
return (a.username ?? "").localeCompare(b.username ?? "") * dir;
|
|
487
|
+
case "name":
|
|
488
|
+
default:
|
|
489
|
+
return (a.name ?? "").localeCompare(b.name ?? "") * dir;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
}, [connections, search, environmentFilter, authTypeFilter, sortField, sortDir]);
|
|
493
|
+
const pageRows = useMemo(
|
|
494
|
+
() => filteredSorted.slice((page - 1) * pageSize, page * pageSize),
|
|
495
|
+
[filteredSorted, page, pageSize]
|
|
496
|
+
);
|
|
497
|
+
useEffect(() => {
|
|
498
|
+
setPage(1);
|
|
499
|
+
}, [search, environmentFilter, authTypeFilter, sortField, sortDir]);
|
|
500
|
+
return /* @__PURE__ */ jsxs(Card, { variant: "bordered", children: [
|
|
501
|
+
/* @__PURE__ */ jsx(
|
|
502
|
+
CardHeader,
|
|
503
|
+
{
|
|
504
|
+
actions: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
505
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => void load(), isLoading, children: "Refresh" }),
|
|
506
|
+
effectiveOnboarding ? /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: openOnboard, children: effectiveOnboarding.label }) : /* @__PURE__ */ jsx(
|
|
507
|
+
Button,
|
|
508
|
+
{
|
|
509
|
+
variant: "secondary",
|
|
510
|
+
size: "sm",
|
|
511
|
+
disabled: true,
|
|
512
|
+
title: "OAuth integration isn't available for this app \u2014 connect with a username/password or API token.",
|
|
513
|
+
children: "Integrate via OAuth"
|
|
514
|
+
}
|
|
515
|
+
),
|
|
516
|
+
/* @__PURE__ */ jsx(Button, { variant: effectiveOnboarding ? "secondary" : "primary", size: "sm", onClick: openCreate, children: "Add connection" })
|
|
517
|
+
] }),
|
|
518
|
+
children: /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 16, fontWeight: 600 }, children: title })
|
|
519
|
+
}
|
|
520
|
+
),
|
|
521
|
+
/* @__PURE__ */ jsxs(CardBody, { children: [
|
|
522
|
+
notice ? /* @__PURE__ */ jsx("div", { role: "status", style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", children: notice }) }) : null,
|
|
523
|
+
error ? /* @__PURE__ */ jsxs("p", { role: "alert", children: [
|
|
524
|
+
"Failed to load connections: ",
|
|
525
|
+
error
|
|
526
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
527
|
+
/* @__PURE__ */ jsxs(
|
|
528
|
+
"div",
|
|
529
|
+
{
|
|
530
|
+
style: {
|
|
531
|
+
display: "flex",
|
|
532
|
+
flexWrap: "wrap",
|
|
533
|
+
gap: 12,
|
|
534
|
+
alignItems: "center",
|
|
535
|
+
justifyContent: "space-between",
|
|
536
|
+
marginBottom: 12
|
|
537
|
+
},
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ jsx(
|
|
540
|
+
FilterBar,
|
|
541
|
+
{
|
|
542
|
+
search: { value: search, onChange: setSearch, placeholder: "Search connections\u2026" },
|
|
543
|
+
filters,
|
|
544
|
+
onClearAll: () => {
|
|
545
|
+
setSearch("");
|
|
546
|
+
setEnvironmentFilter(null);
|
|
547
|
+
setAuthTypeFilter(null);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
),
|
|
551
|
+
/* @__PURE__ */ jsx(
|
|
552
|
+
SortSelect,
|
|
553
|
+
{
|
|
554
|
+
options: sortOptions,
|
|
555
|
+
value: sortField,
|
|
556
|
+
direction: sortDir,
|
|
557
|
+
onChange: (field, direction) => {
|
|
558
|
+
setSortField(field);
|
|
559
|
+
setSortDir(direction);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
),
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
DataTable,
|
|
568
|
+
{
|
|
569
|
+
columns,
|
|
570
|
+
data: pageRows,
|
|
571
|
+
rowKey: (row) => row.id,
|
|
572
|
+
isLoading,
|
|
573
|
+
emptyState: {
|
|
574
|
+
title: "No connections yet",
|
|
575
|
+
description: "Add the credentials this app authenticates with (username/password or a token) and the endpoint they reach."
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
),
|
|
579
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx(
|
|
580
|
+
Pagination,
|
|
581
|
+
{
|
|
582
|
+
page,
|
|
583
|
+
pageSize,
|
|
584
|
+
totalItems: filteredSorted.length,
|
|
585
|
+
onPageChange: setPage,
|
|
586
|
+
onPageSizeChange: setPageSize,
|
|
587
|
+
pageSizeOptions: [10, 25, 50]
|
|
588
|
+
}
|
|
589
|
+
) })
|
|
590
|
+
] })
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsx(
|
|
593
|
+
FormDialog,
|
|
594
|
+
{
|
|
595
|
+
isOpen: dialogOpen,
|
|
596
|
+
onClose: closeDialog,
|
|
597
|
+
title: editing ? `Edit connection "${editing.name}"` : "Add connection",
|
|
598
|
+
description: "Credentials this app authenticates with, plus the API endpoint they reach.",
|
|
599
|
+
onSubmit: handleSubmit,
|
|
600
|
+
submitText: editing ? "Save changes" : "Add connection",
|
|
601
|
+
isSubmitting: submitting,
|
|
602
|
+
submitDisabled: !form.name.trim() || !form.environmentId,
|
|
603
|
+
error: formError,
|
|
604
|
+
size: "md",
|
|
605
|
+
children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
606
|
+
/* @__PURE__ */ jsx(
|
|
607
|
+
Input,
|
|
608
|
+
{
|
|
609
|
+
label: "Name",
|
|
610
|
+
value: form.name,
|
|
611
|
+
onChange: (e) => setField("name", e.target.value),
|
|
612
|
+
placeholder: namePlaceholder,
|
|
613
|
+
fullWidth: true,
|
|
614
|
+
autoFocus: true,
|
|
615
|
+
spellCheck: false,
|
|
616
|
+
autoComplete: "off"
|
|
617
|
+
}
|
|
618
|
+
),
|
|
619
|
+
/* @__PURE__ */ jsx(
|
|
620
|
+
Select,
|
|
621
|
+
{
|
|
622
|
+
label: "Environment",
|
|
623
|
+
options: environmentOptions,
|
|
624
|
+
value: form.environmentId,
|
|
625
|
+
onChange: (value) => setForm((prev) => ({ ...prev, environmentId: value, environmentType: "" })),
|
|
626
|
+
helperText: "The deployment scope this connection belongs to. Manage environments under Environments.",
|
|
627
|
+
fullWidth: true
|
|
628
|
+
}
|
|
629
|
+
),
|
|
630
|
+
selectedEnvTypes.length > 0 && /* @__PURE__ */ jsx(
|
|
631
|
+
Select,
|
|
632
|
+
{
|
|
633
|
+
label: "Applies to type (optional)",
|
|
634
|
+
options: environmentTypeOptions,
|
|
635
|
+
value: form.environmentType,
|
|
636
|
+
onChange: (value) => setField("environmentType", value),
|
|
637
|
+
helperText: "Scope these credentials to a specific type within the environment. 'Any type' applies to all of its types.",
|
|
638
|
+
fullWidth: true
|
|
639
|
+
}
|
|
640
|
+
),
|
|
641
|
+
/* @__PURE__ */ jsx(
|
|
642
|
+
Input,
|
|
643
|
+
{
|
|
644
|
+
label: "Endpoint (optional)",
|
|
645
|
+
value: form.endpoint,
|
|
646
|
+
onChange: (e) => setField("endpoint", e.target.value),
|
|
647
|
+
placeholder: endpointPlaceholder,
|
|
648
|
+
helperText: endpointHelper,
|
|
649
|
+
fullWidth: true,
|
|
650
|
+
spellCheck: false,
|
|
651
|
+
autoComplete: "off"
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
/* @__PURE__ */ jsx(
|
|
655
|
+
Select,
|
|
656
|
+
{
|
|
657
|
+
label: "Auth method",
|
|
658
|
+
options: authTypes,
|
|
659
|
+
value: form.authType,
|
|
660
|
+
onChange: (value) => setField("authType", value),
|
|
661
|
+
fullWidth: true
|
|
662
|
+
}
|
|
663
|
+
),
|
|
664
|
+
/* @__PURE__ */ jsx(
|
|
665
|
+
Input,
|
|
666
|
+
{
|
|
667
|
+
label: usernameFieldLabel,
|
|
668
|
+
value: form.username,
|
|
669
|
+
onChange: (e) => setField("username", e.target.value),
|
|
670
|
+
placeholder: isToken ? tokenUsernamePlaceholder : passwordUsernamePlaceholder,
|
|
671
|
+
fullWidth: true,
|
|
672
|
+
spellCheck: false,
|
|
673
|
+
autoComplete: "off"
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
/* @__PURE__ */ jsx(
|
|
677
|
+
Input,
|
|
678
|
+
{
|
|
679
|
+
label: secretLabel,
|
|
680
|
+
type: "password",
|
|
681
|
+
value: form.secret,
|
|
682
|
+
onChange: (e) => setField("secret", e.target.value),
|
|
683
|
+
placeholder: editing ? "leave blank to keep current" : "",
|
|
684
|
+
helperText: "Write-only \u2014 stored securely and never shown again.",
|
|
685
|
+
fullWidth: true,
|
|
686
|
+
autoComplete: "new-password"
|
|
687
|
+
}
|
|
688
|
+
)
|
|
689
|
+
] })
|
|
690
|
+
}
|
|
691
|
+
),
|
|
692
|
+
effectiveOnboarding ? /* @__PURE__ */ jsx(
|
|
693
|
+
FormDialog,
|
|
694
|
+
{
|
|
695
|
+
isOpen: onboardOpen,
|
|
696
|
+
onClose: () => {
|
|
697
|
+
if (!onboardSubmitting) setOnboardOpen(false);
|
|
698
|
+
},
|
|
699
|
+
title: effectiveOnboarding.label,
|
|
700
|
+
description: effectiveOnboarding.brokered ? "You'll approve this on your provider's sign-in page \u2014 no secret is entered or stored here." : "You'll authorize this on your provider's sign-in page.",
|
|
701
|
+
onSubmit: handleStartOnboarding,
|
|
702
|
+
submitText: "Continue to sign-in",
|
|
703
|
+
isSubmitting: onboardSubmitting,
|
|
704
|
+
submitDisabled: !onboardName.trim() || !onboardEnvironmentId,
|
|
705
|
+
error: onboardError,
|
|
706
|
+
size: "md",
|
|
707
|
+
children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
|
|
708
|
+
/* @__PURE__ */ jsx(
|
|
709
|
+
Input,
|
|
710
|
+
{
|
|
711
|
+
label: "Name",
|
|
712
|
+
value: onboardName,
|
|
713
|
+
onChange: (e) => setOnboardName(e.target.value),
|
|
714
|
+
placeholder: namePlaceholder,
|
|
715
|
+
fullWidth: true,
|
|
716
|
+
autoFocus: true,
|
|
717
|
+
spellCheck: false,
|
|
718
|
+
autoComplete: "off"
|
|
719
|
+
}
|
|
720
|
+
),
|
|
721
|
+
/* @__PURE__ */ jsx(
|
|
722
|
+
Select,
|
|
723
|
+
{
|
|
724
|
+
label: "Environment",
|
|
725
|
+
options: environmentOptions,
|
|
726
|
+
value: onboardEnvironmentId,
|
|
727
|
+
onChange: (value) => setOnboardEnvironmentId(value),
|
|
728
|
+
helperText: "The deployment scope this connection belongs to.",
|
|
729
|
+
fullWidth: true
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
effectiveOnboarding.requiredSettings.map((key) => /* @__PURE__ */ jsx(
|
|
733
|
+
Input,
|
|
734
|
+
{
|
|
735
|
+
label: key,
|
|
736
|
+
value: onboardSettings[key] ?? "",
|
|
737
|
+
onChange: (e) => setOnboardSettings((prev) => ({ ...prev, [key]: e.target.value })),
|
|
738
|
+
fullWidth: true,
|
|
739
|
+
spellCheck: false,
|
|
740
|
+
autoComplete: "off"
|
|
741
|
+
},
|
|
742
|
+
key
|
|
743
|
+
))
|
|
744
|
+
] })
|
|
745
|
+
}
|
|
746
|
+
) : null
|
|
747
|
+
] });
|
|
748
|
+
};
|
|
749
|
+
var connections_default = ConnectionsManager;
|
|
750
|
+
export {
|
|
751
|
+
ConnectionsManager,
|
|
752
|
+
connections_default as default
|
|
753
|
+
};
|
|
754
|
+
//# sourceMappingURL=index.js.map
|