ivoryos 1.0.0__tar.gz → 1.0.1__tar.gz
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-1.0.0/ivoryos.egg-info → ivoryos-1.0.1}/PKG-INFO +3 -3
- {ivoryos-1.0.0 → ivoryos-1.0.1}/README.md +2 -2
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/control/control.py +11 -17
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/database.py +42 -3
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/design/design.py +123 -27
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/design/templates/design/experiment_run.html +62 -123
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/js/socket_handler.js +13 -8
- ivoryos-1.0.1/ivoryos/utils/bo_campaign.py +87 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/db_models.py +27 -7
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/global_config.py +16 -7
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/script_runner.py +55 -40
- ivoryos-1.0.1/ivoryos/utils/task_runner.py +81 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/utils.py +0 -68
- ivoryos-1.0.1/ivoryos/version.py +1 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1/ivoryos.egg-info}/PKG-INFO +3 -3
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos.egg-info/SOURCES.txt +2 -0
- ivoryos-1.0.0/ivoryos/version.py +0 -1
- {ivoryos-1.0.0 → ivoryos-1.0.1}/LICENSE +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/MANIFEST.in +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/config.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/auth/templates/auth/login.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/auth/templates/auth/signup.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers_home.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/control/templates/control/controllers_new.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/experiment_database.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/experiment_step_view.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/step_card.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/database/templates/database/workflow_run_database.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/design/templates/design/experiment_builder.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/main/templates/main/help.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/routes/main/templates/main/home.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/js/sortable_design.js +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/static/style.css +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/client_proxy.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/form.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/setup.cfg +0 -0
- {ivoryos-1.0.0 → ivoryos-1.0.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
6
|
Author: Ivory Zhang
|
|
@@ -14,8 +14,8 @@ License-File: LICENSE
|
|
|
14
14
|
[](https://pypi.org/project/ivoryos/)
|
|
15
15
|

|
|
16
16
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
17
|
-
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
18
|
+
[](https://discord.gg/AX5P9EdGVX)
|
|
19
19
|
|
|
20
20
|

|
|
21
21
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
[](https://pypi.org/project/ivoryos/)
|
|
3
3
|

|
|
4
4
|
[](https://youtu.be/dFfJv9I2-1g)
|
|
5
|
-
[](https://www.nature.com/articles/s41467-025-60514-w)
|
|
6
|
+
[](https://discord.gg/AX5P9EdGVX)
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
# ivoryOS: interoperable Web UI for self-driving laboratories (SDLs)
|
|
@@ -6,8 +6,10 @@ from flask_login import login_required
|
|
|
6
6
|
from ivoryos.utils.global_config import GlobalConfig
|
|
7
7
|
from ivoryos.utils import utils
|
|
8
8
|
from ivoryos.utils.form import create_form_from_module, format_name
|
|
9
|
+
from ivoryos.utils.task_runner import TaskRunner
|
|
9
10
|
|
|
10
11
|
global_config = GlobalConfig()
|
|
12
|
+
runner = TaskRunner()
|
|
11
13
|
|
|
12
14
|
control = Blueprint('control', __name__, template_folder='templates/control')
|
|
13
15
|
|
|
@@ -142,7 +144,9 @@ def controllers(instrument: str):
|
|
|
142
144
|
if form and form.validate_on_submit():
|
|
143
145
|
try:
|
|
144
146
|
kwargs.pop("hidden_name")
|
|
145
|
-
output =
|
|
147
|
+
output = runner.run_single_step(instrument, method_name, kwargs, wait=True,
|
|
148
|
+
current_app=current_app._get_current_object())
|
|
149
|
+
# output = function_executable(**kwargs)
|
|
146
150
|
flash(f"\nRun Success! Output value: {output}.")
|
|
147
151
|
except Exception as e:
|
|
148
152
|
flash(e.__str__())
|
|
@@ -170,24 +174,14 @@ def backend_control(instrument: str=None):
|
|
|
170
174
|
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
171
175
|
|
|
172
176
|
if request.method == 'POST':
|
|
173
|
-
|
|
174
|
-
method_name = all_kwargs.pop("hidden_name", None)
|
|
175
|
-
# if method_name is not None:
|
|
177
|
+
method_name = request.form.get("hidden_name", None)
|
|
176
178
|
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
179
|
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
|
|
180
|
+
kwargs = {field.name: field.data for field in form if field.name not in ['csrf_token', 'hidden_name']}
|
|
181
|
+
wait = request.form.get("hidden_wait", "true") == "true"
|
|
182
|
+
output = runner.run_single_step(component=instrument, method=method_name, kwargs=kwargs, wait=wait,
|
|
183
|
+
current_app=current_app._get_current_object())
|
|
184
|
+
return jsonify(output), 200
|
|
191
185
|
|
|
192
186
|
|
|
193
187
|
@control.route("/backend_control", methods=['GET'])
|
|
@@ -29,6 +29,11 @@ def edit_workflow(workflow_name):
|
|
|
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
|
|
|
@@ -101,7 +106,7 @@ def finalize():
|
|
|
101
106
|
return redirect(url_for('design.experiment_builder'))
|
|
102
107
|
|
|
103
108
|
|
|
104
|
-
@database.route("/database/")
|
|
109
|
+
@database.route("/database/", strict_slashes=False)
|
|
105
110
|
@database.route("/database/<deck_name>")
|
|
106
111
|
@login_required
|
|
107
112
|
def load_from_database(deck_name=None):
|
|
@@ -131,7 +136,15 @@ def load_from_database(deck_name=None):
|
|
|
131
136
|
per_page = 10
|
|
132
137
|
|
|
133
138
|
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
134
|
-
|
|
139
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
140
|
+
workflows = query.all()
|
|
141
|
+
workflow_names = [w.name for w in workflows]
|
|
142
|
+
return jsonify({
|
|
143
|
+
"workflows": workflow_names,
|
|
144
|
+
})
|
|
145
|
+
else:
|
|
146
|
+
# return HTML
|
|
147
|
+
return render_template("experiment_database.html", workflows=workflows, deck_list=deck_list, deck_name=deck_name)
|
|
135
148
|
|
|
136
149
|
|
|
137
150
|
@database.route("/edit_run_name", methods=['POST'])
|
|
@@ -200,6 +213,12 @@ def list_workflows():
|
|
|
200
213
|
per_page = 10
|
|
201
214
|
|
|
202
215
|
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
216
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
217
|
+
workflows = query.all()
|
|
218
|
+
workflow_data = {w.id:{"workflow_name":w.name, "start_time":w.start_time} for w in workflows}
|
|
219
|
+
return jsonify({
|
|
220
|
+
"workflow_data": workflow_data,
|
|
221
|
+
})
|
|
203
222
|
return render_template('workflow_run_database.html', workflows=workflows)
|
|
204
223
|
|
|
205
224
|
|
|
@@ -208,20 +227,40 @@ def get_workflow_steps(workflow_id):
|
|
|
208
227
|
workflow = WorkflowRun.query.get_or_404(workflow_id)
|
|
209
228
|
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
210
229
|
|
|
211
|
-
#
|
|
230
|
+
# Use full objects for template rendering
|
|
212
231
|
grouped = {
|
|
213
232
|
"prep": [],
|
|
214
233
|
"script": {},
|
|
215
234
|
"cleanup": [],
|
|
216
235
|
}
|
|
217
236
|
|
|
237
|
+
# Use dicts for JSON response
|
|
238
|
+
grouped_json = {
|
|
239
|
+
"prep": [],
|
|
240
|
+
"script": {},
|
|
241
|
+
"cleanup": [],
|
|
242
|
+
}
|
|
243
|
+
|
|
218
244
|
for step in steps:
|
|
245
|
+
step_dict = step.as_dict()
|
|
246
|
+
|
|
219
247
|
if step.phase == "prep":
|
|
220
248
|
grouped["prep"].append(step)
|
|
249
|
+
grouped_json["prep"].append(step_dict)
|
|
250
|
+
|
|
221
251
|
elif step.phase == "script":
|
|
222
252
|
grouped["script"].setdefault(step.repeat_index, []).append(step)
|
|
253
|
+
grouped_json["script"].setdefault(step.repeat_index, []).append(step_dict)
|
|
254
|
+
|
|
223
255
|
elif step.phase == "cleanup" or step.method_name == "stop":
|
|
224
256
|
grouped["cleanup"].append(step)
|
|
257
|
+
grouped_json["cleanup"].append(step_dict)
|
|
258
|
+
|
|
259
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
260
|
+
return jsonify({
|
|
261
|
+
"workflow_info": workflow.as_dict(),
|
|
262
|
+
"steps": grouped_json,
|
|
263
|
+
})
|
|
225
264
|
|
|
226
265
|
return render_template("experiment_step_view.html", workflow=workflow, grouped=grouped)
|
|
227
266
|
|
|
@@ -15,7 +15,7 @@ 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
|
|
18
|
-
from ivoryos.utils.db_models import Script
|
|
18
|
+
from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
|
|
19
19
|
from ivoryos.utils.script_runner import ScriptRunner
|
|
20
20
|
# from ivoryos.utils.utils import load_workflows
|
|
21
21
|
|
|
@@ -25,32 +25,45 @@ design = Blueprint('design', __name__, template_folder='templates/design')
|
|
|
25
25
|
global_config = GlobalConfig()
|
|
26
26
|
runner = ScriptRunner()
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
@socketio.on('abort_pending')
|
|
30
|
-
def handle_abort_pending():
|
|
28
|
+
def abort_pending():
|
|
31
29
|
runner.abort_pending()
|
|
32
30
|
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
@socketio.on('abort_current')
|
|
36
|
-
def handle_abort_current():
|
|
32
|
+
def abort_current():
|
|
37
33
|
runner.stop_execution()
|
|
38
34
|
socketio.emit('log', {'message': "stopped next task"})
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
@socketio.on('pause')
|
|
42
|
-
def handle_pause():
|
|
36
|
+
def pause():
|
|
43
37
|
runner.retry = False
|
|
44
38
|
msg = runner.toggle_pause()
|
|
45
39
|
socketio.emit('log', {'message': msg})
|
|
40
|
+
return msg
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
def handle_pause():
|
|
42
|
+
def retry():
|
|
49
43
|
runner.retry = True
|
|
50
44
|
msg = runner.toggle_pause()
|
|
51
45
|
socketio.emit('log', {'message': msg})
|
|
52
46
|
|
|
53
47
|
|
|
48
|
+
# ---- Socket.IO Event Handlers ----
|
|
49
|
+
|
|
50
|
+
@socketio.on('abort_pending')
|
|
51
|
+
def handle_abort_pending():
|
|
52
|
+
abort_pending()
|
|
53
|
+
|
|
54
|
+
@socketio.on('abort_current')
|
|
55
|
+
def handle_abort_current():
|
|
56
|
+
abort_current()
|
|
57
|
+
|
|
58
|
+
@socketio.on('pause')
|
|
59
|
+
def handle_pause():
|
|
60
|
+
pause()
|
|
61
|
+
|
|
62
|
+
@socketio.on('retry')
|
|
63
|
+
def handle_retry():
|
|
64
|
+
retry()
|
|
65
|
+
|
|
66
|
+
|
|
54
67
|
@socketio.on('connect')
|
|
55
68
|
def handle_abort_action():
|
|
56
69
|
# Fetch log messages from local file
|
|
@@ -312,11 +325,18 @@ def experiment_run():
|
|
|
312
325
|
config_preview = []
|
|
313
326
|
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
314
327
|
try:
|
|
315
|
-
|
|
316
|
-
|
|
328
|
+
# todo
|
|
329
|
+
exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
330
|
+
# exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
331
|
+
|
|
332
|
+
except Exception as e:
|
|
317
333
|
flash(e.__str__())
|
|
318
|
-
|
|
319
|
-
|
|
334
|
+
# handle api request
|
|
335
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
336
|
+
return jsonify({"error": e.__str__()})
|
|
337
|
+
else:
|
|
338
|
+
return redirect(url_for("design.experiment_builder"))
|
|
339
|
+
|
|
320
340
|
config_file = request.args.get("filename")
|
|
321
341
|
config = []
|
|
322
342
|
if config_file:
|
|
@@ -356,25 +376,41 @@ def experiment_run():
|
|
|
356
376
|
flash(f"This script is not compatible with current deck, import {script.deck}")
|
|
357
377
|
if request.method == "POST":
|
|
358
378
|
bo_args = None
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
379
|
+
compiled = False
|
|
380
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
381
|
+
payload_json = request.get_json()
|
|
382
|
+
compiled = True
|
|
383
|
+
if "kwargs" in payload_json:
|
|
384
|
+
config = payload_json["kwargs"]
|
|
385
|
+
elif "parameters" in payload_json:
|
|
386
|
+
bo_args = payload_json
|
|
387
|
+
repeat = payload_json.pop("repeat", None)
|
|
388
|
+
else:
|
|
389
|
+
if "bo" in request.form:
|
|
390
|
+
bo_args = request.form.to_dict()
|
|
391
|
+
if "online-config" in request.form:
|
|
392
|
+
config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
|
|
393
|
+
repeat = request.form.get('repeat', None)
|
|
365
394
|
|
|
366
395
|
try:
|
|
367
396
|
datapath = current_app.config["DATA_FOLDER"]
|
|
368
397
|
run_name = script.validate_function_name(run_name)
|
|
369
398
|
runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
|
|
370
399
|
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
371
|
-
output_path=datapath,
|
|
400
|
+
output_path=datapath, compiled=compiled,
|
|
401
|
+
current_app=current_app._get_current_object()
|
|
372
402
|
)
|
|
373
403
|
if utils.check_config_duplicate(config):
|
|
374
404
|
flash(f"WARNING: Duplicate in config entries.")
|
|
375
405
|
except Exception as e:
|
|
376
|
-
|
|
377
|
-
|
|
406
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
407
|
+
return jsonify({"error": e.__str__()})
|
|
408
|
+
else:
|
|
409
|
+
flash(e)
|
|
410
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
411
|
+
return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
|
|
412
|
+
else:
|
|
413
|
+
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
378
414
|
dot_py=exec_string, line_collection=line_collection,
|
|
379
415
|
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
380
416
|
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
@@ -629,6 +665,66 @@ def duplicate_action(id: int):
|
|
|
629
665
|
return redirect(back)
|
|
630
666
|
|
|
631
667
|
|
|
632
|
-
|
|
668
|
+
# ---- HTTP API Endpoints ----
|
|
669
|
+
|
|
670
|
+
@design.route("/api/status", methods=["GET"])
|
|
633
671
|
def runner_status():
|
|
634
|
-
|
|
672
|
+
runner_busy = global_config.runner_lock.locked()
|
|
673
|
+
status = {"busy": runner_busy}
|
|
674
|
+
task_status = global_config.runner_status
|
|
675
|
+
current_step = {}
|
|
676
|
+
# print(task_status)
|
|
677
|
+
if task_status is not None:
|
|
678
|
+
task_type = task_status["type"]
|
|
679
|
+
task_id = task_status["id"]
|
|
680
|
+
if task_type == "task":
|
|
681
|
+
step = SingleStep.query.get(task_id)
|
|
682
|
+
current_step = step.as_dict()
|
|
683
|
+
if task_type == "workflow":
|
|
684
|
+
workflow = WorkflowRun.query.get(task_id)
|
|
685
|
+
if workflow is not None:
|
|
686
|
+
latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
|
|
687
|
+
if latest_step is not None:
|
|
688
|
+
current_step = latest_step.as_dict()
|
|
689
|
+
status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
|
|
690
|
+
status["current_task"] = current_step
|
|
691
|
+
return jsonify(status), 200
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
@design.route("/api/abort_pending", methods=["POST"])
|
|
696
|
+
def api_abort_pending():
|
|
697
|
+
abort_pending()
|
|
698
|
+
return jsonify({"status": "ok"}), 200
|
|
699
|
+
|
|
700
|
+
@design.route("/api/abort_current", methods=["POST"])
|
|
701
|
+
def api_abort_current():
|
|
702
|
+
abort_current()
|
|
703
|
+
return jsonify({"status": "ok"}), 200
|
|
704
|
+
|
|
705
|
+
@design.route("/api/pause", methods=["POST"])
|
|
706
|
+
def api_pause():
|
|
707
|
+
msg = pause()
|
|
708
|
+
return jsonify({"status": "ok", "pause_status": msg}), 200
|
|
709
|
+
|
|
710
|
+
@design.route("/api/retry", methods=["POST"])
|
|
711
|
+
def api_retry():
|
|
712
|
+
retry()
|
|
713
|
+
return jsonify({"status": "ok, retrying failed step"}), 200
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@design.route("/api/get_script", methods=["GET", "POST"])
|
|
717
|
+
def get_script():
|
|
718
|
+
script = utils.get_script_file()
|
|
719
|
+
script.sort_actions()
|
|
720
|
+
script_collection = script.compile()
|
|
721
|
+
if request.method == "POST":
|
|
722
|
+
# create a brand-new script
|
|
723
|
+
deck = global_config.deck
|
|
724
|
+
deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
|
|
725
|
+
script = Script(author=session.get('user'), deck=deck_name)
|
|
726
|
+
script_collection = request.get_json()
|
|
727
|
+
script.python_script = script_collection
|
|
728
|
+
utils.post_script_file(script)
|
|
729
|
+
return jsonify({"status": "ok"}), 200
|
|
730
|
+
return jsonify(script_collection), 200
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
</h2>
|
|
27
27
|
<div id="runpanel" class="accordion-collapse collapse show">
|
|
28
28
|
<div class="accordion-body">
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
<div class="row">
|
|
30
|
+
{% if script['script'] or script['prep'] or script['cleanup'] %}
|
|
31
31
|
<div class="col-lg-6 col-sm-12" id="run-panel" style="{{ 'display: none;' if pause_status else '' }}">
|
|
32
32
|
<ul class="nav nav-tabs" id="myTabs" role="tablist">
|
|
33
33
|
<li class="nav-item" role="presentation">
|
|
@@ -127,129 +127,62 @@
|
|
|
127
127
|
</form>
|
|
128
128
|
{% endif %}
|
|
129
129
|
</div>
|
|
130
|
-
|
|
131
|
-
{# <div class="tab-pane fade " id="tab3" role="tabpanel" aria-labelledby="tab3-tab">#}
|
|
132
|
-
{# <form role="form" method='POST' name="bo" action="{{ url_for('design.experiment_run')}}">#}
|
|
133
|
-
{# <div class="form-group">#}
|
|
134
|
-
{# <p><h5>Parameters:</h5><p>#}
|
|
135
|
-
{# {% for config in config_list %}#}
|
|
136
|
-
{# <div class="row g-3 align-items-center">#}
|
|
137
|
-
{# <div class="col-lg-3 col-sm-6 ">#}
|
|
138
|
-
{# {{config}}:#}
|
|
139
|
-
{# </div>#}
|
|
140
|
-
{# <div class="col-auto">#}
|
|
141
|
-
{# <label class="col-form-label" for="{{config}}_type">Type</label>#}
|
|
142
|
-
{# </div>#}
|
|
143
|
-
{# <div class="col-auto">#}
|
|
144
|
-
{# <select class="form-select" id="{{config}}_type" name="{{config}}_type">#}
|
|
145
|
-
{# <option selected value="range">range</option>#}
|
|
146
|
-
{# <option value="choice">choice</option>#}
|
|
147
|
-
{# <option value="fixed">fixed</option>#}
|
|
148
|
-
{# </select>#}
|
|
149
|
-
{# </div>#}
|
|
150
|
-
{# <div class="col-auto">#}
|
|
151
|
-
{# <label class="" for="{{config}}_value">Values</label>#}
|
|
152
|
-
{# </div>#}
|
|
153
|
-
{# <div class="col-auto">#}
|
|
154
|
-
{# <input type="text" class="form-control" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">#}
|
|
155
|
-
{# </div>#}
|
|
156
|
-
{# <div class="col-auto">#}
|
|
157
|
-
{# <input type="text" class="form-control" id="{{config}}_value_max" style="display: none;" placeholder="1, 2, 3">#}
|
|
158
|
-
{# </div>#}
|
|
159
|
-
{# </div>#}
|
|
160
|
-
{# {% endfor %}#}
|
|
161
|
-
{# <p><h5>Objective:</h5><p>#}
|
|
162
|
-
{# {% for objective in return_list %}#}
|
|
163
|
-
{# <div class="row gy-2 gx-3 align-items-center input-group">#}
|
|
164
|
-
{# <div class="col-3">#}
|
|
165
|
-
{# {{objective}}:#}
|
|
166
|
-
{# </div>#}
|
|
167
|
-
{# <div class="col-auto">#}
|
|
168
|
-
{# <label class="" for="{{objective}}_min">Minimize</label>#}
|
|
169
|
-
{# </div>#}
|
|
170
|
-
{# <div class="col-auto">#}
|
|
171
|
-
{# <select class="form-select" id="{{objective}}_min" name="{{objective}}_min">#}
|
|
172
|
-
{# <option selected>minimize</option>#}
|
|
173
|
-
{# <option>maximize</option>#}
|
|
174
|
-
{# <option>none</option>#}
|
|
175
|
-
{# </select>#}
|
|
176
|
-
{# </div>#}
|
|
177
|
-
{# <div class="col-auto">#}
|
|
178
|
-
{# <label class="" for="{{objective}}_threshold">Threshold</label>#}
|
|
179
|
-
{# </div>#}
|
|
180
|
-
{# <div class="col-auto">#}
|
|
181
|
-
{# <input type="text" class="form-control" id="{{objective}}_threshold" name="{{objective}}_threshold" placeholder="None">#}
|
|
182
|
-
{# </div>#}
|
|
183
|
-
{# </div>#}
|
|
184
|
-
{# {% endfor %}#}
|
|
185
|
-
{# <p><h5>Budget:</h5></p>#}
|
|
186
|
-
{# <div class="input-group mb-3">#}
|
|
187
|
-
{# <label class="input-group-text" for="repeat">Max iteration </label>#}
|
|
188
|
-
{# <input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">#}
|
|
189
|
-
{# </div>#}
|
|
190
|
-
{# {% if not no_deck_warning%}#}
|
|
191
|
-
{# <div class="input-group mb-3">#}
|
|
192
|
-
{# <button class="form-control" type="submit" name="bo">Run</button>#}
|
|
193
|
-
{# </div>#}
|
|
194
|
-
{# {% endif %}#}
|
|
195
|
-
{# </div>#}
|
|
196
|
-
{# </form>#}
|
|
197
|
-
{# </div>#}
|
|
198
|
-
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
|
|
199
|
-
<form method="POST" name="bo" action="{{ url_for('design.experiment_run') }}">
|
|
200
|
-
<div class="container py-2">
|
|
201
130
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<div class="row align-items-center mb-2">
|
|
206
|
-
<div class="col-3 col-form-label-sm">
|
|
207
|
-
{{ config }}:
|
|
208
|
-
</div>
|
|
209
|
-
<div class="col-6">
|
|
210
|
-
<select class="form-select form-select-sm" id="{{config}}_type" name="{{config}}_type">
|
|
211
|
-
<option selected value="range">range</option>
|
|
212
|
-
<option value="choice">choice</option>
|
|
213
|
-
<option value="fixed">fixed</option>
|
|
214
|
-
</select>
|
|
215
|
-
</div>
|
|
216
|
-
<div class="col-3">
|
|
217
|
-
<input type="text" class="form-control form-control-sm" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
{% endfor %}
|
|
131
|
+
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
|
|
132
|
+
<form method="POST" name="bo" action="{{ url_for('design.experiment_run') }}">
|
|
133
|
+
<div class="container py-2">
|
|
221
134
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
135
|
+
<!-- Parameters -->
|
|
136
|
+
<h6 class="fw-bold mt-2 mb-1">Parameters</h6>
|
|
137
|
+
{% for config in config_list %}
|
|
138
|
+
<div class="row align-items-center mb-2">
|
|
139
|
+
<div class="col-3 col-form-label-sm">
|
|
140
|
+
{{ config }}:
|
|
141
|
+
</div>
|
|
142
|
+
<div class="col-6">
|
|
143
|
+
<select class="form-select form-select-sm" id="{{config}}_type" name="{{config}}_type">
|
|
144
|
+
<option selected value="range">range</option>
|
|
145
|
+
<option value="choice">choice</option>
|
|
146
|
+
<option value="fixed">fixed</option>
|
|
147
|
+
</select>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="col-3">
|
|
150
|
+
<input type="text" class="form-control form-control-sm" id="{{config}}_value" name="{{config}}_value" placeholder="1, 2, 3">
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
{% endfor %}
|
|
238
154
|
|
|
239
|
-
|
|
155
|
+
<!-- Objective -->
|
|
156
|
+
<h6 class="fw-bold mt-3 mb-1">Objectives</h6>
|
|
157
|
+
{% for objective in return_list %}
|
|
158
|
+
<div class="row align-items-center mb-2">
|
|
159
|
+
<div class="col-3 col-form-label-sm">
|
|
160
|
+
{{ objective }}:
|
|
161
|
+
</div>
|
|
162
|
+
<div class="col-6">
|
|
163
|
+
<select class="form-select form-select-sm" id="{{objective}}_min" name="{{objective}}_min">
|
|
164
|
+
<option selected>minimize</option>
|
|
165
|
+
<option>maximize</option>
|
|
166
|
+
<option>none</option>
|
|
167
|
+
</select>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
{% endfor %}
|
|
240
171
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</div>
|
|
172
|
+
<h6 class="fw-bold mt-3 mb-1">Budget</h6>
|
|
173
|
+
|
|
174
|
+
<div class="input-group mb-3">
|
|
175
|
+
<label class="input-group-text" for="repeat">Max iteration </label>
|
|
176
|
+
<input class="form-control" type="number" id="repeat" name="repeat" min="1" max="1000" value="25">
|
|
177
|
+
</div>
|
|
178
|
+
{% if not no_deck_warning%}
|
|
179
|
+
<div class="input-group mb-3">
|
|
180
|
+
<button class="form-control" type="submit" name="bo">Run</button>
|
|
181
|
+
</div>
|
|
182
|
+
{% endif %}
|
|
183
|
+
</div>
|
|
184
|
+
</form>
|
|
185
|
+
</div>
|
|
253
186
|
|
|
254
187
|
|
|
255
188
|
<div class="tab-pane fade {{ 'show active' if config_list and config_list|count<=5 else '' }}" id="tab4" role="tabpanel" aria-labelledby="tab4-tab">
|
|
@@ -278,6 +211,12 @@
|
|
|
278
211
|
</div>
|
|
279
212
|
</div>
|
|
280
213
|
</div>
|
|
214
|
+
{% else %}
|
|
215
|
+
<div class="col-lg-6 col-sm-12" id="placeholder-panel">
|
|
216
|
+
|
|
217
|
+
</div>
|
|
218
|
+
{% endif %}
|
|
219
|
+
|
|
281
220
|
<div class="col-lg-6 col-sm-12" id="code-panel" style="{{ '' if pause_status else 'display: none;'}}">
|
|
282
221
|
<p>
|
|
283
222
|
<h5>Progress:</h5>
|
|
@@ -299,7 +238,7 @@
|
|
|
299
238
|
{% endif %}
|
|
300
239
|
{% if "cleanup" in line_collection.keys() %}
|
|
301
240
|
{% set stype = "cleanup" %}
|
|
302
|
-
<h6>
|
|
241
|
+
<h6>Cleanup:</h6>
|
|
303
242
|
{% for code in line_collection["cleanup"] %}
|
|
304
243
|
<pre style="margin: 0; padding: 0; line-height: 1;"><code class="python" id="{{ stype }}-{{ loop.index0 }}" >{{code}}</code></pre>
|
|
305
244
|
{% endfor %}
|
|
@@ -337,7 +276,7 @@
|
|
|
337
276
|
<div id="logging-panel"></div>
|
|
338
277
|
</div>
|
|
339
278
|
</div>
|
|
340
|
-
|
|
279
|
+
|
|
341
280
|
</div>
|
|
342
281
|
</div>
|
|
343
282
|
</div>
|