ivoryos 1.3.8__py3-none-any.whl → 1.4.0__py3-none-any.whl

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.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (34) hide show
  1. ivoryos/optimizer/ax_optimizer.py +44 -25
  2. ivoryos/optimizer/base_optimizer.py +15 -1
  3. ivoryos/optimizer/baybe_optimizer.py +24 -17
  4. ivoryos/optimizer/nimo_optimizer.py +26 -24
  5. ivoryos/routes/data/data.py +27 -9
  6. ivoryos/routes/data/templates/components/step_card.html +47 -11
  7. ivoryos/routes/data/templates/workflow_view.html +14 -5
  8. ivoryos/routes/design/design.py +31 -1
  9. ivoryos/routes/design/design_step.py +2 -1
  10. ivoryos/routes/design/templates/components/edit_action_form.html +16 -3
  11. ivoryos/routes/design/templates/components/python_code_overlay.html +27 -10
  12. ivoryos/routes/design/templates/experiment_builder.html +1 -0
  13. ivoryos/routes/execute/execute.py +36 -7
  14. ivoryos/routes/execute/templates/components/run_tabs.html +45 -2
  15. ivoryos/routes/execute/templates/components/tab_bayesian.html +447 -325
  16. ivoryos/routes/execute/templates/components/tab_configuration.html +303 -18
  17. ivoryos/routes/execute/templates/components/tab_repeat.html +6 -2
  18. ivoryos/routes/execute/templates/experiment_run.html +0 -264
  19. ivoryos/socket_handlers.py +1 -1
  20. ivoryos/static/js/action_handlers.js +252 -118
  21. ivoryos/static/js/sortable_design.js +1 -0
  22. ivoryos/utils/bo_campaign.py +17 -16
  23. ivoryos/utils/db_models.py +122 -18
  24. ivoryos/utils/decorators.py +1 -0
  25. ivoryos/utils/form.py +22 -5
  26. ivoryos/utils/nest_script.py +314 -0
  27. ivoryos/utils/script_runner.py +447 -147
  28. ivoryos/utils/utils.py +11 -1
  29. ivoryos/version.py +1 -1
  30. {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/METADATA +3 -2
  31. {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/RECORD +34 -33
  32. {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/WHEEL +0 -0
  33. {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/licenses/LICENSE +0 -0
  34. {ivoryos-1.3.8.dist-info → ivoryos-1.4.0.dist-info}/top_level.txt +0 -0
@@ -68,24 +68,35 @@
68
68
  </tbody>
69
69
  </table>
70
70
  </div>
71
- <div class="card-footer">
72
- <div class="d-flex justify-content-between align-items-center">
73
- <div class="d-flex gap-2">
74
- <button type="button" class="btn btn-success" onclick="addRow()">
75
- <i class="bi bi-plus-circle"></i> Add Row
76
- </button>
77
- <button type="button" class="btn btn-warning" onclick="clearAllRows()">
78
- <i class="bi bi-trash"></i> Clear All
79
- </button>
80
- <button type="button" class="btn btn-info" onclick="resetToFile()" id="resetToFileBtn" style="display: none;">
81
- <i class="bi bi-arrow-clockwise"></i> Reset to File
82
- </button>
71
+
72
+ <div class="card-footer">
73
+ <div class="d-flex justify-content-between align-items-center flex-wrap gap-3">
74
+ <!-- Left side: Action buttons -->
75
+ <div class="d-flex gap-2 flex-wrap">
76
+ <button type="button" class="btn btn-success" onclick="addRow()">
77
+ <i class="bi bi-plus-circle"></i> Add Row
78
+ </button>
79
+ <button type="button" class="btn btn-warning" onclick="clearAllRows()">
80
+ <i class="bi bi-trash"></i> Clear All
81
+ </button>
82
+ <button type="button" class="btn btn-info" onclick="resetToFile()" id="resetToFileBtn" style="display: none;">
83
+ <i class="bi bi-arrow-clockwise"></i> Reset to File
84
+ </button>
85
+ </div>
86
+
87
+ <!-- Right side: Batch size and Run button -->
88
+ <div class="d-flex gap-2 align-items-center flex-wrap">
89
+ <div class="input-group" style="width: auto;">
90
+ <label class="input-group-text" for="batch_size">Batch size</label>
91
+ <input class="form-control" type="number" id="batch_size" name="batch_size" min="1" max="1000" value="1" style="width: 80px;">
92
+ </div>
93
+ <button type="submit" name="online-config" class="btn btn-primary btn-lg">
94
+ <i class="bi bi-play-circle"></i> Run
95
+ </button>
96
+ </div>
97
+ </div>
83
98
  </div>
84
- <button type="submit" name="online-config" class="btn btn-primary btn-lg">
85
- <i class="bi bi-play-circle"></i> Run
86
- </button>
87
- </div>
88
- </div>
99
+
89
100
  </form>
90
101
  </div>
91
102
  <!-- Config Preview (if loaded from file) -->
@@ -95,4 +106,278 @@
95
106
  </div>
96
107
  {% endif %}
97
108
  </div>
98
- </div>
109
+ </div>
110
+ <script>
111
+ var rowCount = 0;
112
+ var configColumns = [
113
+ {% for column in config_list %}
114
+ '{{ column }}'{{ ',' if not loop.last else '' }}
115
+ {% endfor %}
116
+ ];
117
+ var configTypes = {
118
+ {% for column, type in config_type_list.items() %}
119
+ '{{ column }}': '{{ type }}'{{ ',' if not loop.last else '' }}
120
+ {% endfor %}
121
+ };
122
+
123
+ // State management
124
+ var originalFileData = null;
125
+ var isModifiedFromFile = false;
126
+ var saveTimeout = null;
127
+ var lastSavedData = null;
128
+
129
+ function addRow(data = null, skipSave = false) {
130
+ rowCount++;
131
+ var tableBody = document.getElementById("tableBody");
132
+ var newRow = tableBody.insertRow(-1);
133
+
134
+ // Row number cell
135
+ var rowNumCell = newRow.insertCell(-1);
136
+ rowNumCell.innerHTML = '<span class="badge bg-secondary">' + rowCount + '</span>';
137
+
138
+ // Data cells
139
+ configColumns.forEach(function(column, index) {
140
+ var cell = newRow.insertCell(-1);
141
+ var value = data && data[column] ? data[column] : '';
142
+ var placeholder = configTypes[column] || 'value';
143
+ cell.innerHTML = '<input type="text" class="form-control form-control-sm" name="' +
144
+ column + '[' + rowCount + ']" value="' + value + '" placeholder="' + placeholder +
145
+ '" oninput="onInputChange()" onchange="onInputChange()">';
146
+ });
147
+
148
+ // Action cell
149
+ var actionCell = newRow.insertCell(-1);
150
+ actionCell.innerHTML = '<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeRow(this)" title="Remove row">' +
151
+ '<i class="bi bi-trash"></i></button>';
152
+
153
+ if (!skipSave) {
154
+ markAsModified();
155
+ debouncedSave();
156
+ }
157
+ }
158
+
159
+ function removeRow(button) {
160
+ var row = button.closest('tr');
161
+ row.remove();
162
+ updateRowNumbers();
163
+ markAsModified();
164
+ debouncedSave();
165
+ }
166
+
167
+ function updateRowNumbers() {
168
+ var tableBody = document.getElementById("tableBody");
169
+ var rows = tableBody.getElementsByTagName('tr');
170
+ for (var i = 0; i < rows.length; i++) {
171
+ var badge = rows[i].querySelector('.badge');
172
+ if (badge) {
173
+ badge.textContent = i + 1;
174
+ }
175
+ }
176
+ }
177
+
178
+ function clearAllRows() {
179
+ if (confirm('Are you sure you want to clear all rows?')) {
180
+ var tableBody = document.getElementById("tableBody");
181
+ tableBody.innerHTML = '';
182
+ rowCount = 0;
183
+ markAsModified();
184
+ clearSavedData();
185
+ // Add 5 empty rows by default
186
+ for (let i = 0; i < 5; i++) {
187
+ addRow(null, true);
188
+ }
189
+ debouncedSave();
190
+ }
191
+ }
192
+
193
+ function resetToFile() {
194
+ if (originalFileData && confirm('Reset to original file data? This will lose all manual changes.')) {
195
+ loadDataFromSource(originalFileData, false);
196
+ isModifiedFromFile = false;
197
+ updateStatusIndicators();
198
+ debouncedSave();
199
+ }
200
+ }
201
+
202
+ function onInputChange() {
203
+ markAsModified();
204
+ debouncedSave();
205
+ }
206
+
207
+ function markAsModified() {
208
+ if (originalFileData) {
209
+ isModifiedFromFile = true;
210
+ updateStatusIndicators();
211
+ }
212
+ }
213
+
214
+ function updateStatusIndicators() {
215
+ var modifiedStatus = document.getElementById('modifiedStatus');
216
+ var resetBtn = document.getElementById('resetToFileBtn');
217
+
218
+ if (isModifiedFromFile && originalFileData) {
219
+ modifiedStatus.style.display = 'inline-block';
220
+ resetBtn.style.display = 'inline-block';
221
+ } else {
222
+ modifiedStatus.style.display = 'none';
223
+ resetBtn.style.display = 'none';
224
+ }
225
+ }
226
+
227
+ function showSaveStatus() {
228
+ var saveStatus = document.getElementById('saveStatus');
229
+ saveStatus.style.display = 'inline-block';
230
+ setTimeout(function() {
231
+ saveStatus.style.display = 'none';
232
+ }, 2000);
233
+ }
234
+
235
+ function debouncedSave() {
236
+ clearTimeout(saveTimeout);
237
+ saveTimeout = setTimeout(function() {
238
+ saveFormData();
239
+ showSaveStatus();
240
+ }, 1000); // Save 1 second after user stops typing
241
+ }
242
+
243
+ function saveFormData() {
244
+ var formData = getCurrentFormData();
245
+ try {
246
+ sessionStorage.setItem('configFormData', JSON.stringify(formData));
247
+ sessionStorage.setItem('configModified', isModifiedFromFile.toString());
248
+ lastSavedData = formData;
249
+ console.log('Tab2: Saved form data', formData.length, 'rows');
250
+ } catch (e) {
251
+ console.warn('Could not save form data to sessionStorage:', e);
252
+ }
253
+ }
254
+
255
+ function getCurrentFormData() {
256
+ var tableBody = document.getElementById("tableBody");
257
+ var rows = tableBody.getElementsByTagName('tr');
258
+ var data = [];
259
+
260
+ for (var i = 0; i < rows.length; i++) {
261
+ var inputs = rows[i].getElementsByTagName('input');
262
+ var rowData = {};
263
+ var hasData = false;
264
+
265
+ for (var j = 0; j < inputs.length; j++) {
266
+ var input = inputs[j];
267
+ var name = input.name;
268
+ if (name) {
269
+ var columnName = name.substring(0, name.indexOf('['));
270
+ rowData[columnName] = input.value;
271
+ if (input.value.trim() !== '') {
272
+ hasData = true;
273
+ }
274
+ }
275
+ }
276
+
277
+ if (hasData) {
278
+ data.push(rowData);
279
+ }
280
+ }
281
+
282
+ return data;
283
+ }
284
+
285
+ function loadSavedData() {
286
+ try {
287
+ var savedData = sessionStorage.getItem('configFormData');
288
+ var savedModified = sessionStorage.getItem('configModified');
289
+
290
+ if (savedData) {
291
+ var parsedData = JSON.parse(savedData);
292
+ isModifiedFromFile = savedModified === 'true';
293
+ console.log('Tab2: Loaded saved data', parsedData.length, 'rows');
294
+ return parsedData;
295
+ }
296
+ } catch (e) {
297
+ console.warn('Could not load saved form data:', e);
298
+ }
299
+ return null;
300
+ }
301
+
302
+ function clearSavedData() {
303
+ try {
304
+ sessionStorage.removeItem('configFormData');
305
+ sessionStorage.removeItem('configModified');
306
+ console.log('Tab2: Cleared saved data');
307
+ } catch (e) {
308
+ console.warn('Could not clear saved data:', e);
309
+ }
310
+ }
311
+
312
+ function loadDataFromSource(data, isFromFile = false) {
313
+ // Clear existing rows
314
+ var tableBody = document.getElementById("tableBody");
315
+ tableBody.innerHTML = '';
316
+ rowCount = 0;
317
+
318
+ // Add rows with data
319
+ data.forEach(function(rowData) {
320
+ addRow(rowData, true);
321
+ });
322
+
323
+ // Add a few empty rows for additional input
324
+ for (let i = 0; i < 3; i++) {
325
+ addRow(null, true);
326
+ }
327
+
328
+ if (isFromFile) {
329
+ originalFileData = JSON.parse(JSON.stringify(data)); // Deep copy
330
+ isModifiedFromFile = false;
331
+ clearSavedData(); // Clear saved data when loading from file
332
+ }
333
+
334
+ updateStatusIndicators();
335
+ }
336
+
337
+ function loadConfigData() {
338
+ // Check for saved form data first
339
+ var savedData = loadSavedData();
340
+
341
+ {% if config_preview %}
342
+ var fileData = {{ config_preview | tojson | safe }};
343
+ originalFileData = JSON.parse(JSON.stringify(fileData)); // Deep copy
344
+
345
+ if (savedData && savedData.length > 0) {
346
+ // Load saved data if available
347
+ loadDataFromSource(savedData, false);
348
+ console.log('Tab2: Loaded saved form data');
349
+ } else {
350
+ // Load from file
351
+ loadDataFromSource(fileData, true);
352
+ console.log('Tab2: Loaded file data');
353
+ }
354
+ {% else %}
355
+ if (savedData && savedData.length > 0) {
356
+ // Load saved data
357
+ loadDataFromSource(savedData, false);
358
+ console.log('Tab2: Loaded saved form data');
359
+ } else {
360
+ // Add default empty rows
361
+ for (let i = 0; i < 5; i++) {
362
+ addRow(null, true);
363
+ }
364
+ }
365
+ {% endif %}
366
+ }
367
+
368
+ // Handle page unload
369
+ window.addEventListener('beforeunload', function() {
370
+ saveFormData();
371
+ });
372
+
373
+ // Initialize table when page loads
374
+ document.addEventListener("DOMContentLoaded", function() {
375
+ loadConfigData();
376
+ });
377
+
378
+ // ============================================
379
+ // EXPOSE FUNCTIONS GLOBALLY FOR COORDINATION
380
+ // ============================================
381
+ window.saveFormData = saveFormData;
382
+ window.loadConfigData = loadConfigData;
383
+ </script>
@@ -1,7 +1,11 @@
1
1
  {# Repeat tab component #}
2
2
  <div class="tab-pane fade {{ 'show active' if not config_list else '' }}" id="tab1" role="tabpanel" aria-labelledby="tab1-tab">
3
- <p><h5>Control panel:</h5></p>
3
+ <p></p>
4
4
  <form role="form" method='POST' name="run" action="{{url_for('execute.experiment_run')}}">
5
+ <div class="input-group mb-3">
6
+ <label class="input-group-text" for="batch_size">Batch size </label>
7
+ <input class="form-control" type="number" id="batch_size" name="batch_size" min="1" max="1000" value="1">
8
+ </div>
5
9
  <div class="input-group mb-3">
6
10
  <label class="input-group-text" for="repeat">Repeat for </label>
7
11
  <input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="1">
@@ -11,4 +15,4 @@
11
15
  <button class="form-control" type="submit" class="btn btn-dark">Run</button>
12
16
  </div>
13
17
  </form>
14
- </div>
18
+ </div>
@@ -26,269 +26,5 @@
26
26
  {% include 'components/error_modal.html' %}
27
27
 
28
28
  <script src="{{ url_for('static', filename='js/socket_handler.js') }}"></script>
29
- <script>
30
- var rowCount = 0;
31
- var configColumns = [
32
- {% for column in config_list %}
33
- '{{ column }}'{{ ',' if not loop.last else '' }}
34
- {% endfor %}
35
- ];
36
- var configTypes = {
37
- {% for column, type in config_type_list.items() %}
38
- '{{ column }}': '{{ type }}'{{ ',' if not loop.last else '' }}
39
- {% endfor %}
40
- };
41
29
 
42
- // State management
43
- var originalFileData = null;
44
- var isModifiedFromFile = false;
45
- var saveTimeout = null;
46
- var lastSavedData = null;
47
-
48
- function addRow(data = null, skipSave = false) {
49
- rowCount++;
50
- var tableBody = document.getElementById("tableBody");
51
- var newRow = tableBody.insertRow(-1);
52
-
53
- // Row number cell
54
- var rowNumCell = newRow.insertCell(-1);
55
- rowNumCell.innerHTML = '<span class="badge bg-secondary">' + rowCount + '</span>';
56
-
57
- // Data cells
58
- configColumns.forEach(function(column, index) {
59
- var cell = newRow.insertCell(-1);
60
- var value = data && data[column] ? data[column] : '';
61
- var placeholder = configTypes[column] || 'value';
62
- cell.innerHTML = '<input type="text" class="form-control form-control-sm" name="' +
63
- column + '[' + rowCount + ']" value="' + value + '" placeholder="' + placeholder +
64
- '" oninput="onInputChange()" onchange="onInputChange()">';
65
- });
66
-
67
- // Action cell
68
- var actionCell = newRow.insertCell(-1);
69
- actionCell.innerHTML = '<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeRow(this)" title="Remove row">' +
70
- '<i class="bi bi-trash"></i></button>';
71
-
72
- if (!skipSave) {
73
- markAsModified();
74
- debouncedSave();
75
- }
76
- }
77
-
78
- function removeRow(button) {
79
- var row = button.closest('tr');
80
- row.remove();
81
- updateRowNumbers();
82
- markAsModified();
83
- debouncedSave();
84
- }
85
-
86
- function updateRowNumbers() {
87
- var tableBody = document.getElementById("tableBody");
88
- var rows = tableBody.getElementsByTagName('tr');
89
- for (var i = 0; i < rows.length; i++) {
90
- var badge = rows[i].querySelector('.badge');
91
- if (badge) {
92
- badge.textContent = i + 1;
93
- }
94
- }
95
- }
96
-
97
- function clearAllRows() {
98
- if (confirm('Are you sure you want to clear all rows?')) {
99
- var tableBody = document.getElementById("tableBody");
100
- tableBody.innerHTML = '';
101
- rowCount = 0;
102
- markAsModified();
103
- clearSavedData();
104
- // Add 5 empty rows by default
105
- for (let i = 0; i < 5; i++) {
106
- addRow(null, true);
107
- }
108
- debouncedSave();
109
- }
110
- }
111
-
112
- function resetToFile() {
113
- if (originalFileData && confirm('Reset to original file data? This will lose all manual changes.')) {
114
- loadDataFromSource(originalFileData, false);
115
- isModifiedFromFile = false;
116
- updateStatusIndicators();
117
- debouncedSave();
118
- }
119
- }
120
-
121
- function onInputChange() {
122
- markAsModified();
123
- debouncedSave();
124
- }
125
-
126
- function markAsModified() {
127
- if (originalFileData) {
128
- isModifiedFromFile = true;
129
- updateStatusIndicators();
130
- }
131
- }
132
-
133
- function updateStatusIndicators() {
134
- var modifiedStatus = document.getElementById('modifiedStatus');
135
- var resetBtn = document.getElementById('resetToFileBtn');
136
-
137
- if (isModifiedFromFile && originalFileData) {
138
- modifiedStatus.style.display = 'inline-block';
139
- resetBtn.style.display = 'inline-block';
140
- } else {
141
- modifiedStatus.style.display = 'none';
142
- resetBtn.style.display = 'none';
143
- }
144
- }
145
-
146
- function showSaveStatus() {
147
- var saveStatus = document.getElementById('saveStatus');
148
- saveStatus.style.display = 'inline-block';
149
- setTimeout(function() {
150
- saveStatus.style.display = 'none';
151
- }, 2000);
152
- }
153
-
154
- function debouncedSave() {
155
- clearTimeout(saveTimeout);
156
- saveTimeout = setTimeout(function() {
157
- saveFormData();
158
- showSaveStatus();
159
- }, 1000); // Save 1 second after user stops typing
160
- }
161
-
162
- function saveFormData() {
163
- var formData = getCurrentFormData();
164
- try {
165
- sessionStorage.setItem('configFormData', JSON.stringify(formData));
166
- sessionStorage.setItem('configModified', isModifiedFromFile.toString());
167
- lastSavedData = formData;
168
- } catch (e) {
169
- console.warn('Could not save form data to sessionStorage:', e);
170
- }
171
- }
172
-
173
- function getCurrentFormData() {
174
- var tableBody = document.getElementById("tableBody");
175
- var rows = tableBody.getElementsByTagName('tr');
176
- var data = [];
177
-
178
- for (var i = 0; i < rows.length; i++) {
179
- var inputs = rows[i].getElementsByTagName('input');
180
- var rowData = {};
181
- var hasData = false;
182
-
183
- for (var j = 0; j < inputs.length; j++) {
184
- var input = inputs[j];
185
- var name = input.name;
186
- if (name) {
187
- var columnName = name.substring(0, name.indexOf('['));
188
- rowData[columnName] = input.value;
189
- if (input.value.trim() !== '') {
190
- hasData = true;
191
- }
192
- }
193
- }
194
-
195
- if (hasData) {
196
- data.push(rowData);
197
- }
198
- }
199
-
200
- return data;
201
- }
202
-
203
- function loadSavedData() {
204
- try {
205
- var savedData = sessionStorage.getItem('configFormData');
206
- var savedModified = sessionStorage.getItem('configModified');
207
-
208
- if (savedData) {
209
- var parsedData = JSON.parse(savedData);
210
- isModifiedFromFile = savedModified === 'true';
211
- return parsedData;
212
- }
213
- } catch (e) {
214
- console.warn('Could not load saved form data:', e);
215
- }
216
- return null;
217
- }
218
-
219
- function clearSavedData() {
220
- try {
221
- sessionStorage.removeItem('configFormData');
222
- sessionStorage.removeItem('configModified');
223
- } catch (e) {
224
- console.warn('Could not clear saved data:', e);
225
- }
226
- }
227
-
228
- function loadDataFromSource(data, isFromFile = false) {
229
- // Clear existing rows
230
- var tableBody = document.getElementById("tableBody");
231
- tableBody.innerHTML = '';
232
- rowCount = 0;
233
-
234
- // Add rows with data
235
- data.forEach(function(rowData) {
236
- addRow(rowData, true);
237
- });
238
-
239
- // Add a few empty rows for additional input
240
- for (let i = 0; i < 3; i++) {
241
- addRow(null, true);
242
- }
243
-
244
- if (isFromFile) {
245
- originalFileData = JSON.parse(JSON.stringify(data)); // Deep copy
246
- isModifiedFromFile = false;
247
- clearSavedData(); // Clear saved data when loading from file
248
- }
249
-
250
- updateStatusIndicators();
251
- }
252
-
253
- function loadConfigData() {
254
- // Check for saved form data first
255
- var savedData = loadSavedData();
256
-
257
- {% if config_preview %}
258
- var fileData = {{ config_preview | tojson | safe }};
259
- originalFileData = JSON.parse(JSON.stringify(fileData)); // Deep copy
260
-
261
- if (savedData && savedData.length > 0) {
262
- // Load saved data if available
263
- loadDataFromSource(savedData, false);
264
- console.log('Loaded saved form data');
265
- } else {
266
- // Load from file
267
- loadDataFromSource(fileData, true);
268
- console.log('Loaded file data');
269
- }
270
- {% else %}
271
- if (savedData && savedData.length > 0) {
272
- // Load saved data
273
- loadDataFromSource(savedData, false);
274
- console.log('Loaded saved form data');
275
- } else {
276
- // Add default empty rows
277
- for (let i = 0; i < 5; i++) {
278
- addRow(null, true);
279
- }
280
- }
281
- {% endif %}
282
- }
283
-
284
- // Handle page unload
285
- window.addEventListener('beforeunload', function() {
286
- saveFormData();
287
- });
288
-
289
- // Initialize table when page loads
290
- document.addEventListener("DOMContentLoaded", function() {
291
- loadConfigData();
292
- });
293
- </script>
294
30
  {% endblock %}
@@ -3,7 +3,7 @@ from flask import current_app
3
3
  from flask_socketio import SocketIO
4
4
  from ivoryos.utils.script_runner import ScriptRunner
5
5
 
6
- socketio = SocketIO()
6
+ socketio = SocketIO(cors_allowed_origins="*")
7
7
  runner = ScriptRunner()
8
8
 
9
9
  def abort_pending():