ivoryos 1.0.0__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 +89 -60
- ivoryos/routes/control/templates/control/controllers_home.html +7 -7
- ivoryos/routes/database/database.py +98 -39
- 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 +215 -78
- ivoryos/routes/design/templates/design/experiment_run.html +62 -123
- ivoryos/static/js/socket_handler.js +13 -8
- ivoryos/utils/bo_campaign.py +87 -0
- ivoryos/utils/client_proxy.py +1 -1
- ivoryos/utils/db_models.py +27 -7
- ivoryos/utils/global_config.py +16 -7
- ivoryos/utils/script_runner.py +56 -40
- ivoryos/utils/task_runner.py +81 -0
- ivoryos/utils/utils.py +0 -68
- ivoryos/version.py +1 -1
- {ivoryos-1.0.0.dist-info → ivoryos-1.0.3.dist-info}/METADATA +3 -3
- {ivoryos-1.0.0.dist-info → ivoryos-1.0.3.dist-info}/RECORD +23 -21
- /ivoryos/routes/database/templates/database/{experiment_step_view.html → workflow_view.html} +0 -0
- {ivoryos-1.0.0.dist-info → ivoryos-1.0.3.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.0.dist-info → ivoryos-1.0.3.dist-info}/WHEEL +0 -0
- {ivoryos-1.0.0.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,18 +1,22 @@
|
|
|
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
|
|
11
|
+
from ivoryos.utils.task_runner import TaskRunner
|
|
9
12
|
|
|
10
13
|
global_config = GlobalConfig()
|
|
14
|
+
runner = TaskRunner()
|
|
11
15
|
|
|
12
16
|
control = Blueprint('control', __name__, template_folder='templates/control')
|
|
13
17
|
|
|
14
18
|
|
|
15
|
-
@control.route("/
|
|
19
|
+
@control.route("/control/home/deck", strict_slashes=False)
|
|
16
20
|
@login_required
|
|
17
21
|
def deck_controllers():
|
|
18
22
|
"""
|
|
@@ -20,15 +24,15 @@ def deck_controllers():
|
|
|
20
24
|
|
|
21
25
|
deck control home interface for listing all deck instruments
|
|
22
26
|
|
|
23
|
-
.. http:get:: /
|
|
27
|
+
.. http:get:: /control/home/deck
|
|
24
28
|
"""
|
|
25
29
|
deck_variables = global_config.deck_snapshot.keys()
|
|
26
30
|
deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
|
|
27
31
|
return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
|
|
28
32
|
|
|
29
33
|
|
|
30
|
-
@control.route("/
|
|
31
|
-
@control.route("/
|
|
34
|
+
@control.route("/control/new/", strict_slashes=False)
|
|
35
|
+
@control.route("/control/new/<instrument>", methods=['GET', 'POST'])
|
|
32
36
|
@login_required
|
|
33
37
|
def new_controller(instrument=None):
|
|
34
38
|
"""
|
|
@@ -36,12 +40,12 @@ def new_controller(instrument=None):
|
|
|
36
40
|
|
|
37
41
|
interface for connecting a new <instrument>
|
|
38
42
|
|
|
39
|
-
.. http:get:: /
|
|
43
|
+
.. http:get:: /control/new/
|
|
40
44
|
|
|
41
45
|
:param instrument: instrument name
|
|
42
46
|
:type instrument: str
|
|
43
47
|
|
|
44
|
-
.. http:post:: /
|
|
48
|
+
.. http:post:: /control/new/
|
|
45
49
|
|
|
46
50
|
:form device_name: module instance name (e.g. my_instance = MyClass())
|
|
47
51
|
:form kwargs: dynamic module initialization kwargs fields
|
|
@@ -86,7 +90,7 @@ def new_controller(instrument=None):
|
|
|
86
90
|
device=device, args=args, defined_variables=global_config.defined_variables)
|
|
87
91
|
|
|
88
92
|
|
|
89
|
-
@control.route("/
|
|
93
|
+
@control.route("/control/home/temp", strict_slashes=False)
|
|
90
94
|
@login_required
|
|
91
95
|
def controllers_home():
|
|
92
96
|
"""
|
|
@@ -94,14 +98,15 @@ def controllers_home():
|
|
|
94
98
|
|
|
95
99
|
temporarily connected devices home interface for listing all instruments
|
|
96
100
|
|
|
97
|
-
.. http:get:: /
|
|
101
|
+
.. http:get:: /control/home/temp
|
|
98
102
|
|
|
99
103
|
"""
|
|
100
104
|
# defined_variables = parse_deck(deck)
|
|
101
|
-
|
|
105
|
+
defined_variables = global_config.defined_variables.keys()
|
|
106
|
+
return render_template('controllers_home.html', defined_variables=defined_variables)
|
|
102
107
|
|
|
103
108
|
|
|
104
|
-
@control.route("/
|
|
109
|
+
@control.route("/control/<instrument>/methods", methods=['GET', 'POST'])
|
|
105
110
|
@login_required
|
|
106
111
|
def controllers(instrument: str):
|
|
107
112
|
"""
|
|
@@ -109,12 +114,12 @@ def controllers(instrument: str):
|
|
|
109
114
|
|
|
110
115
|
control interface for selected <instrument>
|
|
111
116
|
|
|
112
|
-
.. http:get:: /
|
|
117
|
+
.. http:get:: /control/<instrument>/methods
|
|
113
118
|
|
|
114
119
|
:param instrument: instrument name
|
|
115
120
|
:type instrument: str
|
|
116
121
|
|
|
117
|
-
.. http:post:: /
|
|
122
|
+
.. http:post:: /control/<instrument>/methods
|
|
118
123
|
|
|
119
124
|
:form hidden_name: function name (hidden field)
|
|
120
125
|
:form kwargs: dynamic kwargs field
|
|
@@ -142,7 +147,9 @@ def controllers(instrument: str):
|
|
|
142
147
|
if form and form.validate_on_submit():
|
|
143
148
|
try:
|
|
144
149
|
kwargs.pop("hidden_name")
|
|
145
|
-
output =
|
|
150
|
+
output = runner.run_single_step(instrument, method_name, kwargs, wait=True,
|
|
151
|
+
current_app=current_app._get_current_object())
|
|
152
|
+
# output = function_executable(**kwargs)
|
|
146
153
|
flash(f"\nRun Success! Output value: {output}.")
|
|
147
154
|
except Exception as e:
|
|
148
155
|
flash(e.__str__())
|
|
@@ -150,81 +157,103 @@ def controllers(instrument: str):
|
|
|
150
157
|
flash(form.errors)
|
|
151
158
|
return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
|
|
152
159
|
|
|
160
|
+
@control.route("/control/download", strict_slashes=False)
|
|
161
|
+
@login_required
|
|
162
|
+
def download_proxy():
|
|
163
|
+
"""
|
|
164
|
+
.. :quickref: Direct Control; download proxy interface
|
|
153
165
|
|
|
154
|
-
|
|
166
|
+
download proxy interface
|
|
167
|
+
|
|
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'])
|
|
155
187
|
def backend_control(instrument: str=None):
|
|
156
188
|
"""
|
|
157
189
|
.. :quickref: Backend Control; backend control
|
|
158
190
|
|
|
159
191
|
backend control through http requests
|
|
160
192
|
|
|
161
|
-
.. http:get:: /
|
|
193
|
+
.. http:get:: /api/control/
|
|
162
194
|
|
|
163
195
|
:param instrument: instrument name
|
|
164
196
|
:type instrument: str
|
|
165
197
|
|
|
166
|
-
.. http:post:: /
|
|
198
|
+
.. http:post:: /api/control/
|
|
167
199
|
|
|
168
200
|
"""
|
|
169
|
-
|
|
170
|
-
|
|
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)
|
|
171
204
|
|
|
172
205
|
if request.method == 'POST':
|
|
173
|
-
|
|
174
|
-
method_name = all_kwargs.pop("hidden_name", None)
|
|
175
|
-
# if method_name is not None:
|
|
206
|
+
method_name = request.form.get("hidden_name", None)
|
|
176
207
|
form = forms.get(method_name, None)
|
|
177
|
-
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
178
|
-
function_executable = getattr(inst_object, method_name)
|
|
179
208
|
if form:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
except Exception as e:
|
|
186
|
-
json_output = jsonify(e.__str__())
|
|
187
|
-
return json_output, 400
|
|
188
|
-
else:
|
|
189
|
-
return "instrument not exist", 400
|
|
190
|
-
return json_output, 200
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
@control.route("/backend_control", methods=['GET'])
|
|
194
|
-
def backend_client():
|
|
195
|
-
"""
|
|
196
|
-
.. :quickref: Backend Control; get snapshot
|
|
197
|
-
|
|
198
|
-
backend control through http requests
|
|
209
|
+
kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
|
|
210
|
+
wait = request.form.get("hidden_wait", "true") == "true"
|
|
211
|
+
output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
|
|
212
|
+
current_app=current_app._get_current_object())
|
|
213
|
+
return jsonify(output), 200
|
|
199
214
|
|
|
200
|
-
.. http:get:: /backend_control
|
|
201
|
-
"""
|
|
202
|
-
# Create a snapshot of the current deck configuration
|
|
203
215
|
snapshot = global_config.deck_snapshot.copy()
|
|
204
|
-
|
|
205
216
|
# Iterate through each instrument in the snapshot
|
|
206
217
|
for instrument_key, instrument_data in snapshot.items():
|
|
207
218
|
# Iterate through each function associated with the current instrument
|
|
208
219
|
for function_key, function_data in instrument_data.items():
|
|
209
220
|
# Convert the function signature to a string representation
|
|
210
221
|
function_data['signature'] = str(function_data['signature'])
|
|
222
|
+
return jsonify(snapshot), 200
|
|
211
223
|
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
214
243
|
|
|
215
244
|
|
|
216
|
-
@control.route("/
|
|
245
|
+
@control.route("/control/import/module", methods=['POST'])
|
|
217
246
|
def import_api():
|
|
218
247
|
"""
|
|
219
248
|
.. :quickref: Advanced Features; Manually import API module(s)
|
|
220
249
|
|
|
221
250
|
importing other Python modules
|
|
222
251
|
|
|
223
|
-
.. http:post:: /
|
|
252
|
+
.. http:post:: /control/import/module
|
|
224
253
|
|
|
225
254
|
:form filepath: API (Python class) module filepath
|
|
226
255
|
|
|
227
|
-
import the module and redirect to :http:get:`/ivoryos/
|
|
256
|
+
import the module and redirect to :http:get:`/ivoryos/control/new/`
|
|
228
257
|
|
|
229
258
|
"""
|
|
230
259
|
filepath = request.form.get('filepath')
|
|
@@ -272,12 +301,12 @@ def import_api():
|
|
|
272
301
|
# return redirect(url_for('control.deck_controllers'))
|
|
273
302
|
|
|
274
303
|
|
|
275
|
-
@control.route("/
|
|
304
|
+
@control.route("/control/import/deck", methods=['POST'])
|
|
276
305
|
def import_deck():
|
|
277
306
|
"""
|
|
278
307
|
.. :quickref: Advanced Features; Manually import a deck
|
|
279
308
|
|
|
280
|
-
.. http:post:: /import_deck
|
|
309
|
+
.. http:post:: /control/import_deck
|
|
281
310
|
|
|
282
311
|
:form filepath: deck module filepath
|
|
283
312
|
|
|
@@ -310,12 +339,12 @@ def import_deck():
|
|
|
310
339
|
return redirect(back)
|
|
311
340
|
|
|
312
341
|
|
|
313
|
-
@control.route('/save-order
|
|
342
|
+
@control.route('/control/<instrument>/save-order', methods=['POST'])
|
|
314
343
|
def save_order(instrument: str):
|
|
315
344
|
"""
|
|
316
345
|
.. :quickref: Control Customization; Save functions' order
|
|
317
346
|
|
|
318
|
-
.. http:post:: /save-order
|
|
347
|
+
.. http:post:: /control/save-order
|
|
319
348
|
|
|
320
349
|
save function drag and drop order for the given <instrument>
|
|
321
350
|
|
|
@@ -326,12 +355,12 @@ def save_order(instrument: str):
|
|
|
326
355
|
return '', 204
|
|
327
356
|
|
|
328
357
|
|
|
329
|
-
@control.route('/
|
|
358
|
+
@control.route('/control/<instrument>/<function>/hide')
|
|
330
359
|
def hide_function(instrument, function):
|
|
331
360
|
"""
|
|
332
361
|
.. :quickref: Control Customization; Hide function
|
|
333
362
|
|
|
334
|
-
.. http:get::
|
|
363
|
+
.. http:get:: //control/<instrument>/<function>/hide
|
|
335
364
|
|
|
336
365
|
Hide the given <instrument> and <function>
|
|
337
366
|
|
|
@@ -347,12 +376,12 @@ def hide_function(instrument, function):
|
|
|
347
376
|
return redirect(back)
|
|
348
377
|
|
|
349
378
|
|
|
350
|
-
@control.route('/
|
|
379
|
+
@control.route('/control/<instrument>/<function>/unhide')
|
|
351
380
|
def remove_hidden(instrument: str, function: str):
|
|
352
381
|
"""
|
|
353
382
|
.. :quickref: Control Customization; Remove a hidden function
|
|
354
383
|
|
|
355
|
-
.. http:get:: /
|
|
384
|
+
.. http:get:: /control/<instrument>/<function>/unhide
|
|
356
385
|
|
|
357
386
|
Un-hide the given <instrument> and <function>
|
|
358
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,51 +8,56 @@ 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", "")
|
|
29
29
|
off_line = current_app.config["OFF_LINE"]
|
|
30
30
|
if off_line and pseudo_name and not script.deck == pseudo_name:
|
|
31
31
|
flash(f"Choose the deck with name {script.deck}")
|
|
32
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
33
|
+
return jsonify({
|
|
34
|
+
"script": script.as_dict(),
|
|
35
|
+
"python_script": script.compile(),
|
|
36
|
+
})
|
|
32
37
|
return redirect(url_for('design.experiment_builder'))
|
|
33
38
|
|
|
34
39
|
|
|
35
|
-
@database.route("/
|
|
40
|
+
@database.route("/database/scripts/delete/<script_name>")
|
|
36
41
|
@login_required
|
|
37
|
-
def delete_workflow(
|
|
42
|
+
def delete_workflow(script_name: str):
|
|
38
43
|
"""
|
|
39
44
|
.. :quickref: Database; delete workflow
|
|
40
45
|
|
|
41
46
|
delete workflow from database
|
|
42
47
|
|
|
43
|
-
.. http:get:: /
|
|
48
|
+
.. http:get:: /database/scripts/delete/<script_name>
|
|
44
49
|
|
|
45
|
-
:param
|
|
46
|
-
:type
|
|
47
|
-
: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/`
|
|
48
53
|
|
|
49
54
|
"""
|
|
50
|
-
Script.query.filter(Script.name ==
|
|
55
|
+
Script.query.filter(Script.name == script_name).delete()
|
|
51
56
|
db.session.commit()
|
|
52
57
|
return redirect(url_for('database.load_from_database'))
|
|
53
58
|
|
|
54
59
|
|
|
55
|
-
@database.route("/
|
|
60
|
+
@database.route("/database/scripts/save")
|
|
56
61
|
@login_required
|
|
57
62
|
def publish():
|
|
58
63
|
"""
|
|
@@ -60,7 +65,7 @@ def publish():
|
|
|
60
65
|
|
|
61
66
|
save workflow to database
|
|
62
67
|
|
|
63
|
-
.. http:get:: /
|
|
68
|
+
.. http:get:: /database/scripts/save
|
|
64
69
|
|
|
65
70
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
66
71
|
"""
|
|
@@ -79,7 +84,7 @@ def publish():
|
|
|
79
84
|
return redirect(url_for('design.experiment_builder'))
|
|
80
85
|
|
|
81
86
|
|
|
82
|
-
@database.route("/finalize")
|
|
87
|
+
@database.route("/database/scripts/finalize")
|
|
83
88
|
@login_required
|
|
84
89
|
def finalize():
|
|
85
90
|
"""
|
|
@@ -101,8 +106,8 @@ def finalize():
|
|
|
101
106
|
return redirect(url_for('design.experiment_builder'))
|
|
102
107
|
|
|
103
108
|
|
|
104
|
-
@database.route("/database/")
|
|
105
|
-
@database.route("/database/<deck_name>")
|
|
109
|
+
@database.route("/database/scripts/", strict_slashes=False)
|
|
110
|
+
@database.route("/database/scripts/<deck_name>")
|
|
106
111
|
@login_required
|
|
107
112
|
def load_from_database(deck_name=None):
|
|
108
113
|
"""
|
|
@@ -110,7 +115,7 @@ def load_from_database(deck_name=None):
|
|
|
110
115
|
|
|
111
116
|
backend control through http requests
|
|
112
117
|
|
|
113
|
-
.. http:get:: /database/<deck_name>
|
|
118
|
+
.. http:get:: /database/scripts/<deck_name>
|
|
114
119
|
|
|
115
120
|
:param deck_name: filter for deck name
|
|
116
121
|
:type deck_name: str
|
|
@@ -130,11 +135,19 @@ def load_from_database(deck_name=None):
|
|
|
130
135
|
page = request.args.get('page', default=1, type=int)
|
|
131
136
|
per_page = 10
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
scripts = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
139
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
140
|
+
scripts = query.all()
|
|
141
|
+
script_names = [script.name for script in scripts]
|
|
142
|
+
return jsonify({
|
|
143
|
+
"workflows": script_names,
|
|
144
|
+
})
|
|
145
|
+
else:
|
|
146
|
+
# return HTML
|
|
147
|
+
return render_template("scripts_database.html", scripts=scripts, deck_list=deck_list, deck_name=deck_name)
|
|
135
148
|
|
|
136
149
|
|
|
137
|
-
@database.route("/
|
|
150
|
+
@database.route("/database/scripts/rename", methods=['POST'])
|
|
138
151
|
@login_required
|
|
139
152
|
def edit_run_name():
|
|
140
153
|
"""
|
|
@@ -142,7 +155,7 @@ def edit_run_name():
|
|
|
142
155
|
|
|
143
156
|
edit the name of the current workflow, won't save to the database
|
|
144
157
|
|
|
145
|
-
.. http:post:: /
|
|
158
|
+
.. http:post:: database/scripts/rename
|
|
146
159
|
|
|
147
160
|
: form run_name: new workflow name
|
|
148
161
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
@@ -160,15 +173,15 @@ def edit_run_name():
|
|
|
160
173
|
return redirect(url_for("design.experiment_builder"))
|
|
161
174
|
|
|
162
175
|
|
|
163
|
-
@database.route("/save_as", methods=['POST'])
|
|
176
|
+
@database.route("/database/scripts/save_as", methods=['POST'])
|
|
164
177
|
@login_required
|
|
165
178
|
def save_as():
|
|
166
179
|
"""
|
|
167
180
|
.. :quickref: Database; save the run name as
|
|
168
181
|
|
|
169
|
-
save the workflow
|
|
182
|
+
save the current workflow script as
|
|
170
183
|
|
|
171
|
-
.. http:post:: /save_as
|
|
184
|
+
.. http:post:: /database/scripts/save_as
|
|
172
185
|
|
|
173
186
|
: form run_name: new workflow name
|
|
174
187
|
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
@@ -190,9 +203,20 @@ def save_as():
|
|
|
190
203
|
return redirect(url_for("design.experiment_builder"))
|
|
191
204
|
|
|
192
205
|
|
|
193
|
-
|
|
206
|
+
# -----------------------------------------------------------
|
|
207
|
+
# ------------------ Workflow logs -----------------------
|
|
208
|
+
# -----------------------------------------------------------
|
|
209
|
+
@database.route('/database/workflows/')
|
|
194
210
|
def list_workflows():
|
|
195
|
-
|
|
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())
|
|
196
220
|
search_term = request.args.get("keyword", None)
|
|
197
221
|
if search_term:
|
|
198
222
|
query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
|
|
@@ -200,45 +224,80 @@ def list_workflows():
|
|
|
200
224
|
per_page = 10
|
|
201
225
|
|
|
202
226
|
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
203
|
-
|
|
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
|
|
204
241
|
|
|
242
|
+
list all workflow logs
|
|
205
243
|
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
.. http:get:: /database/workflows/<int:workflow_id>
|
|
245
|
+
|
|
246
|
+
"""
|
|
208
247
|
workflow = WorkflowRun.query.get_or_404(workflow_id)
|
|
209
248
|
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
210
249
|
|
|
211
|
-
#
|
|
250
|
+
# Use full objects for template rendering
|
|
212
251
|
grouped = {
|
|
213
252
|
"prep": [],
|
|
214
253
|
"script": {},
|
|
215
254
|
"cleanup": [],
|
|
216
255
|
}
|
|
217
256
|
|
|
257
|
+
# Use dicts for JSON response
|
|
258
|
+
grouped_json = {
|
|
259
|
+
"prep": [],
|
|
260
|
+
"script": {},
|
|
261
|
+
"cleanup": [],
|
|
262
|
+
}
|
|
263
|
+
|
|
218
264
|
for step in steps:
|
|
265
|
+
step_dict = step.as_dict()
|
|
266
|
+
|
|
219
267
|
if step.phase == "prep":
|
|
220
268
|
grouped["prep"].append(step)
|
|
269
|
+
grouped_json["prep"].append(step_dict)
|
|
270
|
+
|
|
221
271
|
elif step.phase == "script":
|
|
222
272
|
grouped["script"].setdefault(step.repeat_index, []).append(step)
|
|
273
|
+
grouped_json["script"].setdefault(step.repeat_index, []).append(step_dict)
|
|
274
|
+
|
|
223
275
|
elif step.phase == "cleanup" or step.method_name == "stop":
|
|
224
276
|
grouped["cleanup"].append(step)
|
|
277
|
+
grouped_json["cleanup"].append(step_dict)
|
|
225
278
|
|
|
226
|
-
|
|
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)
|
|
227
286
|
|
|
228
287
|
|
|
229
|
-
@database.route("/
|
|
288
|
+
@database.route("/database/workflows/delete/<int:workflow_id>")
|
|
230
289
|
@login_required
|
|
231
|
-
def delete_workflow_data(workflow_id:
|
|
290
|
+
def delete_workflow_data(workflow_id: int):
|
|
232
291
|
"""
|
|
233
292
|
.. :quickref: Database; delete experiment data from database
|
|
234
293
|
|
|
235
294
|
delete workflow data from database
|
|
236
295
|
|
|
237
|
-
.. http:get:: /
|
|
296
|
+
.. http:get:: /database/workflows/delete/<int:workflow_id>
|
|
238
297
|
|
|
239
298
|
:param workflow_id: workflow id
|
|
240
|
-
:type workflow_id:
|
|
241
|
-
:status 302: redirect to :http:get:`/ivoryos/
|
|
299
|
+
:type workflow_id: int
|
|
300
|
+
:status 302: redirect to :http:get:`/ivoryos/database/workflows/`
|
|
242
301
|
|
|
243
302
|
"""
|
|
244
303
|
run = WorkflowRun.query.get(workflow_id)
|