ivoryos 1.0.9__py3-none-any.whl → 1.1.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.
- ivoryos/__init__.py +18 -6
- ivoryos/routes/api/api.py +109 -0
- ivoryos/routes/auth/auth.py +5 -5
- ivoryos/routes/control/control.py +55 -353
- ivoryos/routes/control/control_file.py +36 -0
- ivoryos/routes/control/control_new_device.py +142 -0
- ivoryos/routes/control/templates/controllers.html +137 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +38 -0
- ivoryos/routes/data/data.py +108 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +7 -7
- ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +96 -517
- ivoryos/routes/design/design_file.py +57 -0
- ivoryos/routes/design/design_step.py +43 -0
- ivoryos/routes/design/templates/components/action_form.html +52 -0
- ivoryos/routes/design/templates/components/action_list.html +15 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +14 -0
- ivoryos/routes/design/templates/components/canvas.html +14 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +5 -0
- ivoryos/routes/design/templates/components/canvas_header.html +54 -0
- ivoryos/routes/design/templates/components/deck_selector.html +12 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +29 -0
- ivoryos/routes/design/templates/components/instrument_panel.html +23 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +19 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +18 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/operations_panel.html +43 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +17 -0
- ivoryos/routes/design/templates/components/script_info.html +31 -0
- ivoryos/routes/design/templates/components/scripts.html +50 -0
- ivoryos/routes/design/templates/components/sidebar.html +16 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +41 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +173 -0
- ivoryos/routes/execute/execute_file.py +44 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +147 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
- ivoryos/routes/execute/templates/experiment_run.html +294 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/{database/database.py → library/library.py} +10 -112
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +8 -8
- ivoryos/routes/main/main.py +1 -1
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/socket_handlers.py +52 -0
- ivoryos/templates/base.html +4 -4
- ivoryos/utils/bo_campaign.py +43 -3
- ivoryos/utils/form.py +1 -0
- ivoryos/utils/py_to_json.py +225 -0
- ivoryos/utils/script_runner.py +30 -7
- ivoryos/version.py +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.1.0.dist-info}/METADATA +5 -8
- ivoryos-1.1.0.dist-info/RECORD +102 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/{database/templates/database → data/templates/components}/step_card.html +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.1.0.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.1.0.dist-info}/WHEEL +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{# Repeat tab component #}
|
|
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>
|
|
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="repeat">Repeat for </label>
|
|
7
|
+
<input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="1">
|
|
8
|
+
<label class="input-group-text" for="repeat"> times</label>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="input-group mb-3">
|
|
11
|
+
<button class="form-control" type="submit" class="btn btn-dark">Run</button>
|
|
12
|
+
</div>
|
|
13
|
+
</form>
|
|
14
|
+
</div>
|
|
@@ -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
|
|
@@ -4,11 +4,11 @@ from flask_login import login_required
|
|
|
4
4
|
from ivoryos.utils.db_models import Script, db, WorkflowRun, WorkflowStep
|
|
5
5
|
from ivoryos.utils.utils import get_script_file, post_script_file
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
library = Blueprint('library', __name__, template_folder='templates')
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@
|
|
11
|
+
@library.route("/edit/<script_name>")
|
|
12
12
|
@login_required
|
|
13
13
|
def edit_workflow(script_name:str):
|
|
14
14
|
"""
|
|
@@ -37,7 +37,7 @@ def edit_workflow(script_name:str):
|
|
|
37
37
|
return redirect(url_for('design.experiment_builder'))
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
@
|
|
40
|
+
@library.route("/delete/<script_name>")
|
|
41
41
|
@login_required
|
|
42
42
|
def delete_workflow(script_name: str):
|
|
43
43
|
"""
|
|
@@ -57,7 +57,7 @@ def delete_workflow(script_name: str):
|
|
|
57
57
|
return redirect(url_for('database.load_from_database'))
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
@
|
|
60
|
+
@library.route("/save")
|
|
61
61
|
@login_required
|
|
62
62
|
def publish():
|
|
63
63
|
"""
|
|
@@ -84,7 +84,7 @@ def publish():
|
|
|
84
84
|
return redirect(url_for('design.experiment_builder'))
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
@
|
|
87
|
+
@library.route("/finalize")
|
|
88
88
|
@login_required
|
|
89
89
|
def finalize():
|
|
90
90
|
"""
|
|
@@ -106,8 +106,8 @@ def finalize():
|
|
|
106
106
|
return redirect(url_for('design.experiment_builder'))
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
@
|
|
110
|
-
@
|
|
109
|
+
@library.route("/get/", strict_slashes=False)
|
|
110
|
+
@library.route("/get/<deck_name>")
|
|
111
111
|
@login_required
|
|
112
112
|
def load_from_database(deck_name=None):
|
|
113
113
|
"""
|
|
@@ -144,10 +144,10 @@ def load_from_database(deck_name=None):
|
|
|
144
144
|
})
|
|
145
145
|
else:
|
|
146
146
|
# return HTML
|
|
147
|
-
return render_template("
|
|
147
|
+
return render_template("library.html", scripts=scripts, deck_list=deck_list, deck_name=deck_name)
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
@
|
|
150
|
+
@library.route("/rename", methods=['POST'])
|
|
151
151
|
@login_required
|
|
152
152
|
def edit_run_name():
|
|
153
153
|
"""
|
|
@@ -173,7 +173,7 @@ def edit_run_name():
|
|
|
173
173
|
return redirect(url_for("design.experiment_builder"))
|
|
174
174
|
|
|
175
175
|
|
|
176
|
-
@
|
|
176
|
+
@library.route("/save_as", methods=['POST'])
|
|
177
177
|
@login_required
|
|
178
178
|
def save_as():
|
|
179
179
|
"""
|
|
@@ -202,105 +202,3 @@ def save_as():
|
|
|
202
202
|
flash("Script name is already exist in database")
|
|
203
203
|
return redirect(url_for("design.experiment_builder"))
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
# -----------------------------------------------------------
|
|
207
|
-
# ------------------ Workflow logs -----------------------
|
|
208
|
-
# -----------------------------------------------------------
|
|
209
|
-
@database.route('/database/workflows/')
|
|
210
|
-
def list_workflows():
|
|
211
|
-
"""
|
|
212
|
-
.. :quickref: Database; list all workflow logs
|
|
213
|
-
|
|
214
|
-
list all workflow logs
|
|
215
|
-
|
|
216
|
-
.. http:get:: /database/workflows/
|
|
217
|
-
|
|
218
|
-
"""
|
|
219
|
-
query = WorkflowRun.query.order_by(WorkflowRun.id.desc())
|
|
220
|
-
search_term = request.args.get("keyword", None)
|
|
221
|
-
if search_term:
|
|
222
|
-
query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
|
|
223
|
-
page = request.args.get('page', default=1, type=int)
|
|
224
|
-
per_page = 10
|
|
225
|
-
|
|
226
|
-
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
227
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
228
|
-
workflows = query.all()
|
|
229
|
-
workflow_data = {w.id:{"workflow_name":w.name, "start_time":w.start_time} for w in workflows}
|
|
230
|
-
return jsonify({
|
|
231
|
-
"workflow_data": workflow_data,
|
|
232
|
-
})
|
|
233
|
-
else:
|
|
234
|
-
return render_template('workflow_database.html', workflows=workflows)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
@database.route("/database/workflows/<int:workflow_id>")
|
|
238
|
-
def get_workflow_steps(workflow_id:int):
|
|
239
|
-
"""
|
|
240
|
-
.. :quickref: Database; list all workflow logs
|
|
241
|
-
|
|
242
|
-
list all workflow logs
|
|
243
|
-
|
|
244
|
-
.. http:get:: /database/workflows/<int:workflow_id>
|
|
245
|
-
|
|
246
|
-
"""
|
|
247
|
-
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
248
|
-
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
249
|
-
|
|
250
|
-
# Use full objects for template rendering
|
|
251
|
-
grouped = {
|
|
252
|
-
"prep": [],
|
|
253
|
-
"script": {},
|
|
254
|
-
"cleanup": [],
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
# Use dicts for JSON response
|
|
258
|
-
grouped_json = {
|
|
259
|
-
"prep": [],
|
|
260
|
-
"script": {},
|
|
261
|
-
"cleanup": [],
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for step in steps:
|
|
265
|
-
step_dict = step.as_dict()
|
|
266
|
-
|
|
267
|
-
if step.phase == "prep":
|
|
268
|
-
grouped["prep"].append(step)
|
|
269
|
-
grouped_json["prep"].append(step_dict)
|
|
270
|
-
|
|
271
|
-
elif step.phase == "script":
|
|
272
|
-
grouped["script"].setdefault(step.repeat_index, []).append(step)
|
|
273
|
-
grouped_json["script"].setdefault(step.repeat_index, []).append(step_dict)
|
|
274
|
-
|
|
275
|
-
elif step.phase == "cleanup" or step.method_name == "stop":
|
|
276
|
-
grouped["cleanup"].append(step)
|
|
277
|
-
grouped_json["cleanup"].append(step_dict)
|
|
278
|
-
|
|
279
|
-
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
280
|
-
return jsonify({
|
|
281
|
-
"workflow_info": workflow.as_dict(),
|
|
282
|
-
"steps": grouped_json,
|
|
283
|
-
})
|
|
284
|
-
else:
|
|
285
|
-
return render_template("workflow_view.html", workflow=workflow, grouped=grouped)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@database.route("/database/workflows/delete/<int:workflow_id>")
|
|
289
|
-
@login_required
|
|
290
|
-
def delete_workflow_data(workflow_id: int):
|
|
291
|
-
"""
|
|
292
|
-
.. :quickref: Database; delete experiment data from database
|
|
293
|
-
|
|
294
|
-
delete workflow data from database
|
|
295
|
-
|
|
296
|
-
.. http:get:: /database/workflows/delete/<int:workflow_id>
|
|
297
|
-
|
|
298
|
-
:param workflow_id: workflow id
|
|
299
|
-
:type workflow_id: int
|
|
300
|
-
:status 302: redirect to :http:get:`/ivoryos/database/workflows/`
|
|
301
|
-
|
|
302
|
-
"""
|
|
303
|
-
run = WorkflowRun.query.get(workflow_id)
|
|
304
|
-
db.session.delete(run)
|
|
305
|
-
db.session.commit()
|
|
306
|
-
return redirect(url_for('database.list_workflows'))
|
ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html}
RENAMED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
{% block body %}
|
|
5
5
|
<div class="database-filter">
|
|
6
6
|
{% for deck_name in deck_list %}
|
|
7
|
-
{% if deck_name == "ALL" %}<a class="btn btn-secondary" href="{{url_for('
|
|
8
|
-
{% else %}<a class="btn btn-secondary" href="{{url_for('
|
|
7
|
+
{% if deck_name == "ALL" %}<a class="btn btn-secondary" href="{{url_for('library.load_from_database')}}">Back</a>
|
|
8
|
+
{% else %}<a class="btn btn-secondary" href="{{url_for('library.load_from_database',deck_name=deck_name)}}">{{deck_name}}</a>
|
|
9
9
|
{% endif %}
|
|
10
10
|
{% endfor %}
|
|
11
11
|
|
|
12
|
-
<form id="search" style="display: inline-block;float: right;" action="{{url_for('
|
|
12
|
+
<form id="search" style="display: inline-block;float: right;" action="{{url_for('library.load_from_database',deck_name=deck_name)}}" method="GET">
|
|
13
13
|
<div class="input-group">
|
|
14
14
|
<div class="form-outline">
|
|
15
15
|
<input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<tbody>
|
|
38
38
|
{% for script in scripts %}
|
|
39
39
|
<tr>
|
|
40
|
-
<td><a href="{{ url_for('
|
|
40
|
+
<td><a href="{{ url_for('library.edit_workflow', script_name=script.name) }}">{{ script.name }}</a></td>
|
|
41
41
|
<td>{{ script.deck }}</td>
|
|
42
42
|
<td>{{ script.status }}</td>
|
|
43
43
|
<td>{{ script.time_created }}</td>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<td>
|
|
48
48
|
{#not workflow.status == "finalized" or#}
|
|
49
49
|
{% if session['user'] == 'admin' or session['user'] == script.author %}
|
|
50
|
-
<a href="{{ url_for('
|
|
50
|
+
<a href="{{ url_for('library.delete_workflow', script_name=script.name) }}">delete</a>
|
|
51
51
|
{% else %}
|
|
52
52
|
<a class="disabled-link">delete</a>
|
|
53
53
|
{% endif %}
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
{# paging#}
|
|
61
61
|
<div class="pagination justify-content-center">
|
|
62
62
|
<div class="page-item {{ 'disabled' if not scripts.has_prev else '' }}">
|
|
63
|
-
<a class="page-link" href="{{ url_for('
|
|
63
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.prev_num) }}">Previous</a>
|
|
64
64
|
</div>
|
|
65
65
|
|
|
66
66
|
{% for num in scripts.iter_pages() %}
|
|
67
67
|
{% if num %}
|
|
68
68
|
<div class="page-item {{ 'active' if num == scripts.page else '' }}">
|
|
69
|
-
<a class="page-link" href="{{ url_for('
|
|
69
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=num) }}">{{ num }}</a>
|
|
70
70
|
</div>
|
|
71
71
|
{% else %}
|
|
72
72
|
<div class="page-item disabled">
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
{% endfor %}
|
|
77
77
|
|
|
78
78
|
<div class="page-item {{ 'disabled' if not scripts.has_next else '' }}">
|
|
79
|
-
<a class="page-link" href="{{ url_for('
|
|
79
|
+
<a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.next_num) }}">Next</a>
|
|
80
80
|
</div>
|
|
81
81
|
</div>
|
|
82
82
|
|
ivoryos/routes/main/main.py
CHANGED
|
@@ -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
|
|
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('
|
|
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('
|
|
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('
|
|
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.
|
|
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})
|
ivoryos/templates/base.html
CHANGED
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
</li>
|
|
39
39
|
{% if enable_design %}
|
|
40
40
|
<li class="nav-item">
|
|
41
|
-
<a class="nav-link" href="{{ url_for('
|
|
41
|
+
<a class="nav-link" href="{{ url_for('library.load_from_database') }}" aria-current="page">Library</a>
|
|
42
42
|
</li>
|
|
43
43
|
<li class="nav-item">
|
|
44
44
|
<a class="nav-link" href="{{ url_for('design.experiment_builder') }}">Design</a>
|
|
45
45
|
</li>
|
|
46
46
|
<li class="nav-item">
|
|
47
|
-
<a class="nav-link" href="{{ url_for('
|
|
47
|
+
<a class="nav-link" href="{{ url_for('execute.experiment_run') }}">Compile/Run</a>
|
|
48
48
|
</li>
|
|
49
49
|
<li class="nav-item">
|
|
50
|
-
<a class="nav-link" href="{{ url_for('
|
|
50
|
+
<a class="nav-link" href="{{ url_for('data.list_workflows') }}">Data</a>
|
|
51
51
|
</li>
|
|
52
52
|
{% endif %}
|
|
53
53
|
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<h1 class="modal-title fs-5" id="importModal">Import deck by file path</h1>
|
|
117
117
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
118
118
|
</div>
|
|
119
|
-
<form method="POST" action="{{ url_for('control.import_deck') }}" enctype="multipart/form-data">
|
|
119
|
+
<form method="POST" action="{{ url_for('control.temp.import_deck') }}" enctype="multipart/form-data">
|
|
120
120
|
<div class="modal-body">
|
|
121
121
|
<h5>from connection history</h5>
|
|
122
122
|
<div class="form-group">
|