ivoryos 1.0.1__py3-none-any.whl → 1.0.3__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/routes/auth/auth.py +3 -3
- ivoryos/routes/control/control.py +78 -43
- ivoryos/routes/control/templates/control/controllers_home.html +7 -7
- ivoryos/routes/database/database.py +62 -42
- ivoryos/routes/database/templates/database/{experiment_database.html → scripts_database.html} +27 -18
- ivoryos/routes/database/templates/database/{workflow_run_database.html → workflow_database.html} +27 -5
- ivoryos/routes/design/design.py +113 -72
- ivoryos/utils/client_proxy.py +1 -1
- ivoryos/utils/script_runner.py +2 -1
- ivoryos/version.py +1 -1
- {ivoryos-1.0.1.dist-info → ivoryos-1.0.3.dist-info}/METADATA +1 -1
- {ivoryos-1.0.1.dist-info → ivoryos-1.0.3.dist-info}/RECORD +16 -16
- /ivoryos/routes/database/templates/database/{experiment_step_view.html → workflow_view.html} +0 -0
- {ivoryos-1.0.1.dist-info → ivoryos-1.0.3.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.1.dist-info → ivoryos-1.0.3.dist-info}/WHEEL +0 -0
- {ivoryos-1.0.1.dist-info → ivoryos-1.0.3.dist-info}/top_level.txt +0 -0
ivoryos/routes/auth/auth.py
CHANGED
|
@@ -9,7 +9,7 @@ login_manager = LoginManager()
|
|
|
9
9
|
auth = Blueprint('auth', __name__, template_folder='templates/auth')
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@auth.route('/login', methods=['GET', 'POST'])
|
|
12
|
+
@auth.route('/auth/login', methods=['GET', 'POST'])
|
|
13
13
|
def login():
|
|
14
14
|
"""
|
|
15
15
|
.. :quickref: User; login user
|
|
@@ -50,7 +50,7 @@ def login():
|
|
|
50
50
|
return render_template('login.html')
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
@auth.route('/signup', methods=['GET', 'POST'])
|
|
53
|
+
@auth.route('/auth/signup', methods=['GET', 'POST'])
|
|
54
54
|
def signup():
|
|
55
55
|
"""
|
|
56
56
|
.. :quickref: User; signup for a new account
|
|
@@ -82,7 +82,7 @@ def signup():
|
|
|
82
82
|
return render_template('signup.html')
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
@auth.route("/logout")
|
|
85
|
+
@auth.route("/auth/logout")
|
|
86
86
|
@login_required
|
|
87
87
|
def logout():
|
|
88
88
|
"""
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
|
|
3
|
+
from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify, \
|
|
4
|
+
send_file
|
|
4
5
|
from flask_login import login_required
|
|
5
6
|
|
|
7
|
+
from ivoryos.utils.client_proxy import export_to_python, create_function
|
|
6
8
|
from ivoryos.utils.global_config import GlobalConfig
|
|
7
9
|
from ivoryos.utils import utils
|
|
8
10
|
from ivoryos.utils.form import create_form_from_module, format_name
|
|
@@ -14,7 +16,7 @@ runner = TaskRunner()
|
|
|
14
16
|
control = Blueprint('control', __name__, template_folder='templates/control')
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
@control.route("/
|
|
19
|
+
@control.route("/control/home/deck", strict_slashes=False)
|
|
18
20
|
@login_required
|
|
19
21
|
def deck_controllers():
|
|
20
22
|
"""
|
|
@@ -22,15 +24,15 @@ def deck_controllers():
|
|
|
22
24
|
|
|
23
25
|
deck control home interface for listing all deck instruments
|
|
24
26
|
|
|
25
|
-
.. http:get:: /
|
|
27
|
+
.. http:get:: /control/home/deck
|
|
26
28
|
"""
|
|
27
29
|
deck_variables = global_config.deck_snapshot.keys()
|
|
28
30
|
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
29
31
|
return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
@control.route("/
|
|
33
|
-
@control.route("/
|
|
34
|
+
@control.route("/control/new/", strict_slashes=False)
|
|
35
|
+
@control.route("/control/new/<instrument>", methods=['GET', 'POST'])
|
|
34
36
|
@login_required
|
|
35
37
|
def new_controller(instrument=None):
|
|
36
38
|
"""
|
|
@@ -38,12 +40,12 @@ def new_controller(instrument=None):
|
|
|
38
40
|
|
|
39
41
|
interface for connecting a new <instrument>
|
|
40
42
|
|
|
41
|
-
.. http:get:: /
|
|
43
|
+
.. http:get:: /control/new/
|
|
42
44
|
|
|
43
45
|
:param instrument: instrument name
|
|
44
46
|
:type instrument: str
|
|
45
47
|
|
|
46
|
-
.. http:post:: /
|
|
48
|
+
.. http:post:: /control/new/
|
|
47
49
|
|
|
48
50
|
:form device_name: module instance name (e.g. my_instance = MyClass())
|
|
49
51
|
:form kwargs: dynamic module initialization kwargs fields
|
|
@@ -88,7 +90,7 @@ def new_controller(instrument=None):
|
|
|
88
90
|
device=device, args=args, defined_variables=global_config.defined_variables)
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
@control.route("/
|
|
93
|
+
@control.route("/control/home/temp", strict_slashes=False)
|
|
92
94
|
@login_required
|
|
93
95
|
def controllers_home():
|
|
94
96
|
"""
|
|
@@ -96,14 +98,15 @@ def controllers_home():
|
|
|
96
98
|
|
|
97
99
|
temporarily connected devices home interface for listing all instruments
|
|
98
100
|
|
|
99
|
-
.. http:get:: /
|
|
101
|
+
.. http:get:: /control/home/temp
|
|
100
102
|
|
|
101
103
|
"""
|
|
102
104
|
# defined_variables = parse_deck(deck)
|
|
103
|
-
|
|
105
|
+
defined_variables = global_config.defined_variables.keys()
|
|
106
|
+
return render_template('controllers_home.html', defined_variables=defined_variables)
|
|
104
107
|
|
|
105
108
|
|
|
106
|
-
@control.route("/
|
|
109
|
+
@control.route("/control/<instrument>/methods", methods=['GET', 'POST'])
|
|
107
110
|
@login_required
|
|
108
111
|
def controllers(instrument: str):
|
|
109
112
|
"""
|
|
@@ -111,12 +114,12 @@ def controllers(instrument: str):
|
|
|
111
114
|
|
|
112
115
|
control interface for selected <instrument>
|
|
113
116
|
|
|
114
|
-
.. http:get:: /
|
|
117
|
+
.. http:get:: /control/<instrument>/methods
|
|
115
118
|
|
|
116
119
|
:param instrument: instrument name
|
|
117
120
|
:type instrument: str
|
|
118
121
|
|
|
119
|
-
.. http:post:: /
|
|
122
|
+
.. http:post:: /control/<instrument>/methods
|
|
120
123
|
|
|
121
124
|
:form hidden_name: function name (hidden field)
|
|
122
125
|
:form kwargs: dynamic kwargs field
|
|
@@ -154,24 +157,50 @@ def controllers(instrument: str):
|
|
|
154
157
|
flash(form.errors)
|
|
155
158
|
return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
|
|
156
159
|
|
|
160
|
+
@control.route("/control/download", strict_slashes=False)
|
|
161
|
+
@login_required
|
|
162
|
+
def download_proxy():
|
|
163
|
+
"""
|
|
164
|
+
.. :quickref: Direct Control; download proxy interface
|
|
165
|
+
|
|
166
|
+
download proxy interface
|
|
157
167
|
|
|
158
|
-
|
|
168
|
+
.. http:get:: /control/download
|
|
169
|
+
"""
|
|
170
|
+
snapshot = global_config.deck_snapshot.copy()
|
|
171
|
+
class_definitions = {}
|
|
172
|
+
# Iterate through each instrument in the snapshot
|
|
173
|
+
for instrument_key, instrument_data in snapshot.items():
|
|
174
|
+
# Iterate through each function associated with the current instrument
|
|
175
|
+
for function_key, function_data in instrument_data.items():
|
|
176
|
+
# Convert the function signature to a string representation
|
|
177
|
+
function_data['signature'] = str(function_data['signature'])
|
|
178
|
+
class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
|
|
179
|
+
class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
|
|
180
|
+
# Export the generated class definitions to a .py script
|
|
181
|
+
export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
|
|
182
|
+
filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
|
|
183
|
+
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
184
|
+
|
|
185
|
+
@control.route("/api/control/", strict_slashes=False, methods=['GET'])
|
|
186
|
+
@control.route("/api/control/<instrument>", methods=['POST'])
|
|
159
187
|
def backend_control(instrument: str=None):
|
|
160
188
|
"""
|
|
161
189
|
.. :quickref: Backend Control; backend control
|
|
162
190
|
|
|
163
191
|
backend control through http requests
|
|
164
192
|
|
|
165
|
-
.. http:get:: /
|
|
193
|
+
.. http:get:: /api/control/
|
|
166
194
|
|
|
167
195
|
:param instrument: instrument name
|
|
168
196
|
:type instrument: str
|
|
169
197
|
|
|
170
|
-
.. http:post:: /
|
|
198
|
+
.. http:post:: /api/control/
|
|
171
199
|
|
|
172
200
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
201
|
+
if instrument:
|
|
202
|
+
inst_object = find_instrument_by_name(instrument)
|
|
203
|
+
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
175
204
|
|
|
176
205
|
if request.method == 'POST':
|
|
177
206
|
method_name = request.form.get("hidden_name", None)
|
|
@@ -183,42 +212,48 @@ def backend_control(instrument: str=None):
|
|
|
183
212
|
current_app=current_app._get_current_object())
|
|
184
213
|
return jsonify(output), 200
|
|
185
214
|
|
|
186
|
-
|
|
187
|
-
@control.route("/backend_control", methods=['GET'])
|
|
188
|
-
def backend_client():
|
|
189
|
-
"""
|
|
190
|
-
.. :quickref: Backend Control; get snapshot
|
|
191
|
-
|
|
192
|
-
backend control through http requests
|
|
193
|
-
|
|
194
|
-
.. http:get:: /backend_control
|
|
195
|
-
"""
|
|
196
|
-
# Create a snapshot of the current deck configuration
|
|
197
215
|
snapshot = global_config.deck_snapshot.copy()
|
|
198
|
-
|
|
199
216
|
# Iterate through each instrument in the snapshot
|
|
200
217
|
for instrument_key, instrument_data in snapshot.items():
|
|
201
218
|
# Iterate through each function associated with the current instrument
|
|
202
219
|
for function_key, function_data in instrument_data.items():
|
|
203
220
|
# Convert the function signature to a string representation
|
|
204
221
|
function_data['signature'] = str(function_data['signature'])
|
|
222
|
+
return jsonify(snapshot), 200
|
|
205
223
|
|
|
206
|
-
|
|
207
|
-
|
|
224
|
+
# @control.route("/api/control", strict_slashes=False, methods=['GET'])
|
|
225
|
+
# def backend_client():
|
|
226
|
+
# """
|
|
227
|
+
# .. :quickref: Backend Control; get snapshot
|
|
228
|
+
#
|
|
229
|
+
# backend control through http requests
|
|
230
|
+
#
|
|
231
|
+
# .. http:get:: /api/control/summary
|
|
232
|
+
# """
|
|
233
|
+
# # Create a snapshot of the current deck configuration
|
|
234
|
+
# snapshot = global_config.deck_snapshot.copy()
|
|
235
|
+
#
|
|
236
|
+
# # Iterate through each instrument in the snapshot
|
|
237
|
+
# for instrument_key, instrument_data in snapshot.items():
|
|
238
|
+
# # Iterate through each function associated with the current instrument
|
|
239
|
+
# for function_key, function_data in instrument_data.items():
|
|
240
|
+
# # Convert the function signature to a string representation
|
|
241
|
+
# function_data['signature'] = str(function_data['signature'])
|
|
242
|
+
# return jsonify(snapshot), 200
|
|
208
243
|
|
|
209
244
|
|
|
210
|
-
@control.route("/
|
|
245
|
+
@control.route("/control/import/module", methods=['POST'])
|
|
211
246
|
def import_api():
|
|
212
247
|
"""
|
|
213
248
|
.. :quickref: Advanced Features; Manually import API module(s)
|
|
214
249
|
|
|
215
250
|
importing other Python modules
|
|
216
251
|
|
|
217
|
-
.. http:post:: /
|
|
252
|
+
.. http:post:: /control/import/module
|
|
218
253
|
|
|
219
254
|
:form filepath: API (Python class) module filepath
|
|
220
255
|
|
|
221
|
-
import the module and redirect to :http:get:`/ivoryos/
|
|
256
|
+
import the module and redirect to :http:get:`/ivoryos/control/new/`
|
|
222
257
|
|
|
223
258
|
"""
|
|
224
259
|
filepath = request.form.get('filepath')
|
|
@@ -266,12 +301,12 @@ def import_api():
|
|
|
266
301
|
# return redirect(url_for('control.deck_controllers'))
|
|
267
302
|
|
|
268
303
|
|
|
269
|
-
@control.route("/
|
|
304
|
+
@control.route("/control/import/deck", methods=['POST'])
|
|
270
305
|
def import_deck():
|
|
271
306
|
"""
|
|
272
307
|
.. :quickref: Advanced Features; Manually import a deck
|
|
273
308
|
|
|
274
|
-
.. http:post:: /import_deck
|
|
309
|
+
.. http:post:: /control/import_deck
|
|
275
310
|
|
|
276
311
|
:form filepath: deck module filepath
|
|
277
312
|
|
|
@@ -304,12 +339,12 @@ def import_deck():
|
|
|
304
339
|
return redirect(back)
|
|
305
340
|
|
|
306
341
|
|
|
307
|
-
@control.route('/save-order
|
|
342
|
+
@control.route('/control/<instrument>/save-order', methods=['POST'])
|
|
308
343
|
def save_order(instrument: str):
|
|
309
344
|
"""
|
|
310
345
|
.. :quickref: Control Customization; Save functions' order
|
|
311
346
|
|
|
312
|
-
.. http:post:: /save-order
|
|
347
|
+
.. http:post:: /control/save-order
|
|
313
348
|
|
|
314
349
|
save function drag and drop order for the given <instrument>
|
|
315
350
|
|
|
@@ -320,12 +355,12 @@ def save_order(instrument: str):
|
|
|
320
355
|
return '', 204
|
|
321
356
|
|
|
322
357
|
|
|
323
|
-
@control.route('/
|
|
358
|
+
@control.route('/control/<instrument>/<function>/hide')
|
|
324
359
|
def hide_function(instrument, function):
|
|
325
360
|
"""
|
|
326
361
|
.. :quickref: Control Customization; Hide function
|
|
327
362
|
|
|
328
|
-
.. http:get::
|
|
363
|
+
.. http:get:: //control/<instrument>/<function>/hide
|
|
329
364
|
|
|
330
365
|
Hide the given <instrument> and <function>
|
|
331
366
|
|
|
@@ -341,12 +376,12 @@ def hide_function(instrument, function):
|
|
|
341
376
|
return redirect(back)
|
|
342
377
|
|
|
343
378
|
|
|
344
|
-
@control.route('/
|
|
379
|
+
@control.route('/control/<instrument>/<function>/unhide')
|
|
345
380
|
def remove_hidden(instrument: str, function: str):
|
|
346
381
|
"""
|
|
347
382
|
.. :quickref: Control Customization; Remove a hidden function
|
|
348
383
|
|
|
349
|
-
.. http:get:: /
|
|
384
|
+
.. http:get:: /control/<instrument>/<function>/unhide
|
|
350
385
|
|
|
351
386
|
Un-hide the given <instrument> and <function>
|
|
352
387
|
|
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
{% for instrument in defined_variables %}
|
|
7
7
|
<div class="col-xl-3 col-lg-4 col-md-6 mb-4 ">
|
|
8
8
|
<div class="bg-white rounded shadow-sm position-relative">
|
|
9
|
-
|
|
9
|
+
{% if deck %}
|
|
10
10
|
{# <a href="{{ url_for('control.disconnect', instrument=instrument) }}" class="stretched-link controller-card" style="float: right;color: red; position: relative;">Disconnect <i class="bi bi-x-square"></i></a>#}
|
|
11
11
|
<div class="p-4 controller-card">
|
|
12
12
|
<h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument.split(".")[1]}}</a></h5>
|
|
13
13
|
</div>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
{% else %}
|
|
15
|
+
<div class="p-4 controller-card">
|
|
16
|
+
<h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a></h5>
|
|
17
|
+
</div>
|
|
18
|
+
{% endif %}
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
{% endfor %}
|
|
22
22
|
<div class="d-flex mb-3">
|
|
23
|
-
<a href="{{ url_for('
|
|
23
|
+
<a href="{{ url_for('control.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary">
|
|
24
24
|
<i class="bi bi-download"></i> Download remote control script
|
|
25
25
|
</a>
|
|
26
26
|
</div>
|
|
@@ -8,21 +8,21 @@ database = Blueprint('database', __name__, template_folder='templates/database')
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@database.route("/
|
|
11
|
+
@database.route("/database/scripts/edit/<script_name>")
|
|
12
12
|
@login_required
|
|
13
|
-
def edit_workflow(
|
|
13
|
+
def edit_workflow(script_name:str):
|
|
14
14
|
"""
|
|
15
|
-
.. :quickref: Database; load workflow to canvas
|
|
15
|
+
.. :quickref: Database; load workflow script to canvas
|
|
16
16
|
|
|
17
17
|
load the selected workflow to the design canvas
|
|
18
18
|
|
|
19
|
-
.. http:get:: /
|
|
19
|
+
.. http:get:: /database/scripts/edit/<script_name>
|
|
20
20
|
|
|
21
|
-
:param
|
|
22
|
-
:type
|
|
21
|
+
:param script_name: script name
|
|
22
|
+
:type script_name: str
|
|
23
23
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
24
24
|
"""
|
|
25
|
-
row = Script.query.get(
|
|
25
|
+
row = Script.query.get(script_name)
|
|
26
26
|
script = Script(**row.as_dict())
|
|
27
27
|
post_script_file(script)
|
|
28
28
|
pseudo_name = session.get("pseudo_deck", "")
|
|
@@ -37,27 +37,27 @@ def edit_workflow(workflow_name):
|
|
|
37
37
|
return redirect(url_for('design.experiment_builder'))
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
@database.route("/
|
|
40
|
+
@database.route("/database/scripts/delete/<script_name>")
|
|
41
41
|
@login_required
|
|
42
|
-
def delete_workflow(
|
|
42
|
+
def delete_workflow(script_name: str):
|
|
43
43
|
"""
|
|
44
44
|
.. :quickref: Database; delete workflow
|
|
45
45
|
|
|
46
46
|
delete workflow from database
|
|
47
47
|
|
|
48
|
-
.. http:get:: /
|
|
48
|
+
.. http:get:: /database/scripts/delete/<script_name>
|
|
49
49
|
|
|
50
|
-
:param
|
|
51
|
-
:type
|
|
52
|
-
:status 302: redirect to :http:get:`/ivoryos/database/`
|
|
50
|
+
:param script_name: workflow name
|
|
51
|
+
:type script_name: str
|
|
52
|
+
:status 302: redirect to :http:get:`/ivoryos/database/scripts/`
|
|
53
53
|
|
|
54
54
|
"""
|
|
55
|
-
Script.query.filter(Script.name ==
|
|
55
|
+
Script.query.filter(Script.name == script_name).delete()
|
|
56
56
|
db.session.commit()
|
|
57
57
|
return redirect(url_for('database.load_from_database'))
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
@database.route("/
|
|
60
|
+
@database.route("/database/scripts/save")
|
|
61
61
|
@login_required
|
|
62
62
|
def publish():
|
|
63
63
|
"""
|
|
@@ -65,7 +65,7 @@ def publish():
|
|
|
65
65
|
|
|
66
66
|
save workflow to database
|
|
67
67
|
|
|
68
|
-
.. http:get:: /
|
|
68
|
+
.. http:get:: /database/scripts/save
|
|
69
69
|
|
|
70
70
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
71
71
|
"""
|
|
@@ -84,7 +84,7 @@ def publish():
|
|
|
84
84
|
return redirect(url_for('design.experiment_builder'))
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
@database.route("/finalize")
|
|
87
|
+
@database.route("/database/scripts/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
|
-
@database.route("/database/", strict_slashes=False)
|
|
110
|
-
@database.route("/database/<deck_name>")
|
|
109
|
+
@database.route("/database/scripts/", strict_slashes=False)
|
|
110
|
+
@database.route("/database/scripts/<deck_name>")
|
|
111
111
|
@login_required
|
|
112
112
|
def load_from_database(deck_name=None):
|
|
113
113
|
"""
|
|
@@ -115,7 +115,7 @@ def load_from_database(deck_name=None):
|
|
|
115
115
|
|
|
116
116
|
backend control through http requests
|
|
117
117
|
|
|
118
|
-
.. http:get:: /database/<deck_name>
|
|
118
|
+
.. http:get:: /database/scripts/<deck_name>
|
|
119
119
|
|
|
120
120
|
:param deck_name: filter for deck name
|
|
121
121
|
:type deck_name: str
|
|
@@ -135,19 +135,19 @@ def load_from_database(deck_name=None):
|
|
|
135
135
|
page = request.args.get('page', default=1, type=int)
|
|
136
136
|
per_page = 10
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
scripts = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
139
139
|
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
scripts = query.all()
|
|
141
|
+
script_names = [script.name for script in scripts]
|
|
142
142
|
return jsonify({
|
|
143
|
-
"workflows":
|
|
143
|
+
"workflows": script_names,
|
|
144
144
|
})
|
|
145
145
|
else:
|
|
146
146
|
# return HTML
|
|
147
|
-
return render_template("
|
|
147
|
+
return render_template("scripts_database.html", scripts=scripts, deck_list=deck_list, deck_name=deck_name)
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
@database.route("/
|
|
150
|
+
@database.route("/database/scripts/rename", methods=['POST'])
|
|
151
151
|
@login_required
|
|
152
152
|
def edit_run_name():
|
|
153
153
|
"""
|
|
@@ -155,7 +155,7 @@ def edit_run_name():
|
|
|
155
155
|
|
|
156
156
|
edit the name of the current workflow, won't save to the database
|
|
157
157
|
|
|
158
|
-
.. http:post:: /
|
|
158
|
+
.. http:post:: database/scripts/rename
|
|
159
159
|
|
|
160
160
|
: form run_name: new workflow name
|
|
161
161
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
@@ -173,15 +173,15 @@ def edit_run_name():
|
|
|
173
173
|
return redirect(url_for("design.experiment_builder"))
|
|
174
174
|
|
|
175
175
|
|
|
176
|
-
@database.route("/save_as", methods=['POST'])
|
|
176
|
+
@database.route("/database/scripts/save_as", methods=['POST'])
|
|
177
177
|
@login_required
|
|
178
178
|
def save_as():
|
|
179
179
|
"""
|
|
180
180
|
.. :quickref: Database; save the run name as
|
|
181
181
|
|
|
182
|
-
save the workflow
|
|
182
|
+
save the current workflow script as
|
|
183
183
|
|
|
184
|
-
.. http:post:: /save_as
|
|
184
|
+
.. http:post:: /database/scripts/save_as
|
|
185
185
|
|
|
186
186
|
: form run_name: new workflow name
|
|
187
187
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
@@ -203,9 +203,20 @@ def save_as():
|
|
|
203
203
|
return redirect(url_for("design.experiment_builder"))
|
|
204
204
|
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
# -----------------------------------------------------------
|
|
207
|
+
# ------------------ Workflow logs -----------------------
|
|
208
|
+
# -----------------------------------------------------------
|
|
209
|
+
@database.route('/database/workflows/')
|
|
207
210
|
def list_workflows():
|
|
208
|
-
|
|
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())
|
|
209
220
|
search_term = request.args.get("keyword", None)
|
|
210
221
|
if search_term:
|
|
211
222
|
query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
|
|
@@ -219,11 +230,20 @@ def list_workflows():
|
|
|
219
230
|
return jsonify({
|
|
220
231
|
"workflow_data": workflow_data,
|
|
221
232
|
})
|
|
222
|
-
|
|
233
|
+
else:
|
|
234
|
+
return render_template('workflow_database.html', workflows=workflows)
|
|
223
235
|
|
|
224
236
|
|
|
225
|
-
@database.route("/
|
|
226
|
-
def get_workflow_steps(workflow_id):
|
|
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
|
+
"""
|
|
227
247
|
workflow = WorkflowRun.query.get_or_404(workflow_id)
|
|
228
248
|
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
229
249
|
|
|
@@ -261,23 +281,23 @@ def get_workflow_steps(workflow_id):
|
|
|
261
281
|
"workflow_info": workflow.as_dict(),
|
|
262
282
|
"steps": grouped_json,
|
|
263
283
|
})
|
|
264
|
-
|
|
265
|
-
|
|
284
|
+
else:
|
|
285
|
+
return render_template("workflow_view.html", workflow=workflow, grouped=grouped)
|
|
266
286
|
|
|
267
287
|
|
|
268
|
-
@database.route("/
|
|
288
|
+
@database.route("/database/workflows/delete/<int:workflow_id>")
|
|
269
289
|
@login_required
|
|
270
|
-
def delete_workflow_data(workflow_id:
|
|
290
|
+
def delete_workflow_data(workflow_id: int):
|
|
271
291
|
"""
|
|
272
292
|
.. :quickref: Database; delete experiment data from database
|
|
273
293
|
|
|
274
294
|
delete workflow data from database
|
|
275
295
|
|
|
276
|
-
.. http:get:: /
|
|
296
|
+
.. http:get:: /database/workflows/delete/<int:workflow_id>
|
|
277
297
|
|
|
278
298
|
:param workflow_id: workflow id
|
|
279
|
-
:type workflow_id:
|
|
280
|
-
:status 302: redirect to :http:get:`/ivoryos/
|
|
299
|
+
:type workflow_id: int
|
|
300
|
+
:status 302: redirect to :http:get:`/ivoryos/database/workflows/`
|
|
281
301
|
|
|
282
302
|
"""
|
|
283
303
|
run = WorkflowRun.query.get(workflow_id)
|
ivoryos/routes/database/templates/database/{experiment_database.html → scripts_database.html}
RENAMED
|
@@ -35,19 +35,19 @@
|
|
|
35
35
|
</tr>
|
|
36
36
|
</thead>
|
|
37
37
|
<tbody>
|
|
38
|
-
{% for
|
|
38
|
+
{% for script in scripts %}
|
|
39
39
|
<tr>
|
|
40
|
-
<td><a href="{{ url_for('database.edit_workflow',
|
|
41
|
-
<td>{{
|
|
42
|
-
<td>{{
|
|
43
|
-
<td>{{
|
|
44
|
-
<td>{{
|
|
45
|
-
<td>{{
|
|
40
|
+
<td><a href="{{ url_for('database.edit_workflow', script_name=script.name) }}">{{ script.name }}</a></td>
|
|
41
|
+
<td>{{ script.deck }}</td>
|
|
42
|
+
<td>{{ script.status }}</td>
|
|
43
|
+
<td>{{ script.time_created }}</td>
|
|
44
|
+
<td>{{ script.last_modified }}</td>
|
|
45
|
+
<td>{{ script.author }}</td>
|
|
46
46
|
{# <td>{{ workflow.registered }}</td>#}
|
|
47
47
|
<td>
|
|
48
48
|
{#not workflow.status == "finalized" or#}
|
|
49
|
-
{% if session['user'] == 'admin' or session['user'] ==
|
|
50
|
-
<a href="{{ url_for('database.delete_workflow',
|
|
49
|
+
{% if session['user'] == 'admin' or session['user'] == script.author %}
|
|
50
|
+
<a href="{{ url_for('database.delete_workflow', script_name=script.name) }}">delete</a>
|
|
51
51
|
{% else %}
|
|
52
52
|
<a class="disabled-link">delete</a>
|
|
53
53
|
{% endif %}
|
|
@@ -57,18 +57,27 @@
|
|
|
57
57
|
</tbody>
|
|
58
58
|
</table>
|
|
59
59
|
|
|
60
|
-
{# paging#}
|
|
60
|
+
{# paging#}
|
|
61
61
|
<div class="pagination justify-content-center">
|
|
62
|
-
<div class="page-item {{ 'disabled' if not
|
|
63
|
-
<a class="page-link" href="{{ url_for('database.load_from_database', page=
|
|
62
|
+
<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>
|
|
64
64
|
</div>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
|
|
66
|
+
{% for num in scripts.iter_pages() %}
|
|
67
|
+
{% if num %}
|
|
68
|
+
<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>
|
|
70
|
+
</div>
|
|
71
|
+
{% else %}
|
|
72
|
+
<div class="page-item disabled">
|
|
73
|
+
<span class="page-link">…</span>
|
|
74
|
+
</div>
|
|
75
|
+
{% endif %}
|
|
69
76
|
{% endfor %}
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
|
|
78
|
+
<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>
|
|
72
80
|
</div>
|
|
73
81
|
</div>
|
|
82
|
+
|
|
74
83
|
{% endblock %}
|
ivoryos/routes/database/templates/database/{workflow_run_database.html → workflow_database.html}
RENAMED
|
@@ -2,11 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
{% block title %}IvoryOS | Design Database{% endblock %}
|
|
4
4
|
{% block body %}
|
|
5
|
+
<div class="div">
|
|
6
|
+
<form id="search" style="display: inline-block;float: right;" action="{{url_for('database.list_workflows',deck_name=deck_name)}}" method="GET">
|
|
7
|
+
<div class="input-group">
|
|
8
|
+
<div class="form-outline">
|
|
9
|
+
<input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
|
|
10
|
+
</div>
|
|
11
|
+
<button type="submit" class="btn btn-primary">
|
|
12
|
+
<i class="bi bi-search"></i>
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
</form>
|
|
16
|
+
</div>
|
|
5
17
|
|
|
6
|
-
|
|
18
|
+
<table class="table table-hover" id="workflowResultLibrary">
|
|
7
19
|
<thead>
|
|
8
20
|
<tr>
|
|
9
21
|
<th scope="col">Workflow name</th>
|
|
22
|
+
<th scope="col">Workflow ID</th>
|
|
10
23
|
<th scope="col">Start time</th>
|
|
11
24
|
<th scope="col">End time</th>
|
|
12
25
|
<th scope="col">Data</th>
|
|
@@ -16,6 +29,7 @@
|
|
|
16
29
|
{% for workflow in workflows %}
|
|
17
30
|
<tr>
|
|
18
31
|
<td><a href="{{ url_for('database.get_workflow_steps', workflow_id=workflow.id) }}">{{ workflow.name }}</a></td>
|
|
32
|
+
<td>{{ workflow.id }}</td>
|
|
19
33
|
<td>{{ workflow.start_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.start_time else '' }}</td>
|
|
20
34
|
<td>{{ workflow.end_time.strftime("%Y-%m-%d %H:%M:%S") if workflow.end_time else '' }}</td>
|
|
21
35
|
|
|
@@ -36,16 +50,24 @@
|
|
|
36
50
|
</tbody>
|
|
37
51
|
</table>
|
|
38
52
|
|
|
39
|
-
{# paging#}
|
|
53
|
+
{# paging#}
|
|
40
54
|
<div class="pagination justify-content-center">
|
|
41
55
|
<div class="page-item {{ 'disabled' if not workflows.has_prev else '' }}">
|
|
42
56
|
<a class="page-link" href="{{ url_for('database.list_workflows', page=workflows.prev_num) }}">Previous</a>
|
|
43
57
|
</div>
|
|
58
|
+
|
|
44
59
|
{% for num in workflows.iter_pages() %}
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
60
|
+
{% if num %}
|
|
61
|
+
<div class="page-item {{ 'active' if num == workflows.page else '' }}">
|
|
62
|
+
<a class="page-link" href="{{ url_for('database.list_workflows', page=num) }}">{{ num }}</a>
|
|
63
|
+
</div>
|
|
64
|
+
{% else %}
|
|
65
|
+
<div class="page-item disabled">
|
|
66
|
+
<span class="page-link">…</span>
|
|
67
|
+
</div>
|
|
68
|
+
{% endif %}
|
|
48
69
|
{% endfor %}
|
|
70
|
+
|
|
49
71
|
<div class="page-item {{ 'disabled' if not workflows.has_next else '' }}">
|
|
50
72
|
<a class="page-link" href="{{ url_for('database.list_workflows', page=workflows.next_num) }}">Next</a>
|
|
51
73
|
</div>
|
ivoryos/routes/design/design.py
CHANGED
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
import pickle
|
|
5
5
|
import sys
|
|
6
|
+
import time
|
|
6
7
|
|
|
7
8
|
from flask import Blueprint, redirect, url_for, flash, jsonify, send_file, request, render_template, session, \
|
|
8
9
|
current_app, g
|
|
@@ -11,7 +12,6 @@ from flask_socketio import SocketIO
|
|
|
11
12
|
from werkzeug.utils import secure_filename
|
|
12
13
|
|
|
13
14
|
from ivoryos.utils import utils
|
|
14
|
-
from ivoryos.utils.client_proxy import create_function, export_to_python
|
|
15
15
|
from ivoryos.utils.global_config import GlobalConfig
|
|
16
16
|
from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
|
|
17
17
|
create_form_from_action, create_all_builtin_forms
|
|
@@ -74,8 +74,8 @@ def handle_abort_action():
|
|
|
74
74
|
socketio.emit('log', {'message': message})
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
@design.route("/
|
|
78
|
-
@design.route("/
|
|
77
|
+
@design.route("/design/script/", methods=['GET', 'POST'])
|
|
78
|
+
@design.route("/design/script/<instrument>/", methods=['GET', 'POST'])
|
|
79
79
|
@login_required
|
|
80
80
|
def experiment_builder(instrument=None):
|
|
81
81
|
"""
|
|
@@ -86,7 +86,7 @@ def experiment_builder(instrument=None):
|
|
|
86
86
|
This route allows users to build and edit experiment workflows. Users can interact with available instruments,
|
|
87
87
|
define variables, and manage experiment scripts.
|
|
88
88
|
|
|
89
|
-
.. http:get:: /
|
|
89
|
+
.. http:get:: /design/script
|
|
90
90
|
|
|
91
91
|
Load the experiment builder interface.
|
|
92
92
|
|
|
@@ -94,7 +94,7 @@ def experiment_builder(instrument=None):
|
|
|
94
94
|
:type instrument: str
|
|
95
95
|
:status 200: Experiment builder loaded successfully.
|
|
96
96
|
|
|
97
|
-
.. http:post:: /
|
|
97
|
+
.. http:post:: /design/script
|
|
98
98
|
|
|
99
99
|
Submit form data to add or modify actions in the experiment script.
|
|
100
100
|
|
|
@@ -248,17 +248,17 @@ def experiment_builder(instrument=None):
|
|
|
248
248
|
use_llm=enable_llm)
|
|
249
249
|
|
|
250
250
|
|
|
251
|
-
@design.route("/generate_code", methods=['POST'])
|
|
251
|
+
@design.route("/design/generate_code", methods=['POST'])
|
|
252
252
|
@login_required
|
|
253
253
|
def generate_code():
|
|
254
254
|
"""
|
|
255
255
|
.. :quickref: Text to Code; Generate code from user input and update the design canvas.
|
|
256
256
|
|
|
257
|
-
.. http:post:: /generate_code
|
|
257
|
+
.. http:post:: /design/generate_code
|
|
258
258
|
|
|
259
259
|
:form prompt: user's prompt
|
|
260
260
|
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
261
|
-
:status 400: failed to initialize the AI agent redirects to :http:get:`/
|
|
261
|
+
:status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
|
|
262
262
|
|
|
263
263
|
"""
|
|
264
264
|
agent = global_config.agent
|
|
@@ -296,17 +296,17 @@ def generate_code():
|
|
|
296
296
|
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
@design.route("/
|
|
299
|
+
@design.route("/design/campaign", methods=['GET', 'POST'])
|
|
300
300
|
@login_required
|
|
301
301
|
def experiment_run():
|
|
302
302
|
"""
|
|
303
303
|
.. :quickref: Workflow Execution; Execute/iterate the workflow
|
|
304
304
|
|
|
305
|
-
.. http:get:: /
|
|
305
|
+
.. http:get:: /design/campaign
|
|
306
306
|
|
|
307
307
|
Compile the workflow and load the experiment execution interface.
|
|
308
308
|
|
|
309
|
-
.. http:post:: /
|
|
309
|
+
.. http:post:: /design/campaign
|
|
310
310
|
|
|
311
311
|
Start workflow execution
|
|
312
312
|
|
|
@@ -328,7 +328,7 @@ def experiment_run():
|
|
|
328
328
|
# todo
|
|
329
329
|
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
330
330
|
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
331
|
-
|
|
331
|
+
# print(exec_string)
|
|
332
332
|
except Exception as e:
|
|
333
333
|
flash(e.__str__())
|
|
334
334
|
# handle api request
|
|
@@ -351,6 +351,7 @@ def experiment_run():
|
|
|
351
351
|
for key, func_str in exec_string.items():
|
|
352
352
|
exec(func_str)
|
|
353
353
|
line_collection = script.convert_to_lines(exec_string)
|
|
354
|
+
|
|
354
355
|
except Exception:
|
|
355
356
|
flash(f"Please check {key} syntax!!")
|
|
356
357
|
return redirect(url_for("design.experiment_builder"))
|
|
@@ -408,6 +409,9 @@ def experiment_run():
|
|
|
408
409
|
else:
|
|
409
410
|
flash(e)
|
|
410
411
|
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
412
|
+
# wait to get a workflow ID
|
|
413
|
+
while not global_config.runner_status:
|
|
414
|
+
time.sleep(1)
|
|
411
415
|
return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
|
|
412
416
|
else:
|
|
413
417
|
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
@@ -418,15 +422,15 @@ def experiment_run():
|
|
|
418
422
|
history=deck_list, pause_status=runner.pause_status())
|
|
419
423
|
|
|
420
424
|
|
|
421
|
-
@design.route("/
|
|
425
|
+
@design.route("/design/script/toggle/<stype>")
|
|
422
426
|
@login_required
|
|
423
427
|
def toggle_script_type(stype=None):
|
|
424
428
|
"""
|
|
425
429
|
.. :quickref: Workflow Design; toggle the experimental phase for design canvas.
|
|
426
430
|
|
|
427
|
-
.. http:get:: /
|
|
431
|
+
.. http:get:: /design/script/toggle/<stype>
|
|
428
432
|
|
|
429
|
-
:status 200: and then redirects to :http:get:`/
|
|
433
|
+
:status 200: and then redirects to :http:get:`/design/script`
|
|
430
434
|
|
|
431
435
|
"""
|
|
432
436
|
script = utils.get_script_file()
|
|
@@ -446,16 +450,16 @@ def update_list():
|
|
|
446
450
|
|
|
447
451
|
|
|
448
452
|
# --------------------handle all the import/export and download/upload--------------------------
|
|
449
|
-
@design.route("/clear")
|
|
453
|
+
@design.route("/design/clear")
|
|
450
454
|
@login_required
|
|
451
455
|
def clear():
|
|
452
456
|
"""
|
|
453
457
|
.. :quickref: Workflow Design; clear the design canvas.
|
|
454
458
|
|
|
455
|
-
.. http:get:: /clear
|
|
459
|
+
.. http:get:: /design/clear
|
|
456
460
|
|
|
457
461
|
:form prompt: user's prompt
|
|
458
|
-
:status 200: clear canvas and then redirects to :http:get:`/
|
|
462
|
+
:status 200: clear canvas and then redirects to :http:get:`/design/script`
|
|
459
463
|
"""
|
|
460
464
|
deck = global_config.deck
|
|
461
465
|
pseudo_name = session.get("pseudo_deck", "")
|
|
@@ -471,16 +475,16 @@ def clear():
|
|
|
471
475
|
return redirect(url_for("design.experiment_builder"))
|
|
472
476
|
|
|
473
477
|
|
|
474
|
-
@design.route("/
|
|
478
|
+
@design.route("/design/import/pseudo", methods=['POST'])
|
|
475
479
|
@login_required
|
|
476
480
|
def import_pseudo():
|
|
477
481
|
"""
|
|
478
482
|
.. :quickref: Workflow Design; Import pseudo deck from deck history
|
|
479
483
|
|
|
480
|
-
.. http:post:: /
|
|
484
|
+
.. http:post:: /design/import/pseudo
|
|
481
485
|
|
|
482
486
|
:form pkl_name: pseudo deck name
|
|
483
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/
|
|
487
|
+
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
484
488
|
"""
|
|
485
489
|
pkl_name = request.form.get('pkl_name')
|
|
486
490
|
script = utils.get_script_file()
|
|
@@ -494,16 +498,16 @@ def import_pseudo():
|
|
|
494
498
|
return redirect(url_for("design.experiment_builder"))
|
|
495
499
|
|
|
496
500
|
|
|
497
|
-
@design.route('/uploads', methods=['POST'])
|
|
501
|
+
@design.route('/design/uploads', methods=['POST'])
|
|
498
502
|
@login_required
|
|
499
503
|
def upload():
|
|
500
504
|
"""
|
|
501
505
|
.. :quickref: Workflow Execution; upload a workflow config file (.CSV)
|
|
502
506
|
|
|
503
|
-
.. http:post:: /uploads
|
|
507
|
+
.. http:post:: /design/uploads
|
|
504
508
|
|
|
505
509
|
:form file: workflow CSV config file
|
|
506
|
-
:status 302: save csv file and then redirects to :http:get:`/
|
|
510
|
+
:status 302: save csv file and then redirects to :http:get:`/design/campaign`
|
|
507
511
|
"""
|
|
508
512
|
if request.method == "POST":
|
|
509
513
|
f = request.files['file']
|
|
@@ -519,14 +523,20 @@ def upload():
|
|
|
519
523
|
return redirect(url_for("design.experiment_run"))
|
|
520
524
|
|
|
521
525
|
|
|
522
|
-
@design.route('/
|
|
526
|
+
@design.route('/design/workflow/download/<filename>')
|
|
523
527
|
@login_required
|
|
524
528
|
def download_results(filename):
|
|
529
|
+
"""
|
|
530
|
+
.. :quickref: Workflow Design; download a workflow data file
|
|
531
|
+
|
|
532
|
+
.. http:get:: /design/workflow/download/<filename>
|
|
533
|
+
|
|
534
|
+
"""
|
|
525
535
|
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
526
536
|
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
527
537
|
|
|
528
538
|
|
|
529
|
-
@design.route('/load_json', methods=['POST'])
|
|
539
|
+
@design.route('/design/load_json', methods=['POST'])
|
|
530
540
|
@login_required
|
|
531
541
|
def load_json():
|
|
532
542
|
"""
|
|
@@ -535,7 +545,7 @@ def load_json():
|
|
|
535
545
|
.. http:post:: /load_json
|
|
536
546
|
|
|
537
547
|
:form file: workflow design JSON file
|
|
538
|
-
:status 302: load pseudo deck and then redirects to :http:get:`/
|
|
548
|
+
:status 302: load pseudo deck and then redirects to :http:get:`/design/script`
|
|
539
549
|
"""
|
|
540
550
|
if request.method == "POST":
|
|
541
551
|
f = request.files['file']
|
|
@@ -549,9 +559,15 @@ def load_json():
|
|
|
549
559
|
return redirect(url_for("design.experiment_builder"))
|
|
550
560
|
|
|
551
561
|
|
|
552
|
-
@design.route('/download/<filetype>')
|
|
562
|
+
@design.route('/design/script/download/<filetype>')
|
|
553
563
|
@login_required
|
|
554
564
|
def download(filetype):
|
|
565
|
+
"""
|
|
566
|
+
.. :quickref: Workflow Design Ext; download a workflow design file
|
|
567
|
+
|
|
568
|
+
.. http:get:: /design/script/download/<filetype>
|
|
569
|
+
|
|
570
|
+
"""
|
|
555
571
|
script = utils.get_script_file()
|
|
556
572
|
run_name = script.name if script.name else "untitled"
|
|
557
573
|
if filetype == "configure":
|
|
@@ -569,40 +585,28 @@ def download(filetype):
|
|
|
569
585
|
outfile.write(json_object)
|
|
570
586
|
elif filetype == "python":
|
|
571
587
|
filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
class_definitions = {}
|
|
575
|
-
# Iterate through each instrument in the snapshot
|
|
576
|
-
for instrument_key, instrument_data in snapshot.items():
|
|
577
|
-
# Iterate through each function associated with the current instrument
|
|
578
|
-
for function_key, function_data in instrument_data.items():
|
|
579
|
-
# Convert the function signature to a string representation
|
|
580
|
-
function_data['signature'] = str(function_data['signature'])
|
|
581
|
-
class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
|
|
582
|
-
class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
|
|
583
|
-
# Export the generated class definitions to a .py script
|
|
584
|
-
export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
|
|
585
|
-
filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
|
|
588
|
+
else:
|
|
589
|
+
return "Unsupported file type", 400
|
|
586
590
|
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
587
591
|
|
|
588
592
|
|
|
589
|
-
@design.route("/edit/<uuid>", methods=['GET', 'POST'])
|
|
593
|
+
@design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
|
|
590
594
|
@login_required
|
|
591
595
|
def edit_action(uuid: str):
|
|
592
596
|
"""
|
|
593
597
|
.. :quickref: Workflow Design; edit parameters of an action step on canvas
|
|
594
598
|
|
|
595
|
-
.. http:get:: /edit
|
|
599
|
+
.. http:get:: /design/step/edit/<uuid>
|
|
596
600
|
|
|
597
601
|
Load parameter form of an action step
|
|
598
602
|
|
|
599
|
-
.. http:post:: /edit
|
|
603
|
+
.. http:post:: /design/step/edit/<uuid>
|
|
600
604
|
|
|
601
605
|
:param uuid: The step's uuid
|
|
602
606
|
:type uuid: str
|
|
603
607
|
|
|
604
608
|
:form dynamic form: workflow step dynamic inputs
|
|
605
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
609
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
606
610
|
"""
|
|
607
611
|
script = utils.get_script_file()
|
|
608
612
|
action = script.find_by_uuid(uuid)
|
|
@@ -625,18 +629,18 @@ def edit_action(uuid: str):
|
|
|
625
629
|
return redirect(url_for('design.experiment_builder'))
|
|
626
630
|
|
|
627
631
|
|
|
628
|
-
@design.route("/delete/<id>")
|
|
632
|
+
@design.route("/design/step/delete/<id>")
|
|
629
633
|
@login_required
|
|
630
634
|
def delete_action(id: int):
|
|
631
635
|
"""
|
|
632
636
|
.. :quickref: Workflow Design; delete an action step on canvas
|
|
633
637
|
|
|
634
|
-
.. http:get:: /delete
|
|
638
|
+
.. http:get:: /design/step/delete/<id>
|
|
635
639
|
|
|
636
640
|
:param id: The step number id
|
|
637
641
|
:type id: int
|
|
638
642
|
|
|
639
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
643
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
640
644
|
"""
|
|
641
645
|
back = request.referrer
|
|
642
646
|
script = utils.get_script_file()
|
|
@@ -645,18 +649,18 @@ def delete_action(id: int):
|
|
|
645
649
|
return redirect(back)
|
|
646
650
|
|
|
647
651
|
|
|
648
|
-
@design.route("/duplicate/<id>")
|
|
652
|
+
@design.route("/design/step/duplicate/<id>")
|
|
649
653
|
@login_required
|
|
650
654
|
def duplicate_action(id: int):
|
|
651
655
|
"""
|
|
652
656
|
.. :quickref: Workflow Design; duplicate an action step on canvas
|
|
653
657
|
|
|
654
|
-
.. http:get:: /duplicate
|
|
658
|
+
.. http:get:: /design/step/duplicate/<id>
|
|
655
659
|
|
|
656
660
|
:param id: The step number id
|
|
657
661
|
:type id: int
|
|
658
662
|
|
|
659
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
663
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
660
664
|
"""
|
|
661
665
|
back = request.referrer
|
|
662
666
|
script = utils.get_script_file()
|
|
@@ -667,8 +671,15 @@ def duplicate_action(id: int):
|
|
|
667
671
|
|
|
668
672
|
# ---- HTTP API Endpoints ----
|
|
669
673
|
|
|
670
|
-
@design.route("/api/status", methods=["GET"])
|
|
674
|
+
@design.route("/api/runner/status", methods=["GET"])
|
|
671
675
|
def runner_status():
|
|
676
|
+
"""
|
|
677
|
+
.. :quickref: Workflow Design; get the execution status
|
|
678
|
+
|
|
679
|
+
.. http:get:: /api/runner/status
|
|
680
|
+
|
|
681
|
+
:status 200: status
|
|
682
|
+
"""
|
|
672
683
|
runner_busy = global_config.runner_lock.locked()
|
|
673
684
|
status = {"busy": runner_busy}
|
|
674
685
|
task_status = global_config.runner_status
|
|
@@ -692,39 +703,69 @@ def runner_status():
|
|
|
692
703
|
|
|
693
704
|
|
|
694
705
|
|
|
695
|
-
@design.route("/api/abort_pending", methods=["POST"])
|
|
706
|
+
@design.route("/api/runner/abort_pending", methods=["POST"])
|
|
696
707
|
def api_abort_pending():
|
|
708
|
+
"""
|
|
709
|
+
.. :quickref: Workflow Design; abort pending action(s) during execution
|
|
710
|
+
|
|
711
|
+
.. http:get:: /api/runner/abort_pending
|
|
712
|
+
|
|
713
|
+
:status 200: {"status": "ok"}
|
|
714
|
+
"""
|
|
697
715
|
abort_pending()
|
|
698
716
|
return jsonify({"status": "ok"}), 200
|
|
699
717
|
|
|
700
|
-
@design.route("/api/abort_current", methods=["POST"])
|
|
718
|
+
@design.route("/api/runner/abort_current", methods=["POST"])
|
|
701
719
|
def api_abort_current():
|
|
720
|
+
"""
|
|
721
|
+
.. :quickref: Workflow Design; abort right after current action during execution
|
|
722
|
+
|
|
723
|
+
.. http:get:: /api/runner/abort_current
|
|
724
|
+
|
|
725
|
+
:status 200: {"status": "ok"}
|
|
726
|
+
"""
|
|
702
727
|
abort_current()
|
|
703
728
|
return jsonify({"status": "ok"}), 200
|
|
704
729
|
|
|
705
|
-
@design.route("/api/pause", methods=["POST"])
|
|
730
|
+
@design.route("/api/runner/pause", methods=["POST"])
|
|
706
731
|
def api_pause():
|
|
732
|
+
"""
|
|
733
|
+
.. :quickref: Workflow Design; pause during execution
|
|
734
|
+
|
|
735
|
+
.. http:get:: /api/runner/pause
|
|
736
|
+
|
|
737
|
+
:status 200: {"status": "ok"}
|
|
738
|
+
"""
|
|
707
739
|
msg = pause()
|
|
708
740
|
return jsonify({"status": "ok", "pause_status": msg}), 200
|
|
709
741
|
|
|
710
|
-
@design.route("/api/retry", methods=["POST"])
|
|
742
|
+
@design.route("/api/runner/retry", methods=["POST"])
|
|
711
743
|
def api_retry():
|
|
744
|
+
"""
|
|
745
|
+
.. :quickref: Workflow Design; retry when error occur during execution
|
|
746
|
+
|
|
747
|
+
.. http:get:: /api/runner/retry
|
|
748
|
+
|
|
749
|
+
:status 200: {"status": "ok"}
|
|
750
|
+
"""
|
|
712
751
|
retry()
|
|
713
752
|
return jsonify({"status": "ok, retrying failed step"}), 200
|
|
714
753
|
|
|
715
754
|
|
|
716
|
-
@design.route("/api/
|
|
717
|
-
def
|
|
718
|
-
|
|
719
|
-
script
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
755
|
+
@design.route("/api/design/submit", methods=["POST"])
|
|
756
|
+
def submit_script():
|
|
757
|
+
"""
|
|
758
|
+
.. :quickref: Workflow Design; submit script
|
|
759
|
+
|
|
760
|
+
.. http:get:: /api/design/submit
|
|
761
|
+
|
|
762
|
+
:status 200: {"status": "ok"}
|
|
763
|
+
"""
|
|
764
|
+
deck = global_config.deck
|
|
765
|
+
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
766
|
+
script = Script(author=session.get('user'), deck=deck_name)
|
|
767
|
+
script_collection = request.get_json()
|
|
768
|
+
script.python_script = script_collection
|
|
769
|
+
# todo check script format
|
|
770
|
+
utils.post_script_file(script)
|
|
771
|
+
return jsonify({"status": "ok"}), 200
|
ivoryos/utils/client_proxy.py
CHANGED
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
|
|
9
9
|
# Function to create class and methods dynamically
|
|
10
10
|
def create_function(url, class_name, functions):
|
|
11
|
-
class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/
|
|
11
|
+
class_template = f'class {class_name.capitalize()}:\n url = "{url}ivoryos/api/control/deck.{class_name}"\n'
|
|
12
12
|
|
|
13
13
|
for function_name, details in functions.items():
|
|
14
14
|
signature = details['signature']
|
ivoryos/utils/script_runner.py
CHANGED
|
@@ -69,7 +69,7 @@ class ScriptRunner:
|
|
|
69
69
|
|
|
70
70
|
if self.current_app is None:
|
|
71
71
|
self.current_app = current_app
|
|
72
|
-
time.sleep(1) # Optional: may help ensure deck readiness
|
|
72
|
+
# time.sleep(1) # Optional: may help ensure deck readiness
|
|
73
73
|
|
|
74
74
|
# Try to acquire lock without blocking
|
|
75
75
|
if not self.lock.acquire(blocking=False):
|
|
@@ -240,6 +240,7 @@ class ScriptRunner:
|
|
|
240
240
|
for i in config:
|
|
241
241
|
try:
|
|
242
242
|
i = utils.convert_config_type(i, arg_type)
|
|
243
|
+
compiled = True
|
|
243
244
|
except Exception as e:
|
|
244
245
|
logger.info(e)
|
|
245
246
|
compiled = False
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.3"
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
ivoryos/__init__.py,sha256=1v6LwuIao8WbmSskifzdfde7E_gH3PWCYaNpLaRtYZk,7341
|
|
2
2
|
ivoryos/config.py,sha256=3FPBYTIBhQTKDvsEoR8ZeTmg65D-CSFEdGmOuIL4pSI,1311
|
|
3
|
-
ivoryos/version.py,sha256=
|
|
3
|
+
ivoryos/version.py,sha256=2plzdEEb24FLjE2I2XyBBcJEPYWHccNL4SgtLC_6erg,22
|
|
4
4
|
ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
ivoryos/routes/auth/auth.py,sha256=
|
|
6
|
+
ivoryos/routes/auth/auth.py,sha256=rvqMf4oeYXIjcsNWPbHoxK4QJRbM4YSMCl3IDRMnYtM,3255
|
|
7
7
|
ivoryos/routes/auth/templates/auth/login.html,sha256=WSRrKbdM_oobqSXFRTo-j9UlOgp6sYzS9tm7TqqPULI,1207
|
|
8
8
|
ivoryos/routes/auth/templates/auth/signup.html,sha256=b5LTXtpfTSkSS7X8u1ldwQbbgEFTk6UNMAediA5BwBY,1465
|
|
9
9
|
ivoryos/routes/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
ivoryos/routes/control/control.py,sha256=
|
|
10
|
+
ivoryos/routes/control/control.py,sha256=AaXqXqaIIed9xBVQsk52CNnNDTSofje5qQAfDGWd1Q0,16404
|
|
11
11
|
ivoryos/routes/control/templates/control/controllers.html,sha256=iIp0h6WA68gQj9OsoiB7dU1BqH8CGomTueR73F4C8eY,4274
|
|
12
|
-
ivoryos/routes/control/templates/control/controllers_home.html,sha256=
|
|
12
|
+
ivoryos/routes/control/templates/control/controllers_home.html,sha256=qAM4iZBEuXvSgGUWWVVIe2E9MPJOeG7U214hYM84jIE,2976
|
|
13
13
|
ivoryos/routes/control/templates/control/controllers_new.html,sha256=uOQo9kYmwX2jk3KZDkMUF_ylfNUIs_oIWb_kk_MMVDM,4921
|
|
14
14
|
ivoryos/routes/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
ivoryos/routes/database/database.py,sha256=
|
|
16
|
-
ivoryos/routes/database/templates/database/
|
|
17
|
-
ivoryos/routes/database/templates/database/experiment_step_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
|
|
15
|
+
ivoryos/routes/database/database.py,sha256=kP5dEb5TvX3GlePk2sxlpwer8AsTM36kdeCkzAiFkR8,9837
|
|
16
|
+
ivoryos/routes/database/templates/database/scripts_database.html,sha256=tEpKOj1UGz7mcDq11guwP48XJxwawjxjPvxRhKiIG2I,3640
|
|
18
17
|
ivoryos/routes/database/templates/database/step_card.html,sha256=F4JRfacrEQfk2rrEbcI_i7G84nzKKDmCrMSmStLb4W4,290
|
|
19
|
-
ivoryos/routes/database/templates/database/
|
|
18
|
+
ivoryos/routes/database/templates/database/workflow_database.html,sha256=fsJHrYeEHGBKRn1pIxWITE6e93tdZsXH3zRs9Ob5FX0,4467
|
|
19
|
+
ivoryos/routes/database/templates/database/workflow_view.html,sha256=u8_XYhiZ98PzglMzFEkuM1Tk9hVWf79xXIrpHVDxKa0,3618
|
|
20
20
|
ivoryos/routes/design/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
ivoryos/routes/design/design.py,sha256=
|
|
21
|
+
ivoryos/routes/design/design.py,sha256=Ju9Hz0khhAk6HmnZd3Er3PglFkvZBNPAJ-fPJMfrrIM,29328
|
|
22
22
|
ivoryos/routes/design/templates/design/experiment_builder.html,sha256=rEdcHj5onJG_4MejdFBPnJVzsvCMp1KDteqNkpx24kQ,29430
|
|
23
23
|
ivoryos/routes/design/templates/design/experiment_run.html,sha256=7VP0Vo98phcYnFennd5vqaMK1M1QBwDmM-b9aZb8jOw,26282
|
|
24
24
|
ivoryos/routes/main/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -37,16 +37,16 @@ ivoryos/static/js/sortable_design.js,sha256=wwpKfIzZGDxfX3moNz0cvPvm9YyHmopZK3wm
|
|
|
37
37
|
ivoryos/templates/base.html,sha256=sDdwqOIUP2Get-py4E59PkieoGWLFpX6wAJe93s4aRo,8518
|
|
38
38
|
ivoryos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
39
|
ivoryos/utils/bo_campaign.py,sha256=CVs7q15Pm2SRuJNaCvZKIxOFuv1xibM2yymtpAMAWOk,3285
|
|
40
|
-
ivoryos/utils/client_proxy.py,sha256=
|
|
40
|
+
ivoryos/utils/client_proxy.py,sha256=0OT2xTMkqh_2ybgCxMV_71ZVUThWwrsnAhTIBY5vDR8,2095
|
|
41
41
|
ivoryos/utils/db_models.py,sha256=zlmmD2600CYyn79gQq8k0Vra7BDBKJBAyNLYclIWdvs,27382
|
|
42
42
|
ivoryos/utils/form.py,sha256=b3JKxRc1jN45-bXyfzSJT1lcssUuxT86FhRmNUDv5-U,20973
|
|
43
43
|
ivoryos/utils/global_config.py,sha256=OqfDrPgOzRdIUMD4V3pA9t6b-BATMjGZl8Jn7nkI56k,2138
|
|
44
44
|
ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,6422
|
|
45
|
-
ivoryos/utils/script_runner.py,sha256=
|
|
45
|
+
ivoryos/utils/script_runner.py,sha256=0b5hLKAF2o0SQKiArhUsG8-4MA-eniAcjwi8gCNVwtY,14542
|
|
46
46
|
ivoryos/utils/task_runner.py,sha256=u4nF0wOADu_HVlGYVTOXnUm1woWGgYAccr-ZCzgtb6Q,2899
|
|
47
47
|
ivoryos/utils/utils.py,sha256=OBwrRu02yh7pqG_lyl10zWr_RYes3xhMporxIz8lGYI,13579
|
|
48
|
-
ivoryos-1.0.
|
|
49
|
-
ivoryos-1.0.
|
|
50
|
-
ivoryos-1.0.
|
|
51
|
-
ivoryos-1.0.
|
|
52
|
-
ivoryos-1.0.
|
|
48
|
+
ivoryos-1.0.3.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
|
|
49
|
+
ivoryos-1.0.3.dist-info/METADATA,sha256=XE0dH-qpGIUYCXwkzDULsuTLOJPlGj0AnUwhtjeiQPY,6992
|
|
50
|
+
ivoryos-1.0.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
51
|
+
ivoryos-1.0.3.dist-info/top_level.txt,sha256=FRIWWdiEvRKqw-XfF_UK3XV0CrnNb6EmVbEgjaVazRM,8
|
|
52
|
+
ivoryos-1.0.3.dist-info/RECORD,,
|
/ivoryos/routes/database/templates/database/{experiment_step_view.html → workflow_view.html}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|