@replicated/portal-components 0.0.25 → 0.0.27

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.
Files changed (172) hide show
  1. package/components/metadata/registry.json +2 -2
  2. package/components/metadata/registry.md +2 -2
  3. package/dist/actions/branding-actions.js +6 -4
  4. package/dist/actions/branding-actions.js.map +1 -1
  5. package/dist/actions/change-team.js +1 -0
  6. package/dist/actions/change-team.js.map +1 -1
  7. package/dist/actions/index.d.mts +1 -1
  8. package/dist/actions/index.d.ts +1 -1
  9. package/dist/actions/index.js +1 -50
  10. package/dist/actions/index.js.map +1 -1
  11. package/dist/actions/install-actions.d.mts +1 -1
  12. package/dist/actions/install-actions.d.ts +1 -1
  13. package/dist/actions/install-actions.js +1 -0
  14. package/dist/actions/install-actions.js.map +1 -1
  15. package/dist/actions/invite-actions.js +6 -5
  16. package/dist/actions/invite-actions.js.map +1 -1
  17. package/dist/actions/magic-link-actions.js +6 -5
  18. package/dist/actions/magic-link-actions.js.map +1 -1
  19. package/dist/actions/service-account.d.mts +1 -1
  20. package/dist/actions/service-account.d.ts +1 -1
  21. package/dist/actions/service-account.js +1 -0
  22. package/dist/actions/service-account.js.map +1 -1
  23. package/dist/actions/support-bundles.d.mts +1 -1
  24. package/dist/actions/support-bundles.d.ts +1 -1
  25. package/dist/actions/support-bundles.js +1 -50
  26. package/dist/actions/support-bundles.js.map +1 -1
  27. package/dist/actions/team-settings.d.mts +1 -1
  28. package/dist/actions/team-settings.d.ts +1 -1
  29. package/dist/actions/team-settings.js +1 -0
  30. package/dist/actions/team-settings.js.map +1 -1
  31. package/dist/actions/trial-signup-actions.js +6 -5
  32. package/dist/actions/trial-signup-actions.js.map +1 -1
  33. package/dist/actions/trial-signup.js +6 -5
  34. package/dist/actions/trial-signup.js.map +1 -1
  35. package/dist/actions/user-settings.d.mts +1 -1
  36. package/dist/actions/user-settings.d.ts +1 -1
  37. package/dist/actions/user-settings.js +1 -0
  38. package/dist/actions/user-settings.js.map +1 -1
  39. package/dist/airgap-instances.d.mts +1 -1
  40. package/dist/airgap-instances.d.ts +1 -1
  41. package/dist/airgap-instances.js +25 -25
  42. package/dist/airgap-instances.js.map +1 -1
  43. package/dist/{saml-handlers.d.mts → api/saml.d.mts} +2 -2
  44. package/dist/{saml-handlers.d.ts → api/saml.d.ts} +2 -2
  45. package/dist/{saml-handlers.js → api/saml.js} +4 -3
  46. package/dist/api/saml.js.map +1 -0
  47. package/dist/api/support-bundles.d.mts +22 -0
  48. package/dist/api/support-bundles.d.ts +22 -0
  49. package/dist/api/support-bundles.js +245 -0
  50. package/dist/api/support-bundles.js.map +1 -0
  51. package/dist/{branding-BsMSywts.d.mts → branding-DmsrDTNE.d.mts} +2 -1
  52. package/dist/{branding-BsMSywts.d.ts → branding-DmsrDTNE.d.ts} +2 -1
  53. package/dist/esm/actions/branding-actions.js +6 -4
  54. package/dist/esm/actions/branding-actions.js.map +1 -1
  55. package/dist/esm/actions/change-team.js +1 -0
  56. package/dist/esm/actions/change-team.js.map +1 -1
  57. package/dist/esm/actions/index.js +2 -50
  58. package/dist/esm/actions/index.js.map +1 -1
  59. package/dist/esm/actions/install-actions.js +1 -0
  60. package/dist/esm/actions/install-actions.js.map +1 -1
  61. package/dist/esm/actions/invite-actions.js +6 -5
  62. package/dist/esm/actions/invite-actions.js.map +1 -1
  63. package/dist/esm/actions/magic-link-actions.js +6 -5
  64. package/dist/esm/actions/magic-link-actions.js.map +1 -1
  65. package/dist/esm/actions/service-account.js +1 -0
  66. package/dist/esm/actions/service-account.js.map +1 -1
  67. package/dist/esm/actions/support-bundles.js +2 -50
  68. package/dist/esm/actions/support-bundles.js.map +1 -1
  69. package/dist/esm/actions/team-settings.js +1 -0
  70. package/dist/esm/actions/team-settings.js.map +1 -1
  71. package/dist/esm/actions/trial-signup-actions.js +6 -5
  72. package/dist/esm/actions/trial-signup-actions.js.map +1 -1
  73. package/dist/esm/actions/trial-signup.js +6 -5
  74. package/dist/esm/actions/trial-signup.js.map +1 -1
  75. package/dist/esm/actions/user-settings.js +1 -0
  76. package/dist/esm/actions/user-settings.js.map +1 -1
  77. package/dist/esm/airgap-instances.js +25 -25
  78. package/dist/esm/airgap-instances.js.map +1 -1
  79. package/dist/esm/{saml-handlers.js → api/saml.js} +4 -3
  80. package/dist/esm/api/saml.js.map +1 -0
  81. package/dist/esm/api/support-bundles.js +243 -0
  82. package/dist/esm/api/support-bundles.js.map +1 -0
  83. package/dist/esm/helm-install-wizard.js +1 -0
  84. package/dist/esm/helm-install-wizard.js.map +1 -1
  85. package/dist/esm/index.js +262 -215
  86. package/dist/esm/index.js.map +1 -1
  87. package/dist/esm/install-actions.js +1 -0
  88. package/dist/esm/install-actions.js.map +1 -1
  89. package/dist/esm/instance-card.js +25 -25
  90. package/dist/esm/instance-card.js.map +1 -1
  91. package/dist/esm/license-details.js +1 -0
  92. package/dist/esm/license-details.js.map +1 -1
  93. package/dist/esm/linux-install-wizard.js +1 -0
  94. package/dist/esm/linux-install-wizard.js.map +1 -1
  95. package/dist/esm/online-instance-list.js +25 -25
  96. package/dist/esm/online-instance-list.js.map +1 -1
  97. package/dist/esm/saml-callback-client.js.map +1 -1
  98. package/dist/esm/security-card.js +39 -24
  99. package/dist/esm/security-card.js.map +1 -1
  100. package/dist/esm/support-card.js +1 -0
  101. package/dist/esm/support-card.js.map +1 -1
  102. package/dist/esm/top-nav.js +82 -79
  103. package/dist/esm/top-nav.js.map +1 -1
  104. package/dist/esm/update-layout.js +82 -79
  105. package/dist/esm/update-layout.js.map +1 -1
  106. package/dist/esm/upload-support-bundle-modal.js +46 -16
  107. package/dist/esm/upload-support-bundle-modal.js.map +1 -1
  108. package/dist/esm/utils/index.js +48 -1
  109. package/dist/esm/utils/index.js.map +1 -1
  110. package/dist/helm-install-wizard.d.mts +2 -2
  111. package/dist/helm-install-wizard.d.ts +2 -2
  112. package/dist/helm-install-wizard.js +1 -0
  113. package/dist/helm-install-wizard.js.map +1 -1
  114. package/dist/{index-DXy7RxOX.d.ts → index-Bcp17Mf3.d.ts} +1 -16
  115. package/dist/{index-JX9-CIMA.d.mts → index-DaH1bSuO.d.mts} +1 -16
  116. package/dist/index.d.mts +5 -4
  117. package/dist/index.d.ts +5 -4
  118. package/dist/index.js +262 -214
  119. package/dist/index.js.map +1 -1
  120. package/dist/install-actions.d.mts +2 -2
  121. package/dist/install-actions.d.ts +2 -2
  122. package/dist/install-actions.js +1 -0
  123. package/dist/install-actions.js.map +1 -1
  124. package/dist/instance-card.d.mts +1 -1
  125. package/dist/instance-card.d.ts +1 -1
  126. package/dist/instance-card.js +25 -25
  127. package/dist/instance-card.js.map +1 -1
  128. package/dist/license-details.js +1 -0
  129. package/dist/license-details.js.map +1 -1
  130. package/dist/linux-install-wizard.d.mts +2 -2
  131. package/dist/linux-install-wizard.d.ts +2 -2
  132. package/dist/linux-install-wizard.js +1 -0
  133. package/dist/linux-install-wizard.js.map +1 -1
  134. package/dist/online-instance-list.d.mts +1 -1
  135. package/dist/online-instance-list.d.ts +1 -1
  136. package/dist/online-instance-list.js +25 -25
  137. package/dist/online-instance-list.js.map +1 -1
  138. package/dist/pending-installations.d.mts +1 -1
  139. package/dist/pending-installations.d.ts +1 -1
  140. package/dist/saml-callback-client.d.mts +1 -1
  141. package/dist/saml-callback-client.d.ts +1 -1
  142. package/dist/saml-callback-client.js.map +1 -1
  143. package/dist/security-card.d.mts +1 -1
  144. package/dist/security-card.d.ts +1 -1
  145. package/dist/security-card.js +43 -24
  146. package/dist/security-card.js.map +1 -1
  147. package/dist/styles.css +4 -12
  148. package/dist/support-bundles-card.d.mts +1 -1
  149. package/dist/support-bundles-card.d.ts +1 -1
  150. package/dist/support-card.js +1 -0
  151. package/dist/support-card.js.map +1 -1
  152. package/dist/{top-nav-CEqw0KpO.d.ts → top-nav-8f2U69MF.d.ts} +1 -1
  153. package/dist/{top-nav-BUQAGoG1.d.mts → top-nav-B2yA3PC7.d.mts} +1 -1
  154. package/dist/top-nav.d.mts +2 -2
  155. package/dist/top-nav.d.ts +2 -2
  156. package/dist/top-nav.js +82 -79
  157. package/dist/top-nav.js.map +1 -1
  158. package/dist/update-layout.js +82 -79
  159. package/dist/update-layout.js.map +1 -1
  160. package/dist/upload-support-bundle-modal.d.mts +9 -9
  161. package/dist/upload-support-bundle-modal.d.ts +9 -9
  162. package/dist/upload-support-bundle-modal.js +46 -16
  163. package/dist/upload-support-bundle-modal.js.map +1 -1
  164. package/dist/upload-with-progress-D760HthX.d.mts +35 -0
  165. package/dist/upload-with-progress-D760HthX.d.ts +35 -0
  166. package/dist/utils/index.d.mts +3 -2
  167. package/dist/utils/index.d.ts +3 -2
  168. package/dist/utils/index.js +48 -0
  169. package/dist/utils/index.js.map +1 -1
  170. package/package.json +10 -5
  171. package/dist/esm/saml-handlers.js.map +0 -1
  172. package/dist/saml-handlers.js.map +0 -1
@@ -1,23 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UploadSupportBundleResult } from './upload-with-progress-D760HthX.mjs';
2
3
 
3
4
  interface UploadSupportBundleModalProps {
4
5
  isOpen: boolean;
5
6
  onClose: () => void;
6
- /** Server action to upload the support bundle */
7
- uploadSupportBundle: (formData: FormData) => Promise<{
8
- success: boolean;
9
- error?: string;
10
- bundleId?: string;
11
- bundleSlug?: string;
12
- }>;
7
+ /**
8
+ * Upload function with progress tracking
9
+ * onProgress receives percentage (0-100)
10
+ * signal is an AbortSignal for cancellation
11
+ */
12
+ uploadWithProgress: (file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<UploadSupportBundleResult>;
13
13
  /** Callback after successful upload - performs async polling to refresh bundle list */
14
14
  onUploadSuccess: () => Promise<void>;
15
15
  appId: string;
16
16
  primaryColor?: string;
17
17
  }
18
18
  declare const UploadSupportBundleModal: {
19
- ({ isOpen, onClose, uploadSupportBundle, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
19
+ ({ isOpen, onClose, uploadWithProgress, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
20
20
  displayName: string;
21
21
  };
22
22
 
23
- export { UploadSupportBundleModal };
23
+ export { UploadSupportBundleModal, UploadSupportBundleResult };
@@ -1,23 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UploadSupportBundleResult } from './upload-with-progress-D760HthX.js';
2
3
 
3
4
  interface UploadSupportBundleModalProps {
4
5
  isOpen: boolean;
5
6
  onClose: () => void;
6
- /** Server action to upload the support bundle */
7
- uploadSupportBundle: (formData: FormData) => Promise<{
8
- success: boolean;
9
- error?: string;
10
- bundleId?: string;
11
- bundleSlug?: string;
12
- }>;
7
+ /**
8
+ * Upload function with progress tracking
9
+ * onProgress receives percentage (0-100)
10
+ * signal is an AbortSignal for cancellation
11
+ */
12
+ uploadWithProgress: (file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<UploadSupportBundleResult>;
13
13
  /** Callback after successful upload - performs async polling to refresh bundle list */
14
14
  onUploadSuccess: () => Promise<void>;
15
15
  appId: string;
16
16
  primaryColor?: string;
17
17
  }
18
18
  declare const UploadSupportBundleModal: {
19
- ({ isOpen, onClose, uploadSupportBundle, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
19
+ ({ isOpen, onClose, uploadWithProgress, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
20
20
  displayName: string;
21
21
  };
22
22
 
23
- export { UploadSupportBundleModal };
23
+ export { UploadSupportBundleModal, UploadSupportBundleResult };
@@ -24,20 +24,27 @@ var DEFAULT_PRIMARY_COLOR = "#4f46e5";
24
24
  var UploadSupportBundleModal = ({
25
25
  isOpen,
26
26
  onClose,
27
- uploadSupportBundle,
27
+ uploadWithProgress,
28
28
  onUploadSuccess,
29
29
  appId,
30
30
  primaryColor = DEFAULT_PRIMARY_COLOR
31
31
  }) => {
32
32
  const [selectedFile, setSelectedFile] = react.useState(null);
33
33
  const [isUploading, setIsUploading] = react.useState(false);
34
+ const [uploadProgress, setUploadProgress] = react.useState(0);
34
35
  const [error, setError] = react.useState(null);
36
+ const [abortController, setAbortController] = react.useState(null);
35
37
  const fileInputRef = react.useRef(null);
36
38
  react.useEffect(() => {
37
39
  if (!isOpen) {
38
40
  setSelectedFile(null);
39
41
  setIsUploading(false);
42
+ setUploadProgress(0);
40
43
  setError(null);
44
+ setAbortController((prev) => {
45
+ prev?.abort();
46
+ return null;
47
+ });
41
48
  }
42
49
  }, [isOpen]);
43
50
  const handleFileChange = react.useCallback((event) => {
@@ -54,30 +61,38 @@ var UploadSupportBundleModal = ({
54
61
  if (!selectedFile || !appId) return;
55
62
  setError(null);
56
63
  setIsUploading(true);
64
+ setUploadProgress(0);
57
65
  try {
58
- const formData = new FormData();
59
- formData.append("file", selectedFile);
60
- formData.append("appId", appId);
61
- const result = await uploadSupportBundle(formData);
66
+ const controller = new AbortController();
67
+ setAbortController(controller);
68
+ const result = await uploadWithProgress(
69
+ selectedFile,
70
+ appId,
71
+ setUploadProgress,
72
+ controller.signal
73
+ );
62
74
  if (!result.success) {
63
75
  throw new Error(result.error ?? "Failed to upload support bundle");
64
76
  }
65
77
  await onUploadSuccess();
66
78
  onClose();
67
79
  } catch (err) {
68
- console.error("Upload error:", err);
69
- setError(err instanceof Error ? err.message : "Upload failed");
80
+ if (err instanceof Error && err.message !== "Upload cancelled") {
81
+ console.error("Upload error:", err);
82
+ setError(err.message);
83
+ }
70
84
  } finally {
71
85
  setIsUploading(false);
86
+ setUploadProgress(0);
87
+ setAbortController(null);
72
88
  }
73
- }, [selectedFile, appId, uploadSupportBundle, onUploadSuccess, onClose]);
89
+ }, [selectedFile, appId, uploadWithProgress, onUploadSuccess, onClose]);
74
90
  const handleCancel = react.useCallback(() => {
75
- if (isUploading) {
76
- setError("Upload cannot be cancelled once started");
77
- return;
91
+ if (isUploading && abortController) {
92
+ abortController.abort();
78
93
  }
79
94
  onClose();
80
- }, [isUploading, onClose]);
95
+ }, [isUploading, abortController, onClose]);
81
96
  const handleBackdropClick = react.useCallback((e) => {
82
97
  if (e.target === e.currentTarget && !isUploading) {
83
98
  onClose();
@@ -113,9 +128,24 @@ var UploadSupportBundleModal = ({
113
128
  ] })
114
129
  ] }),
115
130
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
116
- isUploading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 mb-6 flex items-center justify-center gap-3", children: [
117
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent", style: { borderTopColor: primaryColor } }),
118
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." })
131
+ isUploading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 mb-6", children: [
132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." }),
134
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-gray-900", children: [
135
+ uploadProgress,
136
+ "%"
137
+ ] })
138
+ ] }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-2", children: /* @__PURE__ */ jsxRuntime.jsx(
140
+ "div",
141
+ {
142
+ className: "h-2 rounded-full transition-all duration-300",
143
+ style: {
144
+ width: `${uploadProgress}%`,
145
+ backgroundColor: primaryColor || "#3B82F6"
146
+ }
147
+ }
148
+ ) })
119
149
  ] }),
120
150
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
121
151
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -124,7 +154,7 @@ var UploadSupportBundleModal = ({
124
154
  type: "button",
125
155
  onClick: handleCancel,
126
156
  className: "rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100",
127
- children: isUploading ? "Cancel Upload" : "Cancel"
157
+ children: isUploading && abortController ? "Cancel Upload" : "Cancel"
128
158
  }
129
159
  ),
130
160
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/format.ts","../src/utils/constants.ts","../src/components/upload-support-bundle-modal.tsx"],"names":["useState","useRef","useEffect","useCallback","jsx","jsxs"],"mappings":"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","file":"upload-support-bundle-modal.js","sourcesContent":["/**\n * Format bytes to human-readable string\n * @param bytes - Number of bytes\n * @param decimals - Number of decimal places (default: 1)\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nexport function formatBytes(bytes: number, decimals = 1): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Format date string to short numeric date (no time)\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"01/27/2026\") or \"Never\" if no date\n */\nexport function formatDateShort(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\"\n });\n}\n\n/**\n * Format date string to human-readable local timestamp\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"Jan 27, 2026, 10:32 PM\") or \"Never\" if no date\n */\nexport function formatDate(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Format date string to human-readable UTC timestamp\n * @param dateString - ISO date string\n * @returns Formatted string (e.g., \"01/27/2026, 22:32:39 UTC\")\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleString(\"en-US\", {\n timeZone: \"UTC\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false\n }) + \" UTC\";\n}\n\n/**\n * Format date string to user-friendly local timestamp with 12-hour time\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"1/27/2026, 10:32 PM\") or \"N/A\" if not provided\n */\nexport function formatDateTimeLocal(dateString?: string | null): string {\n if (!dateString) return \"N/A\";\n\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\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 } catch {\n return \"N/A\";\n }\n}\n","/**\n * Default globe favicon matching the CiGlobe icon (Circum Icons)\n * Used as fallback when custom branding doesn't provide a favicon\n */\nexport const DEFAULT_FAVICON = \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='1.5'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cellipse cx='12' cy='12' rx='4' ry='10'/%3E%3Cpath d='M2 12h20'/%3E%3C/svg%3E\";\n\n/**\n * Default primary brand color\n */\nexport const DEFAULT_PRIMARY_COLOR = \"#4f46e5\";\n\n/**\n * Default secondary brand color\n */\nexport const DEFAULT_SECONDARY_COLOR = \"#6366f1\";\n\n/**\n * Check if the API origin is HTTP (used for repldev environments)\n * This determines cookie security settings for cross-origin iframes\n * @returns true if the API origin starts with http:// (not https://)\n */\nexport const isHttpApiOrigin = (): boolean => {\n return process.env.REPLICATED_APP_ORIGIN?.startsWith('http://') || false;\n};\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { formatBytes } from \"../utils/format\";\nimport { DEFAULT_PRIMARY_COLOR } from \"../utils/constants\";\n\ninterface UploadSupportBundleModalProps {\n isOpen: boolean;\n onClose: () => void;\n /** Server action to upload the support bundle */\n uploadSupportBundle: (formData: FormData) => Promise<{ \n success: boolean; \n error?: string; \n bundleId?: string;\n bundleSlug?: string;\n }>;\n /** Callback after successful upload - performs async polling to refresh bundle list */\n onUploadSuccess: () => Promise<void>;\n appId: string;\n primaryColor?: string;\n}\n\nexport const UploadSupportBundleModal = ({\n isOpen,\n onClose,\n uploadSupportBundle,\n onUploadSuccess,\n appId,\n primaryColor = DEFAULT_PRIMARY_COLOR\n}: UploadSupportBundleModalProps) => {\n const [selectedFile, setSelectedFile] = useState<File | null>(null);\n const [isUploading, setIsUploading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (!isOpen) {\n setSelectedFile(null);\n setIsUploading(false);\n setError(null);\n }\n }, [isOpen]);\n\n const handleFileChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith(\".tgz\") && !file.name.endsWith(\".tar.gz\")) {\n setError(\"Invalid file type. Please select a .tgz or .tar.gz file.\");\n return;\n }\n\n setError(null);\n setSelectedFile(file);\n }, []);\n\n const handleUpload = useCallback(async () => {\n if (!selectedFile || !appId) return;\n\n setError(null);\n setIsUploading(true);\n\n try {\n // Create FormData to send to server action\n const formData = new FormData();\n formData.append(\"file\", selectedFile);\n formData.append(\"appId\", appId);\n\n // Upload via server action (streams to Enterprise Portal API)\n const result = await uploadSupportBundle(formData);\n\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to upload support bundle\");\n }\n\n // Success - notify parent and wait for polling to complete\n await onUploadSuccess();\n onClose();\n } catch (err) {\n console.error(\"Upload error:\", err);\n setError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setIsUploading(false);\n }\n }, [selectedFile, appId, uploadSupportBundle, onUploadSuccess, onClose]);\n\n const handleCancel = useCallback(() => {\n // Note: Cannot abort server action once started\n if (isUploading) {\n setError(\"Upload cannot be cancelled once started\");\n return;\n }\n onClose();\n }, [isUploading, onClose]);\n\n const handleBackdropClick = useCallback((e: React.MouseEvent) => {\n if (e.target === e.currentTarget && !isUploading) {\n onClose();\n }\n }, [isUploading, onClose]);\n\n if (!isOpen) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"upload-modal-title\"\n >\n <div className=\"w-full max-w-md rounded-2xl bg-white p-6 shadow-xl\">\n <h2 id=\"upload-modal-title\" className=\"text-lg font-semibold text-gray-900\">\n Upload Support Bundle\n </h2>\n\n <div className=\"mt-4\">\n <label className=\"block text-sm font-medium text-gray-700\">\n Select Support Bundle\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".tgz,.tar.gz,application/gzip,application/x-gzip\"\n onChange={handleFileChange}\n disabled={isUploading}\n className=\"mt-2 block w-full text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:bg-gray-100 file:px-4 file:py-2 file:text-sm file:font-medium file:text-gray-700 hover:file:bg-gray-200 disabled:opacity-50\"\n />\n {selectedFile && (\n <div className=\"mt-3 flex items-center justify-between rounded-lg bg-gray-50 px-3 py-2 text-sm\">\n <span className=\"truncate text-gray-700\">{selectedFile.name}</span>\n <span className=\"ml-2 shrink-0 text-gray-500\">{formatBytes(selectedFile.size)}</span>\n </div>\n )}\n </div>\n\n {error && (\n <div className=\"mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700\">\n {error}\n </div>\n )}\n\n {isUploading && (\n <div className=\"mt-6 mb-6 flex items-center justify-center gap-3\">\n <div className=\"h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent\" style={{ borderTopColor: primaryColor }} />\n <span className=\"text-sm font-medium text-gray-700\">\n Uploading support bundle...\n </span>\n </div>\n )}\n\n <div className=\"mt-6 flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100\"\n >\n {isUploading ? \"Cancel Upload\" : \"Cancel\"}\n </button>\n <button\n type=\"button\"\n onClick={handleUpload}\n disabled={!selectedFile || isUploading}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity duration-200 hover:opacity-90 disabled:opacity-50\"\n style={{ backgroundColor: primaryColor }}\n >\n {isUploading ? \"Uploading...\" : \"Upload\"}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nUploadSupportBundleModal.displayName = \"UploadSupportBundleModal\";\n"]}
1
+ {"version":3,"sources":["../src/utils/format.ts","../src/utils/constants.ts","../src/components/upload-support-bundle-modal.tsx"],"names":["useState","useRef","useEffect","useCallback","jsx","jsxs"],"mappings":";;;;;;;;;;;AAMO,SAAS,WAAA,CAAY,KAAA,EAAe,QAAA,GAAW,CAAA,EAAW;AAC/D,EAAA,IAAI,KAAA,KAAU,GAAG,OAAO,SAAA;AAExB,EAAA,MAAM,CAAA,GAAI,IAAA;AACV,EAAA,MAAM,EAAA,GAAK,QAAA,GAAW,CAAA,GAAI,CAAA,GAAI,QAAA;AAC9B,EAAA,MAAM,QAAQ,CAAC,OAAA,EAAS,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AAE9C,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAElD,EAAA,OAAO,CAAA,EAAG,UAAA,CAAA,CAAY,KAAA,GAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,CAAA,EAAG,OAAA,CAAQ,EAAE,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AACxE;;;ACPO,IAAM,qBAAA,GAAwB,SAAA;ACkB9B,IAAM,2BAA2B,CAAC;AAAA,EACvC,MAAA;AAAA,EACA,OAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA,GAAe;AACjB,CAAA,KAAqC;AACnC,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,eAAsB,IAAI,CAAA;AAClE,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIA,eAAiB,CAAC,CAAA;AAC9D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAIA,eAAiC,IAAI,CAAA;AACnF,EAAA,MAAM,YAAA,GAAeC,aAAyB,IAAI,CAAA;AAGlD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA,iBAAA,CAAkB,CAAC,CAAA;AACnB,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,kBAAA,CAAmB,CAAA,IAAA,KAAQ;AACzB,QAAA,IAAA,EAAM,KAAA,EAAM;AACZ,QAAA,OAAO,IAAA;AAAA,MACT,CAAC,CAAA;AAAA,IACH;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,gBAAA,GAAmBC,iBAAA,CAAY,CAAC,KAAA,KAA+C;AACnF,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,IAAK,CAAC,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AACjE,MAAA,QAAA,CAAS,0DAA0D,CAAA;AACnE,MAAA;AAAA,IACF;AAEA,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,EACtB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,YAAA,GAAeA,kBAAY,YAAY;AAC3C,IAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,KAAA,EAAO;AAE7B,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,iBAAA,CAAkB,CAAC,CAAA;AAEnB,IAAA,IAAI;AAEF,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,kBAAA,CAAmB,UAAU,CAAA;AAE7B,MAAA,MAAM,SAAS,MAAM,kBAAA;AAAA,QACnB,YAAA;AAAA,QACA,KAAA;AAAA,QACA,iBAAA;AAAA,QACA,UAAA,CAAW;AAAA,OACb;AAEA,MAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,QAAA,MAAM,IAAI,KAAA,CAAM,MAAA,CAAO,KAAA,IAAS,iCAAiC,CAAA;AAAA,MACnE;AAGA,MAAA,MAAM,eAAA,EAAgB;AACtB,MAAA,OAAA,EAAQ;AAAA,IACV,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,GAAA,YAAe,KAAA,IAAS,GAAA,CAAI,OAAA,KAAY,kBAAA,EAAoB;AAC9D,QAAA,OAAA,CAAQ,KAAA,CAAM,iBAAiB,GAAG,CAAA;AAClC,QAAA,QAAA,CAAS,IAAI,OAAO,CAAA;AAAA,MACtB;AAAA,IACF,CAAA,SAAE;AACA,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA,iBAAA,CAAkB,CAAC,CAAA;AACnB,MAAA,kBAAA,CAAmB,IAAI,CAAA;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,OAAO,kBAAA,EAAoB,eAAA,EAAiB,OAAO,CAAC,CAAA;AAEtE,EAAA,MAAM,YAAA,GAAeA,kBAAY,MAAM;AACrC,IAAA,IAAI,eAAe,eAAA,EAAiB;AAClC,MAAA,eAAA,CAAgB,KAAA,EAAM;AAAA,IACxB;AACA,IAAA,OAAA,EAAQ;AAAA,EACV,CAAA,EAAG,CAAC,WAAA,EAAa,eAAA,EAAiB,OAAO,CAAC,CAAA;AAE1C,EAAA,MAAM,mBAAA,GAAsBA,iBAAA,CAAY,CAAC,CAAA,KAAwB;AAC/D,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,aAAA,IAAiB,CAAC,WAAA,EAAa;AAChD,MAAA,OAAA,EAAQ;AAAA,IACV;AAAA,EACF,CAAA,EAAG,CAAC,WAAA,EAAa,OAAO,CAAC,CAAA;AAEzB,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAEpB,EAAA,uBACEC,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,iEAAA;AAAA,MACV,OAAA,EAAS,mBAAA;AAAA,MACT,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAW,MAAA;AAAA,MACX,iBAAA,EAAgB,oBAAA;AAAA,MAEhB,QAAA,kBAAAC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oDAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,cAAA,CAAC,IAAA,EAAA,EAAG,EAAA,EAAG,oBAAA,EAAqB,SAAA,EAAU,uCAAsC,QAAA,EAAA,uBAAA,EAE5E,CAAA;AAAA,wBAEAC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,MAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,cAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,yCAAA,EAA0C,QAAA,EAAA,uBAAA,EAE3D,CAAA;AAAA,0BACAA,cAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,YAAA;AAAA,cACL,IAAA,EAAK,MAAA;AAAA,cACL,MAAA,EAAO,kDAAA;AAAA,cACP,QAAA,EAAU,gBAAA;AAAA,cACV,QAAA,EAAU,WAAA;AAAA,cACV,SAAA,EAAU;AAAA;AAAA,WACZ;AAAA,UACC,YAAA,oBACCC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gFAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EAA0B,QAAA,EAAA,YAAA,CAAa,IAAA,EAAK,CAAA;AAAA,2CAC3D,MAAA,EAAA,EAAK,SAAA,EAAU,+BAA+B,QAAA,EAAA,WAAA,CAAY,YAAA,CAAa,IAAI,CAAA,EAAE;AAAA,WAAA,EAChF;AAAA,SAAA,EAEJ,CAAA;AAAA,QAEC,KAAA,oBACCA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4DACZ,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,QAGD,WAAA,oBACCC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAA,EAAoC,QAAA,EAAA,6BAAA,EAEpD,CAAA;AAAA,4BACAC,eAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,cAAA,cAAA;AAAA,cAAe;AAAA,aAAA,EAClB;AAAA,WAAA,EACF,CAAA;AAAA,0BACAD,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qCAAA,EACb,QAAA,kBAAAA,cAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,8CAAA;AAAA,cACV,KAAA,EAAO;AAAA,gBACL,KAAA,EAAO,GAAG,cAAc,CAAA,CAAA,CAAA;AAAA,gBACxB,iBAAiB,YAAA,IAAgB;AAAA;AACnC;AAAA,WACF,EACF;AAAA,SAAA,EACF,CAAA;AAAA,wBAGFC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,YAAA;AAAA,cACT,SAAA,EAAU,qFAAA;AAAA,cAET,QAAA,EAAA,WAAA,IAAe,kBAAkB,eAAA,GAAkB;AAAA;AAAA,WACtD;AAAA,0BACAA,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,YAAA;AAAA,cACT,QAAA,EAAU,CAAC,YAAA,IAAgB,WAAA;AAAA,cAC3B,SAAA,EAAU,0HAAA;AAAA,cACV,KAAA,EAAO,EAAE,eAAA,EAAiB,YAAA,EAAa;AAAA,cAEtC,wBAAc,cAAA,GAAiB;AAAA;AAAA;AAClC,SAAA,EACF;AAAA,OAAA,EACF;AAAA;AAAA,GACF;AAEJ;AAEA,wBAAA,CAAyB,WAAA,GAAc,0BAAA","file":"upload-support-bundle-modal.js","sourcesContent":["/**\n * Format bytes to human-readable string\n * @param bytes - Number of bytes\n * @param decimals - Number of decimal places (default: 1)\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nexport function formatBytes(bytes: number, decimals = 1): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Format date string to short numeric date (no time)\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"01/27/2026\") or \"Never\" if no date\n */\nexport function formatDateShort(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\"\n });\n}\n\n/**\n * Format date string to human-readable local timestamp\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"Jan 27, 2026, 10:32 PM\") or \"Never\" if no date\n */\nexport function formatDate(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Format date string to human-readable UTC timestamp\n * @param dateString - ISO date string\n * @returns Formatted string (e.g., \"01/27/2026, 22:32:39 UTC\")\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleString(\"en-US\", {\n timeZone: \"UTC\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false\n }) + \" UTC\";\n}\n\n/**\n * Format date string to user-friendly local timestamp with 12-hour time\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"1/27/2026, 10:32 PM\") or \"N/A\" if not provided\n */\nexport function formatDateTimeLocal(dateString?: string | null): string {\n if (!dateString) return \"N/A\";\n\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\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 } catch {\n return \"N/A\";\n }\n}\n","/**\n * Default globe favicon matching the CiGlobe icon (Circum Icons)\n * Used as fallback when custom branding doesn't provide a favicon\n */\nexport const DEFAULT_FAVICON = \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='1.5'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cellipse cx='12' cy='12' rx='4' ry='10'/%3E%3Cpath d='M2 12h20'/%3E%3C/svg%3E\";\n\n/**\n * Default primary brand color\n */\nexport const DEFAULT_PRIMARY_COLOR = \"#4f46e5\";\n\n/**\n * Default secondary brand color\n */\nexport const DEFAULT_SECONDARY_COLOR = \"#6366f1\";\n\n/**\n * Check if the API origin is HTTP (used for repldev environments)\n * This determines cookie security settings for cross-origin iframes\n * @returns true if the API origin starts with http:// (not https://)\n */\nexport const isHttpApiOrigin = (): boolean => {\n return process.env.REPLICATED_APP_ORIGIN?.startsWith('http://') || false;\n};\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { formatBytes } from \"../utils/format\";\nimport { DEFAULT_PRIMARY_COLOR } from \"../utils/constants\";\nimport type { UploadSupportBundleResult } from \"../utils/upload-with-progress\";\n\ninterface UploadSupportBundleModalProps {\n isOpen: boolean;\n onClose: () => void;\n /**\n * Upload function with progress tracking\n * onProgress receives percentage (0-100)\n * signal is an AbortSignal for cancellation\n */\n uploadWithProgress: (\n file: File,\n appId: string,\n onProgress: (percent: number) => void,\n signal: AbortSignal\n ) => Promise<UploadSupportBundleResult>;\n /** Callback after successful upload - performs async polling to refresh bundle list */\n onUploadSuccess: () => Promise<void>;\n appId: string;\n primaryColor?: string;\n}\n\nexport const UploadSupportBundleModal = ({\n isOpen,\n onClose,\n uploadWithProgress,\n onUploadSuccess,\n appId,\n primaryColor = DEFAULT_PRIMARY_COLOR\n}: UploadSupportBundleModalProps) => {\n const [selectedFile, setSelectedFile] = useState<File | null>(null);\n const [isUploading, setIsUploading] = useState(false);\n const [uploadProgress, setUploadProgress] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n const [abortController, setAbortController] = useState<AbortController | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (!isOpen) {\n setSelectedFile(null);\n setIsUploading(false);\n setUploadProgress(0);\n setError(null);\n // Abort any in-flight upload before clearing controller\n setAbortController(prev => {\n prev?.abort();\n return null;\n });\n }\n }, [isOpen]);\n\n const handleFileChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith(\".tgz\") && !file.name.endsWith(\".tar.gz\")) {\n setError(\"Invalid file type. Please select a .tgz or .tar.gz file.\");\n return;\n }\n\n setError(null);\n setSelectedFile(file);\n }, []);\n\n const handleUpload = useCallback(async () => {\n if (!selectedFile || !appId) return;\n\n setError(null);\n setIsUploading(true);\n setUploadProgress(0);\n\n try {\n // Upload with progress tracking\n const controller = new AbortController();\n setAbortController(controller);\n\n const result = await uploadWithProgress(\n selectedFile,\n appId,\n setUploadProgress,\n controller.signal\n );\n\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to upload support bundle\");\n }\n\n // Success - notify parent and wait for polling to complete\n await onUploadSuccess();\n onClose();\n } catch (err) {\n if (err instanceof Error && err.message !== \"Upload cancelled\") {\n console.error(\"Upload error:\", err);\n setError(err.message);\n }\n } finally {\n setIsUploading(false);\n setUploadProgress(0);\n setAbortController(null);\n }\n }, [selectedFile, appId, uploadWithProgress, onUploadSuccess, onClose]);\n\n const handleCancel = useCallback(() => {\n if (isUploading && abortController) {\n abortController.abort();\n }\n onClose();\n }, [isUploading, abortController, onClose]);\n\n const handleBackdropClick = useCallback((e: React.MouseEvent) => {\n if (e.target === e.currentTarget && !isUploading) {\n onClose();\n }\n }, [isUploading, onClose]);\n\n if (!isOpen) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"upload-modal-title\"\n >\n <div className=\"w-full max-w-md rounded-2xl bg-white p-6 shadow-xl\">\n <h2 id=\"upload-modal-title\" className=\"text-lg font-semibold text-gray-900\">\n Upload Support Bundle\n </h2>\n\n <div className=\"mt-4\">\n <label className=\"block text-sm font-medium text-gray-700\">\n Select Support Bundle\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".tgz,.tar.gz,application/gzip,application/x-gzip\"\n onChange={handleFileChange}\n disabled={isUploading}\n className=\"mt-2 block w-full text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:bg-gray-100 file:px-4 file:py-2 file:text-sm file:font-medium file:text-gray-700 hover:file:bg-gray-200 disabled:opacity-50\"\n />\n {selectedFile && (\n <div className=\"mt-3 flex items-center justify-between rounded-lg bg-gray-50 px-3 py-2 text-sm\">\n <span className=\"truncate text-gray-700\">{selectedFile.name}</span>\n <span className=\"ml-2 shrink-0 text-gray-500\">{formatBytes(selectedFile.size)}</span>\n </div>\n )}\n </div>\n\n {error && (\n <div className=\"mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700\">\n {error}\n </div>\n )}\n\n {isUploading && (\n <div className=\"mt-6 mb-6\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium text-gray-700\">\n Uploading support bundle...\n </span>\n <span className=\"text-sm font-medium text-gray-900\">\n {uploadProgress}%\n </span>\n </div>\n <div className=\"w-full bg-gray-200 rounded-full h-2\">\n <div\n className=\"h-2 rounded-full transition-all duration-300\"\n style={{\n width: `${uploadProgress}%`,\n backgroundColor: primaryColor || \"#3B82F6\",\n }}\n />\n </div>\n </div>\n )}\n\n <div className=\"mt-6 flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100\"\n >\n {isUploading && abortController ? \"Cancel Upload\" : \"Cancel\"}\n </button>\n <button\n type=\"button\"\n onClick={handleUpload}\n disabled={!selectedFile || isUploading}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity duration-200 hover:opacity-90 disabled:opacity-50\"\n style={{ backgroundColor: primaryColor }}\n >\n {isUploading ? \"Uploading...\" : \"Upload\"}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nUploadSupportBundleModal.displayName = \"UploadSupportBundleModal\";\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Client-side upload function with progress tracking and streaming using XMLHttpRequest
3
+ *
4
+ * This sends the file directly (not as FormData) to avoid proxy issues and enable true streaming.
5
+ * The file is sent with Content-Type: application/gzip and appId as a query parameter.
6
+ *
7
+ * @param file - The file to upload
8
+ * @param appId - The application ID
9
+ * @param onProgress - Callback that receives progress percentage (0-100)
10
+ * @param signal - AbortSignal for cancellation
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { uploadSupportBundleWithProgress } from "@replicated/portal-components/utils";
15
+ *
16
+ * <UploadSupportBundleModal
17
+ * uploadWithProgress={uploadSupportBundleWithProgress}
18
+ * // ... other props
19
+ * />
20
+ * ```
21
+ *
22
+ * Note: This requires a corresponding API route in your Next.js app at:
23
+ * /api/support-bundles/upload
24
+ *
25
+ * The API route should stream the request body directly to the backend without buffering.
26
+ */
27
+ interface UploadSupportBundleResult {
28
+ success: boolean;
29
+ error?: string;
30
+ bundleId?: string;
31
+ bundleSlug?: string;
32
+ }
33
+ declare function uploadSupportBundleWithProgress(file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal): Promise<UploadSupportBundleResult>;
34
+
35
+ export { type UploadSupportBundleResult as U, uploadSupportBundleWithProgress as u };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Client-side upload function with progress tracking and streaming using XMLHttpRequest
3
+ *
4
+ * This sends the file directly (not as FormData) to avoid proxy issues and enable true streaming.
5
+ * The file is sent with Content-Type: application/gzip and appId as a query parameter.
6
+ *
7
+ * @param file - The file to upload
8
+ * @param appId - The application ID
9
+ * @param onProgress - Callback that receives progress percentage (0-100)
10
+ * @param signal - AbortSignal for cancellation
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { uploadSupportBundleWithProgress } from "@replicated/portal-components/utils";
15
+ *
16
+ * <UploadSupportBundleModal
17
+ * uploadWithProgress={uploadSupportBundleWithProgress}
18
+ * // ... other props
19
+ * />
20
+ * ```
21
+ *
22
+ * Note: This requires a corresponding API route in your Next.js app at:
23
+ * /api/support-bundles/upload
24
+ *
25
+ * The API route should stream the request body directly to the backend without buffering.
26
+ */
27
+ interface UploadSupportBundleResult {
28
+ success: boolean;
29
+ error?: string;
30
+ bundleId?: string;
31
+ bundleSlug?: string;
32
+ }
33
+ declare function uploadSupportBundleWithProgress(file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal): Promise<UploadSupportBundleResult>;
34
+
35
+ export { type UploadSupportBundleResult as U, uploadSupportBundleWithProgress as u };
@@ -1,6 +1,7 @@
1
- export { d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-BsMSywts.mjs';
2
- import { a1 as DownloadPortalRelease } from '../index-JX9-CIMA.mjs';
1
+ export { a as BackgroundType, B as BrandingResult, d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-DmsrDTNE.mjs';
2
+ import { a1 as DownloadPortalRelease } from '../index-DaH1bSuO.mjs';
3
3
  import { ReleaseEntry } from '../release-history-panel.mjs';
4
+ export { U as UploadSupportBundleResult, u as uploadSupportBundleWithProgress } from '../upload-with-progress-D760HthX.mjs';
4
5
  import '../fetch-license-iTyF7_GY.mjs';
5
6
  import '../actions/change-team.mjs';
6
7
  import '../actions/trial-signup.mjs';
@@ -1,6 +1,7 @@
1
- export { d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-BsMSywts.js';
2
- import { a1 as DownloadPortalRelease } from '../index-DXy7RxOX.js';
1
+ export { a as BackgroundType, B as BrandingResult, d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-DmsrDTNE.js';
2
+ import { a1 as DownloadPortalRelease } from '../index-Bcp17Mf3.js';
3
3
  import { ReleaseEntry } from '../release-history-panel.js';
4
+ export { U as UploadSupportBundleResult, u as uploadSupportBundleWithProgress } from '../upload-with-progress-D760HthX.js';
4
5
  import '../fetch-license-iTyF7_GY.js';
5
6
  import '../actions/change-team.js';
6
7
  import '../actions/trial-signup.js';
@@ -150,6 +150,7 @@ var decodeBranding = ({ brandingData }) => {
150
150
  const secondaryColorRaw = parsed.secondaryColor ?? parsed.secondary_color;
151
151
  const primaryColor = normalizeColor(primaryColorRaw);
152
152
  const secondaryColor = normalizeColor(secondaryColorRaw);
153
+ const contact = typeof parsed.contact === "string" ? parsed.contact : void 0;
153
154
  const supportPortalLink = typeof parsed.supportPortalLink === "string" ? parsed.supportPortalLink : void 0;
154
155
  const backgroundRaw = parsed.background;
155
156
  const background = backgroundRaw === "minimal" || backgroundRaw === "custom" || backgroundRaw === "image" ? backgroundRaw : void 0;
@@ -162,6 +163,7 @@ var decodeBranding = ({ brandingData }) => {
162
163
  favicon,
163
164
  primaryColor: primaryColor || DEFAULT_PRIMARY_COLOR,
164
165
  secondaryColor: secondaryColor || DEFAULT_SECONDARY_COLOR,
166
+ contact,
165
167
  supportPortalLink,
166
168
  background,
167
169
  backgroundImage,
@@ -211,6 +213,7 @@ var fetchCustomBrandingImpl = async () => {
211
213
  title: payload.title,
212
214
  primaryColor: payload.primaryColor,
213
215
  secondaryColor: payload.secondaryColor,
216
+ contact: payload.contact,
214
217
  favicon: payload.faviconUrl,
215
218
  supportPortalLink: payload.supportPortalLink || "",
216
219
  background: payload.background,
@@ -395,6 +398,50 @@ function convertToReleaseEntry(release, channelName, options) {
395
398
  };
396
399
  }
397
400
 
401
+ // src/utils/upload-with-progress.ts
402
+ async function uploadSupportBundleWithProgress(file, appId, onProgress, signal) {
403
+ return new Promise((resolve, reject) => {
404
+ const xhr = new XMLHttpRequest();
405
+ xhr.upload.addEventListener("progress", (e) => {
406
+ if (e.lengthComputable) {
407
+ const percent = Math.round(e.loaded / e.total * 100);
408
+ onProgress(percent);
409
+ }
410
+ });
411
+ xhr.addEventListener("load", () => {
412
+ if (xhr.status === 200) {
413
+ try {
414
+ const response = JSON.parse(xhr.responseText);
415
+ resolve(response);
416
+ } catch (err) {
417
+ reject(new Error("Failed to parse server response"));
418
+ }
419
+ } else {
420
+ try {
421
+ const response = JSON.parse(xhr.responseText);
422
+ reject(new Error(response.error || `Upload failed: ${xhr.status}`));
423
+ } catch {
424
+ reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
425
+ }
426
+ }
427
+ });
428
+ xhr.addEventListener("error", () => {
429
+ reject(new Error("Network error. Please check your connection."));
430
+ });
431
+ xhr.addEventListener("abort", () => {
432
+ reject(new Error("Upload cancelled"));
433
+ });
434
+ signal.addEventListener("abort", () => {
435
+ xhr.abort();
436
+ });
437
+ const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
438
+ const uploadUrl = `${basePath}/api/support-bundles/upload?appId=${encodeURIComponent(appId)}`;
439
+ xhr.open("POST", uploadUrl, true);
440
+ xhr.setRequestHeader("Content-Type", "application/gzip");
441
+ xhr.send(file);
442
+ });
443
+ }
444
+
398
445
  exports.DEFAULT_FAVICON = DEFAULT_FAVICON;
399
446
  exports.DEFAULT_PRIMARY_COLOR = DEFAULT_PRIMARY_COLOR;
400
447
  exports.DEFAULT_SECONDARY_COLOR = DEFAULT_SECONDARY_COLOR;
@@ -411,6 +458,7 @@ exports.isHttpApiOrigin = isHttpApiOrigin;
411
458
  exports.isRedirectError = isRedirectError;
412
459
  exports.normalizeColor = normalizeColor;
413
460
  exports.sanitizeUrlForCss = sanitizeUrlForCss;
461
+ exports.uploadSupportBundleWithProgress = uploadSupportBundleWithProgress;
414
462
  exports.validateSession = validateSession;
415
463
  //# sourceMappingURL=index.js.map
416
464
  //# sourceMappingURL=index.js.map