@replicated/portal-components 0.0.16 → 0.0.18
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 +2 -2
- package/components/metadata/registry.md +2 -2
- package/dist/actions/index.d.mts +1 -637
- package/dist/actions/index.d.ts +1 -637
- package/dist/actions/index.js +2 -1
- package/dist/actions/index.js.map +1 -1
- package/dist/airgap-instances.d.mts +5 -3
- package/dist/airgap-instances.d.ts +5 -3
- package/dist/airgap-instances.js +94 -5
- package/dist/airgap-instances.js.map +1 -1
- package/dist/esm/actions/index.js +2 -1
- package/dist/esm/actions/index.js.map +1 -1
- package/dist/esm/airgap-instances.js +90 -5
- package/dist/esm/airgap-instances.js.map +1 -1
- package/dist/esm/helm-install-wizard.js +28 -15
- package/dist/esm/helm-install-wizard.js.map +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/install-actions.js +54 -11
- package/dist/esm/install-actions.js.map +1 -1
- package/dist/esm/instance-card.js +79 -3
- package/dist/esm/instance-card.js.map +1 -1
- package/dist/esm/license-details.js +2 -1
- package/dist/esm/license-details.js.map +1 -1
- package/dist/esm/linux-install-wizard.js +90 -14
- package/dist/esm/linux-install-wizard.js.map +1 -1
- package/dist/esm/online-instance-list.js +90 -5
- package/dist/esm/online-instance-list.js.map +1 -1
- package/dist/esm/pending-installations.js +187 -103
- package/dist/esm/pending-installations.js.map +1 -1
- package/dist/esm/security-card.js +76 -16
- package/dist/esm/security-card.js.map +1 -1
- package/dist/esm/support-card.js +2 -1
- package/dist/esm/support-card.js.map +1 -1
- package/dist/esm/top-nav-user-menu.js +4 -2
- package/dist/esm/top-nav-user-menu.js.map +1 -1
- package/dist/esm/top-nav.js +2 -1
- package/dist/esm/top-nav.js.map +1 -1
- package/dist/esm/update-layout.js +2 -1
- package/dist/esm/update-layout.js.map +1 -1
- package/dist/esm/upload-support-bundle-modal.js +19 -19
- package/dist/esm/upload-support-bundle-modal.js.map +1 -1
- package/dist/esm/utils/index.js +2 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/helm-install-wizard.d.mts +5 -4
- package/dist/helm-install-wizard.d.ts +5 -4
- package/dist/helm-install-wizard.js +28 -15
- package/dist/helm-install-wizard.js.map +1 -1
- package/dist/index-DkjaogsF.d.mts +891 -0
- package/dist/index-DkjaogsF.d.ts +891 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/install-actions.d.mts +2 -3
- package/dist/install-actions.d.ts +2 -3
- package/dist/install-actions.js +54 -10
- package/dist/install-actions.js.map +1 -1
- package/dist/install-card.d.mts +1 -1
- package/dist/install-card.d.ts +1 -1
- package/dist/instance-card.d.mts +5 -3
- package/dist/instance-card.d.ts +5 -3
- package/dist/instance-card.js +83 -3
- package/dist/instance-card.js.map +1 -1
- package/dist/license-card.d.mts +1 -1
- package/dist/license-card.d.ts +1 -1
- package/dist/license-details.js +2 -1
- package/dist/license-details.js.map +1 -1
- package/dist/linux-install-wizard.d.mts +6 -6
- package/dist/linux-install-wizard.d.ts +6 -6
- package/dist/linux-install-wizard.js +90 -14
- package/dist/linux-install-wizard.js.map +1 -1
- package/dist/online-instance-list.d.mts +5 -3
- package/dist/online-instance-list.d.ts +5 -3
- package/dist/online-instance-list.js +94 -5
- package/dist/online-instance-list.js.map +1 -1
- package/dist/pending-installations.d.mts +3 -3
- package/dist/pending-installations.d.ts +3 -3
- package/dist/pending-installations.js +186 -102
- package/dist/pending-installations.js.map +1 -1
- package/dist/security-card.d.mts +3 -2
- package/dist/security-card.d.ts +3 -2
- package/dist/security-card.js +76 -16
- package/dist/security-card.js.map +1 -1
- package/dist/styles.css +43 -0
- package/dist/support-bundles-card.d.mts +1 -1
- package/dist/support-bundles-card.d.ts +1 -1
- package/dist/support-card.js +2 -1
- package/dist/support-card.js.map +1 -1
- package/dist/top-nav-user-menu.d.mts +3 -1
- package/dist/top-nav-user-menu.d.ts +3 -1
- package/dist/top-nav-user-menu.js +4 -2
- package/dist/top-nav-user-menu.js.map +1 -1
- package/dist/top-nav.js +2 -1
- package/dist/top-nav.js.map +1 -1
- package/dist/update-layout.js +2 -1
- package/dist/update-layout.js.map +1 -1
- package/dist/upload-support-bundle-modal.d.mts +7 -3
- package/dist/upload-support-bundle-modal.d.ts +7 -3
- package/dist/upload-support-bundle-modal.js +19 -19
- package/dist/upload-support-bundle-modal.js.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/install-B19AaKF_.d.mts +0 -233
- package/dist/install-Bi1qJ8Bu.d.ts +0 -233
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
+
import Link from 'next/link';
|
|
3
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -82,6 +83,33 @@ function UpdateBadge({ count }) {
|
|
|
82
83
|
if (count <= 0) return null;
|
|
83
84
|
return /* @__PURE__ */ jsx("span", { className: "ml-1.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-rose-500 text-xs font-medium text-white", children: count });
|
|
84
85
|
}
|
|
86
|
+
function CVEBadge({ severity, count }) {
|
|
87
|
+
if (count === 0) return null;
|
|
88
|
+
const severityColors = {
|
|
89
|
+
critical: "bg-pink-50 text-pink-600",
|
|
90
|
+
high: "bg-yellow-50 text-yellow-600",
|
|
91
|
+
medium: "bg-blue-50 text-blue-400",
|
|
92
|
+
low: "bg-green-50 text-green-500"
|
|
93
|
+
};
|
|
94
|
+
const severityLabels = {
|
|
95
|
+
critical: "C",
|
|
96
|
+
high: "H",
|
|
97
|
+
medium: "M",
|
|
98
|
+
low: "L"
|
|
99
|
+
};
|
|
100
|
+
const colorClass = severityColors[severity] || "bg-gray-50 text-gray-600";
|
|
101
|
+
return /* @__PURE__ */ jsxs(
|
|
102
|
+
"span",
|
|
103
|
+
{
|
|
104
|
+
className: `inline-flex items-center gap-0.5 rounded-md px-2 py-0.5 text-xs font-medium ${colorClass}`,
|
|
105
|
+
title: `${count} ${severity} CVE${count > 1 ? "s" : ""}`,
|
|
106
|
+
children: [
|
|
107
|
+
count,
|
|
108
|
+
severityLabels[severity]
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
85
113
|
function StatusBadge({ status }) {
|
|
86
114
|
const colorMap = {
|
|
87
115
|
ready: "bg-green-50 text-green-600",
|
|
@@ -103,13 +131,43 @@ function InstanceCard({
|
|
|
103
131
|
channelReleases = [],
|
|
104
132
|
installOptions = [],
|
|
105
133
|
onUpdateClick,
|
|
106
|
-
primaryColor = "#6366f1"
|
|
134
|
+
primaryColor = "#6366f1",
|
|
135
|
+
securityData = {},
|
|
136
|
+
securityDiffData = {},
|
|
137
|
+
securityEnabled = false
|
|
107
138
|
}) {
|
|
108
139
|
const [showInstanceId, setShowInstanceId] = useState(false);
|
|
109
140
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
110
141
|
const instanceName = getInstanceName(instance);
|
|
111
142
|
const installType = getInstallType(instance, installOptions);
|
|
112
143
|
const availableUpdates = calculateUpdatesForInstance(instance, channelReleases);
|
|
144
|
+
let securityInstallType = "linux";
|
|
145
|
+
if (installType === "helm") {
|
|
146
|
+
securityInstallType = "helm";
|
|
147
|
+
} else if (installType === "embedded cluster" || installType === null) {
|
|
148
|
+
securityInstallType = "linux";
|
|
149
|
+
}
|
|
150
|
+
const securityInfoKey = instance.channelSequence ? `${instance.channelSequence}-${securityInstallType}-${instance.isAirgap}` : null;
|
|
151
|
+
const securityInfo = securityInfoKey ? securityData[securityInfoKey] : void 0;
|
|
152
|
+
const hasSecurityInfo = securityInfo && securityInfo.length > 0;
|
|
153
|
+
const cveSummary = hasSecurityInfo ? securityInfo.reduce(
|
|
154
|
+
(acc, image) => {
|
|
155
|
+
if (!image.security?.result) {
|
|
156
|
+
return acc;
|
|
157
|
+
}
|
|
158
|
+
acc.critical += Object.keys(image.security.result.critical || {}).length;
|
|
159
|
+
acc.high += Object.keys(image.security.result.high || {}).length;
|
|
160
|
+
acc.medium += Object.keys(image.security.result.medium || {}).length;
|
|
161
|
+
acc.low += Object.keys(image.security.result.low || {}).length;
|
|
162
|
+
return acc;
|
|
163
|
+
},
|
|
164
|
+
{ critical: 0, high: 0, medium: 0, low: 0 }
|
|
165
|
+
) : { critical: 0, high: 0, medium: 0, low: 0 };
|
|
166
|
+
const hasCVEs = cveSummary.critical > 0 || cveSummary.high > 0 || cveSummary.medium > 0 || cveSummary.low > 0;
|
|
167
|
+
const securityDiff = securityInfoKey ? securityDiffData[securityInfoKey] : void 0;
|
|
168
|
+
const hasCriticalFixed = securityDiff?.images ? Object.entries(securityDiff.images).some(
|
|
169
|
+
([, image]) => image.removed?.critical && Object.keys(image.removed.critical).length > 0
|
|
170
|
+
) : false;
|
|
113
171
|
const displayText = showInstanceId ? instance.id.slice(0, 7) : instanceName || instance.id.slice(0, 7);
|
|
114
172
|
const getTooltipText = () => {
|
|
115
173
|
if (instanceName) {
|
|
@@ -186,6 +244,24 @@ function InstanceCard({
|
|
|
186
244
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: formatDateTime(instance.lastCheckin) })
|
|
187
245
|
] })
|
|
188
246
|
] }),
|
|
247
|
+
securityEnabled && hasSecurityInfo && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
248
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: "CVEs:" }),
|
|
249
|
+
hasCVEs && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
250
|
+
/* @__PURE__ */ jsx(CVEBadge, { severity: "critical", count: cveSummary.critical }),
|
|
251
|
+
/* @__PURE__ */ jsx(CVEBadge, { severity: "high", count: cveSummary.high }),
|
|
252
|
+
/* @__PURE__ */ jsx(CVEBadge, { severity: "medium", count: cveSummary.medium }),
|
|
253
|
+
/* @__PURE__ */ jsx(CVEBadge, { severity: "low", count: cveSummary.low }),
|
|
254
|
+
/* @__PURE__ */ jsx(
|
|
255
|
+
Link,
|
|
256
|
+
{
|
|
257
|
+
href: `/security?sequence=${instance.channelSequence}&installType=${securityInstallType}`,
|
|
258
|
+
className: "text-xs font-medium text-blue-500 hover:underline",
|
|
259
|
+
children: "details"
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
] }),
|
|
263
|
+
!hasCVEs && /* @__PURE__ */ jsx("div", { className: "inline-flex h-5 items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: "None" }) })
|
|
264
|
+
] }),
|
|
189
265
|
instance.tags && instance.tags.filter((tag) => tag.key !== "name").length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: instance.tags.filter((tag) => tag.key !== "name").map((tag) => /* @__PURE__ */ jsx(
|
|
190
266
|
"span",
|
|
191
267
|
{
|
|
@@ -200,8 +276,8 @@ function InstanceCard({
|
|
|
200
276
|
{
|
|
201
277
|
onClick: handleUpdateClick,
|
|
202
278
|
className: "rounded px-4 py-1.5 text-sm font-medium text-white transition-opacity hover:opacity-90",
|
|
203
|
-
style: { backgroundColor: primaryColor },
|
|
204
|
-
children: "Update available"
|
|
279
|
+
style: { backgroundColor: hasCriticalFixed ? "#dc2626" : primaryColor },
|
|
280
|
+
children: hasCriticalFixed ? "Critical update available" : "Update available"
|
|
205
281
|
}
|
|
206
282
|
) })
|
|
207
283
|
] }) });
|
|
@@ -216,6 +292,9 @@ var AirgapInstances = ({
|
|
|
216
292
|
onCreateManuallyClick,
|
|
217
293
|
onCreateClick,
|
|
218
294
|
primaryColor = "#6366f1",
|
|
295
|
+
securityData = {},
|
|
296
|
+
securityDiffData = {},
|
|
297
|
+
securityEnabled = false,
|
|
219
298
|
showCreateButton = true
|
|
220
299
|
}) => {
|
|
221
300
|
const [showInactive, setShowInactive] = useState(true);
|
|
@@ -275,7 +354,10 @@ var AirgapInstances = ({
|
|
|
275
354
|
channelReleases,
|
|
276
355
|
installOptions,
|
|
277
356
|
onUpdateClick,
|
|
278
|
-
primaryColor
|
|
357
|
+
primaryColor,
|
|
358
|
+
securityData,
|
|
359
|
+
securityDiffData,
|
|
360
|
+
securityEnabled
|
|
279
361
|
},
|
|
280
362
|
instance.id
|
|
281
363
|
)) })
|
|
@@ -290,7 +372,10 @@ var AirgapInstances = ({
|
|
|
290
372
|
channelReleases,
|
|
291
373
|
installOptions,
|
|
292
374
|
onUpdateClick,
|
|
293
|
-
primaryColor
|
|
375
|
+
primaryColor,
|
|
376
|
+
securityData,
|
|
377
|
+
securityDiffData,
|
|
378
|
+
securityEnabled
|
|
294
379
|
},
|
|
295
380
|
instance.id
|
|
296
381
|
)) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/actions/instances.ts","../../src/components/instance-card.tsx","../../src/components/airgap-instances.tsx"],"names":["useState","jsxs","jsx"],"mappings":"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","file":"airgap-instances.js","sourcesContent":["/**\n * Instance-related server actions for the Update page.\n * \n * These actions handle fetching and managing instances including:\n * - Listing all instances (online and airgap)\n * - Fetching install options for instances\n * - Calculating update availability\n */\n\nimport { authenticatedFetch } from \"../utils/api-client\";\nimport { getApiOrigin, getCustomerIdFromToken } from \"./index\";\nimport type { PortalActionContext } from \"./index\";\nimport type { ChannelRelease, InstallOptions } from \"./install\";\nexport type { InstallOptions } from \"./install\";\n\n// =============================================================================\n// Types - Instance\n// =============================================================================\n\nexport type ResourceStatus = \"ready\" | \"updating\" | \"unknown\" | \"missing\" | \"unavailable\" | \"degraded\";\n\nexport interface ResourceState {\n kind: string;\n name: string;\n namespace: string;\n state: ResourceStatus;\n}\n\nexport interface InstanceTag {\n key: string;\n value: string;\n origin?: string;\n}\n\nexport interface Instance {\n id: string;\n firstCheckin: string;\n firstReadyAt?: string;\n lastCheckin: string;\n isAirgap: boolean;\n appStatus?: string;\n resourceStates?: ResourceState[];\n versionLabel?: string;\n channelId?: string;\n channelSequence?: number;\n serviceAccountId?: string;\n embeddedClusterId?: string;\n kotsInstallId?: string;\n kurlInstallId?: string;\n k8sVersion?: string;\n k8sDistribution?: string;\n tags?: InstanceTag[];\n}\n\nexport interface FetchInstancesInput {\n token: string;\n}\n\nexport interface FetchInstancesResult {\n instances: Instance[];\n online: Instance[];\n airgap: Instance[];\n}\n\n// =============================================================================\n// Types - Install Options by Instance\n// =============================================================================\n\nexport interface FetchInstallOptionsByInstanceIdsInput {\n token: string;\n instanceIds: string[];\n}\n\nexport interface FetchInstallOptionsByInstanceIdsResult {\n installOptions: InstallOptions[];\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** 24 hours in milliseconds - threshold for active vs inactive instances */\nexport const ACTIVE_INSTANCE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Determines if an instance is active based on last check-in time.\n * Active = last check-in within the last 24 hours.\n */\nexport function isInstanceActive(instance: Instance): boolean {\n if (!instance.lastCheckin) {\n return false;\n }\n const lastCheckinTime = new Date(instance.lastCheckin).getTime();\n const threshold = Date.now() - ACTIVE_INSTANCE_THRESHOLD_MS;\n return lastCheckinTime > threshold;\n}\n\n/**\n * Filters instances into active and inactive lists.\n */\nexport function filterActiveInactiveInstances(instances: Instance[]): {\n activeInstances: Instance[];\n inactiveInstances: Instance[];\n} {\n const activeInstances = instances.filter(isInstanceActive);\n const inactiveInstances = instances.filter((instance) => !isInstanceActive(instance));\n return { activeInstances, inactiveInstances };\n}\n\n/**\n * Gets the install type for an instance based on instance data and install options.\n */\nexport function getInstallType(\n instance: Instance,\n installOptions?: InstallOptions[]\n): \"helm\" | \"embedded cluster\" | null {\n const instanceHasNoInstallTypes =\n !instance.kotsInstallId &&\n !instance.kurlInstallId &&\n !instance.embeddedClusterId;\n const isManuallyCreatedAirgapInstance =\n instance.isAirgap && instanceHasNoInstallTypes;\n\n // Check for embedded cluster first - explicitly set\n if (instance.embeddedClusterId) {\n return \"embedded cluster\";\n }\n\n // Check for helm install with k8s info\n if (instanceHasNoInstallTypes && instance.k8sDistribution) {\n return \"helm\";\n }\n\n // Default fallback for ambiguous cases (manually created airgap without k8s info):\n // Check install options first, then assume embedded cluster\n if (isManuallyCreatedAirgapInstance && !instance.k8sDistribution) {\n if (installOptions) {\n const matchingInstallOption = installOptions.find(\n (option) => option.instance_id === instance.id\n );\n if (matchingInstallOption) {\n if (matchingInstallOption.install_type === \"helm\") {\n return \"helm\";\n }\n if (matchingInstallOption.install_type === \"linux\") {\n return \"embedded cluster\";\n }\n }\n }\n // Final fallback: assume embedded cluster\n return \"embedded cluster\";\n }\n\n return null;\n}\n\n/**\n * Calculates the number of available updates for an instance.\n */\nexport function calculateUpdatesForInstance(\n instance: Instance,\n channelReleases: ChannelRelease[]\n): number {\n if (!channelReleases || !Array.isArray(channelReleases)) {\n return 0;\n }\n\n const instanceChannelSequence = instance.channelSequence ?? 0;\n\n // Filter to matching channel and count releases with higher sequence\n const matchingReleases = channelReleases.filter(\n (release) => release.channelId === instance.channelId\n );\n\n return matchingReleases.filter(\n (release) => release.channelSequence > instanceChannelSequence\n ).length;\n}\n\n/**\n * Gets the instance name from tags or returns truncated ID.\n */\nexport function getInstanceName(instance: Instance): string {\n const nameTag = instance.tags?.find((tag) => tag.key === \"name\");\n return nameTag?.value || instance.id.slice(0, 7);\n}\n\n// =============================================================================\n// Actions\n// =============================================================================\n\n/**\n * Fetches all instances for the customer.\n * Returns instances split into online and airgap categories.\n */\nexport async function fetchInstances(\n input: FetchInstancesInput,\n context?: PortalActionContext\n): Promise<FetchInstancesResult> {\n const { token } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"fetchInstances requires a session token\");\n }\n\n const customerId = getCustomerIdFromToken(token);\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances`);\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] fetching instances via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"GET\",\n token,\n headers: {\n accept: \"application/json\"\n },\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Instances request failed (${response.status} ${response.statusText}): ${errorText}`\n );\n }\n\n const payload = await response.json();\n // Extract from Enterprise Portal API envelope\n const data = payload.data;\n const allInstances: Instance[] = data?.instances || [];\n\n // Split into online and airgap\n const online = allInstances.filter((instance) => !instance.isAirgap);\n const airgap = allInstances.filter((instance) => instance.isAirgap);\n\n return {\n instances: allInstances,\n online,\n airgap\n };\n}\n\n// =============================================================================\n// Types - Create Air Gap Instance\n// =============================================================================\n\nexport type AirgapInstanceStatus = \"unavailable\" | \"missing\";\n\nexport interface CreateAirgapInstanceInput {\n token: string;\n serviceAccountId: string;\n instanceName?: string;\n channelId: string;\n channelSequence: number;\n k8sVersion?: string;\n k8sDistribution?: string;\n appStatus?: AirgapInstanceStatus;\n}\n\nexport interface CreateAirgapInstanceResult {\n instanceId: string;\n licenseId: string;\n createdAt: string;\n lastActive: string;\n appFirstReadyAt: string | null;\n appStatus: string;\n isAirgap: boolean;\n channelId?: string;\n channelSequence?: number;\n k8sVersion?: string;\n k8sDistribution?: string;\n}\n\n/**\n * Creates an air gap instance record manually.\n * This is used when customers want to track air gap instances that can't check in.\n */\nexport async function createAirgapInstance(\n input: CreateAirgapInstanceInput,\n context?: PortalActionContext\n): Promise<CreateAirgapInstanceResult> {\n const {\n token,\n serviceAccountId,\n instanceName,\n channelId,\n channelSequence,\n k8sVersion,\n k8sDistribution,\n appStatus = \"missing\"\n } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"createAirgapInstance requires a session token\");\n }\n\n if (!serviceAccountId?.trim()) {\n throw new Error(\"Service account ID is required\");\n }\n\n if (!channelId?.trim()) {\n throw new Error(\"Channel ID is required\");\n }\n\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances/airgap`);\n\n const body: Record<string, unknown> = {\n service_account_id: serviceAccountId.trim(),\n channel_id: channelId.trim(),\n channel_sequence: channelSequence,\n app_status: appStatus\n };\n\n if (instanceName?.trim()) {\n body.instance_name = instanceName.trim();\n }\n if (k8sVersion?.trim()) {\n body.k8s_version = k8sVersion.trim();\n }\n if (k8sDistribution?.trim()) {\n body.k8s_distribution = k8sDistribution.trim();\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] creating airgap instance via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"POST\",\n token,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\"\n },\n body: JSON.stringify(body),\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"Failed to create airgap instance\";\n try {\n const errorData = JSON.parse(errorText);\n errorMessage = errorData.error || errorData.message || errorMessage;\n } catch {\n // Use default message\n }\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n \n return {\n instanceId: data.instance_id,\n licenseId: data.license_id,\n createdAt: data.created_at,\n lastActive: data.last_active,\n appFirstReadyAt: data.app_first_ready_at,\n appStatus: data.app_status,\n isAirgap: data.is_airgap,\n channelId: data.channel_id,\n channelSequence: data.channel_sequence,\n k8sVersion: data.k8s_version,\n k8sDistribution: data.k8s_distribution\n };\n}\n\n// =============================================================================\n// Types - Update Air Gap Instance\n// =============================================================================\n\nexport interface UpdateAirgapInstanceInput {\n token: string;\n instanceId: string;\n serviceAccountId?: string;\n channelId: string;\n channelSequence: number;\n}\n\nexport interface UpdateAirgapInstanceResult {\n success: boolean;\n}\n\n/**\n * Updates an air gap instance record (mark update complete).\n * This is called after completing an air gap update to update the instance version.\n */\nexport async function updateAirgapInstance(\n input: UpdateAirgapInstanceInput,\n context?: PortalActionContext\n): Promise<UpdateAirgapInstanceResult> {\n const {\n token,\n instanceId,\n serviceAccountId,\n channelId,\n channelSequence\n } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"updateAirgapInstance requires a session token\");\n }\n\n if (!instanceId?.trim()) {\n throw new Error(\"Instance ID is required\");\n }\n\n if (!channelId?.trim()) {\n throw new Error(\"Channel ID is required\");\n }\n\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances/airgap/${instanceId.trim()}`);\n\n const body: Record<string, unknown> = {\n channel_id: channelId.trim(),\n channel_sequence: channelSequence\n };\n\n if (serviceAccountId?.trim()) {\n body.service_account_id = serviceAccountId.trim();\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] updating airgap instance via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"PATCH\",\n token,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\"\n },\n body: JSON.stringify(body),\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"Failed to update airgap instance\";\n try {\n const errorData = JSON.parse(errorText);\n errorMessage = errorData.error || errorData.message || errorMessage;\n } catch {\n // Use default message\n }\n throw new Error(errorMessage);\n }\n\n return { success: true };\n}\n\n/**\n * Fetches install options for a batch of instance IDs.\n * Handles chunking automatically if more than 50 IDs are provided.\n */\nexport async function fetchInstallOptionsByInstanceIds(\n input: FetchInstallOptionsByInstanceIdsInput,\n context?: PortalActionContext\n): Promise<FetchInstallOptionsByInstanceIdsResult> {\n const { token, instanceIds } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"fetchInstallOptionsByInstanceIds requires a session token\");\n }\n\n if (!instanceIds || instanceIds.length === 0) {\n return { installOptions: [] };\n }\n\n const customerId = getCustomerIdFromToken(token);\n const origin = getApiOrigin();\n\n // Chunk instance IDs into groups of 50 (API limit)\n const chunks: string[][] = [];\n for (let i = 0; i < instanceIds.length; i += 50) {\n chunks.push(instanceIds.slice(i, i + 50));\n }\n\n const allInstallOptions: InstallOptions[] = [];\n\n for (const chunk of chunks) {\n if (chunk.length === 0) continue;\n\n // Build query string with multiple instance_id params\n const queryParams = chunk\n .map((id) => `instance_id=${encodeURIComponent(id)}`)\n .join(\"&\");\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = `${origin}/enterprise-portal/install-options?${queryParams}`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] fetching install options via %s\", url);\n }\n\n const response = await authenticatedFetch(url, {\n method: \"GET\",\n token,\n headers: {\n accept: \"application/json\"\n },\n signal: context?.signal\n });\n\n if (response.status === 404) {\n // No install options found for this chunk - continue\n continue;\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Install options request failed (${response.status} ${response.statusText}): ${errorText}`\n );\n }\n\n const envelope = await response.json();\n // Extract from Enterprise Portal API envelope\n const options: InstallOptions[] = envelope?.data?.install_options || [];\n allInstallOptions.push(...options);\n }\n\n return { installOptions: allInstallOptions };\n}\n\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { type Instance, type InstallOptions, getInstallType, getInstanceName, calculateUpdatesForInstance } from \"../actions/instances\";\nimport type { ChannelRelease } from \"../actions/install\";\n\ninterface InstanceCardProps {\n instance: Instance;\n isActive: boolean;\n channelReleases?: ChannelRelease[];\n installOptions?: InstallOptions[];\n onUpdateClick?: (instanceId: string) => void;\n primaryColor?: string;\n}\n\n/**\n * Formats a date string to a human-readable format.\n */\nfunction formatDateTime(dateString?: string): string {\n if (!dateString) return \"N/A\";\n const date = new Date(dateString);\n return date.toLocaleDateString(\"en-US\", {\n month: \"numeric\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Badge component for showing update count.\n */\nfunction UpdateBadge({ count }: { count: number }) {\n if (count <= 0) return null;\n return (\n <span className=\"ml-1.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-rose-500 text-xs font-medium text-white\">\n {count}\n </span>\n );\n}\n\n/**\n * Badge for resource status.\n */\nfunction StatusBadge({ status }: { status: string }) {\n const colorMap: Record<string, string> = {\n ready: \"bg-green-50 text-green-600\",\n updating: \"bg-green-50 text-green-600\",\n unknown: \"bg-blue-50 text-blue-500\",\n missing: \"bg-pink-50 text-pink-600\",\n unavailable: \"bg-pink-50 text-pink-600\",\n degraded: \"bg-yellow-50 text-yellow-600\"\n };\n\n const colorClass = colorMap[status] || \"bg-gray-50 text-gray-600\";\n\n return (\n <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${colorClass}`}>\n {status}\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </span>\n );\n}\n\n/**\n * Instance card component for displaying an instance in the Update page.\n */\nexport function InstanceCard({\n instance,\n isActive,\n channelReleases = [],\n installOptions = [],\n onUpdateClick,\n primaryColor = \"#6366f1\"\n}: InstanceCardProps) {\n const [showInstanceId, setShowInstanceId] = useState(false);\n const [showTooltip, setShowTooltip] = useState(false);\n \n const instanceName = getInstanceName(instance);\n const installType = getInstallType(instance, installOptions);\n const availableUpdates = calculateUpdatesForInstance(instance, channelReleases);\n\n // Get the display text for the instance name/ID\n const displayText = showInstanceId \n ? instance.id.slice(0, 7)\n : instanceName || instance.id.slice(0, 7);\n\n // Get the tooltip text\n const getTooltipText = () => {\n if (instanceName) {\n return showInstanceId \n ? \"Click to show instance name\" \n : \"Click to show instance ID\";\n }\n return \"Instance ID\";\n };\n\n // Get overall resource status\n const getOverallStatus = () => {\n if (!instance.resourceStates || instance.resourceStates.length === 0) {\n return \"unknown\";\n }\n // Count states\n const stateCounts = instance.resourceStates.reduce((acc, resource) => {\n acc[resource.state] = (acc[resource.state] || 0) + 1;\n return acc;\n }, {} as Record<string, number>);\n \n // Return most common state\n let maxCount = 0;\n let maxStatus = \"unknown\";\n for (const [status, count] of Object.entries(stateCounts)) {\n if (count > maxCount) {\n maxCount = count;\n maxStatus = status;\n }\n }\n return maxStatus;\n };\n\n const overallStatus = getOverallStatus();\n\n const handleUpdateClick = () => {\n if (onUpdateClick) {\n onUpdateClick(instance.id);\n }\n };\n\n return (\n <div className=\"rounded-lg border border-gray-200 bg-white p-4\">\n <div className=\"grid grid-cols-[auto_1fr_auto] gap-3\">\n {/* Status indicator dot */}\n <div\n className={`mt-1.5 h-2 w-2 rounded-full ${\n isActive ? \"bg-green-400\" : \"bg-gray-300\"\n }`}\n />\n\n {/* Instance details */}\n <div className=\"flex flex-col gap-3\">\n {/* Instance name, version, badges, status */}\n <div className=\"flex h-min flex-wrap items-center gap-3\">\n <div className=\"relative\">\n <span \n className=\"text-sm font-medium text-gray-900 cursor-pointer hover:underline\"\n onClick={() => setShowInstanceId(!showInstanceId)}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n title={showInstanceId ? instance.id : instanceName || instance.id}\n >\n {displayText}\n </span>\n {showTooltip && (\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white bg-gray-800 rounded whitespace-nowrap z-10\">\n {getTooltipText()}\n <div className=\"absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-800\" />\n </div>\n )}\n </div>\n <span className=\"flex items-center gap-1 text-xs text-gray-600\">\n Version: {instance.versionLabel || \"Unknown\"}\n <UpdateBadge count={availableUpdates} />\n </span>\n {instance.isAirgap && (\n <span className=\"rounded-full bg-indigo-100 px-2 py-0.5 text-xs font-medium text-indigo-700\">\n air gap\n </span>\n )}\n {installType && (\n <span className=\"rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600\">\n {installType}\n </span>\n )}\n {instance.resourceStates && instance.resourceStates.length > 0 && (\n <StatusBadge status={overallStatus} />\n )}\n </div>\n\n {/* Check-in times */}\n <div className=\"flex flex-wrap gap-4\">\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-gray-500\">First check-in:</span>\n <span className=\"text-xs text-gray-600\">\n {formatDateTime(instance.firstCheckin)}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-gray-500\">Last check-in:</span>\n <span className=\"text-xs text-gray-600\">\n {formatDateTime(instance.lastCheckin)}\n </span>\n </div>\n </div>\n\n {/* Tags (excluding name) */}\n {instance.tags && instance.tags.filter(tag => tag.key !== \"name\").length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {instance.tags\n .filter(tag => tag.key !== \"name\")\n .map((tag) => (\n <span\n key={tag.key}\n className=\"rounded-md bg-gray-100 px-2 py-1 text-sm text-gray-600\"\n >\n {tag.value}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Update button */}\n {availableUpdates > 0 && (\n <div className=\"flex items-center\">\n <button\n onClick={handleUpdateClick}\n className=\"rounded px-4 py-1.5 text-sm font-medium text-white transition-opacity hover:opacity-90\"\n style={{ backgroundColor: primaryColor }}\n >\n Update available\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nInstanceCard.displayName = \"InstanceCard\";\n\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { type Instance, type InstallOptions, filterActiveInactiveInstances } from \"../actions/instances\";\nimport type { ChannelRelease } from \"../actions/install\";\nimport { InstanceCard } from \"./instance-card\";\n\ninterface AirgapInstancesProps {\n instances?: Instance[];\n channelReleases?: ChannelRelease[];\n installOptions?: InstallOptions[];\n onUpdateClick?: (instanceId: string) => void;\n /** Called when \"From a support bundle\" is clicked */\n onCreateFromBundleClick?: () => void;\n /** Called when \"Enter information manually\" is clicked */\n onCreateManuallyClick?: () => void;\n /** @deprecated Use onCreateFromBundleClick or onCreateManuallyClick instead */\n onCreateClick?: () => void;\n primaryColor?: string;\n /** Whether to show the create button dropdown */\n showCreateButton?: boolean;\n}\n\nexport const AirgapInstances = ({\n instances = [],\n channelReleases = [],\n installOptions = [],\n onUpdateClick,\n onCreateFromBundleClick,\n onCreateManuallyClick,\n onCreateClick,\n primaryColor = \"#6366f1\",\n showCreateButton = true\n}: AirgapInstancesProps) => {\n const [showInactive, setShowInactive] = useState(true);\n const [isDropdownOpen, setIsDropdownOpen] = useState(false);\n\n // Filter active and inactive instances\n const { activeInstances, inactiveInstances } = filterActiveInactiveInstances(instances);\n\n const hasInactiveInstances = inactiveInstances.length > 0;\n\n const handleFromBundleClick = () => {\n setIsDropdownOpen(false);\n if (onCreateFromBundleClick) {\n onCreateFromBundleClick();\n } else if (onCreateClick) {\n // Fallback for backwards compatibility\n onCreateClick();\n }\n };\n\n const handleManuallyClick = () => {\n setIsDropdownOpen(false);\n if (onCreateManuallyClick) {\n onCreateManuallyClick();\n } else if (onCreateClick) {\n // Fallback for backwards compatibility\n onCreateClick();\n }\n };\n\n return (\n <div className=\"mt-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <h2 className=\"mb-0 flex items-center gap-4 text-xl font-semibold text-gray-900\">\n Air gap instances\n <span className=\"text-sm font-semibold text-emerald-500\">\n {activeInstances.length} active\n </span>\n <span className=\"text-sm font-semibold text-rose-400\">\n {inactiveInstances.length} inactive\n </span>\n </h2>\n {hasInactiveInstances && (\n <button\n onClick={() => setShowInactive(!showInactive)}\n className=\"mb-0 cursor-pointer text-sm font-medium text-blue-500 hover:underline\"\n >\n {showInactive ? \"Hide inactive instances\" : \"Show inactive instances\"}\n </button>\n )}\n </div>\n\n {/* Empty state */}\n {instances.length === 0 && (\n <div className=\"rounded-lg border border-gray-200 bg-white p-6\">\n <p className=\"mb-6 text-gray-600\">\n Air gap updates require an air gap instance record to determine install type, version etc.\n </p>\n </div>\n )}\n\n {/* Instance lists when there are instances */}\n {instances.length > 0 && (\n <div className=\"space-y-6\">\n {/* Active instances section */}\n <div className=\"space-y-4\">\n {activeInstances.length === 0 ? (\n <div className=\"rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-500\">\n No active instances found\n </div>\n ) : (\n <>\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-medium text-gray-800\">\n Active\n <span className=\"ml-2 text-xs font-medium text-gray-400\">\n last 24 hours\n </span>\n </h3>\n </div>\n <div className=\"space-y-4\">\n {activeInstances.map((instance) => (\n <InstanceCard\n key={instance.id}\n instance={instance}\n isActive={true}\n channelReleases={channelReleases}\n installOptions={installOptions}\n onUpdateClick={onUpdateClick}\n primaryColor={primaryColor}\n />\n ))}\n </div>\n </>\n )}\n </div>\n\n {/* Inactive instances section */}\n {showInactive && inactiveInstances.length > 0 && (\n <div className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-medium text-gray-800\">Inactive</h3>\n </div>\n <div className=\"space-y-4\">\n {inactiveInstances.map((instance) => (\n <InstanceCard\n key={instance.id}\n instance={instance}\n isActive={false}\n channelReleases={channelReleases}\n installOptions={installOptions}\n onUpdateClick={onUpdateClick}\n primaryColor={primaryColor}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n\n {/* Create instance button with dropdown */}\n {showCreateButton && (\n <div className=\"relative\">\n <button\n onClick={() => setIsDropdownOpen(!isDropdownOpen)}\n className=\"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white hover:opacity-90\"\n style={{ backgroundColor: primaryColor }}\n >\n Create air gap instance record\n <svg\n className={`h-4 w-4 transition-transform ${isDropdownOpen ? \"rotate-180\" : \"\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n\n {isDropdownOpen && (\n <div className=\"absolute left-0 z-10 mt-2 w-72 rounded-md border border-gray-200 bg-white shadow-lg\">\n <button\n onClick={handleFromBundleClick}\n className=\"flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50\"\n >\n <svg className=\"h-4 w-4 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n From a support bundle\n </button>\n <button\n onClick={handleManuallyClick}\n className=\"flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50\"\n >\n <svg className=\"h-4 w-4 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n Enter information manually\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\nAirgapInstances.displayName = \"AirgapInstances\";\n\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/actions/instances.ts","../../src/components/instance-card.tsx","../../src/components/airgap-instances.tsx"],"names":["useState","jsxs","jsx"],"mappings":"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","file":"airgap-instances.js","sourcesContent":["/**\n * Instance-related server actions for the Update page.\n * \n * These actions handle fetching and managing instances including:\n * - Listing all instances (online and airgap)\n * - Fetching install options for instances\n * - Calculating update availability\n */\n\nimport { authenticatedFetch } from \"../utils/api-client\";\nimport { getApiOrigin, getCustomerIdFromToken } from \"./index\";\nimport type { PortalActionContext } from \"./index\";\nimport type { ChannelRelease, InstallOptions } from \"./install\";\nexport type { InstallOptions } from \"./install\";\n\n// =============================================================================\n// Types - Instance\n// =============================================================================\n\nexport type ResourceStatus = \"ready\" | \"updating\" | \"unknown\" | \"missing\" | \"unavailable\" | \"degraded\";\n\nexport interface ResourceState {\n kind: string;\n name: string;\n namespace: string;\n state: ResourceStatus;\n}\n\nexport interface InstanceTag {\n key: string;\n value: string;\n origin?: string;\n}\n\nexport interface Instance {\n id: string;\n firstCheckin: string;\n firstReadyAt?: string;\n lastCheckin: string;\n isAirgap: boolean;\n appStatus?: string;\n resourceStates?: ResourceState[];\n versionLabel?: string;\n channelId?: string;\n channelSequence?: number;\n serviceAccountId?: string;\n embeddedClusterId?: string;\n kotsInstallId?: string;\n kurlInstallId?: string;\n k8sVersion?: string;\n k8sDistribution?: string;\n tags?: InstanceTag[];\n}\n\nexport interface FetchInstancesInput {\n token: string;\n}\n\nexport interface FetchInstancesResult {\n instances: Instance[];\n online: Instance[];\n airgap: Instance[];\n}\n\n// =============================================================================\n// Types - Install Options by Instance\n// =============================================================================\n\nexport interface FetchInstallOptionsByInstanceIdsInput {\n token: string;\n instanceIds: string[];\n}\n\nexport interface FetchInstallOptionsByInstanceIdsResult {\n installOptions: InstallOptions[];\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** 24 hours in milliseconds - threshold for active vs inactive instances */\nexport const ACTIVE_INSTANCE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Determines if an instance is active based on last check-in time.\n * Active = last check-in within the last 24 hours.\n */\nexport function isInstanceActive(instance: Instance): boolean {\n if (!instance.lastCheckin) {\n return false;\n }\n const lastCheckinTime = new Date(instance.lastCheckin).getTime();\n const threshold = Date.now() - ACTIVE_INSTANCE_THRESHOLD_MS;\n return lastCheckinTime > threshold;\n}\n\n/**\n * Filters instances into active and inactive lists.\n */\nexport function filterActiveInactiveInstances(instances: Instance[]): {\n activeInstances: Instance[];\n inactiveInstances: Instance[];\n} {\n const activeInstances = instances.filter(isInstanceActive);\n const inactiveInstances = instances.filter((instance) => !isInstanceActive(instance));\n return { activeInstances, inactiveInstances };\n}\n\n/**\n * Gets the install type for an instance based on instance data and install options.\n */\nexport function getInstallType(\n instance: Instance,\n installOptions?: InstallOptions[]\n): \"helm\" | \"embedded cluster\" | null {\n const instanceHasNoInstallTypes =\n !instance.kotsInstallId &&\n !instance.kurlInstallId &&\n !instance.embeddedClusterId;\n const isManuallyCreatedAirgapInstance =\n instance.isAirgap && instanceHasNoInstallTypes;\n\n // Check for embedded cluster first - explicitly set\n if (instance.embeddedClusterId) {\n return \"embedded cluster\";\n }\n\n // Check for helm install with k8s info\n if (instanceHasNoInstallTypes && instance.k8sDistribution) {\n return \"helm\";\n }\n\n // Default fallback for ambiguous cases (manually created airgap without k8s info):\n // Check install options first, then assume embedded cluster\n if (isManuallyCreatedAirgapInstance && !instance.k8sDistribution) {\n if (installOptions) {\n const matchingInstallOption = installOptions.find(\n (option) => option.instance_id === instance.id\n );\n if (matchingInstallOption) {\n if (matchingInstallOption.install_type === \"helm\") {\n return \"helm\";\n }\n if (matchingInstallOption.install_type === \"linux\") {\n return \"embedded cluster\";\n }\n }\n }\n // Final fallback: assume embedded cluster\n return \"embedded cluster\";\n }\n\n return null;\n}\n\n/**\n * Calculates the number of available updates for an instance.\n */\nexport function calculateUpdatesForInstance(\n instance: Instance,\n channelReleases: ChannelRelease[]\n): number {\n if (!channelReleases || !Array.isArray(channelReleases)) {\n return 0;\n }\n\n const instanceChannelSequence = instance.channelSequence ?? 0;\n\n // Filter to matching channel and count releases with higher sequence\n const matchingReleases = channelReleases.filter(\n (release) => release.channelId === instance.channelId\n );\n\n return matchingReleases.filter(\n (release) => release.channelSequence > instanceChannelSequence\n ).length;\n}\n\n/**\n * Gets the instance name from tags or returns truncated ID.\n */\nexport function getInstanceName(instance: Instance): string {\n const nameTag = instance.tags?.find((tag) => tag.key === \"name\");\n return nameTag?.value || instance.id.slice(0, 7);\n}\n\n// =============================================================================\n// Actions\n// =============================================================================\n\n/**\n * Fetches all instances for the customer.\n * Returns instances split into online and airgap categories.\n */\nexport async function fetchInstances(\n input: FetchInstancesInput,\n context?: PortalActionContext\n): Promise<FetchInstancesResult> {\n const { token } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"fetchInstances requires a session token\");\n }\n\n const customerId = getCustomerIdFromToken(token);\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances`);\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] fetching instances via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"GET\",\n token,\n headers: {\n accept: \"application/json\"\n },\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Instances request failed (${response.status} ${response.statusText}): ${errorText}`\n );\n }\n\n const payload = await response.json();\n // Extract from Enterprise Portal API envelope\n const data = payload.data;\n const allInstances: Instance[] = data?.instances || [];\n\n // Split into online and airgap\n const online = allInstances.filter((instance) => !instance.isAirgap);\n const airgap = allInstances.filter((instance) => instance.isAirgap);\n\n return {\n instances: allInstances,\n online,\n airgap\n };\n}\n\n// =============================================================================\n// Types - Create Air Gap Instance\n// =============================================================================\n\nexport type AirgapInstanceStatus = \"unavailable\" | \"missing\";\n\nexport interface CreateAirgapInstanceInput {\n token: string;\n serviceAccountId: string;\n instanceName?: string;\n channelId: string;\n channelSequence: number;\n k8sVersion?: string;\n k8sDistribution?: string;\n appStatus?: AirgapInstanceStatus;\n}\n\nexport interface CreateAirgapInstanceResult {\n instanceId: string;\n licenseId: string;\n createdAt: string;\n lastActive: string;\n appFirstReadyAt: string | null;\n appStatus: string;\n isAirgap: boolean;\n channelId?: string;\n channelSequence?: number;\n k8sVersion?: string;\n k8sDistribution?: string;\n}\n\n/**\n * Creates an air gap instance record manually.\n * This is used when customers want to track air gap instances that can't check in.\n */\nexport async function createAirgapInstance(\n input: CreateAirgapInstanceInput,\n context?: PortalActionContext\n): Promise<CreateAirgapInstanceResult> {\n const {\n token,\n serviceAccountId,\n instanceName,\n channelId,\n channelSequence,\n k8sVersion,\n k8sDistribution,\n appStatus = \"missing\"\n } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"createAirgapInstance requires a session token\");\n }\n\n if (!serviceAccountId?.trim()) {\n throw new Error(\"Service account ID is required\");\n }\n\n if (!channelId?.trim()) {\n throw new Error(\"Channel ID is required\");\n }\n\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances/airgap`);\n\n const body: Record<string, unknown> = {\n service_account_id: serviceAccountId.trim(),\n channel_id: channelId.trim(),\n channel_sequence: channelSequence,\n app_status: appStatus\n };\n\n if (instanceName?.trim()) {\n body.instance_name = instanceName.trim();\n }\n if (k8sVersion?.trim()) {\n body.k8s_version = k8sVersion.trim();\n }\n if (k8sDistribution?.trim()) {\n body.k8s_distribution = k8sDistribution.trim();\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] creating airgap instance via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"POST\",\n token,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\"\n },\n body: JSON.stringify(body),\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"Failed to create airgap instance\";\n try {\n const errorData = JSON.parse(errorText);\n errorMessage = errorData.error || errorData.message || errorMessage;\n } catch {\n // Use default message\n }\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n \n return {\n instanceId: data.instance_id,\n licenseId: data.license_id,\n createdAt: data.created_at,\n lastActive: data.last_active,\n appFirstReadyAt: data.app_first_ready_at,\n appStatus: data.app_status,\n isAirgap: data.is_airgap,\n channelId: data.channel_id,\n channelSequence: data.channel_sequence,\n k8sVersion: data.k8s_version,\n k8sDistribution: data.k8s_distribution\n };\n}\n\n// =============================================================================\n// Types - Update Air Gap Instance\n// =============================================================================\n\nexport interface UpdateAirgapInstanceInput {\n token: string;\n instanceId: string;\n serviceAccountId?: string;\n channelId: string;\n channelSequence: number;\n}\n\nexport interface UpdateAirgapInstanceResult {\n success: boolean;\n}\n\n/**\n * Updates an air gap instance record (mark update complete).\n * This is called after completing an air gap update to update the instance version.\n */\nexport async function updateAirgapInstance(\n input: UpdateAirgapInstanceInput,\n context?: PortalActionContext\n): Promise<UpdateAirgapInstanceResult> {\n const {\n token,\n instanceId,\n serviceAccountId,\n channelId,\n channelSequence\n } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"updateAirgapInstance requires a session token\");\n }\n\n if (!instanceId?.trim()) {\n throw new Error(\"Instance ID is required\");\n }\n\n if (!channelId?.trim()) {\n throw new Error(\"Channel ID is required\");\n }\n\n const origin = getApiOrigin();\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = new URL(`${origin}/enterprise-portal/instances/airgap/${instanceId.trim()}`);\n\n const body: Record<string, unknown> = {\n channel_id: channelId.trim(),\n channel_sequence: channelSequence\n };\n\n if (serviceAccountId?.trim()) {\n body.service_account_id = serviceAccountId.trim();\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] updating airgap instance via %s\", url.toString());\n }\n\n const response = await authenticatedFetch(url.toString(), {\n method: \"PATCH\",\n token,\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\"\n },\n body: JSON.stringify(body),\n signal: context?.signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"Failed to update airgap instance\";\n try {\n const errorData = JSON.parse(errorText);\n errorMessage = errorData.error || errorData.message || errorMessage;\n } catch {\n // Use default message\n }\n throw new Error(errorMessage);\n }\n\n return { success: true };\n}\n\n/**\n * Fetches install options for a batch of instance IDs.\n * Handles chunking automatically if more than 50 IDs are provided.\n */\nexport async function fetchInstallOptionsByInstanceIds(\n input: FetchInstallOptionsByInstanceIdsInput,\n context?: PortalActionContext\n): Promise<FetchInstallOptionsByInstanceIdsResult> {\n const { token, instanceIds } = input;\n\n if (!token || typeof token !== \"string\") {\n throw new Error(\"fetchInstallOptionsByInstanceIds requires a session token\");\n }\n\n if (!instanceIds || instanceIds.length === 0) {\n return { installOptions: [] };\n }\n\n const customerId = getCustomerIdFromToken(token);\n const origin = getApiOrigin();\n\n // Chunk instance IDs into groups of 50 (API limit)\n const chunks: string[][] = [];\n for (let i = 0; i < instanceIds.length; i += 50) {\n chunks.push(instanceIds.slice(i, i + 50));\n }\n\n const allInstallOptions: InstallOptions[] = [];\n\n for (const chunk of chunks) {\n if (chunk.length === 0) continue;\n\n // Build query string with multiple instance_id params\n const queryParams = chunk\n .map((id) => `instance_id=${encodeURIComponent(id)}`)\n .join(\"&\");\n\n // NEW: Use Enterprise Portal API endpoint (no customer_id needed)\n const url = `${origin}/enterprise-portal/install-options?${queryParams}`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\"[portal-components] fetching install options via %s\", url);\n }\n\n const response = await authenticatedFetch(url, {\n method: \"GET\",\n token,\n headers: {\n accept: \"application/json\"\n },\n signal: context?.signal\n });\n\n if (response.status === 404) {\n // No install options found for this chunk - continue\n continue;\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Install options request failed (${response.status} ${response.statusText}): ${errorText}`\n );\n }\n\n const envelope = await response.json();\n // Extract from Enterprise Portal API envelope\n const options: InstallOptions[] = envelope?.data?.install_options || [];\n allInstallOptions.push(...options);\n }\n\n return { installOptions: allInstallOptions };\n}\n\n","\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { type Instance, type InstallOptions, getInstallType, getInstanceName, calculateUpdatesForInstance } from \"../actions/instances\";\nimport type { ChannelRelease } from \"../actions/install\";\nimport type { SecurityReleaseImage, GetSecurityInfoDiffResult } from \"../actions\";\n\ninterface InstanceCardProps {\n instance: Instance;\n isActive: boolean;\n channelReleases?: ChannelRelease[];\n installOptions?: InstallOptions[];\n onUpdateClick?: (instanceId: string) => void;\n primaryColor?: string;\n securityData?: Record<string, SecurityReleaseImage[]>;\n securityDiffData?: Record<string, GetSecurityInfoDiffResult>;\n securityEnabled?: boolean;\n}\n\ntype CVESeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\n\n/**\n * Formats a date string to a human-readable format.\n */\nfunction formatDateTime(dateString?: string): string {\n if (!dateString) return \"N/A\";\n const date = new Date(dateString);\n return date.toLocaleDateString(\"en-US\", {\n month: \"numeric\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Badge component for showing update count.\n */\nfunction UpdateBadge({ count }: { count: number }) {\n if (count <= 0) return null;\n return (\n <span className=\"ml-1.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-rose-500 text-xs font-medium text-white\">\n {count}\n </span>\n );\n}\n\n/**\n * Badge component for showing CVE counts by severity.\n */\nfunction CVEBadge({ severity, count }: { severity: CVESeverity; count: number }) {\n if (count === 0) return null;\n\n const severityColors: Record<CVESeverity, string> = {\n critical: \"bg-pink-50 text-pink-600\",\n high: \"bg-yellow-50 text-yellow-600\",\n medium: \"bg-blue-50 text-blue-400\",\n low: \"bg-green-50 text-green-500\"\n };\n\n const severityLabels: Record<CVESeverity, string> = {\n critical: \"C\",\n high: \"H\",\n medium: \"M\",\n low: \"L\"\n };\n\n const colorClass = severityColors[severity] || \"bg-gray-50 text-gray-600\";\n\n return (\n <span\n className={`inline-flex items-center gap-0.5 rounded-md px-2 py-0.5 text-xs font-medium ${colorClass}`}\n title={`${count} ${severity} CVE${count > 1 ? \"s\" : \"\"}`}\n >\n {count}\n {severityLabels[severity]}\n </span>\n );\n}\n\n/**\n * Badge for resource status.\n */\nfunction StatusBadge({ status }: { status: string }) {\n const colorMap: Record<string, string> = {\n ready: \"bg-green-50 text-green-600\",\n updating: \"bg-green-50 text-green-600\",\n unknown: \"bg-blue-50 text-blue-500\",\n missing: \"bg-pink-50 text-pink-600\",\n unavailable: \"bg-pink-50 text-pink-600\",\n degraded: \"bg-yellow-50 text-yellow-600\"\n };\n\n const colorClass = colorMap[status] || \"bg-gray-50 text-gray-600\";\n\n return (\n <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${colorClass}`}>\n {status}\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </span>\n );\n}\n\n/**\n * Instance card component for displaying an instance in the Update page.\n */\nexport function InstanceCard({\n instance,\n isActive,\n channelReleases = [],\n installOptions = [],\n onUpdateClick,\n primaryColor = \"#6366f1\",\n securityData = {},\n securityDiffData = {},\n securityEnabled = false\n}: InstanceCardProps) {\n const [showInstanceId, setShowInstanceId] = useState(false);\n const [showTooltip, setShowTooltip] = useState(false);\n \n const instanceName = getInstanceName(instance);\n const installType = getInstallType(instance, installOptions);\n const availableUpdates = calculateUpdatesForInstance(instance, channelReleases);\n\n // Get security info for this instance\n // Convert install type to match the key format used when storing security data\n // - \"embedded cluster\" -> \"linux\" (security API expects \"linux\")\n // - null (kots/kurl) -> \"linux\" (default used in update-client.tsx)\n // - \"helm\" -> \"helm\"\n let securityInstallType: \"helm\" | \"linux\" = \"linux\";\n if (installType === \"helm\") {\n securityInstallType = \"helm\";\n } else if (installType === \"embedded cluster\" || installType === null) {\n securityInstallType = \"linux\";\n }\n \n const securityInfoKey = instance.channelSequence\n ? `${instance.channelSequence}-${securityInstallType}-${instance.isAirgap}`\n : null;\n const securityInfo = securityInfoKey ? securityData[securityInfoKey] : undefined;\n const hasSecurityInfo = securityInfo && securityInfo.length > 0;\n\n // Calculate CVE summary\n const cveSummary: Record<CVESeverity, number> = hasSecurityInfo\n ? securityInfo.reduce(\n (acc, image) => {\n if (!image.security?.result) {\n return acc;\n }\n acc.critical += Object.keys(image.security.result.critical || {}).length;\n acc.high += Object.keys(image.security.result.high || {}).length;\n acc.medium += Object.keys(image.security.result.medium || {}).length;\n acc.low += Object.keys(image.security.result.low || {}).length;\n return acc;\n },\n { critical: 0, high: 0, medium: 0, low: 0 }\n )\n : { critical: 0, high: 0, medium: 0, low: 0 };\n\n const hasCVEs =\n cveSummary.critical > 0 ||\n cveSummary.high > 0 ||\n cveSummary.medium > 0 ||\n cveSummary.low > 0;\n\n // Get security diff for this instance to check for critical fixes\n const securityDiff = securityInfoKey ? securityDiffData[securityInfoKey] : undefined;\n const hasCriticalFixed = securityDiff?.images\n ? Object.entries(securityDiff.images).some(\n ([, image]) =>\n image.removed?.critical &&\n Object.keys(image.removed.critical).length > 0\n )\n : false;\n\n // Get the display text for the instance name/ID\n const displayText = showInstanceId \n ? instance.id.slice(0, 7)\n : instanceName || instance.id.slice(0, 7);\n\n // Get the tooltip text\n const getTooltipText = () => {\n if (instanceName) {\n return showInstanceId \n ? \"Click to show instance name\" \n : \"Click to show instance ID\";\n }\n return \"Instance ID\";\n };\n\n // Get overall resource status\n const getOverallStatus = () => {\n if (!instance.resourceStates || instance.resourceStates.length === 0) {\n return \"unknown\";\n }\n // Count states\n const stateCounts = instance.resourceStates.reduce((acc, resource) => {\n acc[resource.state] = (acc[resource.state] || 0) + 1;\n return acc;\n }, {} as Record<string, number>);\n \n // Return most common state\n let maxCount = 0;\n let maxStatus = \"unknown\";\n for (const [status, count] of Object.entries(stateCounts)) {\n if (count > maxCount) {\n maxCount = count;\n maxStatus = status;\n }\n }\n return maxStatus;\n };\n\n const overallStatus = getOverallStatus();\n\n const handleUpdateClick = () => {\n if (onUpdateClick) {\n onUpdateClick(instance.id);\n }\n };\n\n return (\n <div className=\"rounded-lg border border-gray-200 bg-white p-4\">\n <div className=\"grid grid-cols-[auto_1fr_auto] gap-3\">\n {/* Status indicator dot */}\n <div\n className={`mt-1.5 h-2 w-2 rounded-full ${\n isActive ? \"bg-green-400\" : \"bg-gray-300\"\n }`}\n />\n\n {/* Instance details */}\n <div className=\"flex flex-col gap-3\">\n {/* Instance name, version, badges, status */}\n <div className=\"flex h-min flex-wrap items-center gap-3\">\n <div className=\"relative\">\n <span \n className=\"text-sm font-medium text-gray-900 cursor-pointer hover:underline\"\n onClick={() => setShowInstanceId(!showInstanceId)}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n title={showInstanceId ? instance.id : instanceName || instance.id}\n >\n {displayText}\n </span>\n {showTooltip && (\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white bg-gray-800 rounded whitespace-nowrap z-10\">\n {getTooltipText()}\n <div className=\"absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-800\" />\n </div>\n )}\n </div>\n <span className=\"flex items-center gap-1 text-xs text-gray-600\">\n Version: {instance.versionLabel || \"Unknown\"}\n <UpdateBadge count={availableUpdates} />\n </span>\n {instance.isAirgap && (\n <span className=\"rounded-full bg-indigo-100 px-2 py-0.5 text-xs font-medium text-indigo-700\">\n air gap\n </span>\n )}\n {installType && (\n <span className=\"rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600\">\n {installType}\n </span>\n )}\n {instance.resourceStates && instance.resourceStates.length > 0 && (\n <StatusBadge status={overallStatus} />\n )}\n </div>\n\n {/* Check-in times */}\n <div className=\"flex flex-wrap gap-4\">\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-gray-500\">First check-in:</span>\n <span className=\"text-xs text-gray-600\">\n {formatDateTime(instance.firstCheckin)}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-gray-500\">Last check-in:</span>\n <span className=\"text-xs text-gray-600\">\n {formatDateTime(instance.lastCheckin)}\n </span>\n </div>\n </div>\n\n {/* CVE Information */}\n {securityEnabled && hasSecurityInfo && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-gray-600\">CVEs:</span>\n {hasCVEs && (\n <div className=\"flex items-center gap-1\">\n <CVEBadge severity=\"critical\" count={cveSummary.critical} />\n <CVEBadge severity=\"high\" count={cveSummary.high} />\n <CVEBadge severity=\"medium\" count={cveSummary.medium} />\n <CVEBadge severity=\"low\" count={cveSummary.low} />\n <Link\n href={`/security?sequence=${instance.channelSequence}&installType=${securityInstallType}`}\n className=\"text-xs font-medium text-blue-500 hover:underline\"\n >\n details\n </Link>\n </div>\n )}\n {!hasCVEs && (\n <div className=\"inline-flex h-5 items-center justify-center\">\n <span className=\"text-xs text-gray-600\">None</span>\n </div>\n )}\n </div>\n )}\n\n {/* Tags (excluding name) */}\n {instance.tags && instance.tags.filter(tag => tag.key !== \"name\").length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {instance.tags\n .filter(tag => tag.key !== \"name\")\n .map((tag) => (\n <span\n key={tag.key}\n className=\"rounded-md bg-gray-100 px-2 py-1 text-sm text-gray-600\"\n >\n {tag.value}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Update button */}\n {availableUpdates > 0 && (\n <div className=\"flex items-center\">\n <button\n onClick={handleUpdateClick}\n className=\"rounded px-4 py-1.5 text-sm font-medium text-white transition-opacity hover:opacity-90\"\n style={{ backgroundColor: hasCriticalFixed ? \"#dc2626\" : primaryColor }}\n >\n {hasCriticalFixed ? \"Critical update available\" : \"Update available\"}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nInstanceCard.displayName = \"InstanceCard\";\n\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { type Instance, type InstallOptions, filterActiveInactiveInstances } from \"../actions/instances\";\nimport type { ChannelRelease } from \"../actions/install\";\nimport type { SecurityReleaseImage, GetSecurityInfoDiffResult } from \"../actions\";\nimport { InstanceCard } from \"./instance-card\";\n\ninterface AirgapInstancesProps {\n instances?: Instance[];\n channelReleases?: ChannelRelease[];\n installOptions?: InstallOptions[];\n onUpdateClick?: (instanceId: string) => void;\n /** Called when \"From a support bundle\" is clicked */\n onCreateFromBundleClick?: () => void;\n /** Called when \"Enter information manually\" is clicked */\n onCreateManuallyClick?: () => void;\n /** @deprecated Use onCreateFromBundleClick or onCreateManuallyClick instead */\n onCreateClick?: () => void;\n primaryColor?: string;\n securityData?: Record<string, SecurityReleaseImage[]>;\n securityDiffData?: Record<string, GetSecurityInfoDiffResult>;\n securityEnabled?: boolean;\n /** Whether to show the create button dropdown */\n showCreateButton?: boolean;\n}\n\nexport const AirgapInstances = ({\n instances = [],\n channelReleases = [],\n installOptions = [],\n onUpdateClick,\n onCreateFromBundleClick,\n onCreateManuallyClick,\n onCreateClick,\n primaryColor = \"#6366f1\",\n securityData = {},\n securityDiffData = {},\n securityEnabled = false,\n showCreateButton = true\n}: AirgapInstancesProps) => {\n const [showInactive, setShowInactive] = useState(true);\n const [isDropdownOpen, setIsDropdownOpen] = useState(false);\n\n // Filter active and inactive instances\n const { activeInstances, inactiveInstances } = filterActiveInactiveInstances(instances);\n\n const hasInactiveInstances = inactiveInstances.length > 0;\n\n const handleFromBundleClick = () => {\n setIsDropdownOpen(false);\n if (onCreateFromBundleClick) {\n onCreateFromBundleClick();\n } else if (onCreateClick) {\n // Fallback for backwards compatibility\n onCreateClick();\n }\n };\n\n const handleManuallyClick = () => {\n setIsDropdownOpen(false);\n if (onCreateManuallyClick) {\n onCreateManuallyClick();\n } else if (onCreateClick) {\n // Fallback for backwards compatibility\n onCreateClick();\n }\n };\n\n return (\n <div className=\"mt-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <h2 className=\"mb-0 flex items-center gap-4 text-xl font-semibold text-gray-900\">\n Air gap instances\n <span className=\"text-sm font-semibold text-emerald-500\">\n {activeInstances.length} active\n </span>\n <span className=\"text-sm font-semibold text-rose-400\">\n {inactiveInstances.length} inactive\n </span>\n </h2>\n {hasInactiveInstances && (\n <button\n onClick={() => setShowInactive(!showInactive)}\n className=\"mb-0 cursor-pointer text-sm font-medium text-blue-500 hover:underline\"\n >\n {showInactive ? \"Hide inactive instances\" : \"Show inactive instances\"}\n </button>\n )}\n </div>\n\n {/* Empty state */}\n {instances.length === 0 && (\n <div className=\"rounded-lg border border-gray-200 bg-white p-6\">\n <p className=\"mb-6 text-gray-600\">\n Air gap updates require an air gap instance record to determine install type, version etc.\n </p>\n </div>\n )}\n\n {/* Instance lists when there are instances */}\n {instances.length > 0 && (\n <div className=\"space-y-6\">\n {/* Active instances section */}\n <div className=\"space-y-4\">\n {activeInstances.length === 0 ? (\n <div className=\"rounded-lg border border-gray-200 bg-white p-4 text-sm text-gray-500\">\n No active instances found\n </div>\n ) : (\n <>\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-medium text-gray-800\">\n Active\n <span className=\"ml-2 text-xs font-medium text-gray-400\">\n last 24 hours\n </span>\n </h3>\n </div>\n <div className=\"space-y-4\">\n {activeInstances.map((instance) => (\n <InstanceCard\n key={instance.id}\n instance={instance}\n isActive={true}\n channelReleases={channelReleases}\n installOptions={installOptions}\n onUpdateClick={onUpdateClick}\n primaryColor={primaryColor}\n securityData={securityData}\n securityDiffData={securityDiffData}\n securityEnabled={securityEnabled}\n />\n ))}\n </div>\n </>\n )}\n </div>\n\n {/* Inactive instances section */}\n {showInactive && inactiveInstances.length > 0 && (\n <div className=\"space-y-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-medium text-gray-800\">Inactive</h3>\n </div>\n <div className=\"space-y-4\">\n {inactiveInstances.map((instance) => (\n <InstanceCard\n key={instance.id}\n instance={instance}\n isActive={false}\n channelReleases={channelReleases}\n installOptions={installOptions}\n onUpdateClick={onUpdateClick}\n primaryColor={primaryColor}\n securityData={securityData}\n securityDiffData={securityDiffData}\n securityEnabled={securityEnabled}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n\n {/* Create instance button with dropdown */}\n {showCreateButton && (\n <div className=\"relative\">\n <button\n onClick={() => setIsDropdownOpen(!isDropdownOpen)}\n className=\"inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white hover:opacity-90\"\n style={{ backgroundColor: primaryColor }}\n >\n Create air gap instance record\n <svg\n className={`h-4 w-4 transition-transform ${isDropdownOpen ? \"rotate-180\" : \"\"}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n\n {isDropdownOpen && (\n <div className=\"absolute left-0 z-10 mt-2 w-72 rounded-md border border-gray-200 bg-white shadow-lg\">\n <button\n onClick={handleFromBundleClick}\n className=\"flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50\"\n >\n <svg className=\"h-4 w-4 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n From a support bundle\n </button>\n <button\n onClick={handleManuallyClick}\n className=\"flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50\"\n >\n <svg className=\"h-4 w-4 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n Enter information manually\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\nAirgapInstances.displayName = \"AirgapInstances\";\n\n"]}
|
|
@@ -226,7 +226,8 @@ var HelmInstallWizard = ({
|
|
|
226
226
|
initialStep,
|
|
227
227
|
initialInstallOptionsId,
|
|
228
228
|
initialInstallOptionsData,
|
|
229
|
-
initialChannelReleases
|
|
229
|
+
initialChannelReleases,
|
|
230
|
+
supportPortalLink
|
|
230
231
|
}) => {
|
|
231
232
|
const [step, setStep] = useState(initialStep ?? 1);
|
|
232
233
|
const [instanceName, setInstanceName] = useState(initialInstallOptionsData?.instance_name ?? "");
|
|
@@ -291,7 +292,7 @@ var HelmInstallWizard = ({
|
|
|
291
292
|
const hasLoadedReleases = useRef(!!initialChannelReleases?.length);
|
|
292
293
|
const hasResumedInstallation = useRef(!!initialInstallOptionsData);
|
|
293
294
|
const helmReleases = useMemo(() => {
|
|
294
|
-
return releases.filter((r) => r.
|
|
295
|
+
return releases.filter((r) => r.helmCharts && r.helmCharts.length > 0);
|
|
295
296
|
}, [releases]);
|
|
296
297
|
const prevInitialStepRef = useRef(initialStep);
|
|
297
298
|
useEffect(() => {
|
|
@@ -300,9 +301,6 @@ var HelmInstallWizard = ({
|
|
|
300
301
|
}
|
|
301
302
|
prevInitialStepRef.current = initialStep;
|
|
302
303
|
}, [initialStep]);
|
|
303
|
-
useEffect(() => {
|
|
304
|
-
onStepChange?.(step);
|
|
305
|
-
}, [step, onStepChange]);
|
|
306
304
|
const prevInstallOptionsIdRef = useRef(installOptionsId);
|
|
307
305
|
useEffect(() => {
|
|
308
306
|
if (installOptionsId !== prevInstallOptionsIdRef.current) {
|
|
@@ -322,7 +320,7 @@ var HelmInstallWizard = ({
|
|
|
322
320
|
setReleases(result.channelReleases || []);
|
|
323
321
|
hasLoadedReleases.current = true;
|
|
324
322
|
const helmReleasesFiltered = (result.channelReleases || []).filter(
|
|
325
|
-
(r) => r.
|
|
323
|
+
(r) => r.helmCharts && r.helmCharts.length > 0
|
|
326
324
|
);
|
|
327
325
|
const firstRelease = helmReleasesFiltered[0];
|
|
328
326
|
if (firstRelease && !selectedRelease) {
|
|
@@ -553,6 +551,7 @@ var HelmInstallWizard = ({
|
|
|
553
551
|
}
|
|
554
552
|
console.debug("[helm-install-wizard] Transitioning to step 2 (reused service account)");
|
|
555
553
|
setStep(2);
|
|
554
|
+
onStepChange?.(2);
|
|
556
555
|
onInstallOptionsIdChange?.(installOptionsId);
|
|
557
556
|
return;
|
|
558
557
|
}
|
|
@@ -600,11 +599,14 @@ var HelmInstallWizard = ({
|
|
|
600
599
|
if (installOptionsResult.instructions) {
|
|
601
600
|
setInstructions(installOptionsResult.instructions);
|
|
602
601
|
}
|
|
602
|
+
console.debug("[helm-install-wizard] Transitioning to step 2");
|
|
603
|
+
setStep(2);
|
|
603
604
|
onInstallOptionsIdChange?.(optionsId);
|
|
605
|
+
} else {
|
|
606
|
+
console.debug("[helm-install-wizard] Transitioning to step 2 (no install options)");
|
|
607
|
+
setStep(2);
|
|
608
|
+
onStepChange?.(2);
|
|
604
609
|
}
|
|
605
|
-
console.debug("[helm-install-wizard] Transitioning to step 2");
|
|
606
|
-
setStep(2);
|
|
607
|
-
onStepChange?.(2);
|
|
608
610
|
} catch (error) {
|
|
609
611
|
console.error("[helm-install-wizard] Failed to continue", error);
|
|
610
612
|
const errorMessage = error instanceof Error ? error.message : "Failed to continue";
|
|
@@ -726,9 +728,19 @@ var HelmInstallWizard = ({
|
|
|
726
728
|
}
|
|
727
729
|
)
|
|
728
730
|
] }),
|
|
729
|
-
/* @__PURE__ */ jsxs("p", { className: "text-center text-xs text-gray-400", children: [
|
|
730
|
-
"Having trouble?
|
|
731
|
-
|
|
731
|
+
supportPortalLink && /* @__PURE__ */ jsxs("p", { className: "text-center text-xs text-gray-400", children: [
|
|
732
|
+
"Having trouble?",
|
|
733
|
+
" ",
|
|
734
|
+
/* @__PURE__ */ jsx(
|
|
735
|
+
"a",
|
|
736
|
+
{
|
|
737
|
+
className: "text-indigo-500 hover:text-indigo-600",
|
|
738
|
+
href: supportPortalLink,
|
|
739
|
+
target: "_blank",
|
|
740
|
+
rel: "noopener noreferrer",
|
|
741
|
+
children: "Open an issue."
|
|
742
|
+
}
|
|
743
|
+
)
|
|
732
744
|
] })
|
|
733
745
|
] }) })
|
|
734
746
|
] });
|
|
@@ -802,7 +814,7 @@ var HelmInstallWizard = ({
|
|
|
802
814
|
),
|
|
803
815
|
"Outbound requests require HTTPS Proxy"
|
|
804
816
|
] }),
|
|
805
|
-
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 text-sm text-gray-600", children: [
|
|
817
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 text-sm text-gray-600", hidden: true, children: [
|
|
806
818
|
/* @__PURE__ */ jsx(
|
|
807
819
|
"input",
|
|
808
820
|
{
|
|
@@ -844,7 +856,7 @@ var HelmInstallWizard = ({
|
|
|
844
856
|
),
|
|
845
857
|
"My workstation can only access the internet AND the registry (NOT the cluster)"
|
|
846
858
|
] }),
|
|
847
|
-
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 text-sm text-gray-600", children: [
|
|
859
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 text-sm text-gray-600", hidden: true, children: [
|
|
848
860
|
/* @__PURE__ */ jsx(
|
|
849
861
|
"input",
|
|
850
862
|
{
|
|
@@ -997,7 +1009,8 @@ var fetchCustomBrandingImpl = async () => {
|
|
|
997
1009
|
title: payload.appName,
|
|
998
1010
|
customColor1: payload.primaryColor,
|
|
999
1011
|
customColor2: payload.secondaryColor,
|
|
1000
|
-
favicon: payload.faviconUrl
|
|
1012
|
+
favicon: payload.faviconUrl,
|
|
1013
|
+
supportPortalLink: payload.supportPortalLink || ""
|
|
1001
1014
|
};
|
|
1002
1015
|
const brandingData = Buffer.from(JSON.stringify(brandingObject)).toString("base64");
|
|
1003
1016
|
return {
|