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.

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