ivoryos 1.0.9__py3-none-any.whl → 1.2.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 (87) hide show
  1. ivoryos/__init__.py +26 -7
  2. ivoryos/routes/api/api.py +56 -0
  3. ivoryos/routes/auth/auth.py +5 -5
  4. ivoryos/routes/control/control.py +77 -372
  5. ivoryos/routes/control/control_file.py +36 -0
  6. ivoryos/routes/control/control_new_device.py +142 -0
  7. ivoryos/routes/control/templates/controllers.html +166 -0
  8. ivoryos/routes/control/templates/controllers_new.html +112 -0
  9. ivoryos/routes/control/utils.py +38 -0
  10. ivoryos/routes/data/data.py +129 -0
  11. ivoryos/routes/data/templates/components/step_card.html +13 -0
  12. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  13. ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
  14. ivoryos/routes/design/__init__.py +4 -0
  15. ivoryos/routes/design/design.py +298 -656
  16. ivoryos/routes/design/design_file.py +68 -0
  17. ivoryos/routes/design/design_step.py +145 -0
  18. ivoryos/routes/design/templates/components/action_form.html +53 -0
  19. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  20. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  21. ivoryos/routes/design/templates/components/canvas.html +5 -0
  22. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  23. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  24. ivoryos/routes/design/templates/components/canvas_main.html +34 -0
  25. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  26. ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
  27. ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
  28. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  29. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  30. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  31. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  32. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  33. ivoryos/routes/design/templates/components/modals.html +6 -0
  34. ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
  35. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  36. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  37. ivoryos/routes/design/templates/experiment_builder.html +41 -0
  38. ivoryos/routes/execute/__init__.py +0 -0
  39. ivoryos/routes/execute/execute.py +317 -0
  40. ivoryos/routes/execute/execute_file.py +78 -0
  41. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  42. ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  43. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  44. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  45. ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  46. ivoryos/routes/execute/templates/components/tab_bayesian.html +398 -0
  47. ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  48. ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  49. ivoryos/routes/execute/templates/experiment_run.html +294 -0
  50. ivoryos/routes/library/__init__.py +0 -0
  51. ivoryos/routes/library/library.py +159 -0
  52. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +30 -22
  53. ivoryos/routes/main/main.py +1 -1
  54. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  55. ivoryos/socket_handlers.py +52 -0
  56. ivoryos/static/js/action_handlers.js +213 -0
  57. ivoryos/static/js/db_delete.js +23 -0
  58. ivoryos/static/js/script_metadata.js +39 -0
  59. ivoryos/static/js/sortable_design.js +89 -56
  60. ivoryos/static/js/ui_state.js +113 -0
  61. ivoryos/templates/base.html +4 -4
  62. ivoryos/utils/bo_campaign.py +179 -3
  63. ivoryos/utils/db_models.py +14 -5
  64. ivoryos/utils/form.py +5 -9
  65. ivoryos/utils/global_config.py +13 -1
  66. ivoryos/utils/py_to_json.py +225 -0
  67. ivoryos/utils/script_runner.py +49 -7
  68. ivoryos/utils/serilize.py +203 -0
  69. ivoryos/utils/task_runner.py +4 -1
  70. ivoryos/version.py +1 -1
  71. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0.dist-info}/METADATA +5 -8
  72. ivoryos-1.2.0.dist-info/RECORD +105 -0
  73. ivoryos/routes/control/templates/control/controllers.html +0 -78
  74. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  75. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  76. ivoryos/routes/database/database.py +0 -306
  77. ivoryos/routes/database/templates/database/step_card.html +0 -7
  78. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  79. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  80. ivoryos-1.0.9.dist-info/RECORD +0 -61
  81. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  82. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  83. /ivoryos/routes/{database → data}/__init__.py +0 -0
  84. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  85. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0.dist-info}/LICENSE +0 -0
  86. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0.dist-info}/WHEEL +0 -0
  87. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,294 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Design execution{% endblock %}
3
+
4
+ {% block body %}
5
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.js"></script>
6
+
7
+ {% if no_deck_warning and not dismiss %}
8
+ {# auto pop import when there is no deck#}
9
+ <script type="text/javascript">
10
+ function OpenBootstrapPopup() {
11
+ $("#importModal").modal('show');
12
+ }
13
+ window.onload = function () {
14
+ OpenBootstrapPopup();
15
+ };
16
+ </script>
17
+ {% endif %}
18
+
19
+ <div class="row">
20
+ {% include 'components/run_panel.html' %}
21
+ {% include 'components/progress_panel.html' %}
22
+ {% include 'components/logging_panel.html' %}
23
+ </div>
24
+
25
+ {# Include error modal #}
26
+ {% include 'components/error_modal.html' %}
27
+
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
+
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
+ {% endblock %}
File without changes
@@ -0,0 +1,159 @@
1
+ from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
2
+ from flask_login import login_required
3
+
4
+ from ivoryos.utils.db_models import Script, db, WorkflowRun, WorkflowStep
5
+ from ivoryos.utils.utils import get_script_file, post_script_file
6
+
7
+ library = Blueprint('library', __name__, template_folder='templates')
8
+
9
+
10
+
11
+ @library.route("/<string:script_name>", methods=["GET", "POST", "DELETE"])
12
+ @login_required
13
+ def workflow_script(script_name:str):
14
+ # todo: split this into two routes, one for GET and POST, another for DELETE
15
+ """
16
+ .. :quickref: Workflow Script Database; get, post, delete workflow script
17
+
18
+ .. http:get:: /<string:script_name>
19
+
20
+ :param script_name: script name
21
+ :type script_name: str
22
+ :status 302: redirect to :http:get:`/ivoryos/design/script/`
23
+
24
+ .. http:post:: /<string:script_name>
25
+
26
+ :param script_name: script name
27
+ :type script_name: str
28
+ :status 200: json response with success status
29
+
30
+ .. http:delete:: /<string:script_name>
31
+
32
+ :param script_name: script name
33
+ :type script_name: str
34
+ :status 302: redirect to :http:get:`/ivoryos/design/script/`
35
+
36
+ """
37
+ row = Script.query.get(script_name)
38
+ if request.method == "DELETE":
39
+ if not row:
40
+ return jsonify(success=False)
41
+ db.session.delete(row)
42
+ db.session.commit()
43
+ return jsonify(success=True)
44
+ if request.method == "GET":
45
+ if not row:
46
+ return jsonify(success=False)
47
+ script = Script(**row.as_dict())
48
+ post_script_file(script)
49
+ pseudo_name = session.get("pseudo_deck", "")
50
+ off_line = current_app.config["OFF_LINE"]
51
+ if off_line and pseudo_name and not script.deck == pseudo_name:
52
+ flash(f"Choose the deck with name {script.deck}")
53
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
54
+ return jsonify({
55
+ "script": script.as_dict(),
56
+ "python_script": script.compile(),
57
+ })
58
+ return redirect(url_for('design.experiment_builder'))
59
+ if request.method == "POST":
60
+ status = publish()
61
+ return jsonify(status)
62
+ return None
63
+
64
+
65
+ def publish():
66
+ script = get_script_file()
67
+
68
+ if script.author is None:
69
+ script.author = session.get('user')
70
+ if not script.name or not script.deck:
71
+ return {"success": False, "error": "Deck cannot be empty, try to re-submit deck configuration on the left panel"}
72
+ row = Script.query.get(script.name)
73
+ if row and row.status == "finalized":
74
+ return {"success": False, "error": "This is a protected script, use save as to rename."}
75
+
76
+ elif row and session.get('user') != row.author:
77
+ return {"success": False, "error": "You are not the author, use save as to rename."}
78
+ else:
79
+ db.session.merge(script)
80
+ db.session.commit()
81
+ return {"success": True, "message": "Script published successfully"}
82
+
83
+
84
+ @library.get("/", strict_slashes=False)
85
+ @login_required
86
+ def load_from_database():
87
+ """
88
+ .. :quickref: Script Database; database page
89
+
90
+ backend control through http requests
91
+
92
+ .. http:get:: /designs/get/<deck_name>
93
+
94
+ :param deck_name: filter for deck name
95
+ :type deck_name: str
96
+
97
+ """
98
+ session.pop('edit_action', None) # reset cache
99
+ query = Script.query
100
+ search_term = request.args.get("keyword", None)
101
+ deck_name = request.args.get("deck", None)
102
+ if search_term:
103
+ query = query.filter(Script.name.like(f'%{search_term}%'))
104
+ if deck_name is None:
105
+ temp = Script.query.with_entities(Script.deck).distinct().all()
106
+ deck_list = [i[0] for i in temp]
107
+ else:
108
+ query = query.filter(Script.deck == deck_name)
109
+ deck_list = ["ALL"]
110
+ page = request.args.get('page', default=1, type=int)
111
+ per_page = 10
112
+
113
+ scripts = query.paginate(page=page, per_page=per_page, error_out=False)
114
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
115
+ scripts = query.all()
116
+ script_names = [script.name for script in scripts]
117
+ return jsonify({
118
+ "workflows": script_names,
119
+ })
120
+ else:
121
+ # return HTML
122
+ return render_template("library.html", scripts=scripts, deck_list=deck_list, deck_name=deck_name)
123
+
124
+
125
+
126
+
127
+ @library.post("/", strict_slashes=False)
128
+ @login_required
129
+ def save_as():
130
+ """
131
+ .. :quickref: Script Database; save the script as
132
+
133
+ save the current workflow script as
134
+
135
+ .. http:post:: /library/save_as
136
+
137
+ : form run_name: new workflow name
138
+ :status 302: redirect to :http:get:`/ivoryos/design/script/`
139
+
140
+ """
141
+ if request.method == "POST":
142
+ run_name = request.form.get("run_name")
143
+ ## TODO: check if run_name is valid
144
+ register_workflow = request.form.get("register_workflow")
145
+ script = get_script_file()
146
+ script.save_as(run_name)
147
+ script.registered = register_workflow == "on"
148
+ script.author = session.get('user')
149
+ post_script_file(script)
150
+ status = publish()
151
+ if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
152
+ return jsonify(status)
153
+ else:
154
+ if status["success"]:
155
+ flash("Script saved successfully")
156
+ else:
157
+ flash(status["error"], "error")
158
+ return redirect(url_for('design.experiment_builder'))
159
+
@@ -2,25 +2,28 @@
2
2
 
3
3
  {% block title %}IvoryOS | Design Database{% endblock %}
4
4
  {% block body %}
5
- <div class="database-filter">
6
- {% for deck_name in deck_list %}
7
- {% if deck_name == "ALL" %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database')}}">Back</a>
8
- {% else %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database',deck_name=deck_name)}}">{{deck_name}}</a>
9
- {% endif %}
5
+ <!-- Deck Filter Buttons -->
6
+ <div class="btn-group" role="group">
7
+ {% for deck in deck_list %}
8
+ <a class="btn {% if deck == current_deck_name %}btn-primary{% else %}btn-secondary{% endif %}"
9
+ href="{{ url_for('library.load_from_database', deck_name=deck) }}">
10
+ {{ deck }}
11
+ </a>
10
12
  {% endfor %}
11
-
12
- <form id="search" style="display: inline-block;float: right;" action="{{url_for('database.load_from_database',deck_name=deck_name)}}" method="GET">
13
- <div class="input-group">
14
- <div class="form-outline">
15
- <input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
16
- </div>
17
- <button type="submit" class="btn btn-primary">
18
- <i class="bi bi-search"></i>
19
- </button>
20
- </div>
21
- </form>
22
13
  </div>
23
14
 
15
+ <!-- Search Form -->
16
+ <form id="search" class="d-flex " method="GET" style="display: inline-block;float: right;"
17
+ action="{{ url_for('library.load_from_database', deck_name=current_deck_name or 'ALL') }}">
18
+ <div class="input-group">
19
+ <input type="search" name="keyword" id="keyword" class="form-control"
20
+ placeholder="Search workflows..." value="{{ request.args.get('keyword', '') }}">
21
+ <button type="submit" class="btn btn-primary">
22
+ <i class="bi bi-search"></i>
23
+ </button>
24
+ </div>
25
+ </form>
26
+
24
27
  <table class="table table-hover" id="workflowLibrary">
25
28
  <thead>
26
29
  <tr>
@@ -37,7 +40,7 @@
37
40
  <tbody>
38
41
  {% for script in scripts %}
39
42
  <tr>
40
- <td><a href="{{ url_for('database.edit_workflow', script_name=script.name) }}">{{ script.name }}</a></td>
43
+ <td><a href="{{ url_for('library.workflow_script', script_name=script.name) }}">{{ script.name }}</a></td>
41
44
  <td>{{ script.deck }}</td>
42
45
  <td>{{ script.status }}</td>
43
46
  <td>{{ script.time_created }}</td>
@@ -47,7 +50,12 @@
47
50
  <td>
48
51
  {#not workflow.status == "finalized" or#}
49
52
  {% if session['user'] == 'admin' or session['user'] == script.author %}
50
- <a href="{{ url_for('database.delete_workflow', script_name=script.name) }}">delete</a>
53
+ <a href="#"
54
+ class="text-danger"
55
+ data-delete-url="{{ url_for('library.workflow_script', script_name=script.name) }}"
56
+ onclick="deleteWorkflow(this); return false;">
57
+ Delete
58
+ </a>
51
59
  {% else %}
52
60
  <a class="disabled-link">delete</a>
53
61
  {% endif %}
@@ -60,13 +68,13 @@
60
68
  {# paging#}
61
69
  <div class="pagination justify-content-center">
62
70
  <div class="page-item {{ 'disabled' if not scripts.has_prev else '' }}">
63
- <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.prev_num) }}">Previous</a>
71
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.prev_num) }}">Previous</a>
64
72
  </div>
65
73
 
66
74
  {% for num in scripts.iter_pages() %}
67
75
  {% if num %}
68
76
  <div class="page-item {{ 'active' if num == scripts.page else '' }}">
69
- <a class="page-link" href="{{ url_for('database.load_from_database', page=num) }}">{{ num }}</a>
77
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=num) }}">{{ num }}</a>
70
78
  </div>
71
79
  {% else %}
72
80
  <div class="page-item disabled">
@@ -76,8 +84,8 @@
76
84
  {% endfor %}
77
85
 
78
86
  <div class="page-item {{ 'disabled' if not scripts.has_next else '' }}">
79
- <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.next_num) }}">Next</a>
87
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.next_num) }}">Next</a>
80
88
  </div>
81
89
  </div>
82
-
90
+ <script src="{{ url_for('static', filename='js/db_delete.js') }}"></script>
83
91
  {% endblock %}
@@ -2,7 +2,7 @@ from flask import Blueprint, render_template, current_app
2
2
  from flask_login import login_required
3
3
  from ivoryos.version import __version__ as ivoryos_version
4
4
 
5
- main = Blueprint('main', __name__, template_folder='templates/main')
5
+ main = Blueprint('main', __name__, template_folder='templates')
6
6
 
7
7
  @main.route("/")
8
8
  @login_required
@@ -19,7 +19,7 @@
19
19
  <i class="bi bi-folder2-open me-2"></i>Browse designs
20
20
  </h5>
21
21
  <p class="card-text">View all saved workflows from the database.</p>
22
- <a href="{{ url_for('database.load_from_database') }}" class="stretched-link"></a>
22
+ <a href="{{ url_for('library.load_from_database') }}" class="stretched-link"></a>
23
23
  </div>
24
24
  </div>
25
25
  </div>
@@ -48,7 +48,7 @@
48
48
  <i class="bi bi-graph-up-arrow me-2"></i>Experiment data
49
49
  </h5>
50
50
  <p class="card-text">Browse workflow logs and output data.</p>
51
- <a href="{{ url_for('database.list_workflows') }}" class="stretched-link"></a>
51
+ <a href="{{ url_for('data.list_workflows') }}" class="stretched-link"></a>
52
52
  </div>
53
53
  </div>
54
54
  </div>
@@ -62,7 +62,7 @@
62
62
  <i class="bi bi-play-circle me-2"></i>Run current workflow
63
63
  </h5>
64
64
  <p class="card-text">Execute workflows with configurable parameters.</p>
65
- <a href="{{ url_for('design.experiment_run') }}" class="stretched-link"></a>
65
+ <a href="{{ url_for('execute.experiment_run') }}" class="stretched-link"></a>
66
66
  </div>
67
67
  </div>
68
68
  </div>
@@ -93,7 +93,7 @@
93
93
  <i class="bi bi-usb-plug me-2"></i>Connect a new device
94
94
  </h5>
95
95
  <p class="card-text">Add new hardware temporarily or for testing purposes.</p>
96
- <a href="{{ url_for('control.controllers_home') }}" class="stretched-link"></a>
96
+ <a href="{{ url_for('control.temp.new_controller') }}" class="stretched-link"></a>
97
97
  </div>
98
98
  </div>
99
99
  </div>
@@ -0,0 +1,52 @@
1
+ import os
2
+ from flask import current_app
3
+ from flask_socketio import SocketIO
4
+ from ivoryos.utils.script_runner import ScriptRunner
5
+
6
+ socketio = SocketIO()
7
+ runner = ScriptRunner()
8
+
9
+ def abort_pending():
10
+ runner.abort_pending()
11
+ socketio.emit('log', {'message': "aborted pending iterations"})
12
+
13
+ def abort_current():
14
+ runner.stop_execution()
15
+ socketio.emit('log', {'message': "stopped next task"})
16
+
17
+ def pause():
18
+ runner.retry = False
19
+ msg = runner.toggle_pause()
20
+ socketio.emit('log', {'message': msg})
21
+ return msg
22
+
23
+ def retry():
24
+ runner.retry = True
25
+ msg = runner.toggle_pause()
26
+ socketio.emit('log', {'message': msg})
27
+
28
+ # Socket.IO Event Handlers
29
+ @socketio.on('abort_pending')
30
+ def handle_abort_pending():
31
+ abort_pending()
32
+
33
+ @socketio.on('abort_current')
34
+ def handle_abort_current():
35
+ abort_current()
36
+
37
+ @socketio.on('pause')
38
+ def handle_pause():
39
+ pause()
40
+
41
+ @socketio.on('retry')
42
+ def handle_retry():
43
+ retry()
44
+
45
+ @socketio.on('connect')
46
+ def handle_connect():
47
+ # Fetch log messages from local file
48
+ filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
49
+ with open(filename, 'r') as log_file:
50
+ log_history = log_file.readlines()
51
+ for message in log_history[-10:]:
52
+ socketio.emit('log', {'message': message})