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/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,11 +12,10 @@ 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
|
|
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
|
|
@@ -61,8 +74,8 @@ def handle_abort_action():
|
|
|
61
74
|
socketio.emit('log', {'message': message})
|
|
62
75
|
|
|
63
76
|
|
|
64
|
-
@design.route("/
|
|
65
|
-
@design.route("/
|
|
77
|
+
@design.route("/design/script/", methods=['GET', 'POST'])
|
|
78
|
+
@design.route("/design/script/<instrument>/", methods=['GET', 'POST'])
|
|
66
79
|
@login_required
|
|
67
80
|
def experiment_builder(instrument=None):
|
|
68
81
|
"""
|
|
@@ -73,7 +86,7 @@ def experiment_builder(instrument=None):
|
|
|
73
86
|
This route allows users to build and edit experiment workflows. Users can interact with available instruments,
|
|
74
87
|
define variables, and manage experiment scripts.
|
|
75
88
|
|
|
76
|
-
.. http:get:: /
|
|
89
|
+
.. http:get:: /design/script
|
|
77
90
|
|
|
78
91
|
Load the experiment builder interface.
|
|
79
92
|
|
|
@@ -81,7 +94,7 @@ def experiment_builder(instrument=None):
|
|
|
81
94
|
:type instrument: str
|
|
82
95
|
:status 200: Experiment builder loaded successfully.
|
|
83
96
|
|
|
84
|
-
.. http:post:: /
|
|
97
|
+
.. http:post:: /design/script
|
|
85
98
|
|
|
86
99
|
Submit form data to add or modify actions in the experiment script.
|
|
87
100
|
|
|
@@ -235,17 +248,17 @@ def experiment_builder(instrument=None):
|
|
|
235
248
|
use_llm=enable_llm)
|
|
236
249
|
|
|
237
250
|
|
|
238
|
-
@design.route("/generate_code", methods=['POST'])
|
|
251
|
+
@design.route("/design/generate_code", methods=['POST'])
|
|
239
252
|
@login_required
|
|
240
253
|
def generate_code():
|
|
241
254
|
"""
|
|
242
255
|
.. :quickref: Text to Code; Generate code from user input and update the design canvas.
|
|
243
256
|
|
|
244
|
-
.. http:post:: /generate_code
|
|
257
|
+
.. http:post:: /design/generate_code
|
|
245
258
|
|
|
246
259
|
:form prompt: user's prompt
|
|
247
260
|
:status 200: and then redirects to :http:get:`/experiment/build`
|
|
248
|
-
: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`
|
|
249
262
|
|
|
250
263
|
"""
|
|
251
264
|
agent = global_config.agent
|
|
@@ -283,17 +296,17 @@ def generate_code():
|
|
|
283
296
|
return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
|
|
284
297
|
|
|
285
298
|
|
|
286
|
-
@design.route("/
|
|
299
|
+
@design.route("/design/campaign", methods=['GET', 'POST'])
|
|
287
300
|
@login_required
|
|
288
301
|
def experiment_run():
|
|
289
302
|
"""
|
|
290
303
|
.. :quickref: Workflow Execution; Execute/iterate the workflow
|
|
291
304
|
|
|
292
|
-
.. http:get:: /
|
|
305
|
+
.. http:get:: /design/campaign
|
|
293
306
|
|
|
294
307
|
Compile the workflow and load the experiment execution interface.
|
|
295
308
|
|
|
296
|
-
.. http:post:: /
|
|
309
|
+
.. http:post:: /design/campaign
|
|
297
310
|
|
|
298
311
|
Start workflow execution
|
|
299
312
|
|
|
@@ -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
|
+
# print(exec_string)
|
|
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:
|
|
@@ -331,6 +351,7 @@ def experiment_run():
|
|
|
331
351
|
for key, func_str in exec_string.items():
|
|
332
352
|
exec(func_str)
|
|
333
353
|
line_collection = script.convert_to_lines(exec_string)
|
|
354
|
+
|
|
334
355
|
except Exception:
|
|
335
356
|
flash(f"Please check {key} syntax!!")
|
|
336
357
|
return redirect(url_for("design.experiment_builder"))
|
|
@@ -356,25 +377,44 @@ def experiment_run():
|
|
|
356
377
|
flash(f"This script is not compatible with current deck, import {script.deck}")
|
|
357
378
|
if request.method == "POST":
|
|
358
379
|
bo_args = None
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
380
|
+
compiled = False
|
|
381
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
382
|
+
payload_json = request.get_json()
|
|
383
|
+
compiled = True
|
|
384
|
+
if "kwargs" in payload_json:
|
|
385
|
+
config = payload_json["kwargs"]
|
|
386
|
+
elif "parameters" in payload_json:
|
|
387
|
+
bo_args = payload_json
|
|
388
|
+
repeat = payload_json.pop("repeat", None)
|
|
389
|
+
else:
|
|
390
|
+
if "bo" in request.form:
|
|
391
|
+
bo_args = request.form.to_dict()
|
|
392
|
+
if "online-config" in request.form:
|
|
393
|
+
config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
|
|
394
|
+
repeat = request.form.get('repeat', None)
|
|
365
395
|
|
|
366
396
|
try:
|
|
367
397
|
datapath = current_app.config["DATA_FOLDER"]
|
|
368
398
|
run_name = script.validate_function_name(run_name)
|
|
369
399
|
runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
|
|
370
400
|
logger=g.logger, socketio=g.socketio, repeat_count=repeat,
|
|
371
|
-
output_path=datapath,
|
|
401
|
+
output_path=datapath, compiled=compiled,
|
|
402
|
+
current_app=current_app._get_current_object()
|
|
372
403
|
)
|
|
373
404
|
if utils.check_config_duplicate(config):
|
|
374
405
|
flash(f"WARNING: Duplicate in config entries.")
|
|
375
406
|
except Exception as e:
|
|
376
|
-
|
|
377
|
-
|
|
407
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
408
|
+
return jsonify({"error": e.__str__()})
|
|
409
|
+
else:
|
|
410
|
+
flash(e)
|
|
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)
|
|
415
|
+
return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
|
|
416
|
+
else:
|
|
417
|
+
return render_template('experiment_run.html', script=script.script_dict, filename=filename,
|
|
378
418
|
dot_py=exec_string, line_collection=line_collection,
|
|
379
419
|
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
380
420
|
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
@@ -382,15 +422,15 @@ def experiment_run():
|
|
|
382
422
|
history=deck_list, pause_status=runner.pause_status())
|
|
383
423
|
|
|
384
424
|
|
|
385
|
-
@design.route("/
|
|
425
|
+
@design.route("/design/script/toggle/<stype>")
|
|
386
426
|
@login_required
|
|
387
427
|
def toggle_script_type(stype=None):
|
|
388
428
|
"""
|
|
389
429
|
.. :quickref: Workflow Design; toggle the experimental phase for design canvas.
|
|
390
430
|
|
|
391
|
-
.. http:get:: /
|
|
431
|
+
.. http:get:: /design/script/toggle/<stype>
|
|
392
432
|
|
|
393
|
-
:status 200: and then redirects to :http:get:`/
|
|
433
|
+
:status 200: and then redirects to :http:get:`/design/script`
|
|
394
434
|
|
|
395
435
|
"""
|
|
396
436
|
script = utils.get_script_file()
|
|
@@ -410,16 +450,16 @@ def update_list():
|
|
|
410
450
|
|
|
411
451
|
|
|
412
452
|
# --------------------handle all the import/export and download/upload--------------------------
|
|
413
|
-
@design.route("/clear")
|
|
453
|
+
@design.route("/design/clear")
|
|
414
454
|
@login_required
|
|
415
455
|
def clear():
|
|
416
456
|
"""
|
|
417
457
|
.. :quickref: Workflow Design; clear the design canvas.
|
|
418
458
|
|
|
419
|
-
.. http:get:: /clear
|
|
459
|
+
.. http:get:: /design/clear
|
|
420
460
|
|
|
421
461
|
:form prompt: user's prompt
|
|
422
|
-
:status 200: clear canvas and then redirects to :http:get:`/
|
|
462
|
+
:status 200: clear canvas and then redirects to :http:get:`/design/script`
|
|
423
463
|
"""
|
|
424
464
|
deck = global_config.deck
|
|
425
465
|
pseudo_name = session.get("pseudo_deck", "")
|
|
@@ -435,16 +475,16 @@ def clear():
|
|
|
435
475
|
return redirect(url_for("design.experiment_builder"))
|
|
436
476
|
|
|
437
477
|
|
|
438
|
-
@design.route("/
|
|
478
|
+
@design.route("/design/import/pseudo", methods=['POST'])
|
|
439
479
|
@login_required
|
|
440
480
|
def import_pseudo():
|
|
441
481
|
"""
|
|
442
482
|
.. :quickref: Workflow Design; Import pseudo deck from deck history
|
|
443
483
|
|
|
444
|
-
.. http:post:: /
|
|
484
|
+
.. http:post:: /design/import/pseudo
|
|
445
485
|
|
|
446
486
|
:form pkl_name: pseudo deck name
|
|
447
|
-
: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`
|
|
448
488
|
"""
|
|
449
489
|
pkl_name = request.form.get('pkl_name')
|
|
450
490
|
script = utils.get_script_file()
|
|
@@ -458,16 +498,16 @@ def import_pseudo():
|
|
|
458
498
|
return redirect(url_for("design.experiment_builder"))
|
|
459
499
|
|
|
460
500
|
|
|
461
|
-
@design.route('/uploads', methods=['POST'])
|
|
501
|
+
@design.route('/design/uploads', methods=['POST'])
|
|
462
502
|
@login_required
|
|
463
503
|
def upload():
|
|
464
504
|
"""
|
|
465
505
|
.. :quickref: Workflow Execution; upload a workflow config file (.CSV)
|
|
466
506
|
|
|
467
|
-
.. http:post:: /uploads
|
|
507
|
+
.. http:post:: /design/uploads
|
|
468
508
|
|
|
469
509
|
:form file: workflow CSV config file
|
|
470
|
-
: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`
|
|
471
511
|
"""
|
|
472
512
|
if request.method == "POST":
|
|
473
513
|
f = request.files['file']
|
|
@@ -483,14 +523,20 @@ def upload():
|
|
|
483
523
|
return redirect(url_for("design.experiment_run"))
|
|
484
524
|
|
|
485
525
|
|
|
486
|
-
@design.route('/
|
|
526
|
+
@design.route('/design/workflow/download/<filename>')
|
|
487
527
|
@login_required
|
|
488
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
|
+
"""
|
|
489
535
|
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
490
536
|
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
491
537
|
|
|
492
538
|
|
|
493
|
-
@design.route('/load_json', methods=['POST'])
|
|
539
|
+
@design.route('/design/load_json', methods=['POST'])
|
|
494
540
|
@login_required
|
|
495
541
|
def load_json():
|
|
496
542
|
"""
|
|
@@ -499,7 +545,7 @@ def load_json():
|
|
|
499
545
|
.. http:post:: /load_json
|
|
500
546
|
|
|
501
547
|
:form file: workflow design JSON file
|
|
502
|
-
: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`
|
|
503
549
|
"""
|
|
504
550
|
if request.method == "POST":
|
|
505
551
|
f = request.files['file']
|
|
@@ -513,9 +559,15 @@ def load_json():
|
|
|
513
559
|
return redirect(url_for("design.experiment_builder"))
|
|
514
560
|
|
|
515
561
|
|
|
516
|
-
@design.route('/download/<filetype>')
|
|
562
|
+
@design.route('/design/script/download/<filetype>')
|
|
517
563
|
@login_required
|
|
518
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
|
+
"""
|
|
519
571
|
script = utils.get_script_file()
|
|
520
572
|
run_name = script.name if script.name else "untitled"
|
|
521
573
|
if filetype == "configure":
|
|
@@ -533,40 +585,28 @@ def download(filetype):
|
|
|
533
585
|
outfile.write(json_object)
|
|
534
586
|
elif filetype == "python":
|
|
535
587
|
filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
class_definitions = {}
|
|
539
|
-
# Iterate through each instrument in the snapshot
|
|
540
|
-
for instrument_key, instrument_data in snapshot.items():
|
|
541
|
-
# Iterate through each function associated with the current instrument
|
|
542
|
-
for function_key, function_data in instrument_data.items():
|
|
543
|
-
# Convert the function signature to a string representation
|
|
544
|
-
function_data['signature'] = str(function_data['signature'])
|
|
545
|
-
class_name = instrument_key.split('.')[-1] # Extracting the class name from the path
|
|
546
|
-
class_definitions[class_name.capitalize()] = create_function(request.url_root, class_name, instrument_data)
|
|
547
|
-
# Export the generated class definitions to a .py script
|
|
548
|
-
export_to_python(class_definitions, current_app.config["OUTPUT_FOLDER"])
|
|
549
|
-
filepath = os.path.join(current_app.config["OUTPUT_FOLDER"], "generated_proxy.py")
|
|
588
|
+
else:
|
|
589
|
+
return "Unsupported file type", 400
|
|
550
590
|
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
551
591
|
|
|
552
592
|
|
|
553
|
-
@design.route("/edit/<uuid>", methods=['GET', 'POST'])
|
|
593
|
+
@design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
|
|
554
594
|
@login_required
|
|
555
595
|
def edit_action(uuid: str):
|
|
556
596
|
"""
|
|
557
597
|
.. :quickref: Workflow Design; edit parameters of an action step on canvas
|
|
558
598
|
|
|
559
|
-
.. http:get:: /edit
|
|
599
|
+
.. http:get:: /design/step/edit/<uuid>
|
|
560
600
|
|
|
561
601
|
Load parameter form of an action step
|
|
562
602
|
|
|
563
|
-
.. http:post:: /edit
|
|
603
|
+
.. http:post:: /design/step/edit/<uuid>
|
|
564
604
|
|
|
565
605
|
:param uuid: The step's uuid
|
|
566
606
|
:type uuid: str
|
|
567
607
|
|
|
568
608
|
:form dynamic form: workflow step dynamic inputs
|
|
569
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
609
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
570
610
|
"""
|
|
571
611
|
script = utils.get_script_file()
|
|
572
612
|
action = script.find_by_uuid(uuid)
|
|
@@ -589,18 +629,18 @@ def edit_action(uuid: str):
|
|
|
589
629
|
return redirect(url_for('design.experiment_builder'))
|
|
590
630
|
|
|
591
631
|
|
|
592
|
-
@design.route("/delete/<id>")
|
|
632
|
+
@design.route("/design/step/delete/<id>")
|
|
593
633
|
@login_required
|
|
594
634
|
def delete_action(id: int):
|
|
595
635
|
"""
|
|
596
636
|
.. :quickref: Workflow Design; delete an action step on canvas
|
|
597
637
|
|
|
598
|
-
.. http:get:: /delete
|
|
638
|
+
.. http:get:: /design/step/delete/<id>
|
|
599
639
|
|
|
600
640
|
:param id: The step number id
|
|
601
641
|
:type id: int
|
|
602
642
|
|
|
603
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
643
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
604
644
|
"""
|
|
605
645
|
back = request.referrer
|
|
606
646
|
script = utils.get_script_file()
|
|
@@ -609,18 +649,18 @@ def delete_action(id: int):
|
|
|
609
649
|
return redirect(back)
|
|
610
650
|
|
|
611
651
|
|
|
612
|
-
@design.route("/duplicate/<id>")
|
|
652
|
+
@design.route("/design/step/duplicate/<id>")
|
|
613
653
|
@login_required
|
|
614
654
|
def duplicate_action(id: int):
|
|
615
655
|
"""
|
|
616
656
|
.. :quickref: Workflow Design; duplicate an action step on canvas
|
|
617
657
|
|
|
618
|
-
.. http:get:: /duplicate
|
|
658
|
+
.. http:get:: /design/step/duplicate/<id>
|
|
619
659
|
|
|
620
660
|
:param id: The step number id
|
|
621
661
|
:type id: int
|
|
622
662
|
|
|
623
|
-
:status 302: save changes and then redirects to :http:get:`/
|
|
663
|
+
:status 302: save changes and then redirects to :http:get:`/design/script`
|
|
624
664
|
"""
|
|
625
665
|
back = request.referrer
|
|
626
666
|
script = utils.get_script_file()
|
|
@@ -629,6 +669,103 @@ def duplicate_action(id: int):
|
|
|
629
669
|
return redirect(back)
|
|
630
670
|
|
|
631
671
|
|
|
632
|
-
|
|
672
|
+
# ---- HTTP API Endpoints ----
|
|
673
|
+
|
|
674
|
+
@design.route("/api/runner/status", methods=["GET"])
|
|
633
675
|
def runner_status():
|
|
634
|
-
|
|
676
|
+
"""
|
|
677
|
+
.. :quickref: Workflow Design; get the execution status
|
|
678
|
+
|
|
679
|
+
.. http:get:: /api/runner/status
|
|
680
|
+
|
|
681
|
+
:status 200: status
|
|
682
|
+
"""
|
|
683
|
+
runner_busy = global_config.runner_lock.locked()
|
|
684
|
+
status = {"busy": runner_busy}
|
|
685
|
+
task_status = global_config.runner_status
|
|
686
|
+
current_step = {}
|
|
687
|
+
# print(task_status)
|
|
688
|
+
if task_status is not None:
|
|
689
|
+
task_type = task_status["type"]
|
|
690
|
+
task_id = task_status["id"]
|
|
691
|
+
if task_type == "task":
|
|
692
|
+
step = SingleStep.query.get(task_id)
|
|
693
|
+
current_step = step.as_dict()
|
|
694
|
+
if task_type == "workflow":
|
|
695
|
+
workflow = WorkflowRun.query.get(task_id)
|
|
696
|
+
if workflow is not None:
|
|
697
|
+
latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
|
|
698
|
+
if latest_step is not None:
|
|
699
|
+
current_step = latest_step.as_dict()
|
|
700
|
+
status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
|
|
701
|
+
status["current_task"] = current_step
|
|
702
|
+
return jsonify(status), 200
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
@design.route("/api/runner/abort_pending", methods=["POST"])
|
|
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
|
+
"""
|
|
715
|
+
abort_pending()
|
|
716
|
+
return jsonify({"status": "ok"}), 200
|
|
717
|
+
|
|
718
|
+
@design.route("/api/runner/abort_current", methods=["POST"])
|
|
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
|
+
"""
|
|
727
|
+
abort_current()
|
|
728
|
+
return jsonify({"status": "ok"}), 200
|
|
729
|
+
|
|
730
|
+
@design.route("/api/runner/pause", methods=["POST"])
|
|
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
|
+
"""
|
|
739
|
+
msg = pause()
|
|
740
|
+
return jsonify({"status": "ok", "pause_status": msg}), 200
|
|
741
|
+
|
|
742
|
+
@design.route("/api/runner/retry", methods=["POST"])
|
|
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
|
+
"""
|
|
751
|
+
retry()
|
|
752
|
+
return jsonify({"status": "ok, retrying failed step"}), 200
|
|
753
|
+
|
|
754
|
+
|
|
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
|