@optimizely/ocp-local-env 1.0.0-beta.8 → 1.0.1

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 (196) hide show
  1. package/README.md +18 -133
  2. package/dist/package.json +17 -9
  3. package/dist/public/bundle.fa87c838198caf8c051a.js +3 -0
  4. package/dist/public/{bundle.0a495807b6ef336cb500.js.LICENSE.txt → bundle.fa87c838198caf8c051a.js.LICENSE.txt} +13 -3
  5. package/dist/public/bundle.fa87c838198caf8c051a.js.map +1 -0
  6. package/dist/public/index.html +1 -1
  7. package/dist/src/cli.js +2 -2
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/executor/FunctionExecutor.js +8 -5
  10. package/dist/src/executor/FunctionExecutor.js.map +1 -1
  11. package/dist/src/executor/JobExecutor.js +0 -17
  12. package/dist/src/executor/JobExecutor.js.map +1 -1
  13. package/dist/src/executor/LifecycleExecutor.js +11 -2
  14. package/dist/src/executor/LifecycleExecutor.js.map +1 -1
  15. package/dist/src/executor/watcher.d.ts +6 -0
  16. package/dist/src/executor/watcher.js +45 -0
  17. package/dist/src/executor/watcher.js.map +1 -1
  18. package/dist/src/local_engine/LocalJobApi.d.ts +31 -2
  19. package/dist/src/local_engine/LocalJobApi.js +42 -17
  20. package/dist/src/local_engine/LocalJobApi.js.map +1 -1
  21. package/dist/src/local_engine/LocalNotifier.d.ts +3 -2
  22. package/dist/src/local_engine/LocalNotifier.js +7 -0
  23. package/dist/src/local_engine/LocalNotifier.js.map +1 -1
  24. package/dist/src/local_engine/LocalSourceApi.d.ts +40 -0
  25. package/dist/src/local_engine/LocalSourceApi.js +69 -0
  26. package/dist/src/local_engine/LocalSourceApi.js.map +1 -0
  27. package/dist/src/local_engine/local-engine-child-base.d.ts +0 -12
  28. package/dist/src/local_engine/local-engine-child-base.js +0 -127
  29. package/dist/src/local_engine/local-engine-child-base.js.map +1 -1
  30. package/dist/src/local_engine/local-engine-client.d.ts +19 -29
  31. package/dist/src/local_engine/local-engine-client.js +45 -94
  32. package/dist/src/local_engine/local-engine-client.js.map +1 -1
  33. package/dist/src/local_engine/local-engine-types.d.ts +13 -50
  34. package/dist/src/local_engine/local-engine-unified.d.ts +6 -13
  35. package/dist/src/local_engine/local-engine-unified.js +108 -222
  36. package/dist/src/local_engine/local-engine-unified.js.map +1 -1
  37. package/dist/src/local_engine/localSDKConfig.d.ts +6 -0
  38. package/dist/src/local_engine/localSDKConfig.js +66 -1
  39. package/dist/src/local_engine/localSDKConfig.js.map +1 -1
  40. package/dist/src/local_engine/storage/BaseKVStoreWrapper.d.ts +1 -2
  41. package/dist/src/local_engine/storage/BaseKVStoreWrapper.js.map +1 -1
  42. package/dist/src/local_engine/storage/LocalConfigStore.d.ts +7 -13
  43. package/dist/src/local_engine/storage/LocalConfigStore.js +9 -31
  44. package/dist/src/local_engine/storage/LocalConfigStore.js.map +1 -1
  45. package/dist/src/local_engine/storage/LocalKVStore.d.ts +4 -0
  46. package/dist/src/local_engine/storage/LocalKVStore.js +23 -0
  47. package/dist/src/local_engine/storage/LocalKVStore.js.map +1 -1
  48. package/dist/src/local_engine/storage/LocalSettingsStore.d.ts +7 -1
  49. package/dist/src/local_engine/storage/LocalSettingsStore.js +46 -4
  50. package/dist/src/local_engine/storage/LocalSettingsStore.js.map +1 -1
  51. package/dist/src/local_engine/storage/SourceDataStore.d.ts +16 -14
  52. package/dist/src/local_engine/storage/SourceDataStore.js +57 -57
  53. package/dist/src/local_engine/storage/SourceDataStore.js.map +1 -1
  54. package/dist/src/local_engine/utils.d.ts +2 -0
  55. package/dist/src/local_engine/utils.js +4 -2
  56. package/dist/src/local_engine/utils.js.map +1 -1
  57. package/dist/src/server/api/deploy.d.ts +20 -0
  58. package/dist/src/server/api/deploy.js +399 -0
  59. package/dist/src/server/api/deploy.js.map +1 -0
  60. package/dist/src/server/api/destinations.js +1 -1
  61. package/dist/src/server/api/destinations.js.map +1 -1
  62. package/dist/src/server/api/env.d.ts +2 -0
  63. package/dist/src/server/api/env.js +28 -0
  64. package/dist/src/server/api/env.js.map +1 -0
  65. package/dist/src/server/api/functions.js +13 -12
  66. package/dist/src/server/api/functions.js.map +1 -1
  67. package/dist/src/server/api/opalTools.d.ts +4 -0
  68. package/dist/src/server/api/opalTools.js +81 -0
  69. package/dist/src/server/api/opalTools.js.map +1 -0
  70. package/dist/src/server/api/schema.d.ts +6 -0
  71. package/dist/src/server/api/schema.js +95 -0
  72. package/dist/src/server/api/schema.js.map +1 -0
  73. package/dist/src/server/api/sources.d.ts +1 -2
  74. package/dist/src/server/api/sources.js +16 -275
  75. package/dist/src/server/api/sources.js.map +1 -1
  76. package/dist/src/server/api/v1.js +67 -70
  77. package/dist/src/server/api/v1.js.map +1 -1
  78. package/dist/src/server/api.d.ts +4 -0
  79. package/dist/src/server/api.js +133 -11
  80. package/dist/src/server/api.js.map +1 -1
  81. package/dist/src/server/app-discovery.js +37 -4
  82. package/dist/src/server/app-discovery.js.map +1 -1
  83. package/dist/src/server/app-env.d.ts +21 -0
  84. package/dist/src/server/app-env.js +122 -0
  85. package/dist/src/server/app-env.js.map +1 -0
  86. package/dist/src/server/browserFocus.d.ts +5 -0
  87. package/dist/src/server/browserFocus.js +39 -0
  88. package/dist/src/server/browserFocus.js.map +1 -0
  89. package/dist/src/server/config.d.ts +3 -0
  90. package/dist/src/server/config.js +7 -0
  91. package/dist/src/server/config.js.map +1 -1
  92. package/dist/src/server/mockDataGenerator.d.ts +1 -2
  93. package/dist/src/server/mockDataGenerator.js +77 -35
  94. package/dist/src/server/mockDataGenerator.js.map +1 -1
  95. package/dist/src/server/mockToolDataGenerator.d.ts +12 -0
  96. package/dist/src/server/mockToolDataGenerator.js +393 -0
  97. package/dist/src/server/mockToolDataGenerator.js.map +1 -0
  98. package/dist/src/server.js +152 -168
  99. package/dist/src/server.js.map +1 -1
  100. package/dist/src/types/app.d.ts +1 -3
  101. package/dist/src/types/app.js.map +1 -1
  102. package/dist/src/ui/components/App.js +311 -50
  103. package/dist/src/ui/components/App.js.map +1 -1
  104. package/dist/src/ui/components/DeployModal.d.ts +16 -0
  105. package/dist/src/ui/components/DeployModal.js +75 -0
  106. package/dist/src/ui/components/DeployModal.js.map +1 -0
  107. package/dist/src/ui/components/DestinationBatchEditor.d.ts +9 -1
  108. package/dist/src/ui/components/DestinationBatchEditor.js +30 -16
  109. package/dist/src/ui/components/DestinationBatchEditor.js.map +1 -1
  110. package/dist/src/ui/components/DestinationsView.js +5 -1
  111. package/dist/src/ui/components/DestinationsView.js.map +1 -1
  112. package/dist/src/ui/components/EntityNotAvailable.d.ts +15 -0
  113. package/dist/src/ui/components/EntityNotAvailable.js +27 -0
  114. package/dist/src/ui/components/EntityNotAvailable.js.map +1 -0
  115. package/dist/src/ui/components/EnvViewer.d.ts +8 -0
  116. package/dist/src/ui/components/EnvViewer.js +35 -0
  117. package/dist/src/ui/components/EnvViewer.js.map +1 -0
  118. package/dist/src/ui/components/FunctionsView.d.ts +2 -6
  119. package/dist/src/ui/components/FunctionsView.js +106 -124
  120. package/dist/src/ui/components/FunctionsView.js.map +1 -1
  121. package/dist/src/ui/components/JobsView.js +28 -5
  122. package/dist/src/ui/components/JobsView.js.map +1 -1
  123. package/dist/src/ui/components/KVStoreViewer.js +5 -5
  124. package/dist/src/ui/components/KVStoreViewer.js.map +1 -1
  125. package/dist/src/ui/components/KeyValueEditor.d.ts +15 -0
  126. package/dist/src/ui/components/KeyValueEditor.js +12 -0
  127. package/dist/src/ui/components/KeyValueEditor.js.map +1 -0
  128. package/dist/src/ui/components/NotificationViewer.d.ts +2 -11
  129. package/dist/src/ui/components/NotificationViewer.js +69 -15
  130. package/dist/src/ui/components/NotificationViewer.js.map +1 -1
  131. package/dist/src/ui/components/OdpSchemaView.d.ts +3 -0
  132. package/dist/src/ui/components/OdpSchemaView.js +58 -0
  133. package/dist/src/ui/components/OdpSchemaView.js.map +1 -0
  134. package/dist/src/ui/components/OpalToolsView.d.ts +9 -0
  135. package/dist/src/ui/components/OpalToolsView.js +399 -0
  136. package/dist/src/ui/components/OpalToolsView.js.map +1 -0
  137. package/dist/src/ui/components/ResponseViewer.d.ts +39 -0
  138. package/dist/src/ui/components/ResponseViewer.js +43 -0
  139. package/dist/src/ui/components/ResponseViewer.js.map +1 -0
  140. package/dist/src/ui/components/SecretsStoreViewer.js +3 -2
  141. package/dist/src/ui/components/SecretsStoreViewer.js.map +1 -1
  142. package/dist/src/ui/components/SettingsStoreViewer.js +3 -2
  143. package/dist/src/ui/components/SettingsStoreViewer.js.map +1 -1
  144. package/dist/src/ui/components/SourceEmittedDataPanel.d.ts +8 -0
  145. package/dist/src/ui/components/SourceEmittedDataPanel.js +103 -0
  146. package/dist/src/ui/components/SourceEmittedDataPanel.js.map +1 -0
  147. package/dist/src/ui/components/SourcesView.js +4 -8
  148. package/dist/src/ui/components/SourcesView.js.map +1 -1
  149. package/dist/src/ui/components/StoreViewer.js +2 -1
  150. package/dist/src/ui/components/StoreViewer.js.map +1 -1
  151. package/dist/src/ui/components/TabbedConsole.d.ts +11 -0
  152. package/dist/src/ui/components/TabbedConsole.js +134 -44
  153. package/dist/src/ui/components/TabbedConsole.js.map +1 -1
  154. package/dist/src/ui/components/ToolSettingsView.d.ts +7 -0
  155. package/dist/src/ui/components/ToolSettingsView.js +25 -0
  156. package/dist/src/ui/components/ToolSettingsView.js.map +1 -0
  157. package/dist/src/ui/components/common/EyeIcon.js +2 -1
  158. package/dist/src/ui/components/common/EyeIcon.js.map +1 -1
  159. package/dist/src/ui/hooks/useKeyValuePairs.d.ts +52 -0
  160. package/dist/src/ui/hooks/useKeyValuePairs.js +66 -0
  161. package/dist/src/ui/hooks/useKeyValuePairs.js.map +1 -0
  162. package/dist/src/ui/index.js +4 -2
  163. package/dist/src/ui/index.js.map +1 -1
  164. package/dist/src/ui/store/formStateSlice.d.ts +71 -0
  165. package/dist/src/ui/store/formStateSlice.js +159 -0
  166. package/dist/src/ui/store/formStateSlice.js.map +1 -0
  167. package/dist/src/ui/store/hooks.d.ts +6 -0
  168. package/dist/src/ui/store/hooks.js +8 -0
  169. package/dist/src/ui/store/hooks.js.map +1 -0
  170. package/dist/src/ui/store/index.d.ts +11 -0
  171. package/dist/src/ui/store/index.js +107 -0
  172. package/dist/src/ui/store/index.js.map +1 -0
  173. package/dist/src/ui/types/common.d.ts +33 -0
  174. package/dist/src/ui/types/common.js +6 -0
  175. package/dist/src/ui/types/common.js.map +1 -0
  176. package/package.json +17 -9
  177. package/dist/public/bundle.0a495807b6ef336cb500.js +0 -3
  178. package/dist/public/bundle.0a495807b6ef336cb500.js.map +0 -1
  179. package/dist/src/executor/SourceExecutor.d.ts +0 -32
  180. package/dist/src/executor/SourceExecutor.js +0 -163
  181. package/dist/src/executor/SourceExecutor.js.map +0 -1
  182. package/dist/src/local_engine/storage/SourceJobExecutionStore.d.ts +0 -25
  183. package/dist/src/local_engine/storage/SourceJobExecutionStore.js +0 -61
  184. package/dist/src/local_engine/storage/SourceJobExecutionStore.js.map +0 -1
  185. package/dist/src/ui/components/SourceDataViewer.d.ts +0 -8
  186. package/dist/src/ui/components/SourceDataViewer.js +0 -84
  187. package/dist/src/ui/components/SourceDataViewer.js.map +0 -1
  188. package/dist/src/ui/components/SourceJobsSection.d.ts +0 -8
  189. package/dist/src/ui/components/SourceJobsSection.js +0 -99
  190. package/dist/src/ui/components/SourceJobsSection.js.map +0 -1
  191. package/dist/src/ui/components/SourceLifecycleSection.d.ts +0 -7
  192. package/dist/src/ui/components/SourceLifecycleSection.js +0 -58
  193. package/dist/src/ui/components/SourceLifecycleSection.js.map +0 -1
  194. package/dist/src/ui/components/SourceWebhookEditor.d.ts +0 -8
  195. package/dist/src/ui/components/SourceWebhookEditor.js +0 -168
  196. package/dist/src/ui/components/SourceWebhookEditor.js.map +0 -1
@@ -11,7 +11,13 @@ const JobsView_1 = __importDefault(require("./JobsView"));
11
11
  const TabbedConsole_1 = __importDefault(require("./TabbedConsole"));
12
12
  const DestinationsView_1 = require("./DestinationsView");
13
13
  const SourcesView_1 = require("./SourcesView");
14
+ const OpalToolsView_1 = __importDefault(require("./OpalToolsView"));
15
+ const ToolSettingsView_1 = __importDefault(require("./ToolSettingsView"));
16
+ const OdpSchemaView_1 = __importDefault(require("./OdpSchemaView"));
14
17
  const useQueryParams_1 = require("../hooks/useQueryParams");
18
+ const store_1 = require("../store");
19
+ const axiom_icons_1 = require("@optimizely/axiom-icons");
20
+ const DeployModal_1 = require("./DeployModal");
15
21
  const IFRAME_RESIZER_MESSAGES_PREFIX = "[iFrameSizer]app_directory_frame_directory";
16
22
  const IFRAME_RESIZER_READY_MESSAGE = `${IFRAME_RESIZER_MESSAGES_PREFIX}:8:false:false:32:true:true:null:bodyOffset:null:null:0:false:parent:scroll:true`;
17
23
  // Ex.: [iFrameSizer]app_directory_frame_directory:7096:1955:mutationObserver
@@ -41,16 +47,39 @@ const App = () => {
41
47
  const [buildStatus, setBuildStatus] = (0, react_1.useState)(null);
42
48
  const [storeTimestamps, setStoreTimestamps] = (0, react_1.useState)(null);
43
49
  const [appInstalled, setAppInstalled] = (0, react_1.useState)(false);
50
+ // Install version counter - increments on each install to force UI refresh
51
+ const [installVersion, setInstallVersion] = (0, react_1.useState)(0);
44
52
  const [notificationCount, setNotificationCount] = (0, react_1.useState)(0);
45
53
  const [config, setConfig] = (0, react_1.useState)({
46
54
  showZaiusAdminSections: false
47
55
  });
48
- const [bannerVisible, setBannerVisible] = (0, react_1.useState)(true);
56
+ const [permanentlyDisconnected, setPermanentlyDisconnected] = (0, react_1.useState)(false);
57
+ const [appChanged, setAppChanged] = (0, react_1.useState)(null);
58
+ const [sourceDataVersion, setSourceDataVersion] = (0, react_1.useState)(0);
59
+ const [sourceDataTotalEmitted, setSourceDataTotalEmitted] = (0, react_1.useState)(0);
60
+ const [notificationVersion, setNotificationVersion] = (0, react_1.useState)(0);
61
+ const [envVersion, setEnvVersion] = (0, react_1.useState)(0);
62
+ const [deployStatus, setDeployStatus] = (0, react_1.useState)({ deployInProgress: false, lastDeploySuccess: null });
63
+ const [deployLogs, setDeployLogs] = (0, react_1.useState)([]);
64
+ const [deployVersion, setDeployVersion] = (0, react_1.useState)(0);
65
+ const [showDeployModal, setShowDeployModal] = (0, react_1.useState)(false);
66
+ const [deployModalData, setDeployModalData] = (0, react_1.useState)(null);
67
+ // Reference to store disconnect timer for 30-second timeout
68
+ const disconnectTimerRef = (0, react_1.useRef)(null);
69
+ // Reference to store initial app info for change detection
70
+ const initialAppInfoRef = (0, react_1.useRef)(null);
71
+ // Initialize Redux store with app name when appInfo is available
72
+ (0, react_1.useEffect)(() => {
73
+ if (appInfo?.name) {
74
+ (0, store_1.initializeStore)(appInfo.name);
75
+ }
76
+ }, [appInfo?.name]);
49
77
  // Reference to store polling interval IDs
50
78
  const pollingIntervals = (0, react_1.useRef)({
51
- logs: null,
52
79
  status: null,
53
80
  });
81
+ // Reference to store EventSource for cleanup on page unload
82
+ const eventSourceRef = (0, react_1.useRef)(null);
54
83
  // Reference to the settings iframe
55
84
  const settingsIframeRef = (0, react_1.useRef)(null);
56
85
  const setIframeRef = (iframe) => {
@@ -76,7 +105,38 @@ const App = () => {
76
105
  }
77
106
  }
78
107
  })}`;
79
- settingsIframeRef.current.contentWindow.postMessage(userFeaturesMessage, "http://localhost:3000");
108
+ settingsIframeRef.current.contentWindow.postMessage(userFeaturesMessage, window.location.origin);
109
+ // Send prismContext with mock organization ID to prevent "Organization ID is missing" errors
110
+ const prismContextMessage = `[iFrameSizer]message:${JSON.stringify({
111
+ method: 'prismContext',
112
+ params: {
113
+ productName: 'local-dev',
114
+ subProductName: 'ocp-local-env',
115
+ trackerId: 'local-development-tracker-id',
116
+ scope: 1,
117
+ originalUser: {
118
+ id: 1,
119
+ email: 'local-dev@example.com',
120
+ name: 'Local Developer',
121
+ isOptiId: false,
122
+ isInternalUser: true,
123
+ optiIdAccessToken: 'local-dev-mock-token'
124
+ },
125
+ organization: {
126
+ id: 1,
127
+ name: 'Local Development Org',
128
+ masterCustomerId: 'local-master-customer',
129
+ turnstileOrganizationId: 'local-test-org'
130
+ },
131
+ account: {
132
+ id: 1,
133
+ name: 'Local Development Account',
134
+ instanceId: 'local-test-instance',
135
+ isOptiHub: false
136
+ }
137
+ }
138
+ })}`;
139
+ settingsIframeRef.current.contentWindow.postMessage(prismContextMessage, window.location.origin);
80
140
  console.log("Sent ready event to iframe after 2 second delay");
81
141
  }
82
142
  }, 2000);
@@ -101,15 +161,19 @@ const App = () => {
101
161
  };
102
162
  // Set up polling on component mount
103
163
  (0, react_1.useEffect)(() => {
104
- // Initial data load
164
+ // Don't set up new connections if permanently disconnected
165
+ if (permanentlyDisconnected) {
166
+ return;
167
+ }
168
+ // Initial data load (logs come via SSE initialLogs event)
105
169
  fetchConfig();
106
170
  fetchAppInfo();
107
171
  fetchStatus();
108
- fetchConsoleLogs();
109
172
  fetchInstallationStatus();
110
173
  fetchNotificationCount();
111
174
  // Set up SSE connection for real-time build status updates
112
175
  const eventSource = new EventSource('/devserver/api/events');
176
+ eventSourceRef.current = eventSource;
113
177
  eventSource.onmessage = (event) => {
114
178
  const data = JSON.parse(event.data);
115
179
  switch (data.type) {
@@ -123,6 +187,10 @@ const App = () => {
123
187
  lastBuildTimestamp: null,
124
188
  lastBuildError: null
125
189
  });
190
+ // Reset deploy success status on code change (build start)
191
+ if (!deployStatus.deployInProgress) {
192
+ setDeployStatus(prev => ({ ...prev, lastDeploySuccess: null }));
193
+ }
126
194
  break;
127
195
  case 'buildSuccess':
128
196
  setBuildStatus({
@@ -131,8 +199,7 @@ const App = () => {
131
199
  lastBuildTimestamp: data.timestamp,
132
200
  lastBuildError: null
133
201
  });
134
- // Refresh logs and store timestamps after build
135
- fetchConsoleLogs();
202
+ // Refresh store timestamps after build (logs come via SSE)
136
203
  fetchStatus();
137
204
  break;
138
205
  case 'buildError':
@@ -142,7 +209,6 @@ const App = () => {
142
209
  lastBuildTimestamp: data.timestamp,
143
210
  lastBuildError: data.error
144
211
  });
145
- fetchConsoleLogs();
146
212
  break;
147
213
  case 'status':
148
214
  // Initial status on connection
@@ -153,10 +219,112 @@ const App = () => {
153
219
  lastBuildError: data.lastBuildError
154
220
  });
155
221
  break;
222
+ case 'log':
223
+ // Append new log, keep last 500
224
+ setLogs(prev => [...prev.slice(-499), data.entry]);
225
+ break;
226
+ case 'initialLogs':
227
+ setLogs(data.logs || []);
228
+ // Handle app change detection (only compare app names, not paths)
229
+ if (data.app && data.app.name) {
230
+ if (!initialAppInfoRef.current) {
231
+ initialAppInfoRef.current = { name: data.app.name, path: data.app.path };
232
+ }
233
+ else if (initialAppInfoRef.current.name !== data.app.name) {
234
+ setAppChanged({ oldApp: initialAppInfoRef.current.name, newApp: data.app.name });
235
+ }
236
+ }
237
+ break;
238
+ case 'logsCleared':
239
+ setLogs([]);
240
+ break;
241
+ case 'serverShutdown':
242
+ // Server is shutting down - immediately show disconnect overlay
243
+ eventSource.close();
244
+ eventSourceRef.current = null;
245
+ setPermanentlyDisconnected(true);
246
+ // Update tab title to indicate disconnection
247
+ document.title = '[DISCONNECTED] ' + document.title;
248
+ break;
249
+ case 'connectionEvicted':
250
+ // Connection evicted due to limit - show disconnect overlay
251
+ eventSource.close();
252
+ eventSourceRef.current = null;
253
+ setPermanentlyDisconnected(true);
254
+ // Update tab title to indicate disconnection
255
+ document.title = '[DISCONNECTED] ' + document.title;
256
+ break;
257
+ case 'sourceDataChanged':
258
+ // Source data was emitted — increment version to trigger re-fetch in panel
259
+ setSourceDataVersion(v => v + 1);
260
+ setSourceDataTotalEmitted(data.totalEmitted ?? 0);
261
+ break;
262
+ case 'notificationChanged':
263
+ // Notification was created — increment version to trigger re-fetch in panel
264
+ setNotificationVersion(v => v + 1);
265
+ fetchNotificationCount();
266
+ break;
267
+ case 'envChanged':
268
+ setEnvVersion(v => v + 1);
269
+ break;
270
+ case 'installSuccess':
271
+ // App was installed via API - update state and increment version
272
+ console.log('SSE: App installed via API');
273
+ setAppInstalled(true);
274
+ setInstallVersion(v => v + 1); // Increment to force UI refresh
275
+ setConnected(true);
276
+ break;
277
+ case 'uninstallSuccess':
278
+ // App was uninstalled via API - update state
279
+ console.log('SSE: App uninstalled via API');
280
+ setAppInstalled(false);
281
+ setConnected(false);
282
+ break;
283
+ case 'deployStart':
284
+ setDeployStatus({ deployInProgress: true, lastDeploySuccess: null });
285
+ // Add a separator between deploy runs instead of clearing logs
286
+ setDeployLogs(prev => prev.length > 0
287
+ ? [...prev, '───────────────────────────────────────']
288
+ : prev);
289
+ setDeployVersion(v => v + 1);
290
+ break;
291
+ case 'deployLog':
292
+ setDeployLogs(prev => [...prev, data.line]);
293
+ setDeployVersion(v => v + 1);
294
+ break;
295
+ case 'deploySuccess':
296
+ setDeployStatus({ deployInProgress: false, lastDeploySuccess: true });
297
+ setDeployVersion(v => v + 1);
298
+ // Refresh app info to update version display in nav
299
+ fetchAppInfo();
300
+ break;
301
+ case 'deployError':
302
+ setDeployStatus({ deployInProgress: false, lastDeploySuccess: false });
303
+ if (data.error) {
304
+ setDeployLogs(prev => [...prev, `Error: ${data.error}`]);
305
+ }
306
+ setDeployVersion(v => v + 1);
307
+ break;
308
+ case 'deployStatus':
309
+ // Initial deploy status on connection
310
+ setDeployStatus({
311
+ deployInProgress: data.deployInProgress || false,
312
+ lastDeploySuccess: data.lastDeploySuccess ?? null
313
+ });
314
+ break;
156
315
  }
157
316
  };
158
317
  eventSource.onerror = () => {
159
318
  setConnected(false);
319
+ // Start 30-second timer if not already started
320
+ if (!disconnectTimerRef.current && !permanentlyDisconnected) {
321
+ disconnectTimerRef.current = setTimeout(() => {
322
+ // Permanently disconnect - close EventSource and show message
323
+ eventSource.close();
324
+ eventSourceRef.current = null;
325
+ setPermanentlyDisconnected(true);
326
+ }, 30000);
327
+ }
160
328
  // Fallback to polling if SSE fails
161
329
  if (!pollingIntervals.current.status) {
162
330
  pollingIntervals.current.status = setInterval(fetchStatus, 3000);
@@ -164,25 +332,43 @@ const App = () => {
164
332
  };
165
333
  eventSource.onopen = () => {
166
334
  setConnected(true);
335
+ // Clear disconnect timer if we reconnected in time
336
+ if (disconnectTimerRef.current) {
337
+ clearTimeout(disconnectTimerRef.current);
338
+ disconnectTimerRef.current = null;
339
+ }
340
+ // Check if app has changed (server restarted with different app)
341
+ // Only check if we had a previous app loaded
342
+ if (initialAppInfoRef.current) {
343
+ fetchAppInfo(true);
344
+ }
167
345
  // Stop fallback polling if it was started
168
346
  if (pollingIntervals.current.status) {
169
347
  clearInterval(pollingIntervals.current.status);
170
348
  pollingIntervals.current.status = null;
171
349
  }
172
350
  };
173
- // Keep log polling (can be extended to SSE later if needed)
174
- pollingIntervals.current.logs = setInterval(fetchConsoleLogs, 2000);
351
+ // Close SSE on page unload/refresh to prevent connection accumulation
352
+ // This is critical for Firefox which has a strict 6-connection limit per domain
353
+ const handleBeforeUnload = () => {
354
+ if (eventSourceRef.current) {
355
+ eventSourceRef.current.close();
356
+ }
357
+ };
358
+ window.addEventListener('beforeunload', handleBeforeUnload);
175
359
  // Cleanup
176
360
  return () => {
177
- eventSource.close();
178
- if (pollingIntervals.current.logs) {
179
- clearInterval(pollingIntervals.current.logs);
361
+ if (disconnectTimerRef.current) {
362
+ clearTimeout(disconnectTimerRef.current);
180
363
  }
364
+ eventSource.close();
365
+ eventSourceRef.current = null;
366
+ window.removeEventListener('beforeunload', handleBeforeUnload);
181
367
  if (pollingIntervals.current.status) {
182
368
  clearInterval(pollingIntervals.current.status);
183
369
  }
184
370
  };
185
- }, []);
371
+ }, [permanentlyDisconnected]);
186
372
  // Listen for events from the iframe
187
373
  (0, react_1.useEffect)(() => {
188
374
  const deserializeResizerMessage = (data) => {
@@ -216,11 +402,11 @@ const App = () => {
216
402
  const message = deserializeResizerMessage(event.data);
217
403
  // Handle getTracker message
218
404
  if (message.method === "getTracker") {
219
- const message = serializeResizerMessage({
405
+ const response = serializeResizerMessage({
220
406
  method: "setTracker",
221
407
  params: "local-development-tracker-id",
222
408
  });
223
- settingsIframeRef.current?.contentWindow?.postMessage(message, event.origin);
409
+ settingsIframeRef.current?.contentWindow?.postMessage(response, event.origin);
224
410
  console.log("getTracker sent");
225
411
  }
226
412
  // Handle installation lifecycle events
@@ -231,6 +417,7 @@ const App = () => {
231
417
  case "install_success":
232
418
  console.log("Installation successful, updating app state");
233
419
  setAppInstalled(true);
420
+ setInstallVersion(v => v + 1); // Increment to force UI refresh
234
421
  setConnected(true);
235
422
  break;
236
423
  case "uninstall_success":
@@ -262,11 +449,25 @@ const App = () => {
262
449
  // eslint-disable-next-line react-hooks/exhaustive-deps
263
450
  }, []);
264
451
  // Fetch app info from the API
265
- const fetchAppInfo = async () => {
452
+ const fetchAppInfo = async (checkForChanges = false) => {
266
453
  try {
267
454
  const response = await fetch("/devserver/api/app");
268
455
  if (response.ok) {
269
456
  const data = await response.json();
457
+ // Store initial app info on first successful fetch
458
+ if (!initialAppInfoRef.current && data.name) {
459
+ initialAppInfoRef.current = { name: data.name, path: data.path };
460
+ }
461
+ // Check if app has changed (different app being served) - only compare names
462
+ if (checkForChanges && initialAppInfoRef.current && data.name) {
463
+ if (initialAppInfoRef.current.name !== data.name) {
464
+ setAppChanged({
465
+ oldApp: initialAppInfoRef.current.name,
466
+ newApp: data.name
467
+ });
468
+ return; // Don't update appInfo - show the change notification instead
469
+ }
470
+ }
270
471
  setAppInfo(data);
271
472
  setConnected(true);
272
473
  }
@@ -300,34 +501,11 @@ const App = () => {
300
501
  setConnected(false);
301
502
  }
302
503
  };
303
- // Fetch console logs from the API
304
- const fetchConsoleLogs = async () => {
305
- try {
306
- const response = await fetch("/devserver/api/console/logs?limit=50");
307
- if (response.ok) {
308
- const data = await response.json();
309
- if (data.logs && data.logs.length > 0) {
310
- setLogs(data.logs); // Pass raw LogEntry[] directly
311
- }
312
- else {
313
- setLogs([]);
314
- }
315
- setConnected(true);
316
- }
317
- else {
318
- setConnected(false);
319
- }
320
- }
321
- catch (error) {
322
- console.error("Error fetching console logs:", error);
323
- setConnected(false);
324
- }
325
- };
326
504
  // Clear console logs
327
505
  const clearConsoleLogs = async () => {
328
506
  try {
329
507
  await fetch("/devserver/api/console/logs", { method: "DELETE" });
330
- fetchConsoleLogs();
508
+ // SSE logsCleared event will update the UI
331
509
  }
332
510
  catch (error) {
333
511
  console.error("Error clearing logs:", error);
@@ -385,9 +563,8 @@ const App = () => {
385
563
  },
386
564
  });
387
565
  if (response.ok) {
388
- // Immediately fetch new status and logs
566
+ // Immediately fetch new status (logs come via SSE)
389
567
  fetchStatus();
390
- fetchConsoleLogs();
391
568
  }
392
569
  else {
393
570
  console.error("Failed to trigger build");
@@ -397,16 +574,100 @@ const App = () => {
397
574
  console.error("Error triggering build:", error);
398
575
  }
399
576
  };
400
- return ((0, jsx_runtime_1.jsxs)("div", { className: "app postman-layout", children: [bannerVisible && ((0, jsx_runtime_1.jsxs)("div", { className: "beta-banner", children: [(0, jsx_runtime_1.jsx)("span", { className: "beta-banner-icon", children: "\u26A0\uFE0F" }), (0, jsx_runtime_1.jsxs)("span", { className: "beta-banner-text", children: [(0, jsx_runtime_1.jsx)("strong", { children: "BETA:" }), " This tool is in beta mode - not all features might be available yet"] }), (0, jsx_runtime_1.jsx)("button", { className: "beta-banner-close", onClick: () => setBannerVisible(false), "aria-label": "Close banner", children: "\u2715" })] })), (0, jsx_runtime_1.jsxs)("header", { className: "top-nav", children: [(0, jsx_runtime_1.jsxs)("div", { className: "nav-left", children: [(0, jsx_runtime_1.jsx)("div", { className: "app-title", children: (0, jsx_runtime_1.jsx)("h1", { children: "OCP Local Environment" }) }), appInfo && ((0, jsx_runtime_1.jsxs)("div", { className: "app-info-nav", children: [(0, jsx_runtime_1.jsx)("span", { className: "app-name", children: appInfo.displayName || appInfo.name }), (0, jsx_runtime_1.jsxs)("span", { className: "app-version", children: ["v", appInfo.version] })] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "nav-center", children: buildStatus && ((0, jsx_runtime_1.jsx)("div", { className: "build-status-nav", children: (0, jsx_runtime_1.jsxs)("div", { className: `build-indicator ${buildStatus.buildInProgress ? 'building' :
401
- buildStatus.lastBuildSuccess === true ? 'success' :
402
- buildStatus.lastBuildSuccess === false ? 'error' : 'idle'}`, children: [(0, jsx_runtime_1.jsx)("span", { className: "status-dot" }), (0, jsx_runtime_1.jsx)("span", { className: "status-text", children: buildStatus.buildInProgress ? 'Building...' :
403
- buildStatus.lastBuildSuccess === true ? 'Build Success' :
404
- buildStatus.lastBuildSuccess === false ? 'Build Failed' : 'Ready' })] }) })) }), (0, jsx_runtime_1.jsxs)("div", { className: "nav-right", children: [(0, jsx_runtime_1.jsx)("button", { className: "rebuild-btn", onClick: triggerBuild, disabled: buildStatus?.buildInProgress, children: buildStatus?.buildInProgress ? "Building..." : "Rebuild" }), (0, jsx_runtime_1.jsxs)("div", { className: "connection-status", children: [(0, jsx_runtime_1.jsx)("span", { className: `connection-dot ${connected ? 'connected' : 'disconnected'}` }), connected ? "Connected" : "Disconnected"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "main-layout", children: [(0, jsx_runtime_1.jsx)("aside", { className: "sidebar", children: (0, jsx_runtime_1.jsxs)("nav", { className: "sidebar-nav", children: [(0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "settings" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/settings'), children: [(0, jsx_runtime_1.jsx)("span", { className: "nav-icon", children: "\u2699\uFE0F" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "App Settings" })] }), (0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "functions" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/functions'), children: [(0, jsx_runtime_1.jsx)("span", { className: "nav-icon", children: "\u26A1" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Functions" })] }), (0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "jobs" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/jobs'), children: [(0, jsx_runtime_1.jsx)("span", { className: "nav-icon", children: "\uD83D\uDCCB" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Jobs" })] }), appInfo && appInfo.destinations && appInfo.destinations.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "destinations" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/destinations'), children: [(0, jsx_runtime_1.jsx)("span", { className: "nav-icon", children: "\uD83D\uDCE4" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Destinations" })] })), appInfo && appInfo.sources && appInfo.sources.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "sources" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/sources'), children: [(0, jsx_runtime_1.jsx)("span", { className: "nav-icon", children: "\uD83D\uDCE5" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Sources" })] }))] }) }), (0, jsx_runtime_1.jsxs)("main", { className: "content-area", children: [(0, jsx_runtime_1.jsxs)("div", { className: "content-header", children: [(0, jsx_runtime_1.jsxs)("h2", { children: [activeView === "settings" && "App Settings", activeView === "functions" && "Functions", activeView === "jobs" && "Jobs", activeView === "destinations" && "Destinations", activeView === "sources" && "Sources"] }), appInfo && !appInfo.isValid && ((0, jsx_runtime_1.jsxs)("div", { className: "validation-errors-banner", children: [(0, jsx_runtime_1.jsx)("span", { className: "error-icon", children: "\u26A0\uFE0F" }), (0, jsx_runtime_1.jsx)("span", { children: "App has validation errors" })] }))] }), (0, jsx_runtime_1.jsx)("div", { className: "content-body", children: (0, jsx_runtime_1.jsxs)(react_router_dom_1.Routes, { children: [(0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/", element: (0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/settings", element: (0, jsx_runtime_1.jsxs)("div", { className: "settings-view", children: [appInfo && !appInfo.isValid && appInfo.errors && appInfo.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "validation-errors", children: [(0, jsx_runtime_1.jsx)("h4", { children: "Validation Errors:" }), (0, jsx_runtime_1.jsx)("ul", { children: appInfo.errors.map((err, idx) => ((0, jsx_runtime_1.jsx)("li", { children: err }, idx))) })] })), buildStatus?.buildInProgress || (buildStatus?.lastBuildSuccess === null && buildStatus?.lastBuildTimestamp === null) ? ((0, jsx_runtime_1.jsx)("div", { className: "settings-loading-container", children: (0, jsx_runtime_1.jsxs)("div", { className: "placeholder-content", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Building App..." }), (0, jsx_runtime_1.jsx)("p", { children: "Please wait while the app is being built." }), (0, jsx_runtime_1.jsx)("div", { className: "spinner" })] }) })) : buildStatus?.lastBuildSuccess === false ? ((0, jsx_runtime_1.jsx)("div", { className: "settings-loading-container", children: (0, jsx_runtime_1.jsxs)("div", { className: "placeholder-content", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Build Failed" }), (0, jsx_runtime_1.jsx)("p", { children: "The app failed to build. Please check the console for errors." }), buildStatus.lastBuildError && ((0, jsx_runtime_1.jsx)("pre", { className: "error-details", children: buildStatus.lastBuildError }))] }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "settings-iframe-container", children: (0, jsx_runtime_1.jsx)("iframe", { id: "app_directory_frame", style: {
577
+ // Compute deploy button CSS modifier class based on current deploy status
578
+ const getDeployStatusClass = () => {
579
+ if (deployStatus.deployInProgress)
580
+ return 'deploying';
581
+ if (deployStatus.lastDeploySuccess === true)
582
+ return 'deploy-success';
583
+ if (deployStatus.lastDeploySuccess === false)
584
+ return 'deploy-error';
585
+ return '';
586
+ };
587
+ // Append a deploy log message and increment the version counter
588
+ const appendDeployLog = (message) => {
589
+ setDeployLogs(prev => [...prev, message]);
590
+ setDeployVersion(v => v + 1);
591
+ };
592
+ // Trigger a deploy
593
+ const triggerDeploy = async () => {
594
+ try {
595
+ // First, resolve the version from Rivendell
596
+ const resolveResponse = await fetch("/devserver/api/deploy/resolve-version", {
597
+ method: "POST",
598
+ headers: { "Content-Type": "application/json" },
599
+ });
600
+ if (!resolveResponse.ok) {
601
+ const errorData = await resolveResponse.json().catch(() => ({}));
602
+ appendDeployLog(`Error resolving version: ${errorData.error || 'Unknown error'}`);
603
+ return;
604
+ }
605
+ const versionData = await resolveResponse.json();
606
+ if (versionData.searchError) {
607
+ appendDeployLog(`Warning: Rivendell search failed (${versionData.searchError}), bumping version locally`);
608
+ }
609
+ if (versionData.isDevVersion && (0, DeployModal_1.shouldSkipDevConfirmation)()) {
610
+ // Dev version with "do not show again" enabled: deploy immediately
611
+ startDeploy(versionData.suggestedVersion);
612
+ }
613
+ else {
614
+ // Show confirmation modal
615
+ setDeployModalData({
616
+ currentVersion: versionData.currentVersion,
617
+ suggestedVersion: versionData.suggestedVersion,
618
+ isDevVersion: versionData.isDevVersion
619
+ });
620
+ setShowDeployModal(true);
621
+ }
622
+ }
623
+ catch (error) {
624
+ console.error("Error triggering deploy:", error);
625
+ appendDeployLog(`Error: ${error instanceof Error ? error.message : String(error)}`);
626
+ }
627
+ };
628
+ // Start the deploy with a specific version
629
+ const startDeploy = async (version) => {
630
+ setShowDeployModal(false);
631
+ try {
632
+ const response = await fetch("/devserver/api/deploy/start", {
633
+ method: "POST",
634
+ headers: { "Content-Type": "application/json" },
635
+ body: JSON.stringify({ version }),
636
+ });
637
+ if (!response.ok) {
638
+ const errorData = await response.json().catch(() => ({}));
639
+ appendDeployLog(`Error starting deploy: ${errorData.error || 'Unknown error'}`);
640
+ }
641
+ // Success: SSE events will handle state updates
642
+ }
643
+ catch (error) {
644
+ console.error("Error starting deploy:", error);
645
+ appendDeployLog(`Error: ${error instanceof Error ? error.message : String(error)}`);
646
+ }
647
+ };
648
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "app postman-layout", children: [(0, jsx_runtime_1.jsxs)("header", { className: "top-nav", children: [(0, jsx_runtime_1.jsx)("div", { className: "nav-left", children: (0, jsx_runtime_1.jsx)("div", { className: "app-title", children: (0, jsx_runtime_1.jsx)("h1", { children: "OCP Local Environment" }) }) }), appInfo && ((0, jsx_runtime_1.jsx)("div", { className: "nav-center", children: (0, jsx_runtime_1.jsxs)("div", { className: "app-info-nav", children: [(0, jsx_runtime_1.jsx)("span", { className: "app-name", children: appInfo.displayName || appInfo.name }), (0, jsx_runtime_1.jsxs)("span", { className: "app-version", children: ["v", appInfo.version] })] }) })), (0, jsx_runtime_1.jsxs)("div", { className: "nav-right", children: [(0, jsx_runtime_1.jsxs)("button", { className: `deploy-btn ${getDeployStatusClass()}`, onClick: triggerDeploy, disabled: deployStatus.deployInProgress || !!buildStatus?.buildInProgress, title: "Deploy app version to OCP", children: [(0, jsx_runtime_1.jsx)("span", { className: "deploy-status-dot" }), deployStatus.deployInProgress ? "Deploying..." :
649
+ deployStatus.lastDeploySuccess === true ? "Deployed" : "Deploy"] }), (0, jsx_runtime_1.jsxs)("button", { className: `rebuild-btn ${buildStatus?.buildInProgress ? 'building' :
650
+ buildStatus?.lastBuildSuccess === true ? 'success' :
651
+ buildStatus?.lastBuildSuccess === false ? 'error' : ''}`, onClick: triggerBuild, disabled: buildStatus?.buildInProgress, children: [(0, jsx_runtime_1.jsx)("span", { className: "rebuild-status-dot" }), buildStatus?.buildInProgress ? "Building..." : "Rebuild"] }), config.hasSchemaFiles && ((0, jsx_runtime_1.jsxs)("div", { className: "odp-status-badge", children: [(0, jsx_runtime_1.jsx)("span", { className: `odp-status-dot ${config.odpKeyConfigured ? 'configured' : 'not-configured'}` }), "ODP/OCP", (0, jsx_runtime_1.jsx)("span", { className: "odp-tooltip", children: config.odpKeyConfigured
652
+ ? 'ODP API Key is configured'
653
+ : 'ODP API Key is not configured — set it in Tool Settings' })] })), (0, jsx_runtime_1.jsx)("div", { className: "nav-separator-vertical" }), (0, jsx_runtime_1.jsxs)("a", { href: "https://github.com/optimizely/ocp-developer-feedback/issues/new/choose", target: "_blank", rel: "noopener noreferrer", className: "feedback-btn", title: "Provide Feedback", children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconPenField, { size: "14" }), "Feedback"] }), (0, jsx_runtime_1.jsxs)("a", { href: "https://docs.developers.optimizely.com/optimizely-connect-platform/docs/local-testing", target: "_blank", rel: "noopener noreferrer", className: "docs-btn", title: "Documentation", children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconBookOpen, { size: "14" }), " Docs"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "main-layout", children: [(0, jsx_runtime_1.jsx)("aside", { className: "sidebar", children: (0, jsx_runtime_1.jsxs)("nav", { className: "sidebar-nav", children: [(0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "settings" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/settings'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconGear, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "App Settings" })] }), appInfo && appInfo.functions && appInfo.functions.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "functions" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/functions'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconBolt, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Functions" })] })), appInfo && appInfo.jobs && appInfo.jobs.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "jobs" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/jobs'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconCubes, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Jobs" })] })), appInfo && appInfo.destinations && appInfo.destinations.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "destinations" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/destinations'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconUpload, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Destinations" })] })), appInfo && appInfo.sources && appInfo.sources.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "sources" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/sources'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconDownload, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Sources" })] })), appInfo && appInfo.opalTools && appInfo.opalTools.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "opal-tools" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/opal-tools'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconScrewdriverWrench, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Opal Tools" })] })), config.hasSchemaFiles && ((0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "odp-schema" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/odp-schema'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconMemoPad, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "ODP Schema" })] })), (0, jsx_runtime_1.jsx)("div", { className: "nav-separator", style: { height: '1px', background: '#3e3e42', margin: '4px 0' } }), (0, jsx_runtime_1.jsxs)("button", { className: `nav-item ${activeView === "tool-settings" ? "active" : ""}`, onClick: () => navigatePreservingConsole('/tool-settings'), children: [(0, jsx_runtime_1.jsx)(axiom_icons_1.IconGear, { size: "2xs" }), (0, jsx_runtime_1.jsx)("span", { className: "nav-label", children: "Tool Settings" })] })] }) }), (0, jsx_runtime_1.jsxs)("main", { className: "content-area", children: [activeView !== "jobs" && activeView !== "functions" && activeView !== "opal-tools" && activeView !== "destinations" && activeView !== "tool-settings" && activeView !== "odp-schema" && ((0, jsx_runtime_1.jsxs)("div", { className: "content-header", children: [(0, jsx_runtime_1.jsxs)("h2", { children: [activeView === "settings" && "App Settings", activeView === "sources" && "Sources"] }), appInfo && !appInfo.isValid && ((0, jsx_runtime_1.jsxs)("div", { className: "validation-errors-banner", children: [(0, jsx_runtime_1.jsx)("span", { className: "error-icon", children: "\u26A0\uFE0F" }), (0, jsx_runtime_1.jsx)("span", { children: "App has validation errors" })] }))] })), (0, jsx_runtime_1.jsx)("div", { className: "content-body", children: (0, jsx_runtime_1.jsxs)(react_router_dom_1.Routes, { children: [(0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/", element: (0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/settings", element: (0, jsx_runtime_1.jsxs)("div", { className: "settings-view", children: [appInfo && !appInfo.isValid && appInfo.errors && appInfo.errors.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "validation-errors", children: [(0, jsx_runtime_1.jsx)("h4", { children: "Validation Errors:" }), (0, jsx_runtime_1.jsx)("ul", { children: appInfo.errors.map((err, idx) => ((0, jsx_runtime_1.jsx)("li", { children: err }, idx))) })] })), buildStatus?.buildInProgress || (buildStatus?.lastBuildSuccess === null && buildStatus?.lastBuildTimestamp === null) ? ((0, jsx_runtime_1.jsx)("div", { className: "settings-loading-container", children: (0, jsx_runtime_1.jsxs)("div", { className: "placeholder-content", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Building App..." }), (0, jsx_runtime_1.jsx)("p", { children: "Please wait while the app is being built." }), (0, jsx_runtime_1.jsx)("div", { className: "spinner" })] }) })) : buildStatus?.lastBuildSuccess === false ? ((0, jsx_runtime_1.jsx)("div", { className: "settings-loading-container", children: (0, jsx_runtime_1.jsxs)("div", { className: "placeholder-content", children: [(0, jsx_runtime_1.jsx)("h3", { children: "Build Failed" }), (0, jsx_runtime_1.jsx)("p", { children: "The app failed to build. Please check the console for errors." }), buildStatus.lastBuildError && ((0, jsx_runtime_1.jsx)("pre", { className: "error-details", children: buildStatus.lastBuildError }))] }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "settings-iframe-container", children: (0, jsx_runtime_1.jsx)("iframe", { id: "app_directory_frame", style: {
405
654
  width: "100%",
406
655
  height: "100%",
407
656
  border: "none",
408
657
  overflow: "hidden",
409
- }, ref: setIframeRef, src: `${window.location.origin}/app/${appInfo?.name || "unknown"}`, onLoad: handleIframeLoad }) }))] }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/functions/*", element: (0, jsx_runtime_1.jsx)(FunctionsView_1.default, { appInstalled: appInstalled, buildStatus: buildStatus }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/jobs/*", element: (0, jsx_runtime_1.jsx)(JobsView_1.default, { appInstalled: appInstalled, buildStatus: buildStatus }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/destinations/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "destinations-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.destinations && appInfo.destinations.length > 0 ? ((0, jsx_runtime_1.jsx)(DestinationsView_1.DestinationsView, { apiBaseUrl: "/devserver/api", appInstalled: appInstalled, buildStatus: buildStatus })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/sources/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "sources-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.sources && appInfo.sources.length > 0 ? ((0, jsx_runtime_1.jsx)(SourcesView_1.SourcesView, { apiBaseUrl: "/devserver/api", appInstalled: appInstalled, buildStatus: buildStatus })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "*", element: (0, jsx_runtime_1.jsxs)("div", { className: "not-found-view", style: {
658
+ }, ref: setIframeRef, src: `${window.location.origin}/app/${appInfo?.name || "unknown"}`, onLoad: handleIframeLoad }) }))] }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/functions/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "functions-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.functions && appInfo.functions.length > 0 ? ((0, jsx_runtime_1.jsx)(FunctionsView_1.default, { appInstalled: appInstalled, buildStatus: buildStatus, installVersion: installVersion })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/jobs/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "jobs-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.jobs && appInfo.jobs.length > 0 ? ((0, jsx_runtime_1.jsx)(JobsView_1.default, { appInstalled: appInstalled, buildStatus: buildStatus })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/destinations/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "destinations-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.destinations && appInfo.destinations.length > 0 ? ((0, jsx_runtime_1.jsx)(DestinationsView_1.DestinationsView, { apiBaseUrl: "/devserver/api", appInstalled: appInstalled, buildStatus: buildStatus })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/sources/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "sources-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.sources && appInfo.sources.length > 0 ? ((0, jsx_runtime_1.jsx)(SourcesView_1.SourcesView, { apiBaseUrl: "/devserver/api", appInstalled: appInstalled, buildStatus: buildStatus })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/opal-tools/*", element: !appInfo ? ((0, jsx_runtime_1.jsx)("div", { className: "functions-view", children: (0, jsx_runtime_1.jsx)("div", { className: "placeholder-content", children: (0, jsx_runtime_1.jsx)("p", { children: "Loading..." }) }) })) : appInfo.opalTools && appInfo.opalTools.length > 0 ? ((0, jsx_runtime_1.jsx)(OpalToolsView_1.default, { appInstalled: appInstalled, buildStatus: buildStatus, installVersion: installVersion })) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/odp-schema", element: config.hasSchemaFiles ? ((0, jsx_runtime_1.jsx)(OdpSchemaView_1.default, {})) : ((0, jsx_runtime_1.jsx)(react_router_dom_1.Navigate, { to: "/settings", replace: true })) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "/tool-settings", element: (0, jsx_runtime_1.jsx)(ToolSettingsView_1.default, { odpApiKey: config.odpApiKey || '', onSave: async (odpApiKey) => {
659
+ try {
660
+ await fetch('/devserver/api/config/odp', {
661
+ method: 'POST',
662
+ headers: { 'Content-Type': 'application/json' },
663
+ body: JSON.stringify({ odpApiKey }),
664
+ });
665
+ fetchConfig();
666
+ }
667
+ catch (error) {
668
+ console.error('Error saving ODP config:', error);
669
+ }
670
+ } }) }), (0, jsx_runtime_1.jsx)(react_router_dom_1.Route, { path: "*", element: (0, jsx_runtime_1.jsxs)("div", { className: "not-found-view", style: {
410
671
  display: 'flex',
411
672
  flexDirection: 'column',
412
673
  alignItems: 'center',
@@ -421,7 +682,7 @@ const App = () => {
421
682
  color: 'white',
422
683
  border: 'none',
423
684
  borderRadius: '4px'
424
- }, children: "Go to Settings" })] }) })] }) })] })] }), (0, jsx_runtime_1.jsx)(TabbedConsole_1.default, { logs: logs, onClearLogs: clearConsoleLogs, storeTimestamps: storeTimestamps, onRefreshStores: fetchStatus, notificationCount: notificationCount })] }));
685
+ }, children: "Go to Settings" })] }) })] }) })] })] }), (0, jsx_runtime_1.jsx)(TabbedConsole_1.default, { logs: logs, onClearLogs: clearConsoleLogs, storeTimestamps: storeTimestamps, onRefreshStores: fetchStatus, notificationCount: notificationCount, hasSources: !!(appInfo?.sources && appInfo.sources.length > 0), sourceDataVersion: sourceDataVersion, sourceDataTotalEmitted: sourceDataTotalEmitted, notificationVersion: notificationVersion, envVersion: envVersion, deployLogs: deployLogs, deployVersion: deployVersion, deployStatus: deployStatus }), (0, jsx_runtime_1.jsx)(DeployModal_1.DeployModal, { isOpen: showDeployModal, onClose: () => setShowDeployModal(false), currentVersion: deployModalData?.currentVersion || '', suggestedVersion: deployModalData?.suggestedVersion || '', isDevVersion: deployModalData?.isDevVersion || false, onDeploy: startDeploy }), permanentlyDisconnected && ((0, jsx_runtime_1.jsx)("div", { className: "disconnect-overlay", children: (0, jsx_runtime_1.jsxs)("div", { className: "disconnect-message", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Server Disconnected" }), (0, jsx_runtime_1.jsx)("p", { children: "The development server has been stopped or is unreachable." }), (0, jsx_runtime_1.jsx)("button", { onClick: () => window.location.reload(), children: "Reconnect" })] }) })), appChanged && ((0, jsx_runtime_1.jsx)("div", { className: "disconnect-overlay", children: (0, jsx_runtime_1.jsxs)("div", { className: "disconnect-message app-changed", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Different App Detected" }), (0, jsx_runtime_1.jsxs)("p", { children: ["The server is now running a different app.", (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsxs)("span", { className: "app-change-details", children: [(0, jsx_runtime_1.jsx)("strong", { children: appChanged.oldApp }), " \u2192 ", (0, jsx_runtime_1.jsx)("strong", { children: appChanged.newApp })] })] }), (0, jsx_runtime_1.jsx)("button", { onClick: () => window.location.reload(), children: "Load New App" })] }) }))] }));
425
686
  };
426
687
  exports.default = App;
427
688
  //# sourceMappingURL=App.js.map