ivoryos 1.0.9__py3-none-any.whl → 1.2.0b1__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.

Files changed (87) hide show
  1. ivoryos/__init__.py +26 -7
  2. ivoryos/routes/api/api.py +56 -0
  3. ivoryos/routes/auth/auth.py +5 -5
  4. ivoryos/routes/control/control.py +77 -372
  5. ivoryos/routes/control/control_file.py +36 -0
  6. ivoryos/routes/control/control_new_device.py +142 -0
  7. ivoryos/routes/control/templates/controllers.html +166 -0
  8. ivoryos/routes/control/templates/controllers_new.html +112 -0
  9. ivoryos/routes/control/utils.py +38 -0
  10. ivoryos/routes/data/data.py +129 -0
  11. ivoryos/routes/data/templates/components/step_card.html +13 -0
  12. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  13. ivoryos/routes/{database/templates/database → data/templates}/workflow_view.html +3 -3
  14. ivoryos/routes/design/__init__.py +4 -0
  15. ivoryos/routes/design/design.py +298 -656
  16. ivoryos/routes/design/design_file.py +68 -0
  17. ivoryos/routes/design/design_step.py +145 -0
  18. ivoryos/routes/design/templates/components/action_form.html +53 -0
  19. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  20. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  21. ivoryos/routes/design/templates/components/canvas.html +5 -0
  22. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  23. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  24. ivoryos/routes/design/templates/components/canvas_main.html +34 -0
  25. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  26. ivoryos/routes/design/templates/components/edit_action_form.html +38 -0
  27. ivoryos/routes/design/templates/components/instruments_panel.html +66 -0
  28. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  29. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  30. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  31. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  32. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  33. ivoryos/routes/design/templates/components/modals.html +6 -0
  34. ivoryos/routes/design/templates/components/python_code_overlay.html +39 -0
  35. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  36. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  37. ivoryos/routes/design/templates/experiment_builder.html +41 -0
  38. ivoryos/routes/execute/__init__.py +0 -0
  39. ivoryos/routes/execute/execute.py +317 -0
  40. ivoryos/routes/execute/execute_file.py +78 -0
  41. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  42. ivoryos/routes/execute/templates/components/logging_panel.html +31 -0
  43. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  44. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  45. ivoryos/routes/execute/templates/components/run_tabs.html +17 -0
  46. ivoryos/routes/execute/templates/components/tab_bayesian.html +399 -0
  47. ivoryos/routes/execute/templates/components/tab_configuration.html +98 -0
  48. ivoryos/routes/execute/templates/components/tab_repeat.html +14 -0
  49. ivoryos/routes/execute/templates/experiment_run.html +294 -0
  50. ivoryos/routes/library/__init__.py +0 -0
  51. ivoryos/routes/library/library.py +159 -0
  52. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +30 -22
  53. ivoryos/routes/main/main.py +1 -1
  54. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  55. ivoryos/socket_handlers.py +52 -0
  56. ivoryos/static/js/action_handlers.js +213 -0
  57. ivoryos/static/js/db_delete.js +23 -0
  58. ivoryos/static/js/script_metadata.js +39 -0
  59. ivoryos/static/js/sortable_design.js +89 -56
  60. ivoryos/static/js/ui_state.js +113 -0
  61. ivoryos/templates/base.html +4 -4
  62. ivoryos/utils/bo_campaign.py +179 -3
  63. ivoryos/utils/db_models.py +14 -5
  64. ivoryos/utils/form.py +5 -9
  65. ivoryos/utils/global_config.py +13 -1
  66. ivoryos/utils/py_to_json.py +225 -0
  67. ivoryos/utils/script_runner.py +49 -7
  68. ivoryos/utils/serilize.py +203 -0
  69. ivoryos/utils/task_runner.py +4 -1
  70. ivoryos/version.py +1 -1
  71. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/METADATA +5 -8
  72. ivoryos-1.2.0b1.dist-info/RECORD +105 -0
  73. ivoryos/routes/control/templates/control/controllers.html +0 -78
  74. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  75. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  76. ivoryos/routes/database/database.py +0 -306
  77. ivoryos/routes/database/templates/database/step_card.html +0 -7
  78. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  79. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  80. ivoryos-1.0.9.dist-info/RECORD +0 -61
  81. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  82. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  83. /ivoryos/routes/{database → data}/__init__.py +0 -0
  84. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  85. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/LICENSE +0 -0
  86. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/WHEEL +0 -0
  87. {ivoryos-1.0.9.dist-info → ivoryos-1.2.0b1.dist-info}/top_level.txt +0 -0
@@ -1,786 +1,428 @@
1
- import csv
2
- import json
3
1
  import os
4
- import pickle
5
- import sys
6
- import time
7
2
 
8
- from flask import Blueprint, redirect, url_for, flash, jsonify, send_file, request, render_template, session, \
9
- current_app, g
3
+ from flask import Blueprint, redirect, url_for, flash, jsonify, request, render_template, session, current_app
10
4
  from flask_login import login_required
11
- from flask_socketio import SocketIO
12
- from werkzeug.utils import secure_filename
13
5
 
6
+ from ivoryos.routes.library.library import publish
14
7
  from ivoryos.utils import utils
15
8
  from ivoryos.utils.global_config import GlobalConfig
16
- from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
17
- create_form_from_action, create_all_builtin_forms
18
- from ivoryos.utils.db_models import Script, WorkflowRun, SingleStep, WorkflowStep
19
- from ivoryos.utils.script_runner import ScriptRunner
20
- # from ivoryos.utils.utils import load_workflows
9
+ from ivoryos.utils.form import create_action_button, create_form_from_pseudo, create_all_builtin_forms
10
+ from ivoryos.utils.db_models import Script
11
+ from ivoryos.utils.py_to_json import convert_to_cards
21
12
 
22
- socketio = SocketIO()
23
- design = Blueprint('design', __name__, template_folder='templates/design')
13
+ # Import the new modular components
14
+ from ivoryos.routes.design.design_file import files
15
+ from ivoryos.routes.design.design_step import steps
24
16
 
25
- global_config = GlobalConfig()
26
- runner = ScriptRunner()
27
-
28
- def abort_pending():
29
- runner.abort_pending()
30
- socketio.emit('log', {'message': "aborted pending iterations"})
31
-
32
- def abort_current():
33
- runner.stop_execution()
34
- socketio.emit('log', {'message': "stopped next task"})
35
-
36
- def pause():
37
- runner.retry = False
38
- msg = runner.toggle_pause()
39
- socketio.emit('log', {'message': msg})
40
- return msg
41
-
42
- def retry():
43
- runner.retry = True
44
- msg = runner.toggle_pause()
45
- socketio.emit('log', {'message': msg})
46
-
47
-
48
- # ---- Socket.IO Event Handlers ----
49
-
50
- @socketio.on('abort_pending')
51
- def handle_abort_pending():
52
- abort_pending()
53
17
 
54
- @socketio.on('abort_current')
55
- def handle_abort_current():
56
- abort_current()
18
+ design = Blueprint('design', __name__, template_folder='templates')
57
19
 
58
- @socketio.on('pause')
59
- def handle_pause():
60
- pause()
61
-
62
- @socketio.on('retry')
63
- def handle_retry():
64
- retry()
20
+ # Register sub-blueprints
21
+ design.register_blueprint(files)
22
+ design.register_blueprint(steps)
65
23
 
24
+ global_config = GlobalConfig()
66
25
 
67
- @socketio.on('connect')
68
- def handle_abort_action():
69
- # Fetch log messages from local file
70
- filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
71
- with open(filename, 'r') as log_file:
72
- log_history = log_file.readlines()
73
- for message in log_history[-10:]:
74
- socketio.emit('log', {'message': message})
26
+ # ---- Main Design Routes ----
75
27
 
76
28
 
77
- @design.route("/design/script/", methods=['GET', 'POST'])
78
- @design.route("/design/script/<instrument>/", methods=['GET', 'POST'])
29
+ def _create_forms(instrument, script, autofill, pseudo_deck = None):
30
+ deck = global_config.deck
31
+ functions = {}
32
+ if instrument == 'flow_control':
33
+ forms = create_all_builtin_forms(script=script)
34
+ elif instrument in global_config.defined_variables.keys():
35
+ _object = global_config.defined_variables.get(instrument)
36
+ functions = utils._inspect_class(_object)
37
+ forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
38
+ else:
39
+ if deck:
40
+ functions = global_config.deck_snapshot.get(instrument, {})
41
+ elif pseudo_deck:
42
+ functions = pseudo_deck.get(instrument, {})
43
+ forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
44
+ return functions, forms
45
+
46
+ @design.route("/draft")
79
47
  @login_required
80
- def experiment_builder(instrument=None):
48
+ def experiment_builder():
81
49
  """
82
50
  .. :quickref: Workflow Design; Build experiment workflow
83
51
 
84
52
  **Experiment Builder**
85
53
 
86
- This route allows users to build and edit experiment workflows. Users can interact with available instruments,
87
- define variables, and manage experiment scripts.
88
-
89
- .. http:get:: /design/script
54
+ .. http:get:: /draft
90
55
 
91
- Load the experiment builder interface.
56
+ Load the experiment builder page where users can design their workflow by adding actions, instruments, and logic.
92
57
 
93
- :param instrument: The specific instrument for which to load functions and forms.
94
- :type instrument: str
95
58
  :status 200: Experiment builder loaded successfully.
96
59
 
97
- .. http:post:: /design/script
98
-
99
- Submit form data to add or modify actions in the experiment script.
100
-
101
- **Adding action to canvas**
102
-
103
- :form return: (optional) The name of the function or method to add to the script.
104
- :form dynamic: depend on the selected instrument and its metadata.
105
-
106
- :status 200: Action added or modified successfully.
107
- :status 400: Validation errors in submitted form data.
108
- :status 302: Toggles autofill or redirects to refresh the page.
109
-
110
- **Toggle auto parameter name fill**:
111
-
112
- :status 200: autofill toggled successfully
113
-
114
60
  """
115
61
  deck = global_config.deck
116
62
  script = utils.get_script_file()
117
- # load_workflows(script)
118
- # registered_workflows = global_config.registered_workflows
119
63
 
120
64
  if deck and script.deck is None:
121
65
  script.deck = os.path.splitext(os.path.basename(deck.__file__))[
122
66
  0] if deck.__name__ == "__main__" else deck.__name__
123
- # script.sort_actions()
124
67
 
125
68
  pseudo_deck_name = session.get('pseudo_deck', '')
126
69
  pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
127
70
  off_line = current_app.config["OFF_LINE"]
128
- enable_llm = current_app.config["ENABLE_LLM"]
129
- autofill = session.get('autofill')
130
71
 
131
- # autofill is not allowed for prep and cleanup
132
- autofill = autofill if script.editing_type == "script" else False
133
- forms = None
134
72
  pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
135
73
  if off_line and pseudo_deck is None:
136
74
  flash("Choose available deck below.")
137
75
 
138
76
  deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
139
77
 
140
- functions = {}
141
78
  if deck:
142
79
  deck_variables = list(global_config.deck_snapshot.keys())
143
- # deck_variables.insert(0, "registered_workflows")
144
- deck_variables.insert(0, "flow_control")
145
-
80
+ # deck_variables.insert(0, "flow_control")
146
81
  else:
147
82
  deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
148
83
  deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
149
- edit_action_info = session.get("edit_action")
150
- if edit_action_info:
151
- forms = create_form_from_action(edit_action_info, script=script)
152
- elif instrument:
153
- # if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
154
- # forms = create_builtin_form(instrument, script=script)
155
- if instrument == 'flow_control':
156
- forms = create_all_builtin_forms(script=script)
157
- # elif instrument == 'registered_workflows':
158
- # functions = utils._inspect_class(registered_workflows)
159
- # # forms = create_workflow_forms(script=script)
160
- # forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
161
- elif instrument in global_config.defined_variables.keys():
162
- _object = global_config.defined_variables.get(instrument)
163
- functions = utils._inspect_class(_object)
164
- forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
165
- else:
166
- if deck:
167
- functions = global_config.deck_snapshot.get(instrument, {})
168
- elif pseudo_deck:
169
- functions = pseudo_deck.get(instrument, {})
170
- forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
171
- if request.method == 'POST' and "hidden_name" in request.form:
172
- # all_kwargs = request.form.copy()
173
- method_name = request.form.get("hidden_name", None)
174
- # if method_name is not None:
175
- form = forms.get(method_name)
176
- insert_position = request.form.get("drop_target_id", None)
177
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
178
- if form and form.validate_on_submit():
179
- function_name = kwargs.pop("hidden_name")
180
- save_data = kwargs.pop('return', '')
181
-
182
- primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
183
84
 
184
- script.eval_list(kwargs, primitive_arg_types)
185
- kwargs = script.validate_variables(kwargs)
186
- action = {"instrument": instrument, "action": function_name,
187
- "args": kwargs,
188
- "return": save_data,
189
- 'arg_types': primitive_arg_types}
190
- script.add_action(action=action, insert_position=insert_position)
191
- else:
192
- flash(form.errors)
193
-
194
- elif request.method == 'POST' and "builtin_name" in request.form:
195
- function_name = request.form.get("builtin_name")
196
- form = forms.get(function_name)
197
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
198
- insert_position = request.form.get("drop_target_id", None)
199
-
200
- if form.validate_on_submit():
201
- # print(kwargs)
202
- logic_type = kwargs.pop('builtin_name')
203
- if 'variable' in kwargs:
204
- try:
205
- script.add_variable(insert_position=insert_position, **kwargs)
206
- except ValueError:
207
- flash("Invalid variable type")
208
- else:
209
- script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
210
- else:
211
- flash(form.errors)
212
- elif request.method == 'POST' and "workflow_name" in request.form:
213
- workflow_name = request.form.get("workflow_name")
214
- form = forms.get(workflow_name)
215
- kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
216
- insert_position = request.form.get("drop_target_id", None)
85
+ # edit_action_info = session.get("edit_action")
217
86
 
218
- if form.validate_on_submit():
219
- # workflow_name = kwargs.pop('workflow_name')
220
- save_data = kwargs.pop('return', '')
221
-
222
- primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
223
-
224
- script.eval_list(kwargs, primitive_arg_types)
225
- kwargs = script.validate_variables(kwargs)
226
- action = {"instrument": instrument, "action": workflow_name,
227
- "args": kwargs,
228
- "return": save_data,
229
- 'arg_types': primitive_arg_types}
230
- script.add_action(action=action, insert_position=insert_position)
231
- script.add_workflow(**kwargs, insert_position=insert_position)
232
- else:
233
- flash(form.errors)
234
-
235
- # toggle autofill, autofill doesn't apply to control flow ops
236
- elif request.method == 'POST' and "autofill" in request.form:
237
- autofill = not autofill
238
- session['autofill'] = autofill
239
- if not instrument == 'flow_control':
240
- forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
241
-
242
- utils.post_script_file(script)
243
87
 
244
88
  exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
245
89
  session['python_code'] = exec_string
246
90
 
247
- design_buttons = create_action_button(script)
248
- return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
249
- script=script, defined_variables=deck_variables,
250
- local_variables=global_config.defined_variables,
251
- forms=forms, buttons=design_buttons, format_name=format_name,
252
- use_llm=enable_llm)
253
-
254
-
255
- @design.route("/design/generate_code", methods=['POST'])
256
- @login_required
257
- def generate_code():
258
- """
259
- .. :quickref: Text to Code; Generate code from user input and update the design canvas.
91
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
260
92
 
261
- .. http:post:: /design/generate_code
93
+ return render_template('experiment_builder.html', off_line=off_line, history=deck_list,
94
+ script=script, defined_variables=deck_variables, buttons_dict=design_buttons,
95
+ local_variables=global_config.defined_variables)
262
96
 
263
- :form prompt: user's prompt
264
- :status 200: and then redirects to :http:get:`/experiment/build`
265
- :status 400: failed to initialize the AI agent redirects to :http:get:`/design/script`
266
97
 
267
- """
268
- agent = global_config.agent
269
- enable_llm = current_app.config["ENABLE_LLM"]
270
- instrument = request.form.get("instrument")
271
-
272
- if request.method == 'POST' and "clear" in request.form:
273
- session['prompt'][instrument] = ''
274
- if request.method == 'POST' and "gen" in request.form:
275
- prompt = request.form.get("prompt")
276
- session['prompt'][instrument] = prompt
277
- # sdl_module = utils.parse_functions(find_instrument_by_name(f'deck.{instrument}'), doc_string=True)
278
- sdl_module = global_config.deck_snapshot.get(instrument, {})
279
- empty_script = Script(author=session.get('user'))
280
- if enable_llm and agent is None:
281
- try:
282
- model = current_app.config["LLM_MODEL"]
283
- server = current_app.config["LLM_SERVER"]
284
- module = current_app.config["MODULE"]
285
- from ivoryos.utils.llm_agent import LlmAgent
286
- agent = LlmAgent(host=server, model=model, output_path=os.path.dirname(os.path.abspath(module)))
287
- except Exception as e:
288
- flash(e.__str__())
289
- return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True)), 400
290
- action_list = agent.generate_code(sdl_module, prompt)
291
- for action in action_list:
292
- action['instrument'] = instrument
293
- action['return'] = ''
294
- if "args" not in action:
295
- action['args'] = {}
296
- if "arg_types" not in action:
297
- action['arg_types'] = {}
298
- empty_script.add_action(action)
299
- utils.post_script_file(empty_script)
300
- return redirect(url_for("design.experiment_builder", instrument=instrument, use_llm=True))
301
-
302
-
303
- @design.route("/design/campaign", methods=['GET', 'POST'])
98
+ @design.route("/draft/meta", methods=["PATCH"])
304
99
  @login_required
305
- def experiment_run():
100
+ def update_script_meta():
306
101
  """
307
- .. :quickref: Workflow Execution; Execute/iterate the workflow
308
-
309
- .. http:get:: /design/campaign
102
+ .. :quickref: Workflow Design; update the script metadata.
310
103
 
311
- Compile the workflow and load the experiment execution interface.
104
+ .. http:patch:: /draft/meta
312
105
 
313
- .. http:post:: /design/campaign
106
+ Update the script metadata, including the script name and status. If the script name is provided,
107
+ it saves the script with that name. If the status is "finished", it finalizes the script.
314
108
 
315
- Start workflow execution
109
+ :form name: The name to save the script as.
110
+ :form status: The status of the script (e.g., "finished").
316
111
 
112
+ :status 200: Successfully updated the script metadata.
317
113
  """
318
- deck = global_config.deck
319
- script = utils.get_script_file()
320
-
321
- # script.sort_actions() # handled in update list
322
- off_line = current_app.config["OFF_LINE"]
323
- deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
324
- # if not off_line and deck is None:
325
- # # print("loading deck")
326
- # module = current_app.config.get('MODULE', '')
327
- # deck = sys.modules[module] if module else None
328
- # script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
329
- design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
330
- config_preview = []
331
- config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
332
- try:
333
- # todo
334
- exec_string = script.python_script if script.python_script else script.compile(current_app.config['SCRIPT_FOLDER'])
335
- # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
336
- # print(exec_string)
337
- except Exception as e:
338
- flash(e.__str__())
339
- # handle api request
340
- if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
341
- return jsonify({"error": e.__str__()})
342
- else:
343
- return redirect(url_for("design.experiment_builder"))
344
-
345
- config_file = request.args.get("filename")
346
- config = []
347
- if config_file:
348
- session['config_file'] = config_file
349
- filename = session.get("config_file")
350
- if filename:
351
- # config_preview = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
352
- config = list(csv.DictReader(open(os.path.join(current_app.config['CSV_FOLDER'], filename))))
353
- config_preview = config[1:]
354
- arg_type = config.pop(0) # first entry is types
355
- try:
356
- for key, func_str in exec_string.items():
357
- exec(func_str)
358
- line_collection = script.convert_to_lines(exec_string)
359
-
360
- except Exception:
361
- flash(f"Please check {key} syntax!!")
362
- return redirect(url_for("design.experiment_builder"))
363
- # runner.globals_dict.update(globals())
364
- run_name = script.name if script.name else "untitled"
365
-
366
- dismiss = session.get("dismiss", None)
114
+ data = request.get_json()
367
115
  script = utils.get_script_file()
368
- no_deck_warning = False
369
-
370
- _, return_list = script.config_return()
371
- config_list, config_type_list = script.config("script")
372
- # config = script.config("script")
373
- data_list = os.listdir(current_app.config['DATA_FOLDER'])
374
- data_list.remove(".gitkeep") if ".gitkeep" in data_list else data_list
375
- if deck is None:
376
- no_deck_warning = True
377
- flash(f"No deck is found, import {script.deck}")
378
- elif script.deck:
379
- is_deck_match = script.deck == deck.__name__ or script.deck == \
380
- os.path.splitext(os.path.basename(deck.__file__))[0]
381
- if not is_deck_match:
382
- flash(f"This script is not compatible with current deck, import {script.deck}")
383
- if request.method == "POST":
384
- bo_args = None
385
- compiled = False
386
- if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
387
- payload_json = request.get_json()
388
- compiled = True
389
- if "kwargs" in payload_json:
390
- config = payload_json["kwargs"]
391
- elif "parameters" in payload_json:
392
- bo_args = payload_json
393
- repeat = payload_json.pop("repeat", None)
116
+ if 'name' in data:
117
+ run_name = data.get("name")
118
+ exist_script = Script.query.get(run_name)
119
+ if exist_script is None:
120
+ script.save_as(run_name)
121
+ utils.post_script_file(script)
122
+ return jsonify(success=True)
394
123
  else:
395
- if "bo" in request.form:
396
- bo_args = request.form.to_dict()
397
- if "online-config" in request.form:
398
- config = utils.web_config_entry_wrapper(request.form.to_dict(), config_list)
399
- repeat = request.form.get('repeat', None)
124
+ flash("Script name is already exist in database")
125
+ return jsonify(success=False)
400
126
 
401
- try:
402
- datapath = current_app.config["DATA_FOLDER"]
403
- run_name = script.validate_function_name(run_name)
404
- runner.run_script(script=script, run_name=run_name, config=config, bo_args=bo_args,
405
- logger=g.logger, socketio=g.socketio, repeat_count=repeat,
406
- output_path=datapath, compiled=compiled,
407
- current_app=current_app._get_current_object()
408
- )
409
- if utils.check_config_duplicate(config):
410
- flash(f"WARNING: Duplicate in config entries.")
411
- except Exception as e:
412
- if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
413
- return jsonify({"error": e.__str__()})
414
- else:
415
- flash(e)
416
- if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
417
- # wait to get a workflow ID
418
- while not global_config.runner_status:
419
- time.sleep(1)
420
- return jsonify({"status": "task started", "task_id": global_config.runner_status.get("id")})
421
- else:
422
- return render_template('experiment_run.html', script=script.script_dict, filename=filename,
423
- dot_py=exec_string, line_collection=line_collection,
424
- return_list=return_list, config_list=config_list, config_file_list=config_file_list,
425
- config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
426
- no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
427
- history=deck_list, pause_status=runner.pause_status())
127
+ if 'status' in data:
128
+ if data['status'] == "finished":
129
+ script.finalize()
130
+ utils.post_script_file(script)
131
+ return jsonify(success=True)
132
+ return jsonify(success=False)
428
133
 
429
134
 
430
- @design.route("/design/script/toggle/<stype>")
135
+ @design.route("/draft/ui-state", methods=["PATCH"])
431
136
  @login_required
432
- def toggle_script_type(stype=None):
137
+ def update_ui_state():
433
138
  """
434
- .. :quickref: Workflow Design; toggle the experimental phase for design canvas.
139
+ .. :quickref: Workflow Design; update the UI state for the design canvas.
435
140
 
436
- .. http:get:: /design/script/toggle/<stype>
141
+ .. http:patch:: /draft/ui-state
437
142
 
438
- :status 200: and then redirects to :http:get:`/design/script`
439
-
440
- """
441
- script = utils.get_script_file()
442
- script.editing_type = stype
443
- utils.post_script_file(script)
444
- return redirect(url_for('design.experiment_builder'))
143
+ Update the UI state for the design canvas, including showing code overlays, setting editing types,
144
+ and handling deck selection.
445
145
 
146
+ :form show_code: Whether to show the code overlay (true/false).
147
+ :form editing_type: The type of editing to set (prep, script, cleanup).
148
+ :form autofill: Whether to enable autofill for the instrument panel (true/false).
149
+ :form deck_name: The name of the deck to select.
446
150
 
447
- @design.route("/updateList", methods=['POST'])
448
- @login_required
449
- def update_list():
450
- order = request.form['order']
451
- script = utils.get_script_file()
452
- script.currently_editing_order = order.split(",", len(script.currently_editing_script))
453
- script.sort_actions()
454
- exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
455
- utils.post_script_file(script)
456
- session['python_code'] = exec_string
457
-
458
- return jsonify({'success': True})
459
-
151
+ :status 200: Updates the UI state and returns a success message.
152
+ """
153
+ data = request.get_json()
460
154
 
461
- @design.route("/toggle_show_code", methods=["POST"])
462
- def toggle_show_code():
463
- session["show_code"] = not session.get("show_code", False)
464
- return redirect(request.referrer or url_for("design.experiment_builder"))
155
+ if "show_code" in data:
156
+ session["show_code"] = bool(data["show_code"])
157
+ return jsonify({"success": True})
158
+ if "editing_type" in data:
159
+ stype = data.get("editing_type")
465
160
 
161
+ script = utils.get_script_file()
162
+ script.editing_type = stype
163
+ utils.post_script_file(script)
466
164
 
467
- # --------------------handle all the import/export and download/upload--------------------------
468
- @design.route("/design/clear")
165
+ # Re-render only the part of the page you want to update
166
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
167
+ rendered_html = render_template("components/canvas.html", script=script, buttons_dict=design_buttons)
168
+ return jsonify({"html": rendered_html})
169
+
170
+ if "autofill" in data:
171
+ script = utils.get_script_file()
172
+ instrument = data.get("instrument", '')
173
+ autofill = data.get("autofill", False)
174
+ session['autofill'] = autofill
175
+ _, forms = _create_forms(instrument, script, autofill)
176
+ rendered_html = render_template("components/methods_panel.html", forms=forms, script=script, instrument=instrument)
177
+ return jsonify({"html": rendered_html})
178
+
179
+ if "deck_name" in data:
180
+ pkl_name = data.get('deck_name', "")
181
+ script = utils.get_script_file()
182
+ session['pseudo_deck'] = pkl_name
183
+ deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
184
+
185
+ if script.deck is None or script.isEmpty():
186
+ script.deck = pkl_name.split('.')[0]
187
+ utils.post_script_file(script)
188
+ elif script.deck and not script.deck == pkl_name.split('.')[0]:
189
+ flash(f"Choose the deck with name {script.deck}")
190
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pkl_name)
191
+ pseudo_deck = utils.load_deck(pseudo_deck_path)
192
+ deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
193
+ deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
194
+ html = render_template("components/sidebar.html", history=deck_list,
195
+ defined_variables=deck_variables, local_variables = global_config.defined_variables)
196
+ return jsonify({"html": html})
197
+ return jsonify({"error": "Invalid request"}), 400
198
+
199
+
200
+ # @design.route("/draft/steps/order", methods=['POST'])
201
+ # @login_required
202
+ # def update_list():
203
+ # """
204
+ # .. :quickref: Workflow Design Steps; update the order of steps in the design canvas when reordering steps.
205
+ #
206
+ # .. http:post:: /draft/steps/order
207
+ #
208
+ # Update the order of steps in the design canvas when reordering steps.
209
+ #
210
+ # :form order: A comma-separated string representing the new order of steps.
211
+ # :status 200: Successfully updated the order of steps.
212
+ # """
213
+ # order = request.form['order']
214
+ # script = utils.get_script_file()
215
+ # script.currently_editing_order = order.split(",", len(script.currently_editing_script))
216
+ # script.sort_actions()
217
+ # exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
218
+ # utils.post_script_file(script)
219
+ # session['python_code'] = exec_string
220
+ #
221
+ # return jsonify({'success': True})
222
+
223
+
224
+
225
+ @design.route("/draft", methods=['DELETE'])
469
226
  @login_required
470
- def clear():
227
+ def clear_draft():
471
228
  """
472
229
  .. :quickref: Workflow Design; clear the design canvas.
473
230
 
474
- .. http:get:: /design/clear
231
+ .. http:delete:: /draft
475
232
 
476
- :form prompt: user's prompt
477
- :status 200: clear canvas and then redirects to :http:get:`/design/script`
233
+ :status 200: clear canvas
478
234
  """
479
235
  deck = global_config.deck
480
- pseudo_name = session.get("pseudo_deck", "")
481
236
  if deck:
482
237
  deck_name = os.path.splitext(os.path.basename(deck.__file__))[
483
238
  0] if deck.__name__ == "__main__" else deck.__name__
484
- elif pseudo_name:
485
- deck_name = pseudo_name
486
239
  else:
487
- deck_name = ''
240
+ deck_name = session.get("pseudo_deck", "")
488
241
  script = Script(deck=deck_name, author=session.get('username'))
489
242
  utils.post_script_file(script)
490
- return redirect(url_for("design.experiment_builder"))
491
-
492
-
493
- @design.route("/design/import/pseudo", methods=['POST'])
494
- @login_required
495
- def import_pseudo():
496
- """
497
- .. :quickref: Workflow Design; Import pseudo deck from deck history
243
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
244
+ session['python_code'] = exec_string
245
+ return jsonify({'success': True})
498
246
 
499
- .. http:post:: /design/import/pseudo
500
247
 
501
- :form pkl_name: pseudo deck name
502
- :status 302: load pseudo deck and then redirects to :http:get:`/design/script`
503
- """
504
- pkl_name = request.form.get('pkl_name')
505
- script = utils.get_script_file()
506
- session['pseudo_deck'] = pkl_name
507
248
 
508
- if script.deck is None or script.isEmpty():
509
- script.deck = pkl_name.split('.')[0]
510
- utils.post_script_file(script)
511
- elif script.deck and not script.deck == pkl_name.split('.')[0]:
512
- flash(f"Choose the deck with name {script.deck}")
513
- return redirect(url_for("design.experiment_builder"))
514
249
 
515
250
 
516
- @design.route('/design/uploads', methods=['POST'])
517
- @login_required
518
- def upload():
251
+ @design.route("/draft/submit_python", methods=["POST"])
252
+ def submit_script():
519
253
  """
520
- .. :quickref: Workflow Execution; upload a workflow config file (.CSV)
521
-
522
- .. http:post:: /design/uploads
254
+ .. :quickref: Workflow Design; convert Python to workflow script
523
255
 
524
- :form file: workflow CSV config file
525
- :status 302: save csv file and then redirects to :http:get:`/design/campaign`
526
- """
527
- if request.method == "POST":
528
- f = request.files['file']
529
- if 'file' not in request.files:
530
- flash('No file part')
531
- if f.filename.split('.')[-1] == "csv":
532
- filename = secure_filename(f.filename)
533
- f.save(os.path.join(current_app.config['CSV_FOLDER'], filename))
534
- session['config_file'] = filename
535
- return redirect(url_for("design.experiment_run"))
536
- else:
537
- flash("Config file is in csv format")
538
- return redirect(url_for("design.experiment_run"))
256
+ .. http:post:: /design/submit_python
539
257
 
258
+ Convert a Python script to a workflow script and save it in the database.
540
259
 
541
- @design.route('/design/workflow/download/<filename>')
542
- @login_required
543
- def download_results(filename):
260
+ :form workflow_name: workflow name
261
+ :form script: main script
262
+ :form prep: prep script
263
+ :form cleanup: post script
264
+ :status 200: clear canvas
544
265
  """
545
- .. :quickref: Workflow Design; download a workflow data file
546
-
547
- .. http:get:: /design/workflow/download/<filename>
266
+ deck = global_config.deck
267
+ deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
268
+ script = Script(author=session.get('user'), deck=deck_name)
269
+ script_collection = request.get_json()
270
+ workflow_name = script_collection.pop("workflow_name")
271
+ script.python_script = script_collection
272
+ # todo check script format
273
+ script.name = workflow_name
274
+ result = {}
275
+ for stype, py_str in script_collection.items():
276
+ try:
277
+ card = convert_to_cards(py_str)
278
+ script.script_dict[stype] = card
279
+ result[stype] = "success"
280
+ except Exception as e:
281
+ result[stype] = f"failed to transcript, but function can still run. error: {str(e)}"
282
+ utils.post_script_file(script)
283
+ status = publish()
284
+ return jsonify({"script": result, "db": status}), 200
548
285
 
549
- """
550
- filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
551
- return send_file(os.path.abspath(filepath), as_attachment=True)
552
286
 
553
287
 
554
- @design.route('/design/load_json', methods=['POST'])
288
+ @design.post("/draft/instruments/<string:instrument>")
555
289
  @login_required
556
- def load_json():
290
+ def methods_handler(instrument: str = ''):
557
291
  """
558
- .. :quickref: Workflow Design Ext; upload a workflow design file (.JSON)
559
-
560
- .. http:post:: /load_json
292
+ .. :quickref: Workflow Design; handle methods of a specific instrument
561
293
 
562
- :form file: workflow design JSON file
563
- :status 302: load pseudo deck and then redirects to :http:get:`/design/script`
564
- """
565
- if request.method == "POST":
566
- f = request.files['file']
567
- if 'file' not in request.files:
568
- flash('No file part')
569
- if f.filename.endswith("json"):
570
- script_dict = json.load(f)
571
- utils.post_script_file(script_dict, is_dict=True)
572
- else:
573
- flash("Script file need to be JSON file")
574
- return redirect(url_for("design.experiment_builder"))
575
-
576
-
577
- @design.route('/design/script/download/<filetype>')
578
- @login_required
579
- def download(filetype):
580
- """
581
- .. :quickref: Workflow Design Ext; download a workflow design file
294
+ .. http:post:: /draft/instruments/<string:instrument>
582
295
 
583
- .. http:get:: /design/script/download/<filetype>
296
+ Add methods for a specific instrument in the workflow design.
584
297
 
298
+ :param instrument: The name of the instrument to handle methods for.
299
+ :type instrument: str
300
+ :status 200: Render the methods for the specified instrument.
585
301
  """
586
302
  script = utils.get_script_file()
587
- run_name = script.name if script.name else "untitled"
588
- if filetype == "configure":
589
- filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}_config.csv")
590
- with open(filepath, 'w', newline='') as f:
591
- writer = csv.writer(f)
592
- cfg, cfg_types = script.config("script")
593
- writer.writerow(cfg)
594
- writer.writerow(list(cfg_types.values()))
595
- elif filetype == "script":
596
- script.sort_actions()
597
- json_object = json.dumps(script.as_dict())
598
- filepath = os.path.join(current_app.config['SCRIPT_FOLDER'], f"{run_name}.json")
599
- with open(filepath, "w") as outfile:
600
- outfile.write(json_object)
601
- elif filetype == "python":
602
- filepath = os.path.join(current_app.config["SCRIPT_FOLDER"], f"{run_name}.py")
603
- else:
604
- return "Unsupported file type", 400
605
- return send_file(os.path.abspath(filepath), as_attachment=True)
606
-
607
-
608
- @design.route("/design/step/edit/<uuid>", methods=['GET', 'POST'])
609
- @login_required
610
- def edit_action(uuid: str):
611
- """
612
- .. :quickref: Workflow Design; edit parameters of an action step on canvas
613
-
614
- .. http:get:: /design/step/edit/<uuid>
303
+ pseudo_deck_name = session.get('pseudo_deck', '')
304
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
305
+ off_line = current_app.config["OFF_LINE"]
306
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
307
+ autofill = session.get('autofill', False)
615
308
 
616
- Load parameter form of an action step
309
+ functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
617
310
 
618
- .. http:post:: /design/step/edit/<uuid>
311
+ success = True
312
+ msg = ""
313
+ if "hidden_name" in request.form:
314
+ method_name = request.form.get("hidden_name", None)
315
+ form = forms.get(method_name) if forms else None
316
+ insert_position = request.form.get("drop_target_id", None)
317
+ if form:
318
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
319
+ if form.validate_on_submit():
320
+ function_name = kwargs.pop("hidden_name")
321
+ save_data = kwargs.pop('return', '')
322
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
619
323
 
620
- :param uuid: The step's uuid
621
- :type uuid: str
324
+ # todo
325
+ print(primitive_arg_types)
622
326
 
623
- :form dynamic form: workflow step dynamic inputs
624
- :status 302: save changes and then redirects to :http:get:`/design/script`
625
- """
626
- script = utils.get_script_file()
627
- action = script.find_by_uuid(uuid)
628
- session['edit_action'] = action
629
-
630
- if request.method == "POST" and action is not None:
631
- forms = create_form_from_action(action, script=script)
632
- if "back" not in request.form:
633
- kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
634
- # print(kwargs)
635
- if forms and forms.validate_on_submit():
636
- save_as = kwargs.pop('return', '')
327
+ script.eval_list(kwargs, primitive_arg_types)
637
328
  kwargs = script.validate_variables(kwargs)
638
- # try:
639
- script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
640
- # except Exception as e:
329
+ action = {"instrument": instrument, "action": function_name,
330
+ "args": kwargs,
331
+ "return": save_data,
332
+ 'arg_types': primitive_arg_types}
333
+ script.add_action(action=action, insert_position=insert_position)
641
334
  else:
642
- flash(forms.errors)
643
- session.pop('edit_action')
644
- return redirect(url_for('design.experiment_builder'))
645
-
646
-
647
- @design.route("/design/step/delete/<id>")
648
- @login_required
649
- def delete_action(id: int):
650
- """
651
- .. :quickref: Workflow Design; delete an action step on canvas
652
-
653
- .. http:get:: /design/step/delete/<id>
654
-
655
- :param id: The step number id
656
- :type id: int
657
-
658
- :status 302: save changes and then redirects to :http:get:`/design/script`
659
- """
660
- back = request.referrer
661
- script = utils.get_script_file()
662
- script.delete_action(id)
335
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
336
+ success = False
337
+ elif "builtin_name" in request.form:
338
+ function_name = request.form.get("builtin_name")
339
+ form = forms.get(function_name) if forms else None
340
+ insert_position = request.form.get("drop_target_id", None)
341
+ if form:
342
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
343
+ if form.validate_on_submit():
344
+ logic_type = kwargs.pop('builtin_name')
345
+ if 'variable' in kwargs:
346
+ try:
347
+ script.add_variable(insert_position=insert_position, **kwargs)
348
+ except ValueError:
349
+ success = False
350
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
351
+ else:
352
+ script.add_logic_action(logic_type=logic_type, insert_position=insert_position, **kwargs)
353
+ else:
354
+ success = False
355
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
356
+ elif "workflow_name" in request.form:
357
+ workflow_name = request.form.get("workflow_name")
358
+ form = forms.get(workflow_name) if forms else None
359
+ insert_position = request.form.get("drop_target_id", None)
360
+ if form:
361
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
362
+ if form.validate_on_submit():
363
+ save_data = kwargs.pop('return', '')
364
+ primitive_arg_types = utils.get_arg_type(kwargs, functions[workflow_name])
365
+ script.eval_list(kwargs, primitive_arg_types)
366
+ kwargs = script.validate_variables(kwargs)
367
+ action = {"instrument": instrument, "action": workflow_name,
368
+ "args": kwargs,
369
+ "return": save_data,
370
+ 'arg_types': primitive_arg_types}
371
+ script.add_action(action=action, insert_position=insert_position)
372
+ script.add_workflow(**kwargs, insert_position=insert_position)
373
+ else:
374
+ success = False
375
+ msg = [f"{field}: {', '.join(messages)}" for field, messages in form.errors.items()]
663
376
  utils.post_script_file(script)
664
- return redirect(back)
377
+ exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
378
+ session['python_code'] = exec_string
379
+ design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
380
+ html = render_template("components/canvas_main.html", script=script, buttons_dict=design_buttons)
381
+ return jsonify({"html": html, "success": success, "error": msg})
665
382
 
666
383
 
667
- @design.route("/design/step/duplicate/<id>")
384
+ @design.get("/draft/instruments", strict_slashes=False)
385
+ @design.get("/draft/instruments/<string:instrument>")
668
386
  @login_required
669
- def duplicate_action(id: int):
387
+ def get_operation_sidebar(instrument: str = ''):
670
388
  """
671
- .. :quickref: Workflow Design; duplicate an action step on canvas
389
+ .. :quickref: Workflow Design; handle methods of a specific instrument
672
390
 
673
- .. http:get:: /design/step/duplicate/<id>
391
+ .. http:get:: /design/instruments/<string:instrument>
674
392
 
675
- :param id: The step number id
676
- :type id: int
393
+ :param instrument: The name of the instrument to handle methods for.
394
+ :type instrument: str
677
395
 
678
- :status 302: save changes and then redirects to :http:get:`/design/script`
396
+ :status 200: Render the methods for the specified instrument.
679
397
  """
680
- back = request.referrer
681
398
  script = utils.get_script_file()
682
- script.duplicate_action(id)
683
- utils.post_script_file(script)
684
- return redirect(back)
685
-
686
-
687
- # ---- HTTP API Endpoints ----
688
-
689
- @design.route("/api/runner/status", methods=["GET"])
690
- def runner_status():
691
- """
692
- .. :quickref: Workflow Design; get the execution status
693
-
694
- .. http:get:: /api/runner/status
695
-
696
- :status 200: status
697
- """
698
- runner_busy = global_config.runner_lock.locked()
699
- status = {"busy": runner_busy}
700
- task_status = global_config.runner_status
701
- current_step = {}
702
- # print(task_status)
703
- if task_status is not None:
704
- task_type = task_status["type"]
705
- task_id = task_status["id"]
706
- if task_type == "task":
707
- step = SingleStep.query.get(task_id)
708
- current_step = step.as_dict()
709
- if task_type == "workflow":
710
- workflow = WorkflowRun.query.get(task_id)
711
- if workflow is not None:
712
- latest_step = WorkflowStep.query.filter_by(workflow_id=workflow.id).order_by(WorkflowStep.start_time.desc()).first()
713
- if latest_step is not None:
714
- current_step = latest_step.as_dict()
715
- status["workflow_status"] = {"workflow_info": workflow.as_dict(), "runner_status": runner.get_status()}
716
- status["current_task"] = current_step
717
- return jsonify(status), 200
718
-
719
-
720
-
721
- @design.route("/api/runner/abort_pending", methods=["POST"])
722
- def api_abort_pending():
723
- """
724
- .. :quickref: Workflow Design; abort pending action(s) during execution
725
-
726
- .. http:get:: /api/runner/abort_pending
727
-
728
- :status 200: {"status": "ok"}
729
- """
730
- abort_pending()
731
- return jsonify({"status": "ok"}), 200
732
-
733
- @design.route("/api/runner/abort_current", methods=["POST"])
734
- def api_abort_current():
735
- """
736
- .. :quickref: Workflow Design; abort right after current action during execution
737
-
738
- .. http:get:: /api/runner/abort_current
739
-
740
- :status 200: {"status": "ok"}
741
- """
742
- abort_current()
743
- return jsonify({"status": "ok"}), 200
744
-
745
- @design.route("/api/runner/pause", methods=["POST"])
746
- def api_pause():
747
- """
748
- .. :quickref: Workflow Design; pause during execution
749
-
750
- .. http:get:: /api/runner/pause
751
-
752
- :status 200: {"status": "ok"}
753
- """
754
- msg = pause()
755
- return jsonify({"status": "ok", "pause_status": msg}), 200
756
-
757
- @design.route("/api/runner/retry", methods=["POST"])
758
- def api_retry():
759
- """
760
- .. :quickref: Workflow Design; retry when error occur during execution
761
-
762
- .. http:get:: /api/runner/retry
763
-
764
- :status 200: {"status": "ok"}
765
- """
766
- retry()
767
- return jsonify({"status": "ok, retrying failed step"}), 200
399
+ pseudo_deck_name = session.get('pseudo_deck', '')
400
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
401
+ off_line = current_app.config["OFF_LINE"]
402
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
403
+ autofill = session.get('autofill', False)
768
404
 
405
+ functions, forms = _create_forms(instrument, script, autofill, pseudo_deck)
769
406
 
770
- @design.route("/api/design/submit", methods=["POST"])
771
- def submit_script():
772
- """
773
- .. :quickref: Workflow Design; submit script
407
+ if instrument:
408
+ html = render_template("components/sidebar.html", forms=forms, instrument=instrument, script=script)
409
+ else:
410
+ pseudo_deck_name = session.get('pseudo_deck', '')
411
+ pseudo_deck_path = os.path.join(current_app.config["DUMMY_DECK"], pseudo_deck_name)
412
+ off_line = current_app.config["OFF_LINE"]
413
+ pseudo_deck = utils.load_deck(pseudo_deck_path) if off_line and pseudo_deck_name else None
414
+ if off_line and pseudo_deck is None:
415
+ flash("Choose available deck below.")
416
+ deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
417
+ if not off_line:
418
+ deck_variables = list(global_config.deck_snapshot.keys())
419
+ else:
420
+ deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
421
+ deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
422
+ # edit_action_info = session.get("edit_action")
423
+ html = render_template("components/sidebar.html", off_line=off_line, history=deck_list,
424
+ defined_variables=deck_variables,
425
+ local_variables=global_config.defined_variables)
426
+ return jsonify({"html": html})
774
427
 
775
- .. http:get:: /api/design/submit
776
428
 
777
- :status 200: {"status": "ok"}
778
- """
779
- deck = global_config.deck
780
- deck_name = os.path.splitext(os.path.basename(deck.__file__))[0] if deck.__name__ == "__main__" else deck.__name__
781
- script = Script(author=session.get('user'), deck=deck_name)
782
- script_collection = request.get_json()
783
- script.python_script = script_collection
784
- # todo check script format
785
- utils.post_script_file(script)
786
- return jsonify({"status": "ok"}), 200