@replicated/portal-components 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/metadata/registry.json +109 -3
- package/components/metadata/registry.md +53 -3
- package/dist/actions/index.d.mts +590 -2
- package/dist/actions/index.d.ts +590 -2
- package/dist/actions/index.js +1931 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/airgap-instances.d.mts +26 -0
- package/dist/airgap-instances.d.ts +26 -0
- package/dist/airgap-instances.js +354 -0
- package/dist/airgap-instances.js.map +1 -0
- package/dist/error-page.d.mts +14 -0
- package/dist/error-page.d.ts +14 -0
- package/dist/error-page.js +153 -0
- package/dist/error-page.js.map +1 -0
- package/dist/error.d.mts +15 -0
- package/dist/error.d.ts +15 -0
- package/dist/error.js +144 -0
- package/dist/error.js.map +1 -0
- package/dist/esm/actions/index.js +1892 -1
- package/dist/esm/actions/index.js.map +1 -1
- package/dist/esm/airgap-instances.js +352 -0
- package/dist/esm/airgap-instances.js.map +1 -0
- package/dist/esm/error-page.js +151 -0
- package/dist/esm/error-page.js.map +1 -0
- package/dist/esm/error.js +142 -0
- package/dist/esm/error.js.map +1 -0
- package/dist/esm/helm-install-wizard.js +1007 -0
- package/dist/esm/helm-install-wizard.js.map +1 -0
- package/dist/esm/index.js +2240 -82
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/install-actions.js +746 -0
- package/dist/esm/install-actions.js.map +1 -0
- package/dist/esm/install-card.js +115 -0
- package/dist/esm/install-card.js.map +1 -0
- package/dist/esm/install-targets.js +48 -0
- package/dist/esm/install-targets.js.map +1 -0
- package/dist/esm/instance-card.js +197 -0
- package/dist/esm/instance-card.js.map +1 -0
- package/dist/esm/join-team.js +218 -0
- package/dist/esm/join-team.js.map +1 -0
- package/dist/esm/license-card.js +131 -0
- package/dist/esm/license-card.js.map +1 -0
- package/dist/esm/license-details.js +667 -0
- package/dist/esm/license-details.js.map +1 -0
- package/dist/esm/linux-install-wizard.js +1083 -0
- package/dist/esm/linux-install-wizard.js.map +1 -0
- package/dist/esm/login.js +261 -0
- package/dist/esm/login.js.map +1 -0
- package/dist/esm/online-instance-list.js +287 -0
- package/dist/esm/online-instance-list.js.map +1 -0
- package/dist/esm/pending-installations.js +235 -0
- package/dist/esm/pending-installations.js.map +1 -0
- package/dist/esm/release-history-panel.js +100 -0
- package/dist/esm/release-history-panel.js.map +1 -0
- package/dist/esm/release-notes-card.js +23 -0
- package/dist/esm/release-notes-card.js.map +1 -0
- package/dist/esm/security-card.js +700 -0
- package/dist/esm/security-card.js.map +1 -0
- package/dist/esm/support-bundle-collection-card.js +170 -0
- package/dist/esm/support-bundle-collection-card.js.map +1 -0
- package/dist/esm/support-bundles-card.js +306 -0
- package/dist/esm/support-bundles-card.js.map +1 -0
- package/dist/esm/support-card.js +305 -0
- package/dist/esm/support-card.js.map +1 -0
- package/dist/esm/team-selection.js +117 -0
- package/dist/esm/team-selection.js.map +1 -0
- package/dist/esm/team-settings-card.js +78 -0
- package/dist/esm/team-settings-card.js.map +1 -0
- package/dist/esm/team-settings.js +136 -0
- package/dist/esm/team-settings.js.map +1 -0
- package/dist/esm/top-nav-user-menu.js +173 -0
- package/dist/esm/top-nav-user-menu.js.map +1 -0
- package/dist/esm/top-nav.js +398 -0
- package/dist/esm/top-nav.js.map +1 -0
- package/dist/esm/update-layout.js +405 -0
- package/dist/esm/update-layout.js.map +1 -0
- package/dist/esm/updates-card.js +85 -0
- package/dist/esm/updates-card.js.map +1 -0
- package/dist/esm/upload-support-bundle-modal.js +143 -0
- package/dist/esm/upload-support-bundle-modal.js.map +1 -0
- package/dist/esm/user-settings-card.js +21 -0
- package/dist/esm/user-settings-card.js.map +1 -0
- package/dist/esm/user-settings.js +368 -0
- package/dist/esm/user-settings.js.map +1 -0
- package/dist/esm/utils/index.js +170 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/helm-install-wizard.d.mts +38 -0
- package/dist/helm-install-wizard.d.ts +38 -0
- package/dist/helm-install-wizard.js +1011 -0
- package/dist/helm-install-wizard.js.map +1 -0
- package/dist/index.d.mts +11 -20
- package/dist/index.d.ts +11 -20
- package/dist/index.js +2266 -81
- package/dist/index.js.map +1 -1
- package/dist/install-B19AaKF_.d.mts +233 -0
- package/dist/install-Bi1qJ8Bu.d.ts +233 -0
- package/dist/install-actions.d.mts +141 -0
- package/dist/install-actions.d.ts +141 -0
- package/dist/install-actions.js +765 -0
- package/dist/install-actions.js.map +1 -0
- package/dist/install-card.d.mts +15 -0
- package/dist/install-card.d.ts +15 -0
- package/dist/install-card.js +117 -0
- package/dist/install-card.js.map +1 -0
- package/dist/install-targets.d.mts +19 -0
- package/dist/install-targets.d.ts +19 -0
- package/dist/install-targets.js +50 -0
- package/dist/install-targets.js.map +1 -0
- package/dist/instance-card.d.mts +22 -0
- package/dist/instance-card.d.ts +22 -0
- package/dist/instance-card.js +199 -0
- package/dist/instance-card.js.map +1 -0
- package/dist/join-team.d.mts +30 -0
- package/dist/join-team.d.ts +30 -0
- package/dist/join-team.js +220 -0
- package/dist/join-team.js.map +1 -0
- package/dist/license-card.d.mts +15 -0
- package/dist/license-card.d.ts +15 -0
- package/dist/license-card.js +133 -0
- package/dist/license-card.js.map +1 -0
- package/dist/license-details.d.mts +10 -0
- package/dist/license-details.d.ts +10 -0
- package/dist/license-details.js +669 -0
- package/dist/license-details.js.map +1 -0
- package/dist/linux-install-wizard.d.mts +66 -0
- package/dist/linux-install-wizard.d.ts +66 -0
- package/dist/linux-install-wizard.js +1093 -0
- package/dist/linux-install-wizard.js.map +1 -0
- package/dist/login.d.mts +37 -0
- package/dist/login.d.ts +37 -0
- package/dist/login.js +263 -0
- package/dist/login.js.map +1 -0
- package/dist/online-instance-list.d.mts +22 -0
- package/dist/online-instance-list.d.ts +22 -0
- package/dist/online-instance-list.js +289 -0
- package/dist/online-instance-list.js.map +1 -0
- package/dist/pending-installations.d.mts +15 -0
- package/dist/pending-installations.d.ts +15 -0
- package/dist/pending-installations.js +237 -0
- package/dist/pending-installations.js.map +1 -0
- package/dist/release-history-panel.d.mts +22 -0
- package/dist/release-history-panel.d.ts +22 -0
- package/dist/release-history-panel.js +102 -0
- package/dist/release-history-panel.js.map +1 -0
- package/dist/release-notes-card.d.mts +13 -0
- package/dist/release-notes-card.d.ts +13 -0
- package/dist/release-notes-card.js +25 -0
- package/dist/release-notes-card.js.map +1 -0
- package/dist/security-card.d.mts +73 -0
- package/dist/security-card.d.ts +73 -0
- package/dist/security-card.js +702 -0
- package/dist/security-card.js.map +1 -0
- package/dist/styles.css +1885 -191
- package/dist/support-bundle-collection-card.d.mts +20 -0
- package/dist/support-bundle-collection-card.d.ts +20 -0
- package/dist/support-bundle-collection-card.js +172 -0
- package/dist/support-bundle-collection-card.js.map +1 -0
- package/dist/support-bundles-card.d.mts +19 -0
- package/dist/support-bundles-card.d.ts +19 -0
- package/dist/support-bundles-card.js +308 -0
- package/dist/support-bundles-card.js.map +1 -0
- package/dist/support-card.d.mts +8 -0
- package/dist/support-card.d.ts +8 -0
- package/dist/support-card.js +307 -0
- package/dist/support-card.js.map +1 -0
- package/dist/team-selection.d.mts +23 -0
- package/dist/team-selection.d.ts +23 -0
- package/dist/team-selection.js +119 -0
- package/dist/team-selection.js.map +1 -0
- package/dist/team-settings-card-Dq1d9b5c.d.mts +14 -0
- package/dist/team-settings-card-Dq1d9b5c.d.ts +14 -0
- package/dist/team-settings-card.d.mts +2 -0
- package/dist/team-settings-card.d.ts +2 -0
- package/dist/team-settings-card.js +80 -0
- package/dist/team-settings-card.js.map +1 -0
- package/dist/team-settings.d.mts +25 -0
- package/dist/team-settings.d.ts +25 -0
- package/dist/team-settings.js +138 -0
- package/dist/team-settings.js.map +1 -0
- package/dist/top-nav-0mb1K_H0.d.mts +32 -0
- package/dist/top-nav-0mb1K_H0.d.ts +32 -0
- package/dist/top-nav-user-menu.d.mts +18 -0
- package/dist/top-nav-user-menu.d.ts +18 -0
- package/dist/top-nav-user-menu.js +175 -0
- package/dist/top-nav-user-menu.js.map +1 -0
- package/dist/top-nav.d.mts +3 -0
- package/dist/top-nav.d.ts +3 -0
- package/dist/top-nav.js +400 -0
- package/dist/top-nav.js.map +1 -0
- package/dist/update-layout.d.mts +12 -0
- package/dist/update-layout.d.ts +12 -0
- package/dist/update-layout.js +407 -0
- package/dist/update-layout.js.map +1 -0
- package/dist/updates-card-BbubBrVR.d.mts +18 -0
- package/dist/updates-card-BbubBrVR.d.ts +18 -0
- package/dist/updates-card.d.mts +2 -0
- package/dist/updates-card.d.ts +2 -0
- package/dist/updates-card.js +87 -0
- package/dist/updates-card.js.map +1 -0
- package/dist/upload-support-bundle-modal.d.mts +19 -0
- package/dist/upload-support-bundle-modal.d.ts +19 -0
- package/dist/upload-support-bundle-modal.js +145 -0
- package/dist/upload-support-bundle-modal.js.map +1 -0
- package/dist/user-settings-card.d.mts +8 -0
- package/dist/user-settings-card.d.ts +8 -0
- package/dist/user-settings-card.js +23 -0
- package/dist/user-settings-card.js.map +1 -0
- package/dist/user-settings.d.mts +47 -0
- package/dist/user-settings.d.ts +47 -0
- package/dist/user-settings.js +370 -0
- package/dist/user-settings.js.map +1 -0
- package/dist/utils/index.d.mts +70 -0
- package/dist/utils/index.d.ts +70 -0
- package/dist/utils/index.js +177 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +163 -3
package/dist/actions/index.js
CHANGED
|
@@ -1,13 +1,1944 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Enterprise Portal Components
|
|
5
7
|
* This file is generated by tsup. Do not edit manually.
|
|
6
8
|
*/
|
|
7
9
|
|
|
10
|
+
|
|
11
|
+
// src/utils/api-client.ts
|
|
12
|
+
async function authenticatedFetch(url, options = {}) {
|
|
13
|
+
const { token, ...fetchOptions } = options;
|
|
14
|
+
const headers = new Headers(fetchOptions.headers);
|
|
15
|
+
if (token) {
|
|
16
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
17
|
+
}
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
...fetchOptions,
|
|
20
|
+
headers
|
|
21
|
+
});
|
|
22
|
+
if (response.status === 401) {
|
|
23
|
+
await handle401();
|
|
24
|
+
}
|
|
25
|
+
if (response.status === 502 || response.status === 503 || response.status === 504) {
|
|
26
|
+
await handleServerError(response.status);
|
|
27
|
+
}
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
async function handle401() {
|
|
31
|
+
const { redirect } = await import('next/navigation');
|
|
32
|
+
return redirect("/?expired=1");
|
|
33
|
+
}
|
|
34
|
+
async function handleServerError(statusCode) {
|
|
35
|
+
const { redirect } = await import('next/navigation');
|
|
36
|
+
let sourceUrl;
|
|
37
|
+
try {
|
|
38
|
+
const { headers } = await import('next/headers');
|
|
39
|
+
const headersList = await headers();
|
|
40
|
+
const referer = headersList.get("referer");
|
|
41
|
+
const host = headersList.get("host");
|
|
42
|
+
const pathname = headersList.get("x-invoke-path") || headersList.get("x-forwarded-path");
|
|
43
|
+
if (referer) {
|
|
44
|
+
sourceUrl = referer;
|
|
45
|
+
} else if (host && pathname) {
|
|
46
|
+
const protocol = headersList.get("x-forwarded-proto") || "https";
|
|
47
|
+
sourceUrl = `${protocol}://${host}${pathname}`;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.debug("[portal-components] Could not determine source URL", error);
|
|
51
|
+
}
|
|
52
|
+
const params = new URLSearchParams({ code: String(statusCode) });
|
|
53
|
+
if (sourceUrl) {
|
|
54
|
+
params.set("source", sourceUrl);
|
|
55
|
+
}
|
|
56
|
+
return redirect(`/error?${params.toString()}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
8
59
|
// src/actions/index.ts
|
|
60
|
+
var getApiOrigin = () => {
|
|
61
|
+
return (process.env.REPLICATED_APP_ORIGIN || "https://replicated.app").replace(/\/+$/, "");
|
|
62
|
+
};
|
|
9
63
|
var defineServerAction = (definition) => definition;
|
|
64
|
+
var createServiceAccount = defineServerAction({
|
|
65
|
+
id: "service-account/create",
|
|
66
|
+
description: "Creates a service account for installing applications",
|
|
67
|
+
visibility: "customer",
|
|
68
|
+
tags: ["service-account", "install"],
|
|
69
|
+
async run({ token, name }) {
|
|
70
|
+
if (!token || typeof token !== "string") {
|
|
71
|
+
throw new Error("Service account creation requires a session token");
|
|
72
|
+
}
|
|
73
|
+
if (!name || typeof name !== "string" || !name.trim()) {
|
|
74
|
+
throw new Error("Service account name is required");
|
|
75
|
+
}
|
|
76
|
+
const endpoint = `${getApiOrigin()}/v3/service-account`;
|
|
77
|
+
if (process.env.NODE_ENV !== "production") {
|
|
78
|
+
console.debug(
|
|
79
|
+
"[portal-components] creating service account via %s",
|
|
80
|
+
endpoint
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const response = await authenticatedFetch(endpoint, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
token,
|
|
86
|
+
headers: {
|
|
87
|
+
"content-type": "application/json"
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({ account_name: name.trim() })
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const errorText = await response.text();
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Service account creation failed (${response.status} ${response.statusText}): ${errorText}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
var initiateLogin = defineServerAction({
|
|
102
|
+
id: "auth/initiate-login",
|
|
103
|
+
description: "Begins the passwordless login flow by dispatching a magic link email.",
|
|
104
|
+
visibility: "customer",
|
|
105
|
+
tags: ["auth", "login", "session"],
|
|
106
|
+
async run(input) {
|
|
107
|
+
const endpoint = `${getApiOrigin()}/v3/login/magic-link`;
|
|
108
|
+
const appSlug = process.env.PORTAL_APP_SLUG;
|
|
109
|
+
if (!appSlug) {
|
|
110
|
+
throw new Error("PORTAL_APP_SLUG is not configured");
|
|
111
|
+
}
|
|
112
|
+
const portalOrigin = process.env.PORTAL_ORIGIN ?? "https://enterprise.replicated.com";
|
|
113
|
+
const redirectUri = `${portalOrigin.replace(/\/+$/, "")}/${appSlug}/login`;
|
|
114
|
+
const response = await fetch(endpoint, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"content-type": "application/json"
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
app_slug: appSlug,
|
|
121
|
+
email_address: input.email,
|
|
122
|
+
redirect_uri: redirectUri
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Magic link request failed (${response.status} ${response.statusText})`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const data = await response.json();
|
|
131
|
+
if (data.saml_redirect_required && data.saml_customer_id) {
|
|
132
|
+
return {
|
|
133
|
+
status: "saml_redirect",
|
|
134
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
135
|
+
message: "SAML authentication required",
|
|
136
|
+
saml: {
|
|
137
|
+
redirectRequired: true,
|
|
138
|
+
customerId: data.saml_customer_id,
|
|
139
|
+
email: input.email,
|
|
140
|
+
appSlug
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
status: "ok",
|
|
146
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
147
|
+
message: `Magic link requested for ${input.email}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
var verifyMagicLink = defineServerAction({
|
|
152
|
+
id: "auth/verify-magic-link",
|
|
153
|
+
description: "Verifies the 12-digit code provided via email and returns a JWT.",
|
|
154
|
+
visibility: "customer",
|
|
155
|
+
tags: ["auth", "login", "verify"],
|
|
156
|
+
async run({ nonce }) {
|
|
157
|
+
const endpoint = `${getApiOrigin()}/v3/login/magic-link/verify`;
|
|
158
|
+
if (process.env.NODE_ENV !== "production") {
|
|
159
|
+
console.debug(
|
|
160
|
+
"[portal-components] verifying magic link via %s",
|
|
161
|
+
endpoint
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const response = await fetch(endpoint, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
"content-type": "application/json"
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify({ nonce })
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
if (response.status === 401) {
|
|
173
|
+
try {
|
|
174
|
+
const errorBody = await response.json();
|
|
175
|
+
if (errorBody?.is_expired === true) {
|
|
176
|
+
const error3 = {
|
|
177
|
+
code: "expired",
|
|
178
|
+
message: "Magic link has expired. A new link has been sent to your email.",
|
|
179
|
+
isExpired: true
|
|
180
|
+
};
|
|
181
|
+
throw error3;
|
|
182
|
+
}
|
|
183
|
+
} catch (parseError) {
|
|
184
|
+
if (parseError?.code === "expired") {
|
|
185
|
+
throw parseError;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const error2 = {
|
|
189
|
+
code: "invalid_code",
|
|
190
|
+
message: "Incorrect code, check your email and try again."
|
|
191
|
+
};
|
|
192
|
+
throw error2;
|
|
193
|
+
}
|
|
194
|
+
const error = {
|
|
195
|
+
code: "unknown",
|
|
196
|
+
message: `Magic link verification failed (${response.status} ${response.statusText})`
|
|
197
|
+
};
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
const payload = await response.json();
|
|
201
|
+
const token = payload?.token ?? payload?.jwt ?? payload?.access_token;
|
|
202
|
+
if (typeof token !== "string") {
|
|
203
|
+
throw new Error("Magic link verification succeeded but no token returned");
|
|
204
|
+
}
|
|
205
|
+
return { token, raw: payload };
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
var fetchCustomBrandingImpl = async () => {
|
|
209
|
+
const appSlug = process.env.PORTAL_APP_SLUG;
|
|
210
|
+
if (!appSlug) {
|
|
211
|
+
throw new Error("PORTAL_APP_SLUG is not configured");
|
|
212
|
+
}
|
|
213
|
+
const url = `${getApiOrigin()}/v3/custom-branding?app_slug=${encodeURIComponent(
|
|
214
|
+
appSlug
|
|
215
|
+
)}`;
|
|
216
|
+
if (process.env.NODE_ENV !== "production") {
|
|
217
|
+
console.debug(
|
|
218
|
+
"[portal-components] fetching custom branding via %s",
|
|
219
|
+
url
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const response = await fetch(url, {
|
|
223
|
+
headers: {
|
|
224
|
+
accept: "application/json"
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Custom branding request failed (${response.status} ${response.statusText})`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const payload = await response.json();
|
|
233
|
+
const brandingData = payload?.branding_data;
|
|
234
|
+
if (typeof brandingData !== "string") {
|
|
235
|
+
throw new Error("Custom branding response missing branding_data string");
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
brandingData,
|
|
239
|
+
documentation: payload?.documentation ?? null
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
var fetchCustomBranding = react.cache(fetchCustomBrandingImpl);
|
|
243
|
+
var decodeJwtPayload = (token) => {
|
|
244
|
+
const parts = token.split(".");
|
|
245
|
+
if (parts.length !== 3) {
|
|
246
|
+
throw new Error("Invalid JWT received");
|
|
247
|
+
}
|
|
248
|
+
const payloadSegment = parts[1];
|
|
249
|
+
if (!payloadSegment) {
|
|
250
|
+
throw new Error("JWT payload segment missing");
|
|
251
|
+
}
|
|
252
|
+
const padded = payloadSegment.padEnd(
|
|
253
|
+
payloadSegment.length + (4 - payloadSegment.length % 4) % 4,
|
|
254
|
+
"="
|
|
255
|
+
);
|
|
256
|
+
const decoded = Buffer.from(padded, "base64").toString("utf-8");
|
|
257
|
+
return JSON.parse(decoded);
|
|
258
|
+
};
|
|
259
|
+
var getCustomerIdFromToken = (token) => {
|
|
260
|
+
const payload = decodeJwtPayload(token);
|
|
261
|
+
const customerId = payload?.customer_id || payload?.customerId;
|
|
262
|
+
if (typeof customerId !== "string" || !customerId.trim()) {
|
|
263
|
+
throw new Error("Unable to determine customer_id from session token");
|
|
264
|
+
}
|
|
265
|
+
return customerId.trim();
|
|
266
|
+
};
|
|
267
|
+
var resolveSupportBundlesEndpoint = () => {
|
|
268
|
+
const fallback = `${getApiOrigin()}/v3/supportbundles`;
|
|
269
|
+
const explicit = process.env.SUPPORT_BUNDLES_ENDPOINT;
|
|
270
|
+
if (!explicit) {
|
|
271
|
+
return new URL(fallback);
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
return new URL(explicit);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.warn(
|
|
277
|
+
`[portal-components] invalid SUPPORT_BUNDLES_ENDPOINT, using fallback`,
|
|
278
|
+
error
|
|
279
|
+
);
|
|
280
|
+
return new URL(fallback);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
var listSupportBundles = defineServerAction({
|
|
284
|
+
id: "support/list-bundles",
|
|
285
|
+
description: "Fetches support bundles associated with the customer found in the portal session JWT.",
|
|
286
|
+
visibility: "customer",
|
|
287
|
+
tags: ["support", "bundles"],
|
|
288
|
+
async run({ token }, context) {
|
|
289
|
+
if (!token || typeof token !== "string") {
|
|
290
|
+
throw new Error("Support bundle listing requires a session token");
|
|
291
|
+
}
|
|
292
|
+
const payload = decodeJwtPayload(token);
|
|
293
|
+
const customerId = payload?.customer_id;
|
|
294
|
+
if (typeof customerId !== "string" || !customerId.trim()) {
|
|
295
|
+
throw new Error("Unable to determine customer_id from session token");
|
|
296
|
+
}
|
|
297
|
+
const url = resolveSupportBundlesEndpoint();
|
|
298
|
+
url.searchParams.set("customer_id", customerId.trim());
|
|
299
|
+
if (process.env.NODE_ENV !== "production") {
|
|
300
|
+
console.debug("[portal-components] fetching support bundles via %s", url);
|
|
301
|
+
}
|
|
302
|
+
const response = await authenticatedFetch(url.toString(), {
|
|
303
|
+
token,
|
|
304
|
+
headers: {
|
|
305
|
+
accept: "application/json"
|
|
306
|
+
},
|
|
307
|
+
signal: context?.signal
|
|
308
|
+
});
|
|
309
|
+
if (context?.signal?.aborted) {
|
|
310
|
+
throw new Error("Support bundles request was aborted");
|
|
311
|
+
}
|
|
312
|
+
if (!response.ok) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Support bundles request failed (${response.status} ${response.statusText})`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const raw = await response.json();
|
|
318
|
+
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
319
|
+
const parseInsights = (raw2) => {
|
|
320
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
321
|
+
return raw2.filter((i) => i && typeof i === "object").map((i) => ({
|
|
322
|
+
level: String(i.level ?? ""),
|
|
323
|
+
primary: String(i.primary ?? ""),
|
|
324
|
+
key: typeof i.key === "string" ? i.key : void 0,
|
|
325
|
+
detail: typeof i.detail === "string" ? i.detail : void 0
|
|
326
|
+
}));
|
|
327
|
+
};
|
|
328
|
+
const bundles = Array.isArray(
|
|
329
|
+
rawRecord?.supportBundles
|
|
330
|
+
) ? (rawRecord?.supportBundles).map((item) => {
|
|
331
|
+
if (!item || typeof item !== "object") {
|
|
332
|
+
return {
|
|
333
|
+
id: "",
|
|
334
|
+
createdAt: void 0,
|
|
335
|
+
status: void 0,
|
|
336
|
+
size: void 0,
|
|
337
|
+
instanceId: void 0,
|
|
338
|
+
insights: void 0,
|
|
339
|
+
metadata: void 0
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const record = item;
|
|
343
|
+
return {
|
|
344
|
+
id: String(record.id ?? ""),
|
|
345
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : void 0,
|
|
346
|
+
status: typeof record.status === "string" ? record.status : void 0,
|
|
347
|
+
size: typeof record.size === "number" ? record.size : void 0,
|
|
348
|
+
instanceId: typeof record.instanceId === "string" ? record.instanceId : void 0,
|
|
349
|
+
insights: parseInsights(record.insights),
|
|
350
|
+
metadata: record
|
|
351
|
+
};
|
|
352
|
+
}) : Array.isArray(raw) ? raw.map((item) => {
|
|
353
|
+
if (!item || typeof item !== "object") {
|
|
354
|
+
return {
|
|
355
|
+
id: "",
|
|
356
|
+
createdAt: void 0,
|
|
357
|
+
status: void 0,
|
|
358
|
+
size: void 0,
|
|
359
|
+
instanceId: void 0,
|
|
360
|
+
insights: void 0,
|
|
361
|
+
metadata: void 0
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const record = item;
|
|
365
|
+
return {
|
|
366
|
+
id: String(record.id ?? ""),
|
|
367
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : void 0,
|
|
368
|
+
status: typeof record.status === "string" ? record.status : void 0,
|
|
369
|
+
size: typeof record.size === "number" ? record.size : void 0,
|
|
370
|
+
instanceId: typeof record.instanceId === "string" ? record.instanceId : void 0,
|
|
371
|
+
insights: parseInsights(record.insights),
|
|
372
|
+
metadata: record
|
|
373
|
+
};
|
|
374
|
+
}) : [];
|
|
375
|
+
const totalCount = (() => {
|
|
376
|
+
if (rawRecord) {
|
|
377
|
+
if (typeof rawRecord.totalCount === "number" && Number.isFinite(rawRecord.totalCount)) {
|
|
378
|
+
return rawRecord.totalCount;
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(rawRecord.supportBundles)) {
|
|
381
|
+
return rawRecord.supportBundles.length;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (Array.isArray(raw)) {
|
|
385
|
+
return raw.length;
|
|
386
|
+
}
|
|
387
|
+
return bundles.length;
|
|
388
|
+
})();
|
|
389
|
+
return {
|
|
390
|
+
bundles,
|
|
391
|
+
totalCount,
|
|
392
|
+
raw
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
var downloadSupportBundle = defineServerAction({
|
|
397
|
+
id: "support/download-bundle",
|
|
398
|
+
description: "Gets a signed URL for downloading a support bundle.",
|
|
399
|
+
visibility: "customer",
|
|
400
|
+
tags: ["support", "bundles", "download"],
|
|
401
|
+
async run({ token, bundleId }, context) {
|
|
402
|
+
if (!token || typeof token !== "string") {
|
|
403
|
+
throw new Error("Support bundle download requires a session token");
|
|
404
|
+
}
|
|
405
|
+
if (!bundleId || typeof bundleId !== "string") {
|
|
406
|
+
throw new Error("Support bundle download requires a bundle ID");
|
|
407
|
+
}
|
|
408
|
+
const payload = decodeJwtPayload(token);
|
|
409
|
+
const customerId = payload?.customer_id;
|
|
410
|
+
if (typeof customerId !== "string" || !customerId.trim()) {
|
|
411
|
+
throw new Error("Unable to determine customer_id from session token");
|
|
412
|
+
}
|
|
413
|
+
const endpoint = `${getApiOrigin()}/v3/supportbundle/${encodeURIComponent(bundleId)}/download?customer_id=${encodeURIComponent(customerId.trim())}`;
|
|
414
|
+
if (process.env.NODE_ENV !== "production") {
|
|
415
|
+
console.debug("[portal-components] getting support bundle download URL via %s", endpoint);
|
|
416
|
+
}
|
|
417
|
+
const response = await authenticatedFetch(endpoint, {
|
|
418
|
+
method: "GET",
|
|
419
|
+
token,
|
|
420
|
+
headers: {
|
|
421
|
+
accept: "application/json"
|
|
422
|
+
},
|
|
423
|
+
signal: context?.signal
|
|
424
|
+
});
|
|
425
|
+
if (!response.ok) {
|
|
426
|
+
const errorText = await response.text().catch(() => "");
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Support bundle download URL request failed (${response.status} ${response.statusText}): ${errorText}`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
const data = await response.json();
|
|
432
|
+
const signedUrl = data?.signedUrl;
|
|
433
|
+
if (typeof signedUrl !== "string" || !signedUrl) {
|
|
434
|
+
throw new Error("Support bundle download response missing signedUrl");
|
|
435
|
+
}
|
|
436
|
+
return { signedUrl };
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
var deleteSupportBundle = defineServerAction({
|
|
440
|
+
id: "support/delete-bundle",
|
|
441
|
+
description: "Deletes a support bundle.",
|
|
442
|
+
visibility: "customer",
|
|
443
|
+
tags: ["support", "bundles", "delete"],
|
|
444
|
+
async run({ token, bundleId }, context) {
|
|
445
|
+
if (!token || typeof token !== "string") {
|
|
446
|
+
throw new Error("Support bundle deletion requires a session token");
|
|
447
|
+
}
|
|
448
|
+
if (!bundleId || typeof bundleId !== "string") {
|
|
449
|
+
throw new Error("Support bundle deletion requires a bundle ID");
|
|
450
|
+
}
|
|
451
|
+
const payload = decodeJwtPayload(token);
|
|
452
|
+
const customerId = payload?.customer_id;
|
|
453
|
+
if (typeof customerId !== "string" || !customerId.trim()) {
|
|
454
|
+
throw new Error("Unable to determine customer_id from session token");
|
|
455
|
+
}
|
|
456
|
+
const endpoint = `${getApiOrigin()}/v3/supportbundle/${encodeURIComponent(bundleId)}?customer_id=${encodeURIComponent(customerId.trim())}`;
|
|
457
|
+
if (process.env.NODE_ENV !== "production") {
|
|
458
|
+
console.debug("[portal-components] deleting support bundle via %s", endpoint);
|
|
459
|
+
}
|
|
460
|
+
const response = await authenticatedFetch(endpoint, {
|
|
461
|
+
method: "DELETE",
|
|
462
|
+
token,
|
|
463
|
+
headers: {
|
|
464
|
+
accept: "application/json"
|
|
465
|
+
},
|
|
466
|
+
signal: context?.signal
|
|
467
|
+
});
|
|
468
|
+
if (!response.ok) {
|
|
469
|
+
const errorText = await response.text().catch(() => "");
|
|
470
|
+
if (response.status === 404) {
|
|
471
|
+
throw new Error("Support bundle not found");
|
|
472
|
+
}
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Support bundle deletion failed (${response.status} ${response.statusText}): ${errorText}`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return { success: true };
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
var uploadSupportBundle = defineServerAction({
|
|
481
|
+
id: "support/upload-bundle",
|
|
482
|
+
description: "Uploads a support bundle to the server.",
|
|
483
|
+
visibility: "customer",
|
|
484
|
+
tags: ["support", "bundles", "upload"],
|
|
485
|
+
async run({ token, appId, fileContent, contentLength }, context) {
|
|
486
|
+
if (!token || typeof token !== "string") {
|
|
487
|
+
throw new Error("Support bundle upload requires a session token");
|
|
488
|
+
}
|
|
489
|
+
if (!appId || typeof appId !== "string") {
|
|
490
|
+
throw new Error("Support bundle upload requires an app ID");
|
|
491
|
+
}
|
|
492
|
+
if (!fileContent || !(fileContent instanceof ArrayBuffer)) {
|
|
493
|
+
throw new Error("Support bundle upload requires file content");
|
|
494
|
+
}
|
|
495
|
+
const endpoint = `${getApiOrigin()}/v3/supportbundle/upload/${encodeURIComponent(appId)}`;
|
|
496
|
+
if (process.env.NODE_ENV !== "production") {
|
|
497
|
+
console.debug("[portal-components] uploading support bundle via %s", endpoint);
|
|
498
|
+
}
|
|
499
|
+
const response = await authenticatedFetch(endpoint, {
|
|
500
|
+
method: "POST",
|
|
501
|
+
token,
|
|
502
|
+
headers: {
|
|
503
|
+
"content-type": "application/gzip",
|
|
504
|
+
"content-length": String(contentLength)
|
|
505
|
+
},
|
|
506
|
+
body: fileContent,
|
|
507
|
+
signal: context?.signal
|
|
508
|
+
});
|
|
509
|
+
if (!response.ok) {
|
|
510
|
+
const errorText = await response.text().catch(() => "");
|
|
511
|
+
throw new Error(
|
|
512
|
+
`Support bundle upload failed (${response.status} ${response.statusText}): ${errorText}`
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
const data = await response.json();
|
|
516
|
+
return {
|
|
517
|
+
bundleId: data?.bundleId ?? data?.bundle_id ?? "",
|
|
518
|
+
slug: data?.slug ?? ""
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
var getSupportBundleUploadUrl = (appId) => {
|
|
523
|
+
return `${getApiOrigin()}/v3/supportbundle/upload/${encodeURIComponent(appId)}`;
|
|
524
|
+
};
|
|
525
|
+
var listReleases = defineServerAction({
|
|
526
|
+
id: "releases/list",
|
|
527
|
+
description: "Lists available releases for the authenticated customer.",
|
|
528
|
+
visibility: "customer",
|
|
529
|
+
tags: ["releases"],
|
|
530
|
+
async run({ token }, context) {
|
|
531
|
+
if (!token || typeof token !== "string") {
|
|
532
|
+
throw new Error("List releases requires a session token");
|
|
533
|
+
}
|
|
534
|
+
const endpoint = `${getApiOrigin()}/v3/release-history`;
|
|
535
|
+
console.log("[portal-components] listReleases request", {
|
|
536
|
+
endpoint
|
|
537
|
+
});
|
|
538
|
+
const response = await authenticatedFetch(endpoint, {
|
|
539
|
+
method: "GET",
|
|
540
|
+
token,
|
|
541
|
+
headers: {
|
|
542
|
+
accept: "application/json"
|
|
543
|
+
},
|
|
544
|
+
signal: context?.signal
|
|
545
|
+
});
|
|
546
|
+
const bodyText = await response.text().catch((error) => {
|
|
547
|
+
console.warn("[portal-components] listReleases read error", error);
|
|
548
|
+
return null;
|
|
549
|
+
});
|
|
550
|
+
console.log("[portal-components] listReleases response", response.status, bodyText);
|
|
551
|
+
if (!response.ok) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`List releases request failed (${response.status} ${response.statusText})`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
status: response.status,
|
|
558
|
+
body: bodyText
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
var asRecord = (value) => {
|
|
563
|
+
if (value && typeof value === "object") {
|
|
564
|
+
return value;
|
|
565
|
+
}
|
|
566
|
+
return void 0;
|
|
567
|
+
};
|
|
568
|
+
var getValue = (record, key) => record ? record[key] : void 0;
|
|
569
|
+
var getString = (record, key) => {
|
|
570
|
+
const value = getValue(record, key);
|
|
571
|
+
return typeof value === "string" ? value : void 0;
|
|
572
|
+
};
|
|
573
|
+
var getBoolean = (record, key) => {
|
|
574
|
+
const value = getValue(record, key);
|
|
575
|
+
if (typeof value === "boolean") {
|
|
576
|
+
return value;
|
|
577
|
+
}
|
|
578
|
+
if (typeof value === "number") {
|
|
579
|
+
return value === 1;
|
|
580
|
+
}
|
|
581
|
+
if (typeof value === "string") {
|
|
582
|
+
const normalized = value.trim().toLowerCase();
|
|
583
|
+
if (["true", "1", "yes"].includes(normalized)) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (["false", "0", "no"].includes(normalized)) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return void 0;
|
|
591
|
+
};
|
|
592
|
+
var toDisplayValue = (value) => {
|
|
593
|
+
if (value === null || value === void 0) {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
if (typeof value === "string") {
|
|
597
|
+
return value;
|
|
598
|
+
}
|
|
599
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
600
|
+
return String(value);
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
return JSON.stringify(value);
|
|
604
|
+
} catch {
|
|
605
|
+
return String(value);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
var normalizeStringArray = (value) => {
|
|
609
|
+
if (Array.isArray(value)) {
|
|
610
|
+
const normalized = value.map(
|
|
611
|
+
(item) => typeof item === "string" ? item.trim() : ""
|
|
612
|
+
).filter((item) => item.length > 0);
|
|
613
|
+
return normalized.length ? normalized : void 0;
|
|
614
|
+
}
|
|
615
|
+
if (typeof value === "string") {
|
|
616
|
+
const normalized = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
617
|
+
return normalized.length ? normalized : void 0;
|
|
618
|
+
}
|
|
619
|
+
return void 0;
|
|
620
|
+
};
|
|
621
|
+
var normalizeLicenseFields = (input) => {
|
|
622
|
+
if (!input) {
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
if (Array.isArray(input)) {
|
|
626
|
+
return input.map((field, index) => {
|
|
627
|
+
if (!field || typeof field !== "object") {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
const candidate = field;
|
|
631
|
+
const key = typeof candidate.key === "string" && candidate.key.trim().length ? candidate.key.trim() : typeof candidate.name === "string" && candidate.name.trim().length ? candidate.name.trim() : typeof candidate.label === "string" && candidate.label.trim().length ? candidate.label.trim() : `field-${index}`;
|
|
632
|
+
const label = typeof candidate.label === "string" && candidate.label.trim().length ? candidate.label.trim() : typeof candidate.name === "string" && candidate.name.trim().length ? candidate.name.trim() : key;
|
|
633
|
+
let value = candidate.value ?? candidate.data ?? candidate.content;
|
|
634
|
+
if ((value === void 0 || value === null) && typeof candidate.text === "string") {
|
|
635
|
+
value = candidate.text;
|
|
636
|
+
}
|
|
637
|
+
if ((value === void 0 || value === null) && typeof candidate.defaultValue === "string") {
|
|
638
|
+
value = candidate.defaultValue;
|
|
639
|
+
}
|
|
640
|
+
const isSecret = Boolean(
|
|
641
|
+
candidate.isSecret ?? candidate.secret ?? candidate.masked
|
|
642
|
+
);
|
|
643
|
+
const resolved = toDisplayValue(value);
|
|
644
|
+
return {
|
|
645
|
+
key,
|
|
646
|
+
label,
|
|
647
|
+
value: resolved,
|
|
648
|
+
isSecret
|
|
649
|
+
};
|
|
650
|
+
}).filter((field) => Boolean(field));
|
|
651
|
+
}
|
|
652
|
+
if (typeof input === "object") {
|
|
653
|
+
return Object.entries(input).map(
|
|
654
|
+
([key, value]) => {
|
|
655
|
+
let resolvedValue = value;
|
|
656
|
+
let isSecret = false;
|
|
657
|
+
if (value && typeof value === "object") {
|
|
658
|
+
const obj = value;
|
|
659
|
+
if ("value" in obj) {
|
|
660
|
+
resolvedValue = obj.value;
|
|
661
|
+
}
|
|
662
|
+
isSecret = Boolean(obj.isSecret ?? obj.secret ?? obj.masked);
|
|
663
|
+
}
|
|
664
|
+
const normalized = toDisplayValue(resolvedValue);
|
|
665
|
+
return {
|
|
666
|
+
key,
|
|
667
|
+
label: key,
|
|
668
|
+
value: normalized,
|
|
669
|
+
isSecret
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return [];
|
|
675
|
+
};
|
|
676
|
+
var extractChannelNames = (input) => {
|
|
677
|
+
if (!Array.isArray(input)) {
|
|
678
|
+
return void 0;
|
|
679
|
+
}
|
|
680
|
+
const names = input.map((item) => {
|
|
681
|
+
if (typeof item === "string") {
|
|
682
|
+
return item.trim();
|
|
683
|
+
}
|
|
684
|
+
const record = asRecord(item);
|
|
685
|
+
if (!record) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
return getString(record, "name") ?? getString(record, "channelName") ?? getString(record, "channel") ?? getString(record, "channelSlug") ?? getString(record, "slug") ?? void 0;
|
|
689
|
+
}).filter((name) => Boolean(name && name.length));
|
|
690
|
+
return names.length ? names : void 0;
|
|
691
|
+
};
|
|
692
|
+
var normalizeEntitlementFields = (fieldsInput, valuesInput) => {
|
|
693
|
+
const valuesMap = /* @__PURE__ */ new Map();
|
|
694
|
+
const assignValue = (key, value) => {
|
|
695
|
+
if (!key) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
valuesMap.set(key, toDisplayValue(value));
|
|
699
|
+
};
|
|
700
|
+
if (Array.isArray(valuesInput)) {
|
|
701
|
+
valuesInput.forEach((item) => {
|
|
702
|
+
const record = asRecord(item);
|
|
703
|
+
if (!record) {
|
|
704
|
+
if (typeof item === "string") {
|
|
705
|
+
assignValue(item, item);
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const key = getString(record, "name") ?? getString(record, "field") ?? getString(record, "title") ?? getString(record, "label") ?? getString(record, "slug") ?? (() => {
|
|
710
|
+
const idValue = getValue(record, "id");
|
|
711
|
+
if (typeof idValue === "string" || typeof idValue === "number") {
|
|
712
|
+
return String(idValue);
|
|
713
|
+
}
|
|
714
|
+
return void 0;
|
|
715
|
+
})();
|
|
716
|
+
const value = getValue(record, "value") ?? getValue(record, "currentValue") ?? getValue(record, "entitlementValue") ?? getValue(record, "content") ?? getValue(record, "data") ?? getValue(record, "defaultVal") ?? getValue(record, "defaultValue");
|
|
717
|
+
assignValue(key, value);
|
|
718
|
+
});
|
|
719
|
+
} else if (valuesInput && typeof valuesInput === "object") {
|
|
720
|
+
Object.entries(valuesInput).forEach(
|
|
721
|
+
([key, value]) => assignValue(key, value)
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
const normalized = [];
|
|
725
|
+
if (Array.isArray(fieldsInput)) {
|
|
726
|
+
fieldsInput.forEach((item, index) => {
|
|
727
|
+
const record = asRecord(item);
|
|
728
|
+
if (!record) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const baseKey = getString(record, "name") ?? getString(record, "field") ?? getString(record, "slug") ?? `entitlement-${index}`;
|
|
732
|
+
const key = `entitlement-${baseKey}`;
|
|
733
|
+
const label = getString(record, "title") ?? getString(record, "label") ?? baseKey;
|
|
734
|
+
const defaultValue = getString(record, "defaultVal") ?? getString(record, "default") ?? getString(record, "defaultValue");
|
|
735
|
+
const value = valuesMap.get(baseKey) ?? valuesMap.get(label) ?? defaultValue ?? null;
|
|
736
|
+
const isSecret = Boolean(
|
|
737
|
+
getBoolean(record, "secret") ?? getBoolean(record, "isSecret") ?? getBoolean(record, "masked")
|
|
738
|
+
);
|
|
739
|
+
normalized.push({
|
|
740
|
+
key,
|
|
741
|
+
label,
|
|
742
|
+
value,
|
|
743
|
+
isSecret
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
valuesMap.forEach((value, key) => {
|
|
748
|
+
const normalizedKey = `entitlement-${key}`;
|
|
749
|
+
if (!normalized.some((field) => field.key === normalizedKey)) {
|
|
750
|
+
normalized.push({
|
|
751
|
+
key: normalizedKey,
|
|
752
|
+
label: key,
|
|
753
|
+
value
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
return normalized;
|
|
758
|
+
};
|
|
759
|
+
var normalizeLicensePayload = (payload) => {
|
|
760
|
+
const payloadRecord = asRecord(payload);
|
|
761
|
+
const rootRecord = asRecord(getValue(payloadRecord, "license")) ?? asRecord(getValue(payloadRecord, "data")) ?? payloadRecord ?? {};
|
|
762
|
+
const sourceRecord = asRecord(getValue(rootRecord, "metadata")) ?? rootRecord;
|
|
763
|
+
const customer = asRecord(getValue(rootRecord, "customer")) ?? asRecord(getValue(sourceRecord, "customer")) ?? asRecord(getValue(payloadRecord, "customer")) ?? {};
|
|
764
|
+
let releaseChannels = normalizeStringArray(
|
|
765
|
+
getValue(rootRecord, "releaseChannels") ?? getValue(sourceRecord, "releaseChannels") ?? getValue(sourceRecord, "channels") ?? getValue(rootRecord, "channels") ?? getValue(sourceRecord, "channel") ?? getValue(rootRecord, "channel")
|
|
766
|
+
) ?? void 0;
|
|
767
|
+
if (!releaseChannels) {
|
|
768
|
+
releaseChannels = extractChannelNames(getValue(rootRecord, "channels")) ?? extractChannelNames(getValue(sourceRecord, "channels")) ?? void 0;
|
|
769
|
+
}
|
|
770
|
+
let installMethods = normalizeStringArray(
|
|
771
|
+
getValue(rootRecord, "installMethods") ?? getValue(sourceRecord, "installMethods") ?? getValue(sourceRecord, "install_options") ?? getValue(rootRecord, "install_options") ?? getValue(sourceRecord, "installOptions")
|
|
772
|
+
) ?? void 0;
|
|
773
|
+
if (!installMethods || installMethods.length === 0) {
|
|
774
|
+
const resolved = [];
|
|
775
|
+
const flag = (key) => getBoolean(rootRecord, key) ?? getBoolean(sourceRecord, key) ?? false;
|
|
776
|
+
if (flag("isKotsInstallEnabled")) {
|
|
777
|
+
resolved.push("Replicated KOTS");
|
|
778
|
+
}
|
|
779
|
+
if (flag("isHelmInstallEnabled")) {
|
|
780
|
+
resolved.push("Helm");
|
|
781
|
+
}
|
|
782
|
+
if (flag("isHelmAirgapEnabled")) {
|
|
783
|
+
resolved.push("Helm Airgap");
|
|
784
|
+
}
|
|
785
|
+
if (flag("isEmbeddedClusterDownloadEnabled") || flag("isEmbeddedClusterMultiNodeEnabled")) {
|
|
786
|
+
resolved.push("Embedded Cluster");
|
|
787
|
+
}
|
|
788
|
+
if (flag("isKurlInstallEnabled")) {
|
|
789
|
+
resolved.push("kURL");
|
|
790
|
+
}
|
|
791
|
+
if (flag("isGitopsSupported")) {
|
|
792
|
+
resolved.push("GitOps");
|
|
793
|
+
}
|
|
794
|
+
if (resolved.length) {
|
|
795
|
+
installMethods = Array.from(new Set(resolved));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const expiresAtSource = getValue(sourceRecord, "expiresAt") ?? getValue(sourceRecord, "expireAt") ?? getValue(sourceRecord, "expire_at") ?? getValue(sourceRecord, "expiration") ?? getValue(sourceRecord, "expirationDate") ?? getValue(sourceRecord, "expires_on") ?? getValue(rootRecord, "expiresAt") ?? getValue(rootRecord, "expireAt") ?? getValue(rootRecord, "expire_at") ?? getValue(rootRecord, "expiration");
|
|
799
|
+
const expiresAt = typeof expiresAtSource === "string" && expiresAtSource.trim().length ? expiresAtSource : expiresAtSource === null ? null : void 0;
|
|
800
|
+
const baseFields = normalizeLicenseFields(
|
|
801
|
+
getValue(rootRecord, "additionalFields") ?? getValue(sourceRecord, "additionalFields") ?? getValue(sourceRecord, "fields") ?? getValue(rootRecord, "fields") ?? getValue(payloadRecord, "fields") ?? getValue(payloadRecord, "additional_fields")
|
|
802
|
+
);
|
|
803
|
+
const entitlementFields = normalizeEntitlementFields(
|
|
804
|
+
getValue(rootRecord, "entitlementFields") ?? getValue(sourceRecord, "entitlementFields"),
|
|
805
|
+
getValue(rootRecord, "entitlementValues") ?? getValue(sourceRecord, "entitlementValues")
|
|
806
|
+
);
|
|
807
|
+
const fields = [
|
|
808
|
+
...baseFields,
|
|
809
|
+
...entitlementFields.filter(
|
|
810
|
+
(field) => !baseFields.some((existing) => existing.key === field.key)
|
|
811
|
+
)
|
|
812
|
+
];
|
|
813
|
+
const statusFromSource = getString(sourceRecord, "status") ?? getString(sourceRecord, "state");
|
|
814
|
+
const statusLabelFromSource = getString(sourceRecord, "statusLabel") ?? getString(sourceRecord, "stateLabel");
|
|
815
|
+
const expiredFlag = getBoolean(sourceRecord, "isExpired") ?? getBoolean(rootRecord, "isExpired");
|
|
816
|
+
const derivedStatus = statusFromSource ?? (typeof expiredFlag === "boolean" ? expiredFlag ? "expired" : "active" : void 0);
|
|
817
|
+
const statusLabel = statusLabelFromSource ?? (derivedStatus ? derivedStatus.charAt(0).toUpperCase() + derivedStatus.slice(1) : void 0);
|
|
818
|
+
const licenseType = getString(sourceRecord, "licenseType") ?? getString(rootRecord, "licenseType");
|
|
819
|
+
const status = derivedStatus;
|
|
820
|
+
const license = {
|
|
821
|
+
id: getString(rootRecord, "id") ?? getString(sourceRecord, "id") ?? getString(sourceRecord, "licenseId") ?? getString(customer, "licenseId") ?? void 0,
|
|
822
|
+
status,
|
|
823
|
+
statusLabel,
|
|
824
|
+
environment: getString(sourceRecord, "environment") ?? getString(sourceRecord, "tier") ?? licenseType ?? void 0,
|
|
825
|
+
expiresAt: expiresAt ?? null,
|
|
826
|
+
releaseChannels: releaseChannels ?? [
|
|
827
|
+
getString(rootRecord, "channelName") ?? getString(rootRecord, "channel") ?? void 0
|
|
828
|
+
].filter((value) => Boolean(value)),
|
|
829
|
+
installMethods,
|
|
830
|
+
installNotes: getString(sourceRecord, "installNotes"),
|
|
831
|
+
customerName: getString(sourceRecord, "customerName") ?? getString(customer, "name") ?? void 0,
|
|
832
|
+
customerId: getString(sourceRecord, "customerId") ?? getString(customer, "id") ?? getString(rootRecord, "customerId") ?? void 0,
|
|
833
|
+
customerOrganization: getString(customer, "organization") ?? getString(sourceRecord, "customerOrganization") ?? getString(rootRecord, "customerOrganization") ?? void 0,
|
|
834
|
+
fields
|
|
835
|
+
};
|
|
836
|
+
return license;
|
|
837
|
+
};
|
|
838
|
+
var fetchLicenseDetails = defineServerAction({
|
|
839
|
+
id: "license/fetch-details",
|
|
840
|
+
description: "Fetches the authenticated user's enterprise license details.",
|
|
841
|
+
visibility: "customer",
|
|
842
|
+
tags: ["license", "entitlements"],
|
|
843
|
+
async run({ token }, context) {
|
|
844
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
845
|
+
throw new Error("fetchLicenseDetails requires a non-empty token");
|
|
846
|
+
}
|
|
847
|
+
const endpoint = `${getApiOrigin()}/v3/license`;
|
|
848
|
+
const response = await authenticatedFetch(endpoint, {
|
|
849
|
+
method: "GET",
|
|
850
|
+
token,
|
|
851
|
+
headers: {
|
|
852
|
+
accept: "application/json"
|
|
853
|
+
},
|
|
854
|
+
signal: context?.signal
|
|
855
|
+
});
|
|
856
|
+
if (!response.ok) {
|
|
857
|
+
throw new Error(
|
|
858
|
+
`License request failed (${response.status} ${response.statusText})`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
const payload = await response.json();
|
|
862
|
+
const license = normalizeLicensePayload(payload);
|
|
863
|
+
return {
|
|
864
|
+
license,
|
|
865
|
+
raw: payload ?? null
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
var fetchInstallOptions = defineServerAction({
|
|
870
|
+
id: "license/fetch-install-options",
|
|
871
|
+
description: "Fetches install options based on license entitlements.",
|
|
872
|
+
visibility: "customer",
|
|
873
|
+
tags: ["license", "install"],
|
|
874
|
+
async run({ token }, context) {
|
|
875
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
876
|
+
throw new Error("fetchInstallOptions requires a non-empty token");
|
|
877
|
+
}
|
|
878
|
+
const endpoint = `${getApiOrigin()}/v3/license`;
|
|
879
|
+
const response = await authenticatedFetch(endpoint, {
|
|
880
|
+
method: "GET",
|
|
881
|
+
token,
|
|
882
|
+
headers: {
|
|
883
|
+
accept: "application/json"
|
|
884
|
+
},
|
|
885
|
+
signal: context?.signal
|
|
886
|
+
});
|
|
887
|
+
if (!response.ok) {
|
|
888
|
+
throw new Error(
|
|
889
|
+
`License request failed (${response.status} ${response.statusText})`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const payload = await response.json();
|
|
893
|
+
const getBoolean2 = (obj, key) => {
|
|
894
|
+
if (obj && typeof obj === "object" && key in obj) {
|
|
895
|
+
const val = obj[key];
|
|
896
|
+
return val === true || val === "true";
|
|
897
|
+
}
|
|
898
|
+
return false;
|
|
899
|
+
};
|
|
900
|
+
const license = payload?.license ?? payload ?? {};
|
|
901
|
+
const showLinux = getBoolean2(license, "isEmbeddedClusterDownloadEnabled");
|
|
902
|
+
const showHelm = getBoolean2(license, "isHelmInstallEnabled");
|
|
903
|
+
return {
|
|
904
|
+
showLinux,
|
|
905
|
+
showHelm
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
var fetchLicenseSummary = defineServerAction({
|
|
910
|
+
id: "license/fetch-summary",
|
|
911
|
+
description: "Fetches license summary for the license card.",
|
|
912
|
+
visibility: "customer",
|
|
913
|
+
tags: ["license"],
|
|
914
|
+
async run({ token }, context) {
|
|
915
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
916
|
+
throw new Error("fetchLicenseSummary requires a non-empty token");
|
|
917
|
+
}
|
|
918
|
+
const endpoint = `${getApiOrigin()}/v3/license`;
|
|
919
|
+
const response = await authenticatedFetch(endpoint, {
|
|
920
|
+
method: "GET",
|
|
921
|
+
token,
|
|
922
|
+
headers: {
|
|
923
|
+
accept: "application/json"
|
|
924
|
+
},
|
|
925
|
+
signal: context?.signal
|
|
926
|
+
});
|
|
927
|
+
if (!response.ok) {
|
|
928
|
+
throw new Error(
|
|
929
|
+
`License request failed (${response.status} ${response.statusText})`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
const payload = await response.json();
|
|
933
|
+
const license = normalizeLicensePayload(payload);
|
|
934
|
+
const type = license.environment || "Unknown";
|
|
935
|
+
const expiresAt = license.expiresAt || null;
|
|
936
|
+
return {
|
|
937
|
+
type,
|
|
938
|
+
expiresAt
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
var fetchCustomers = defineServerAction({
|
|
943
|
+
id: "auth/fetch-customers",
|
|
944
|
+
description: "Fetches the list of customers/teams for the authenticated user.",
|
|
945
|
+
visibility: "customer",
|
|
946
|
+
tags: ["auth", "customers"],
|
|
947
|
+
async run({ token }, context) {
|
|
948
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
949
|
+
throw new Error("fetchCustomers requires a non-empty token");
|
|
950
|
+
}
|
|
951
|
+
const endpoint = `${getApiOrigin()}/v3/customers`;
|
|
952
|
+
const response = await authenticatedFetch(endpoint, {
|
|
953
|
+
method: "GET",
|
|
954
|
+
token,
|
|
955
|
+
headers: {
|
|
956
|
+
accept: "application/json"
|
|
957
|
+
},
|
|
958
|
+
signal: context?.signal
|
|
959
|
+
});
|
|
960
|
+
if (!response.ok) {
|
|
961
|
+
throw new Error(
|
|
962
|
+
`Fetch customers request failed (${response.status} ${response.statusText})`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
const payload = await response.json();
|
|
966
|
+
return {
|
|
967
|
+
customers: payload.customers || []
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
var switchCustomer = defineServerAction({
|
|
972
|
+
id: "auth/switch-customer",
|
|
973
|
+
description: "Switches the JWT to a different customer/team.",
|
|
974
|
+
visibility: "customer",
|
|
975
|
+
tags: ["auth", "customers"],
|
|
976
|
+
async run({ token, customerId }, context) {
|
|
977
|
+
if (typeof token !== "string" || token.trim().length === 0) {
|
|
978
|
+
throw new Error("switchCustomer requires a non-empty token");
|
|
979
|
+
}
|
|
980
|
+
if (typeof customerId !== "string" || customerId.trim().length === 0) {
|
|
981
|
+
throw new Error("switchCustomer requires a non-empty customerId");
|
|
982
|
+
}
|
|
983
|
+
const endpoint = `${getApiOrigin()}/v3/select-customer`;
|
|
984
|
+
const requestBody = { customer_id: customerId };
|
|
985
|
+
const response = await authenticatedFetch(endpoint, {
|
|
986
|
+
method: "PUT",
|
|
987
|
+
token,
|
|
988
|
+
headers: {
|
|
989
|
+
"content-type": "application/json",
|
|
990
|
+
accept: "application/json"
|
|
991
|
+
},
|
|
992
|
+
body: JSON.stringify(requestBody),
|
|
993
|
+
signal: context?.signal
|
|
994
|
+
});
|
|
995
|
+
console.log("[portal-components] switchCustomer response status:", response.status);
|
|
996
|
+
if (!response.ok) {
|
|
997
|
+
const errorText = await response.text();
|
|
998
|
+
console.error("[portal-components] switchCustomer error response:", errorText);
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`Switch customer request failed (${response.status} ${response.statusText}): ${errorText}`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
const payload = await response.json();
|
|
1004
|
+
console.log("[portal-components] switchCustomer response payload:", payload);
|
|
1005
|
+
const newToken = payload.jwt || payload.token || token;
|
|
1006
|
+
console.log("[portal-components] switchCustomer using token field:", payload.jwt ? "jwt" : payload.token ? "token" : "fallback");
|
|
1007
|
+
return {
|
|
1008
|
+
token: newToken
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
var getSecurityInfo = defineServerAction({
|
|
1013
|
+
id: "security/get-info",
|
|
1014
|
+
description: "Fetches CVE security scan results for a specific release",
|
|
1015
|
+
visibility: "customer",
|
|
1016
|
+
tags: ["security", "cve"],
|
|
1017
|
+
async run({ token, installType, channelSequence, isAirgap = false }, context) {
|
|
1018
|
+
if (!token || typeof token !== "string") {
|
|
1019
|
+
throw new Error("Security info request requires a session token");
|
|
1020
|
+
}
|
|
1021
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1022
|
+
const params = new URLSearchParams({
|
|
1023
|
+
customer_id: customerId,
|
|
1024
|
+
install_type: installType,
|
|
1025
|
+
channel_sequence: channelSequence.toString(),
|
|
1026
|
+
is_airgap: isAirgap.toString()
|
|
1027
|
+
});
|
|
1028
|
+
const url = `${getApiOrigin()}/v3/security-info?${params.toString()}`;
|
|
1029
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1030
|
+
console.debug("[portal-components] fetching security info via %s", url);
|
|
1031
|
+
}
|
|
1032
|
+
const response = await authenticatedFetch(url, {
|
|
1033
|
+
token,
|
|
1034
|
+
headers: { accept: "application/json" },
|
|
1035
|
+
signal: context?.signal
|
|
1036
|
+
});
|
|
1037
|
+
if (!response.ok) {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
`Security info request failed (${response.status} ${response.statusText})`
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
const data = await response.json();
|
|
1043
|
+
return data;
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
var getSecurityInfoDiff = defineServerAction({
|
|
1047
|
+
id: "security/get-info-diff",
|
|
1048
|
+
description: "Fetches CVE diff between two releases showing fixed and added vulnerabilities",
|
|
1049
|
+
visibility: "customer",
|
|
1050
|
+
tags: ["security", "cve", "diff"],
|
|
1051
|
+
async run({ token, installType, fromChannelSequence, toChannelSequence, isAirgap = false }, context) {
|
|
1052
|
+
if (!token || typeof token !== "string") {
|
|
1053
|
+
throw new Error("Security info diff request requires a session token");
|
|
1054
|
+
}
|
|
1055
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1056
|
+
const params = new URLSearchParams({
|
|
1057
|
+
customer_id: customerId,
|
|
1058
|
+
install_type: installType,
|
|
1059
|
+
from_channel_sequence: fromChannelSequence.toString(),
|
|
1060
|
+
to_channel_sequence: toChannelSequence.toString(),
|
|
1061
|
+
is_airgap: isAirgap.toString()
|
|
1062
|
+
});
|
|
1063
|
+
const url = `${getApiOrigin()}/v3/security-info-diff?${params.toString()}`;
|
|
1064
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1065
|
+
console.debug("[portal-components] fetching security info diff via %s", url);
|
|
1066
|
+
}
|
|
1067
|
+
const response = await authenticatedFetch(url, {
|
|
1068
|
+
token,
|
|
1069
|
+
headers: { accept: "application/json" },
|
|
1070
|
+
signal: context?.signal
|
|
1071
|
+
});
|
|
1072
|
+
if (!response.ok) {
|
|
1073
|
+
throw new Error(
|
|
1074
|
+
`Security info diff request failed (${response.status} ${response.statusText})`
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
const data = await response.json();
|
|
1078
|
+
return data;
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
var getSecurityInfoSBOM = defineServerAction({
|
|
1082
|
+
id: "security/get-sbom",
|
|
1083
|
+
description: "Fetches Software Bill of Materials (SBOM) for a specific release",
|
|
1084
|
+
visibility: "customer",
|
|
1085
|
+
tags: ["security", "sbom"],
|
|
1086
|
+
async run({ token, installType, channelSequence, isAirgap = false, unifiedSbom = true }, context) {
|
|
1087
|
+
if (!token || typeof token !== "string") {
|
|
1088
|
+
throw new Error("Security SBOM request requires a session token");
|
|
1089
|
+
}
|
|
1090
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1091
|
+
const params = new URLSearchParams({
|
|
1092
|
+
customer_id: customerId,
|
|
1093
|
+
install_type: installType,
|
|
1094
|
+
channel_sequence: channelSequence.toString(),
|
|
1095
|
+
is_airgap: isAirgap.toString(),
|
|
1096
|
+
unified_sbom: unifiedSbom.toString()
|
|
1097
|
+
});
|
|
1098
|
+
const url = `${getApiOrigin()}/v3/security-info-sbom?${params.toString()}`;
|
|
1099
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1100
|
+
console.debug("[portal-components] fetching security SBOM via %s", url);
|
|
1101
|
+
}
|
|
1102
|
+
const response = await authenticatedFetch(url, {
|
|
1103
|
+
token,
|
|
1104
|
+
headers: { accept: "application/json" },
|
|
1105
|
+
signal: context?.signal
|
|
1106
|
+
});
|
|
1107
|
+
if (response.status === 204) {
|
|
1108
|
+
return { sboms: {} };
|
|
1109
|
+
}
|
|
1110
|
+
if (!response.ok) {
|
|
1111
|
+
throw new Error(
|
|
1112
|
+
`Security SBOM request failed (${response.status} ${response.statusText})`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
const data = await response.json();
|
|
1116
|
+
return data;
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
var fetchTeamStats = defineServerAction({
|
|
1120
|
+
id: "dashboard/fetch-team-stats",
|
|
1121
|
+
description: "Fetches user and service account counts for the dashboard",
|
|
1122
|
+
visibility: "customer",
|
|
1123
|
+
tags: ["dashboard", "team"],
|
|
1124
|
+
async run({ token }, context) {
|
|
1125
|
+
if (!token || typeof token !== "string") {
|
|
1126
|
+
throw new Error("Team stats request requires a session token");
|
|
1127
|
+
}
|
|
1128
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1129
|
+
const origin = getApiOrigin();
|
|
1130
|
+
let userCount = 0;
|
|
1131
|
+
try {
|
|
1132
|
+
const usersUrl = `${origin}/v3/users?exclude_invites=false&customer_id=${encodeURIComponent(customerId)}`;
|
|
1133
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1134
|
+
console.debug("[portal-components] fetching team users via %s", usersUrl);
|
|
1135
|
+
}
|
|
1136
|
+
const usersResponse = await authenticatedFetch(usersUrl, {
|
|
1137
|
+
method: "GET",
|
|
1138
|
+
token,
|
|
1139
|
+
headers: { accept: "application/json" },
|
|
1140
|
+
signal: context?.signal
|
|
1141
|
+
});
|
|
1142
|
+
if (usersResponse.ok) {
|
|
1143
|
+
const usersData = await usersResponse.json();
|
|
1144
|
+
userCount = Array.isArray(usersData.users) ? usersData.users.length : 0;
|
|
1145
|
+
}
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error("[portal-components] Error fetching users:", error);
|
|
1148
|
+
}
|
|
1149
|
+
let serviceAccountCount = 0;
|
|
1150
|
+
try {
|
|
1151
|
+
const saUrl = `${origin}/v3/service-accounts?customer_id=${encodeURIComponent(customerId)}`;
|
|
1152
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1153
|
+
console.debug("[portal-components] fetching service accounts via %s", saUrl);
|
|
1154
|
+
}
|
|
1155
|
+
const saResponse = await authenticatedFetch(saUrl, {
|
|
1156
|
+
method: "GET",
|
|
1157
|
+
token,
|
|
1158
|
+
headers: { accept: "application/json" },
|
|
1159
|
+
signal: context?.signal
|
|
1160
|
+
});
|
|
1161
|
+
if (saResponse.ok) {
|
|
1162
|
+
const saData = await saResponse.json();
|
|
1163
|
+
serviceAccountCount = Array.isArray(saData.serviceAccounts) ? saData.serviceAccounts.length : 0;
|
|
1164
|
+
}
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
console.error("[portal-components] Error fetching service accounts:", error);
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
userCount,
|
|
1170
|
+
serviceAccountCount
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
var fetchDashboardInstances = defineServerAction({
|
|
1175
|
+
id: "dashboard/fetch-instances",
|
|
1176
|
+
description: "Fetches instance counts and update availability for the dashboard",
|
|
1177
|
+
visibility: "customer",
|
|
1178
|
+
tags: ["dashboard", "instances", "updates"],
|
|
1179
|
+
async run({ token }, context) {
|
|
1180
|
+
if (!token || typeof token !== "string") {
|
|
1181
|
+
throw new Error("Dashboard instances request requires a session token");
|
|
1182
|
+
}
|
|
1183
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1184
|
+
const origin = getApiOrigin();
|
|
1185
|
+
const instancesUrl = `${origin}/v3/instances?customer_id=${encodeURIComponent(customerId)}`;
|
|
1186
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1187
|
+
console.debug("[portal-components] fetching instances via %s", instancesUrl);
|
|
1188
|
+
}
|
|
1189
|
+
const instancesResponse = await authenticatedFetch(instancesUrl, {
|
|
1190
|
+
method: "GET",
|
|
1191
|
+
token,
|
|
1192
|
+
headers: { accept: "application/json" },
|
|
1193
|
+
signal: context?.signal
|
|
1194
|
+
});
|
|
1195
|
+
if (!instancesResponse.ok) {
|
|
1196
|
+
throw new Error(
|
|
1197
|
+
`Instances request failed (${instancesResponse.status} ${instancesResponse.statusText})`
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
const instancesData = await instancesResponse.json();
|
|
1201
|
+
const allInstances = instancesData.instances || [];
|
|
1202
|
+
const onlineInstances = allInstances.filter((i) => !i.isAirgap);
|
|
1203
|
+
const airgapInstances = allInstances.filter((i) => i.isAirgap);
|
|
1204
|
+
const twentyFourHoursAgo = Date.now() - 24 * 60 * 60 * 1e3;
|
|
1205
|
+
const activeOnlineInstances = onlineInstances.filter((instance) => {
|
|
1206
|
+
const lastCheckin = instance.lastCheckin ? new Date(instance.lastCheckin).getTime() : 0;
|
|
1207
|
+
return lastCheckin > twentyFourHoursAgo;
|
|
1208
|
+
});
|
|
1209
|
+
const onlineActiveCount = activeOnlineInstances.length;
|
|
1210
|
+
const airgapCount = airgapInstances.length;
|
|
1211
|
+
let channelReleases = [];
|
|
1212
|
+
try {
|
|
1213
|
+
const releasesUrl = `${origin}/v3/channel-releases?customer_id=${encodeURIComponent(customerId)}`;
|
|
1214
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1215
|
+
console.debug("[portal-components] fetching channel releases via %s", releasesUrl);
|
|
1216
|
+
}
|
|
1217
|
+
const releasesResponse = await authenticatedFetch(releasesUrl, {
|
|
1218
|
+
method: "GET",
|
|
1219
|
+
token,
|
|
1220
|
+
headers: { accept: "application/json" },
|
|
1221
|
+
signal: context?.signal
|
|
1222
|
+
});
|
|
1223
|
+
if (releasesResponse.ok) {
|
|
1224
|
+
const releasesData = await releasesResponse.json();
|
|
1225
|
+
channelReleases = releasesData.channelReleases || [];
|
|
1226
|
+
}
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
console.error("[portal-components] Error fetching channel releases:", error);
|
|
1229
|
+
}
|
|
1230
|
+
const calculateUpdates = (instances) => {
|
|
1231
|
+
if (!channelReleases.length) return 0;
|
|
1232
|
+
let numUpdates = 0;
|
|
1233
|
+
for (const instance of instances) {
|
|
1234
|
+
const instanceSequence = instance.channelSequence ?? 0;
|
|
1235
|
+
const matchingReleases = channelReleases.filter(
|
|
1236
|
+
(release) => release.channelId === instance.channelId
|
|
1237
|
+
);
|
|
1238
|
+
for (const release of matchingReleases) {
|
|
1239
|
+
if (release.channelSequence > instanceSequence) {
|
|
1240
|
+
numUpdates++;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return numUpdates;
|
|
1245
|
+
};
|
|
1246
|
+
const onlineUpdates = calculateUpdates(activeOnlineInstances);
|
|
1247
|
+
const airgapUpdates = calculateUpdates(airgapInstances);
|
|
1248
|
+
return {
|
|
1249
|
+
onlineActiveCount,
|
|
1250
|
+
airgapCount,
|
|
1251
|
+
onlineUpdates,
|
|
1252
|
+
airgapUpdates
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
var fetchCurrentUser = defineServerAction({
|
|
1257
|
+
id: "user/fetch-current",
|
|
1258
|
+
description: "Fetches the current user's profile information",
|
|
1259
|
+
visibility: "customer",
|
|
1260
|
+
tags: ["user", "profile"],
|
|
1261
|
+
async run({ token }, context) {
|
|
1262
|
+
if (!token || typeof token !== "string") {
|
|
1263
|
+
throw new Error("Fetch current user requires a session token");
|
|
1264
|
+
}
|
|
1265
|
+
const endpoint = `${getApiOrigin()}/v3/user`;
|
|
1266
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1267
|
+
console.debug("[portal-components] fetching current user via %s", endpoint);
|
|
1268
|
+
}
|
|
1269
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1270
|
+
method: "GET",
|
|
1271
|
+
token,
|
|
1272
|
+
headers: { accept: "application/json" },
|
|
1273
|
+
signal: context?.signal
|
|
1274
|
+
});
|
|
1275
|
+
if (!response.ok) {
|
|
1276
|
+
throw new Error(
|
|
1277
|
+
`Fetch current user request failed (${response.status} ${response.statusText})`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const data = await response.json();
|
|
1281
|
+
return {
|
|
1282
|
+
user: {
|
|
1283
|
+
emailAddress: data.emailAddress || "",
|
|
1284
|
+
firstName: data.firstName || "",
|
|
1285
|
+
lastName: data.lastName || ""
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
var updateUser = defineServerAction({
|
|
1291
|
+
id: "user/update",
|
|
1292
|
+
description: "Updates the current user's first and/or last name",
|
|
1293
|
+
visibility: "customer",
|
|
1294
|
+
tags: ["user", "profile"],
|
|
1295
|
+
async run({ token, firstName, lastName }, context) {
|
|
1296
|
+
if (!token || typeof token !== "string") {
|
|
1297
|
+
throw new Error("Update user requires a session token");
|
|
1298
|
+
}
|
|
1299
|
+
if (!firstName && !lastName) {
|
|
1300
|
+
throw new Error("At least one of firstName or lastName must be provided");
|
|
1301
|
+
}
|
|
1302
|
+
const endpoint = `${getApiOrigin()}/v3/user`;
|
|
1303
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1304
|
+
console.debug("[portal-components] updating user via %s", endpoint);
|
|
1305
|
+
}
|
|
1306
|
+
const body = {};
|
|
1307
|
+
if (firstName !== void 0) body.firstName = firstName;
|
|
1308
|
+
if (lastName !== void 0) body.lastName = lastName;
|
|
1309
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1310
|
+
method: "POST",
|
|
1311
|
+
token,
|
|
1312
|
+
headers: {
|
|
1313
|
+
"content-type": "application/json",
|
|
1314
|
+
accept: "application/json"
|
|
1315
|
+
},
|
|
1316
|
+
body: JSON.stringify(body),
|
|
1317
|
+
signal: context?.signal
|
|
1318
|
+
});
|
|
1319
|
+
if (!response.ok) {
|
|
1320
|
+
const errorText = await response.text().catch(() => "");
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Update user request failed (${response.status} ${response.statusText}): ${errorText}`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
return { success: true };
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
var fetchNotifications = defineServerAction({
|
|
1329
|
+
id: "notifications/fetch",
|
|
1330
|
+
description: "Fetches notification preferences for a specific team",
|
|
1331
|
+
visibility: "customer",
|
|
1332
|
+
tags: ["notifications", "user"],
|
|
1333
|
+
async run({ token, customerId }, context) {
|
|
1334
|
+
if (!token || typeof token !== "string") {
|
|
1335
|
+
throw new Error("Fetch notifications requires a session token");
|
|
1336
|
+
}
|
|
1337
|
+
if (!customerId || typeof customerId !== "string") {
|
|
1338
|
+
throw new Error("Fetch notifications requires a customerId");
|
|
1339
|
+
}
|
|
1340
|
+
const endpoint = `${getApiOrigin()}/v3/notifications?customer_id=${encodeURIComponent(customerId)}`;
|
|
1341
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1342
|
+
console.debug("[portal-components] fetching notifications via %s", endpoint);
|
|
1343
|
+
}
|
|
1344
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1345
|
+
method: "GET",
|
|
1346
|
+
token,
|
|
1347
|
+
headers: { accept: "application/json" },
|
|
1348
|
+
signal: context?.signal
|
|
1349
|
+
});
|
|
1350
|
+
if (!response.ok) {
|
|
1351
|
+
throw new Error(
|
|
1352
|
+
`Fetch notifications request failed (${response.status} ${response.statusText})`
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
const data = await response.json();
|
|
1356
|
+
return {
|
|
1357
|
+
notifications: data.notifications || []
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
var updateNotifications = defineServerAction({
|
|
1362
|
+
id: "notifications/update",
|
|
1363
|
+
description: "Updates notification preferences for a specific team",
|
|
1364
|
+
visibility: "customer",
|
|
1365
|
+
tags: ["notifications", "user"],
|
|
1366
|
+
async run({ token, customerId, notifications }, context) {
|
|
1367
|
+
if (!token || typeof token !== "string") {
|
|
1368
|
+
throw new Error("Update notifications requires a session token");
|
|
1369
|
+
}
|
|
1370
|
+
if (!customerId || typeof customerId !== "string") {
|
|
1371
|
+
throw new Error("Update notifications requires a customerId");
|
|
1372
|
+
}
|
|
1373
|
+
if (!Array.isArray(notifications)) {
|
|
1374
|
+
throw new Error("Update notifications requires a notifications array");
|
|
1375
|
+
}
|
|
1376
|
+
const endpoint = `${getApiOrigin()}/v3/notifications?customer_id=${encodeURIComponent(customerId)}`;
|
|
1377
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1378
|
+
console.debug("[portal-components] updating notifications via %s", endpoint);
|
|
1379
|
+
}
|
|
1380
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1381
|
+
method: "PUT",
|
|
1382
|
+
token,
|
|
1383
|
+
headers: {
|
|
1384
|
+
"content-type": "application/json",
|
|
1385
|
+
accept: "application/json"
|
|
1386
|
+
},
|
|
1387
|
+
body: JSON.stringify({ notifications }),
|
|
1388
|
+
signal: context?.signal
|
|
1389
|
+
});
|
|
1390
|
+
if (!response.ok) {
|
|
1391
|
+
const errorText = await response.text().catch(() => "");
|
|
1392
|
+
throw new Error(
|
|
1393
|
+
`Update notifications request failed (${response.status} ${response.statusText}): ${errorText}`
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
const data = await response.json();
|
|
1397
|
+
return {
|
|
1398
|
+
notifications: data.notifications || []
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
var fetchTeamUsers = defineServerAction({
|
|
1403
|
+
id: "team/fetch-users",
|
|
1404
|
+
description: "Fetches paginated list of team users and pending invites",
|
|
1405
|
+
visibility: "customer",
|
|
1406
|
+
tags: ["team", "users"],
|
|
1407
|
+
async run({ token, limit = 25, offset = 0 }, context) {
|
|
1408
|
+
if (!token || typeof token !== "string") {
|
|
1409
|
+
throw new Error("Fetch team users requires a session token");
|
|
1410
|
+
}
|
|
1411
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1412
|
+
const params = new URLSearchParams({
|
|
1413
|
+
customer_id: customerId,
|
|
1414
|
+
limit: limit.toString(),
|
|
1415
|
+
offset: offset.toString()
|
|
1416
|
+
});
|
|
1417
|
+
const endpoint = `${getApiOrigin()}/v3/users?${params.toString()}`;
|
|
1418
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1419
|
+
console.debug("[portal-components] fetching team users via %s", endpoint);
|
|
1420
|
+
}
|
|
1421
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1422
|
+
method: "GET",
|
|
1423
|
+
token,
|
|
1424
|
+
headers: { accept: "application/json" },
|
|
1425
|
+
signal: context?.signal
|
|
1426
|
+
});
|
|
1427
|
+
if (!response.ok) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
`Fetch team users request failed (${response.status} ${response.statusText})`
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
const data = await response.json();
|
|
1433
|
+
return {
|
|
1434
|
+
users: data.users || [],
|
|
1435
|
+
total: data.total || 0
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
var inviteUser = defineServerAction({
|
|
1440
|
+
id: "team/invite-user",
|
|
1441
|
+
description: "Sends an invitation email to join the team",
|
|
1442
|
+
visibility: "customer",
|
|
1443
|
+
tags: ["team", "users", "invite"],
|
|
1444
|
+
async run({ token, email }, context) {
|
|
1445
|
+
if (!token || typeof token !== "string") {
|
|
1446
|
+
throw new Error("Invite user requires a session token");
|
|
1447
|
+
}
|
|
1448
|
+
if (!email || typeof email !== "string") {
|
|
1449
|
+
throw new Error("Invite user requires an email address");
|
|
1450
|
+
}
|
|
1451
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1452
|
+
const params = new URLSearchParams({
|
|
1453
|
+
customer_id: customerId,
|
|
1454
|
+
email_address: email
|
|
1455
|
+
});
|
|
1456
|
+
const endpoint = `${getApiOrigin()}/v3/invite?${params.toString()}`;
|
|
1457
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1458
|
+
console.debug("[portal-components] inviting user via %s", endpoint);
|
|
1459
|
+
}
|
|
1460
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1461
|
+
method: "POST",
|
|
1462
|
+
token,
|
|
1463
|
+
headers: { accept: "application/json" },
|
|
1464
|
+
signal: context?.signal
|
|
1465
|
+
});
|
|
1466
|
+
if (!response.ok) {
|
|
1467
|
+
let errorMessage = "Failed to invite user";
|
|
1468
|
+
try {
|
|
1469
|
+
const data = await response.json();
|
|
1470
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1471
|
+
} catch {
|
|
1472
|
+
}
|
|
1473
|
+
throw new Error(errorMessage);
|
|
1474
|
+
}
|
|
1475
|
+
return { success: true };
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
var deleteUser = defineServerAction({
|
|
1479
|
+
id: "team/delete-user",
|
|
1480
|
+
description: "Removes a user from the team",
|
|
1481
|
+
visibility: "customer",
|
|
1482
|
+
tags: ["team", "users", "delete"],
|
|
1483
|
+
async run({ token, email }, context) {
|
|
1484
|
+
if (!token || typeof token !== "string") {
|
|
1485
|
+
throw new Error("Delete user requires a session token");
|
|
1486
|
+
}
|
|
1487
|
+
if (!email || typeof email !== "string") {
|
|
1488
|
+
throw new Error("Delete user requires an email address");
|
|
1489
|
+
}
|
|
1490
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1491
|
+
const params = new URLSearchParams({
|
|
1492
|
+
customer_id: customerId,
|
|
1493
|
+
email_address: email
|
|
1494
|
+
});
|
|
1495
|
+
const endpoint = `${getApiOrigin()}/v3/user?${params.toString()}`;
|
|
1496
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1497
|
+
console.debug("[portal-components] deleting user via %s", endpoint);
|
|
1498
|
+
}
|
|
1499
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1500
|
+
method: "DELETE",
|
|
1501
|
+
token,
|
|
1502
|
+
headers: { accept: "application/json" },
|
|
1503
|
+
signal: context?.signal
|
|
1504
|
+
});
|
|
1505
|
+
if (!response.ok) {
|
|
1506
|
+
let errorMessage = "Failed to delete user";
|
|
1507
|
+
try {
|
|
1508
|
+
const data = await response.json();
|
|
1509
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1510
|
+
} catch {
|
|
1511
|
+
}
|
|
1512
|
+
throw new Error(errorMessage);
|
|
1513
|
+
}
|
|
1514
|
+
return { success: true };
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
var fetchServiceAccounts = defineServerAction({
|
|
1518
|
+
id: "team/fetch-service-accounts",
|
|
1519
|
+
description: "Fetches paginated list of service accounts",
|
|
1520
|
+
visibility: "customer",
|
|
1521
|
+
tags: ["team", "service-accounts"],
|
|
1522
|
+
async run({ token, limit = 50, offset = 0, includeRevoked = false }, context) {
|
|
1523
|
+
if (!token || typeof token !== "string") {
|
|
1524
|
+
throw new Error("Fetch service accounts requires a session token");
|
|
1525
|
+
}
|
|
1526
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1527
|
+
const params = new URLSearchParams({
|
|
1528
|
+
customer_id: customerId,
|
|
1529
|
+
limit: limit.toString(),
|
|
1530
|
+
offset: offset.toString()
|
|
1531
|
+
});
|
|
1532
|
+
if (!includeRevoked) {
|
|
1533
|
+
params.set("filterRevoked", "false");
|
|
1534
|
+
}
|
|
1535
|
+
const endpoint = `${getApiOrigin()}/v3/service-accounts?${params.toString()}`;
|
|
1536
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1537
|
+
console.debug("[portal-components] fetching service accounts via %s", endpoint);
|
|
1538
|
+
}
|
|
1539
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1540
|
+
method: "GET",
|
|
1541
|
+
token,
|
|
1542
|
+
headers: { accept: "application/json" },
|
|
1543
|
+
signal: context?.signal
|
|
1544
|
+
});
|
|
1545
|
+
if (!response.ok) {
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
`Fetch service accounts request failed (${response.status} ${response.statusText})`
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
const data = await response.json();
|
|
1551
|
+
return {
|
|
1552
|
+
serviceAccounts: data.serviceAccounts || [],
|
|
1553
|
+
total: data.total || 0
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
var revokeServiceAccount = defineServerAction({
|
|
1558
|
+
id: "team/revoke-service-account",
|
|
1559
|
+
description: "Revokes a service account (soft delete)",
|
|
1560
|
+
visibility: "customer",
|
|
1561
|
+
tags: ["team", "service-accounts", "revoke"],
|
|
1562
|
+
async run({ token, accountId }, context) {
|
|
1563
|
+
if (!token || typeof token !== "string") {
|
|
1564
|
+
throw new Error("Revoke service account requires a session token");
|
|
1565
|
+
}
|
|
1566
|
+
if (!accountId || typeof accountId !== "string") {
|
|
1567
|
+
throw new Error("Revoke service account requires an account ID");
|
|
1568
|
+
}
|
|
1569
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1570
|
+
const endpoint = `${getApiOrigin()}/v3/service-account/${encodeURIComponent(accountId)}?customer_id=${encodeURIComponent(customerId)}`;
|
|
1571
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1572
|
+
console.debug("[portal-components] revoking service account via %s", endpoint);
|
|
1573
|
+
}
|
|
1574
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1575
|
+
method: "DELETE",
|
|
1576
|
+
token,
|
|
1577
|
+
headers: { accept: "application/json" },
|
|
1578
|
+
signal: context?.signal
|
|
1579
|
+
});
|
|
1580
|
+
if (!response.ok) {
|
|
1581
|
+
let errorMessage = "Failed to revoke service account";
|
|
1582
|
+
try {
|
|
1583
|
+
const data = await response.json();
|
|
1584
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1585
|
+
} catch {
|
|
1586
|
+
}
|
|
1587
|
+
throw new Error(errorMessage);
|
|
1588
|
+
}
|
|
1589
|
+
return { success: true };
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
var rotateServiceAccountToken = defineServerAction({
|
|
1593
|
+
id: "team/rotate-service-account-token",
|
|
1594
|
+
description: "Generates a new token for a service account",
|
|
1595
|
+
visibility: "customer",
|
|
1596
|
+
tags: ["team", "service-accounts", "rotate"],
|
|
1597
|
+
async run({ token, accountId }, context) {
|
|
1598
|
+
if (!token || typeof token !== "string") {
|
|
1599
|
+
throw new Error("Rotate service account token requires a session token");
|
|
1600
|
+
}
|
|
1601
|
+
if (!accountId || typeof accountId !== "string") {
|
|
1602
|
+
throw new Error("Rotate service account token requires an account ID");
|
|
1603
|
+
}
|
|
1604
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1605
|
+
const endpoint = `${getApiOrigin()}/v3/service-account/${encodeURIComponent(accountId)}/rotate-token?customer_id=${encodeURIComponent(customerId)}`;
|
|
1606
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1607
|
+
console.debug("[portal-components] rotating service account token via %s", endpoint);
|
|
1608
|
+
}
|
|
1609
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1610
|
+
method: "POST",
|
|
1611
|
+
token,
|
|
1612
|
+
headers: { accept: "application/json" },
|
|
1613
|
+
signal: context?.signal
|
|
1614
|
+
});
|
|
1615
|
+
if (!response.ok) {
|
|
1616
|
+
let errorMessage = "Failed to rotate service account token";
|
|
1617
|
+
try {
|
|
1618
|
+
const data2 = await response.json();
|
|
1619
|
+
errorMessage = data2.message || data2.error || errorMessage;
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
throw new Error(errorMessage);
|
|
1623
|
+
}
|
|
1624
|
+
const data = await response.json();
|
|
1625
|
+
return {
|
|
1626
|
+
serviceAccount: data.service_account,
|
|
1627
|
+
helmLoginCommand: data.helm_login_cmd || "",
|
|
1628
|
+
redeployHelm: data.redeploy_helm || []
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
var fetchInstances = defineServerAction({
|
|
1633
|
+
id: "team/fetch-instances",
|
|
1634
|
+
description: "Fetches instances to determine service account usage",
|
|
1635
|
+
visibility: "customer",
|
|
1636
|
+
tags: ["team", "instances"],
|
|
1637
|
+
async run({ token }, context) {
|
|
1638
|
+
if (!token || typeof token !== "string") {
|
|
1639
|
+
throw new Error("Fetch instances requires a session token");
|
|
1640
|
+
}
|
|
1641
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1642
|
+
const endpoint = `${getApiOrigin()}/v3/instances?customer_id=${encodeURIComponent(customerId)}`;
|
|
1643
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1644
|
+
console.debug("[portal-components] fetching instances via %s", endpoint);
|
|
1645
|
+
}
|
|
1646
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1647
|
+
method: "GET",
|
|
1648
|
+
token,
|
|
1649
|
+
headers: { accept: "application/json" },
|
|
1650
|
+
signal: context?.signal
|
|
1651
|
+
});
|
|
1652
|
+
if (!response.ok) {
|
|
1653
|
+
throw new Error(
|
|
1654
|
+
`Fetch instances request failed (${response.status} ${response.statusText})`
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
const data = await response.json();
|
|
1658
|
+
return {
|
|
1659
|
+
instances: data.instances || []
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
var fetchSamlConfig = defineServerAction({
|
|
1664
|
+
id: "team/fetch-saml-config",
|
|
1665
|
+
description: "Fetches SAML SSO configuration for the team",
|
|
1666
|
+
visibility: "customer",
|
|
1667
|
+
tags: ["team", "saml"],
|
|
1668
|
+
async run({ token }, context) {
|
|
1669
|
+
if (!token || typeof token !== "string") {
|
|
1670
|
+
throw new Error("Fetch SAML config requires a session token");
|
|
1671
|
+
}
|
|
1672
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1673
|
+
const endpoint = `${getApiOrigin()}/v3/customer/saml/config?customer_id=${encodeURIComponent(customerId)}`;
|
|
1674
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1675
|
+
console.debug("[portal-components] fetching SAML config via %s", endpoint);
|
|
1676
|
+
}
|
|
1677
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1678
|
+
method: "GET",
|
|
1679
|
+
token,
|
|
1680
|
+
headers: { accept: "application/json" },
|
|
1681
|
+
signal: context?.signal
|
|
1682
|
+
});
|
|
1683
|
+
if (!response.ok) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
`Fetch SAML config request failed (${response.status} ${response.statusText})`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
const data = await response.json();
|
|
1689
|
+
return {
|
|
1690
|
+
config: {
|
|
1691
|
+
samlAllowed: data.samlAllowed || false,
|
|
1692
|
+
samlEnabled: data.samlEnabled || false,
|
|
1693
|
+
entityId: data.entityId || "",
|
|
1694
|
+
acsUrl: data.acsUrl || "",
|
|
1695
|
+
hasIdpMetadata: data.hasIdpMetadata || false,
|
|
1696
|
+
hasIdpCert: data.hasIdpCert || false
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
var updateSamlConfig = defineServerAction({
|
|
1702
|
+
id: "team/update-saml-config",
|
|
1703
|
+
description: "Uploads IdP metadata and certificate for SAML SSO",
|
|
1704
|
+
visibility: "customer",
|
|
1705
|
+
tags: ["team", "saml", "update"],
|
|
1706
|
+
async run({ token, idpMetadataXml, idpPublicCert }, context) {
|
|
1707
|
+
if (!token || typeof token !== "string") {
|
|
1708
|
+
throw new Error("Update SAML config requires a session token");
|
|
1709
|
+
}
|
|
1710
|
+
if (!idpMetadataXml || !idpPublicCert) {
|
|
1711
|
+
throw new Error("Both IdP metadata and certificate are required");
|
|
1712
|
+
}
|
|
1713
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1714
|
+
const endpoint = `${getApiOrigin()}/v3/customer/saml/config?customer_id=${encodeURIComponent(customerId)}`;
|
|
1715
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1716
|
+
console.debug("[portal-components] updating SAML config via %s", endpoint);
|
|
1717
|
+
}
|
|
1718
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1719
|
+
method: "PUT",
|
|
1720
|
+
token,
|
|
1721
|
+
headers: {
|
|
1722
|
+
"content-type": "application/json",
|
|
1723
|
+
accept: "application/json"
|
|
1724
|
+
},
|
|
1725
|
+
body: JSON.stringify({
|
|
1726
|
+
idpMetadataXml,
|
|
1727
|
+
idpPublicCert
|
|
1728
|
+
}),
|
|
1729
|
+
signal: context?.signal
|
|
1730
|
+
});
|
|
1731
|
+
if (!response.ok) {
|
|
1732
|
+
let errorMessage = "Failed to update SAML configuration";
|
|
1733
|
+
try {
|
|
1734
|
+
const data = await response.json();
|
|
1735
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1736
|
+
} catch {
|
|
1737
|
+
}
|
|
1738
|
+
throw new Error(errorMessage);
|
|
1739
|
+
}
|
|
1740
|
+
return { success: true };
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
var toggleSamlEnabled = defineServerAction({
|
|
1744
|
+
id: "team/toggle-saml-enabled",
|
|
1745
|
+
description: "Enables or disables SAML authentication",
|
|
1746
|
+
visibility: "customer",
|
|
1747
|
+
tags: ["team", "saml", "toggle"],
|
|
1748
|
+
async run({ token, enabled }, context) {
|
|
1749
|
+
if (!token || typeof token !== "string") {
|
|
1750
|
+
throw new Error("Toggle SAML enabled requires a session token");
|
|
1751
|
+
}
|
|
1752
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1753
|
+
const endpoint = `${getApiOrigin()}/v3/customer/saml/enable?customer_id=${encodeURIComponent(customerId)}`;
|
|
1754
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1755
|
+
console.debug("[portal-components] toggling SAML enabled via %s", endpoint);
|
|
1756
|
+
}
|
|
1757
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1758
|
+
method: "PUT",
|
|
1759
|
+
token,
|
|
1760
|
+
headers: {
|
|
1761
|
+
"content-type": "application/json",
|
|
1762
|
+
accept: "application/json"
|
|
1763
|
+
},
|
|
1764
|
+
body: JSON.stringify({ enabled }),
|
|
1765
|
+
signal: context?.signal
|
|
1766
|
+
});
|
|
1767
|
+
if (!response.ok) {
|
|
1768
|
+
let errorMessage = "Failed to toggle SAML";
|
|
1769
|
+
try {
|
|
1770
|
+
const data2 = await response.json();
|
|
1771
|
+
errorMessage = data2.message || data2.error || errorMessage;
|
|
1772
|
+
} catch {
|
|
1773
|
+
}
|
|
1774
|
+
throw new Error(errorMessage);
|
|
1775
|
+
}
|
|
1776
|
+
const data = await response.json();
|
|
1777
|
+
return {
|
|
1778
|
+
success: true,
|
|
1779
|
+
samlEnabled: data.samlEnabled || enabled
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
var deprovisionSaml = defineServerAction({
|
|
1784
|
+
id: "team/deprovision-saml",
|
|
1785
|
+
description: "Removes all SAML configuration",
|
|
1786
|
+
visibility: "customer",
|
|
1787
|
+
tags: ["team", "saml", "delete"],
|
|
1788
|
+
async run({ token }, context) {
|
|
1789
|
+
if (!token || typeof token !== "string") {
|
|
1790
|
+
throw new Error("Deprovision SAML requires a session token");
|
|
1791
|
+
}
|
|
1792
|
+
const customerId = getCustomerIdFromToken(token);
|
|
1793
|
+
const endpoint = `${getApiOrigin()}/v3/customer/saml/config?customer_id=${encodeURIComponent(customerId)}`;
|
|
1794
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1795
|
+
console.debug("[portal-components] deprovisioning SAML via %s", endpoint);
|
|
1796
|
+
}
|
|
1797
|
+
const response = await authenticatedFetch(endpoint, {
|
|
1798
|
+
method: "DELETE",
|
|
1799
|
+
token,
|
|
1800
|
+
headers: { accept: "application/json" },
|
|
1801
|
+
signal: context?.signal
|
|
1802
|
+
});
|
|
1803
|
+
if (!response.ok) {
|
|
1804
|
+
let errorMessage = "Failed to remove SAML configuration";
|
|
1805
|
+
try {
|
|
1806
|
+
const data = await response.json();
|
|
1807
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
throw new Error(errorMessage);
|
|
1811
|
+
}
|
|
1812
|
+
return { success: true };
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
var acceptInvite = defineServerAction({
|
|
1816
|
+
id: "auth/accept-invite",
|
|
1817
|
+
description: "Accepts a team invitation and returns a session token",
|
|
1818
|
+
visibility: "customer",
|
|
1819
|
+
tags: ["auth", "invite", "join"],
|
|
1820
|
+
async run({ code }) {
|
|
1821
|
+
if (!code || typeof code !== "string") {
|
|
1822
|
+
const error = {
|
|
1823
|
+
code: "invalid_code",
|
|
1824
|
+
message: "Invite code is required"
|
|
1825
|
+
};
|
|
1826
|
+
throw error;
|
|
1827
|
+
}
|
|
1828
|
+
const endpoint = `${getApiOrigin()}/v3/invite/accept`;
|
|
1829
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1830
|
+
console.debug("[portal-components] accepting invite via %s", endpoint);
|
|
1831
|
+
}
|
|
1832
|
+
const response = await fetch(endpoint, {
|
|
1833
|
+
method: "POST",
|
|
1834
|
+
headers: {
|
|
1835
|
+
"content-type": "application/json",
|
|
1836
|
+
accept: "application/json"
|
|
1837
|
+
},
|
|
1838
|
+
body: JSON.stringify({ code })
|
|
1839
|
+
});
|
|
1840
|
+
if (!response.ok) {
|
|
1841
|
+
if (response.status === 404) {
|
|
1842
|
+
const error2 = {
|
|
1843
|
+
code: "invalid_code",
|
|
1844
|
+
message: "Invalid or expired invite code. Please check your code and try again."
|
|
1845
|
+
};
|
|
1846
|
+
throw error2;
|
|
1847
|
+
}
|
|
1848
|
+
let errorMessage = "Failed to accept invitation";
|
|
1849
|
+
try {
|
|
1850
|
+
const data = await response.json();
|
|
1851
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
const error = {
|
|
1855
|
+
code: "unknown",
|
|
1856
|
+
message: errorMessage
|
|
1857
|
+
};
|
|
1858
|
+
throw error;
|
|
1859
|
+
}
|
|
1860
|
+
const payload = await response.json();
|
|
1861
|
+
const token = payload?.jwt ?? payload?.token;
|
|
1862
|
+
if (typeof token !== "string") {
|
|
1863
|
+
throw new Error("Invite accepted but no token returned");
|
|
1864
|
+
}
|
|
1865
|
+
return { token };
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
var refreshInvite = defineServerAction({
|
|
1869
|
+
id: "auth/refresh-invite",
|
|
1870
|
+
description: "Refreshes an expired invite and resends the invitation email",
|
|
1871
|
+
visibility: "customer",
|
|
1872
|
+
tags: ["auth", "invite", "refresh"],
|
|
1873
|
+
async run({ code }) {
|
|
1874
|
+
if (!code || typeof code !== "string") {
|
|
1875
|
+
throw new Error("Invite code is required");
|
|
1876
|
+
}
|
|
1877
|
+
const endpoint = `${getApiOrigin()}/v3/invite/refresh`;
|
|
1878
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1879
|
+
console.debug("[portal-components] refreshing invite via %s", endpoint);
|
|
1880
|
+
}
|
|
1881
|
+
const response = await fetch(endpoint, {
|
|
1882
|
+
method: "POST",
|
|
1883
|
+
headers: {
|
|
1884
|
+
"content-type": "application/json",
|
|
1885
|
+
accept: "application/json"
|
|
1886
|
+
},
|
|
1887
|
+
body: JSON.stringify({ code })
|
|
1888
|
+
});
|
|
1889
|
+
if (!response.ok) {
|
|
1890
|
+
let errorMessage = "Failed to refresh invitation";
|
|
1891
|
+
try {
|
|
1892
|
+
const data = await response.json();
|
|
1893
|
+
errorMessage = data.message || data.error || errorMessage;
|
|
1894
|
+
} catch {
|
|
1895
|
+
}
|
|
1896
|
+
throw new Error(errorMessage);
|
|
1897
|
+
}
|
|
1898
|
+
return { success: true };
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
10
1901
|
|
|
1902
|
+
exports.acceptInvite = acceptInvite;
|
|
1903
|
+
exports.createServiceAccount = createServiceAccount;
|
|
1904
|
+
exports.decodeJwtPayload = decodeJwtPayload;
|
|
11
1905
|
exports.defineServerAction = defineServerAction;
|
|
1906
|
+
exports.deleteSupportBundle = deleteSupportBundle;
|
|
1907
|
+
exports.deleteUser = deleteUser;
|
|
1908
|
+
exports.deprovisionSaml = deprovisionSaml;
|
|
1909
|
+
exports.downloadSupportBundle = downloadSupportBundle;
|
|
1910
|
+
exports.fetchCurrentUser = fetchCurrentUser;
|
|
1911
|
+
exports.fetchCustomBranding = fetchCustomBranding;
|
|
1912
|
+
exports.fetchCustomers = fetchCustomers;
|
|
1913
|
+
exports.fetchDashboardInstances = fetchDashboardInstances;
|
|
1914
|
+
exports.fetchInstallOptions = fetchInstallOptions;
|
|
1915
|
+
exports.fetchInstances = fetchInstances;
|
|
1916
|
+
exports.fetchLicenseDetails = fetchLicenseDetails;
|
|
1917
|
+
exports.fetchLicenseSummary = fetchLicenseSummary;
|
|
1918
|
+
exports.fetchNotifications = fetchNotifications;
|
|
1919
|
+
exports.fetchSamlConfig = fetchSamlConfig;
|
|
1920
|
+
exports.fetchServiceAccounts = fetchServiceAccounts;
|
|
1921
|
+
exports.fetchTeamStats = fetchTeamStats;
|
|
1922
|
+
exports.fetchTeamUsers = fetchTeamUsers;
|
|
1923
|
+
exports.getApiOrigin = getApiOrigin;
|
|
1924
|
+
exports.getCustomerIdFromToken = getCustomerIdFromToken;
|
|
1925
|
+
exports.getSecurityInfo = getSecurityInfo;
|
|
1926
|
+
exports.getSecurityInfoDiff = getSecurityInfoDiff;
|
|
1927
|
+
exports.getSecurityInfoSBOM = getSecurityInfoSBOM;
|
|
1928
|
+
exports.getSupportBundleUploadUrl = getSupportBundleUploadUrl;
|
|
1929
|
+
exports.initiateLogin = initiateLogin;
|
|
1930
|
+
exports.inviteUser = inviteUser;
|
|
1931
|
+
exports.listReleases = listReleases;
|
|
1932
|
+
exports.listSupportBundles = listSupportBundles;
|
|
1933
|
+
exports.refreshInvite = refreshInvite;
|
|
1934
|
+
exports.revokeServiceAccount = revokeServiceAccount;
|
|
1935
|
+
exports.rotateServiceAccountToken = rotateServiceAccountToken;
|
|
1936
|
+
exports.switchCustomer = switchCustomer;
|
|
1937
|
+
exports.toggleSamlEnabled = toggleSamlEnabled;
|
|
1938
|
+
exports.updateNotifications = updateNotifications;
|
|
1939
|
+
exports.updateSamlConfig = updateSamlConfig;
|
|
1940
|
+
exports.updateUser = updateUser;
|
|
1941
|
+
exports.uploadSupportBundle = uploadSupportBundle;
|
|
1942
|
+
exports.verifyMagicLink = verifyMagicLink;
|
|
12
1943
|
//# sourceMappingURL=index.js.map
|
|
13
1944
|
//# sourceMappingURL=index.js.map
|