@replicated/portal-components 0.0.24 → 0.0.26

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 (180) 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 -53
  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 -53
  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 -220
  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/logout-action.js +1 -1
  96. package/dist/esm/logout-action.js.map +1 -1
  97. package/dist/esm/logout-button.js +5 -7
  98. package/dist/esm/logout-button.js.map +1 -1
  99. package/dist/esm/online-instance-list.js +25 -25
  100. package/dist/esm/online-instance-list.js.map +1 -1
  101. package/dist/esm/saml-callback-client.js.map +1 -1
  102. package/dist/esm/support-card.js +1 -0
  103. package/dist/esm/support-card.js.map +1 -1
  104. package/dist/esm/top-nav.js +82 -79
  105. package/dist/esm/top-nav.js.map +1 -1
  106. package/dist/esm/update-layout.js +82 -79
  107. package/dist/esm/update-layout.js.map +1 -1
  108. package/dist/esm/upload-support-bundle-modal.js +46 -16
  109. package/dist/esm/upload-support-bundle-modal.js.map +1 -1
  110. package/dist/esm/utils/index.js +48 -6
  111. package/dist/esm/utils/index.js.map +1 -1
  112. package/dist/helm-install-wizard.d.mts +2 -2
  113. package/dist/helm-install-wizard.d.ts +2 -2
  114. package/dist/helm-install-wizard.js +1 -0
  115. package/dist/helm-install-wizard.js.map +1 -1
  116. package/dist/{index-DXy7RxOX.d.ts → index-Bcp17Mf3.d.ts} +1 -16
  117. package/dist/{index-JX9-CIMA.d.mts → index-DaH1bSuO.d.mts} +1 -16
  118. package/dist/index.d.mts +6 -5
  119. package/dist/index.d.ts +6 -5
  120. package/dist/index.js +262 -220
  121. package/dist/index.js.map +1 -1
  122. package/dist/install-actions.d.mts +2 -2
  123. package/dist/install-actions.d.ts +2 -2
  124. package/dist/install-actions.js +1 -0
  125. package/dist/install-actions.js.map +1 -1
  126. package/dist/instance-card.d.mts +1 -1
  127. package/dist/instance-card.d.ts +1 -1
  128. package/dist/instance-card.js +25 -25
  129. package/dist/instance-card.js.map +1 -1
  130. package/dist/license-details.js +1 -0
  131. package/dist/license-details.js.map +1 -1
  132. package/dist/linux-install-wizard.d.mts +2 -2
  133. package/dist/linux-install-wizard.d.ts +2 -2
  134. package/dist/linux-install-wizard.js +1 -0
  135. package/dist/linux-install-wizard.js.map +1 -1
  136. package/dist/logout-action.d.mts +1 -1
  137. package/dist/logout-action.d.ts +1 -1
  138. package/dist/logout-action.js +1 -1
  139. package/dist/logout-action.js.map +1 -1
  140. package/dist/logout-button.d.mts +1 -1
  141. package/dist/logout-button.d.ts +1 -1
  142. package/dist/logout-button.js +5 -7
  143. package/dist/logout-button.js.map +1 -1
  144. package/dist/online-instance-list.d.mts +1 -1
  145. package/dist/online-instance-list.d.ts +1 -1
  146. package/dist/online-instance-list.js +25 -25
  147. package/dist/online-instance-list.js.map +1 -1
  148. package/dist/pending-installations.d.mts +1 -1
  149. package/dist/pending-installations.d.ts +1 -1
  150. package/dist/saml-callback-client.d.mts +1 -1
  151. package/dist/saml-callback-client.d.ts +1 -1
  152. package/dist/saml-callback-client.js.map +1 -1
  153. package/dist/security-card.d.mts +1 -1
  154. package/dist/security-card.d.ts +1 -1
  155. package/dist/styles.css +4 -0
  156. package/dist/support-bundles-card.d.mts +1 -1
  157. package/dist/support-bundles-card.d.ts +1 -1
  158. package/dist/support-card.js +1 -0
  159. package/dist/support-card.js.map +1 -1
  160. package/dist/{top-nav-CEqw0KpO.d.ts → top-nav-8f2U69MF.d.ts} +1 -1
  161. package/dist/{top-nav-BUQAGoG1.d.mts → top-nav-B2yA3PC7.d.mts} +1 -1
  162. package/dist/top-nav.d.mts +2 -2
  163. package/dist/top-nav.d.ts +2 -2
  164. package/dist/top-nav.js +82 -79
  165. package/dist/top-nav.js.map +1 -1
  166. package/dist/update-layout.js +82 -79
  167. package/dist/update-layout.js.map +1 -1
  168. package/dist/upload-support-bundle-modal.d.mts +9 -9
  169. package/dist/upload-support-bundle-modal.d.ts +9 -9
  170. package/dist/upload-support-bundle-modal.js +46 -16
  171. package/dist/upload-support-bundle-modal.js.map +1 -1
  172. package/dist/upload-with-progress-D760HthX.d.mts +35 -0
  173. package/dist/upload-with-progress-D760HthX.d.ts +35 -0
  174. package/dist/utils/index.d.mts +4 -18
  175. package/dist/utils/index.d.ts +4 -18
  176. package/dist/utils/index.js +48 -6
  177. package/dist/utils/index.js.map +1 -1
  178. package/package.json +16 -6
  179. package/dist/esm/saml-handlers.js.map +0 -1
  180. package/dist/saml-handlers.js.map +0 -1
@@ -22,20 +22,27 @@ var DEFAULT_PRIMARY_COLOR = "#4f46e5";
22
22
  var UploadSupportBundleModal = ({
23
23
  isOpen,
24
24
  onClose,
25
- uploadSupportBundle,
25
+ uploadWithProgress,
26
26
  onUploadSuccess,
27
27
  appId,
28
28
  primaryColor = DEFAULT_PRIMARY_COLOR
29
29
  }) => {
30
30
  const [selectedFile, setSelectedFile] = useState(null);
31
31
  const [isUploading, setIsUploading] = useState(false);
32
+ const [uploadProgress, setUploadProgress] = useState(0);
32
33
  const [error, setError] = useState(null);
34
+ const [abortController, setAbortController] = useState(null);
33
35
  const fileInputRef = useRef(null);
34
36
  useEffect(() => {
35
37
  if (!isOpen) {
36
38
  setSelectedFile(null);
37
39
  setIsUploading(false);
40
+ setUploadProgress(0);
38
41
  setError(null);
42
+ setAbortController((prev) => {
43
+ prev?.abort();
44
+ return null;
45
+ });
39
46
  }
40
47
  }, [isOpen]);
41
48
  const handleFileChange = useCallback((event) => {
@@ -52,30 +59,38 @@ var UploadSupportBundleModal = ({
52
59
  if (!selectedFile || !appId) return;
53
60
  setError(null);
54
61
  setIsUploading(true);
62
+ setUploadProgress(0);
55
63
  try {
56
- const formData = new FormData();
57
- formData.append("file", selectedFile);
58
- formData.append("appId", appId);
59
- const result = await uploadSupportBundle(formData);
64
+ const controller = new AbortController();
65
+ setAbortController(controller);
66
+ const result = await uploadWithProgress(
67
+ selectedFile,
68
+ appId,
69
+ setUploadProgress,
70
+ controller.signal
71
+ );
60
72
  if (!result.success) {
61
73
  throw new Error(result.error ?? "Failed to upload support bundle");
62
74
  }
63
75
  await onUploadSuccess();
64
76
  onClose();
65
77
  } catch (err) {
66
- console.error("Upload error:", err);
67
- setError(err instanceof Error ? err.message : "Upload failed");
78
+ if (err instanceof Error && err.message !== "Upload cancelled") {
79
+ console.error("Upload error:", err);
80
+ setError(err.message);
81
+ }
68
82
  } finally {
69
83
  setIsUploading(false);
84
+ setUploadProgress(0);
85
+ setAbortController(null);
70
86
  }
71
- }, [selectedFile, appId, uploadSupportBundle, onUploadSuccess, onClose]);
87
+ }, [selectedFile, appId, uploadWithProgress, onUploadSuccess, onClose]);
72
88
  const handleCancel = useCallback(() => {
73
- if (isUploading) {
74
- setError("Upload cannot be cancelled once started");
75
- return;
89
+ if (isUploading && abortController) {
90
+ abortController.abort();
76
91
  }
77
92
  onClose();
78
- }, [isUploading, onClose]);
93
+ }, [isUploading, abortController, onClose]);
79
94
  const handleBackdropClick = useCallback((e) => {
80
95
  if (e.target === e.currentTarget && !isUploading) {
81
96
  onClose();
@@ -111,9 +126,24 @@ var UploadSupportBundleModal = ({
111
126
  ] })
112
127
  ] }),
113
128
  error && /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
114
- isUploading && /* @__PURE__ */ jsxs("div", { className: "mt-6 mb-6 flex items-center justify-center gap-3", children: [
115
- /* @__PURE__ */ jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent", style: { borderTopColor: primaryColor } }),
116
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." })
129
+ isUploading && /* @__PURE__ */ jsxs("div", { className: "mt-6 mb-6", children: [
130
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
131
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." }),
132
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-gray-900", children: [
133
+ uploadProgress,
134
+ "%"
135
+ ] })
136
+ ] }),
137
+ /* @__PURE__ */ jsx("div", { className: "w-full bg-gray-200 rounded-full h-2", children: /* @__PURE__ */ jsx(
138
+ "div",
139
+ {
140
+ className: "h-2 rounded-full transition-all duration-300",
141
+ style: {
142
+ width: `${uploadProgress}%`,
143
+ backgroundColor: primaryColor || "#3B82F6"
144
+ }
145
+ }
146
+ ) })
117
147
  ] }),
118
148
  /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
119
149
  /* @__PURE__ */ jsx(
@@ -122,7 +152,7 @@ var UploadSupportBundleModal = ({
122
152
  type: "button",
123
153
  onClick: handleCancel,
124
154
  className: "rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100",
125
- children: isUploading ? "Cancel Upload" : "Cancel"
155
+ children: isUploading && abortController ? "Cancel Upload" : "Cancel"
126
156
  }
127
157
  ),
128
158
  /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/format.ts","../../src/utils/constants.ts","../../src/components/upload-support-bundle-modal.tsx"],"names":[],"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":[],"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\";\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"]}
@@ -148,6 +148,7 @@ var decodeBranding = ({ brandingData }) => {
148
148
  const secondaryColorRaw = parsed.secondaryColor ?? parsed.secondary_color;
149
149
  const primaryColor = normalizeColor(primaryColorRaw);
150
150
  const secondaryColor = normalizeColor(secondaryColorRaw);
151
+ const contact = typeof parsed.contact === "string" ? parsed.contact : void 0;
151
152
  const supportPortalLink = typeof parsed.supportPortalLink === "string" ? parsed.supportPortalLink : void 0;
152
153
  const backgroundRaw = parsed.background;
153
154
  const background = backgroundRaw === "minimal" || backgroundRaw === "custom" || backgroundRaw === "image" ? backgroundRaw : void 0;
@@ -160,6 +161,7 @@ var decodeBranding = ({ brandingData }) => {
160
161
  favicon,
161
162
  primaryColor: primaryColor || DEFAULT_PRIMARY_COLOR,
162
163
  secondaryColor: secondaryColor || DEFAULT_SECONDARY_COLOR,
164
+ contact,
163
165
  supportPortalLink,
164
166
  background,
165
167
  backgroundImage,
@@ -209,6 +211,7 @@ var fetchCustomBrandingImpl = async () => {
209
211
  title: payload.title,
210
212
  primaryColor: payload.primaryColor,
211
213
  secondaryColor: payload.secondaryColor,
214
+ contact: payload.contact,
212
215
  favicon: payload.faviconUrl,
213
216
  supportPortalLink: payload.supportPortalLink || "",
214
217
  background: payload.background,
@@ -286,11 +289,6 @@ async function validateSession(token) {
286
289
  }
287
290
  return true;
288
291
  }
289
- async function deleteSessionCookie() {
290
- const { cookies } = await import('next/headers');
291
- const cookieStore = await cookies();
292
- cookieStore.delete("portal_session");
293
- }
294
292
 
295
293
  // src/utils/format.ts
296
294
  function formatBytes(bytes, decimals = 1) {
@@ -398,6 +396,50 @@ function convertToReleaseEntry(release, channelName, options) {
398
396
  };
399
397
  }
400
398
 
401
- export { DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, decodeBranding, deleteSessionCookie, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, normalizeColor, sanitizeUrlForCss, validateSession };
399
+ // src/utils/upload-with-progress.ts
400
+ async function uploadSupportBundleWithProgress(file, appId, onProgress, signal) {
401
+ return new Promise((resolve, reject) => {
402
+ const xhr = new XMLHttpRequest();
403
+ xhr.upload.addEventListener("progress", (e) => {
404
+ if (e.lengthComputable) {
405
+ const percent = Math.round(e.loaded / e.total * 100);
406
+ onProgress(percent);
407
+ }
408
+ });
409
+ xhr.addEventListener("load", () => {
410
+ if (xhr.status === 200) {
411
+ try {
412
+ const response = JSON.parse(xhr.responseText);
413
+ resolve(response);
414
+ } catch (err) {
415
+ reject(new Error("Failed to parse server response"));
416
+ }
417
+ } else {
418
+ try {
419
+ const response = JSON.parse(xhr.responseText);
420
+ reject(new Error(response.error || `Upload failed: ${xhr.status}`));
421
+ } catch {
422
+ reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
423
+ }
424
+ }
425
+ });
426
+ xhr.addEventListener("error", () => {
427
+ reject(new Error("Network error. Please check your connection."));
428
+ });
429
+ xhr.addEventListener("abort", () => {
430
+ reject(new Error("Upload cancelled"));
431
+ });
432
+ signal.addEventListener("abort", () => {
433
+ xhr.abort();
434
+ });
435
+ const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
436
+ const uploadUrl = `${basePath}/api/support-bundles/upload?appId=${encodeURIComponent(appId)}`;
437
+ xhr.open("POST", uploadUrl, true);
438
+ xhr.setRequestHeader("Content-Type", "application/gzip");
439
+ xhr.send(file);
440
+ });
441
+ }
442
+
443
+ export { DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, decodeBranding, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, normalizeColor, sanitizeUrlForCss, uploadSupportBundleWithProgress, validateSession };
402
444
  //# sourceMappingURL=index.js.map
403
445
  //# sourceMappingURL=index.js.map