ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
- docs/source/conf.py +84 -0
- ivoryos/__init__.py +17 -207
- ivoryos/app.py +154 -0
- ivoryos/config.py +1 -0
- ivoryos/optimizer/ax_optimizer.py +191 -0
- ivoryos/optimizer/base_optimizer.py +84 -0
- ivoryos/optimizer/baybe_optimizer.py +193 -0
- ivoryos/optimizer/nimo_optimizer.py +173 -0
- ivoryos/optimizer/registry.py +11 -0
- ivoryos/routes/auth/auth.py +43 -14
- ivoryos/routes/auth/templates/change_password.html +32 -0
- ivoryos/routes/control/control.py +101 -366
- ivoryos/routes/control/control_file.py +33 -0
- ivoryos/routes/control/control_new_device.py +152 -0
- ivoryos/routes/control/templates/controllers.html +193 -0
- ivoryos/routes/control/templates/controllers_new.html +112 -0
- ivoryos/routes/control/utils.py +40 -0
- ivoryos/routes/data/data.py +197 -0
- ivoryos/routes/data/templates/components/step_card.html +78 -0
- ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
- ivoryos/routes/data/templates/workflow_view.html +360 -0
- ivoryos/routes/design/__init__.py +4 -0
- ivoryos/routes/design/design.py +348 -657
- ivoryos/routes/design/design_file.py +68 -0
- ivoryos/routes/design/design_step.py +171 -0
- ivoryos/routes/design/templates/components/action_form.html +53 -0
- ivoryos/routes/design/templates/components/actions_panel.html +25 -0
- ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
- ivoryos/routes/design/templates/components/canvas.html +5 -0
- ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
- ivoryos/routes/design/templates/components/canvas_header.html +75 -0
- ivoryos/routes/design/templates/components/canvas_main.html +39 -0
- ivoryos/routes/design/templates/components/deck_selector.html +10 -0
- ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
- ivoryos/routes/design/templates/components/info_modal.html +318 -0
- ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
- ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
- ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
- ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
- ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
- ivoryos/routes/design/templates/components/modals.html +6 -0
- ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
- ivoryos/routes/design/templates/components/sidebar.html +15 -0
- ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
- ivoryos/routes/design/templates/experiment_builder.html +44 -0
- ivoryos/routes/execute/__init__.py +0 -0
- ivoryos/routes/execute/execute.py +377 -0
- ivoryos/routes/execute/execute_file.py +78 -0
- ivoryos/routes/execute/templates/components/error_modal.html +20 -0
- ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
- ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
- ivoryos/routes/execute/templates/components/run_panel.html +9 -0
- ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
- ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
- ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
- ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
- ivoryos/routes/execute/templates/experiment_run.html +30 -0
- ivoryos/routes/library/__init__.py +0 -0
- ivoryos/routes/library/library.py +157 -0
- ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
- ivoryos/routes/main/main.py +31 -3
- ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
- ivoryos/server.py +180 -0
- ivoryos/socket_handlers.py +52 -0
- ivoryos/static/ivoryos_logo.png +0 -0
- ivoryos/static/js/action_handlers.js +384 -0
- ivoryos/static/js/db_delete.js +23 -0
- ivoryos/static/js/script_metadata.js +39 -0
- ivoryos/static/js/socket_handler.js +40 -5
- ivoryos/static/js/sortable_design.js +107 -56
- ivoryos/static/js/ui_state.js +114 -0
- ivoryos/templates/base.html +67 -8
- ivoryos/utils/bo_campaign.py +180 -3
- ivoryos/utils/client_proxy.py +267 -36
- ivoryos/utils/db_models.py +300 -65
- ivoryos/utils/decorators.py +34 -0
- ivoryos/utils/form.py +63 -29
- ivoryos/utils/global_config.py +34 -1
- ivoryos/utils/nest_script.py +314 -0
- ivoryos/utils/py_to_json.py +295 -0
- ivoryos/utils/script_runner.py +599 -165
- ivoryos/utils/serilize.py +201 -0
- ivoryos/utils/task_runner.py +71 -21
- ivoryos/utils/utils.py +50 -6
- ivoryos/version.py +1 -1
- ivoryos-1.4.4.dist-info/METADATA +263 -0
- ivoryos-1.4.4.dist-info/RECORD +119 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
- tests/unit/test_type_conversion.py +42 -0
- tests/unit/test_util.py +3 -0
- ivoryos/routes/control/templates/control/controllers.html +0 -78
- ivoryos/routes/control/templates/control/controllers_home.html +0 -55
- ivoryos/routes/control/templates/control/controllers_new.html +0 -89
- ivoryos/routes/database/database.py +0 -306
- ivoryos/routes/database/templates/database/step_card.html +0 -7
- ivoryos/routes/database/templates/database/workflow_view.html +0 -130
- ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
- ivoryos/routes/design/templates/design/experiment_run.html +0 -558
- ivoryos-1.0.9.dist-info/METADATA +0 -218
- ivoryos-1.0.9.dist-info/RECORD +0 -61
- /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
- /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
- /ivoryos/routes/{database → data}/__init__.py +0 -0
- /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
- {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
{% extends 'base.html' %}
|
|
2
|
-
{% block title %}IvoryOS | New devices{% endblock %}
|
|
3
|
-
|
|
4
|
-
{% block body %}
|
|
5
|
-
<div class="row">
|
|
6
|
-
<div class="col-xl-4 col-lg-4 col-md-6 mb-4 ">
|
|
7
|
-
<h5>Available Python API</h5>
|
|
8
|
-
<hr>
|
|
9
|
-
{% for instrument in api_variables %}
|
|
10
|
-
<div class="bg-white rounded shadow-sm position-relative">
|
|
11
|
-
<h5 class="p-3 controller-card">
|
|
12
|
-
<a href="{{ url_for('control.new_controller', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a>
|
|
13
|
-
</h5>
|
|
14
|
-
</div>
|
|
15
|
-
{% endfor %}
|
|
16
|
-
<div class="bg-white rounded shadow-sm position-relative">
|
|
17
|
-
<h5 class="p-3 controller-card">
|
|
18
|
-
<a data-bs-toggle="modal" href="#importAPI" class="stretched-link"><i class="bi bi-folder-plus"></i> Import API</a>
|
|
19
|
-
</h5>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="col-xl-5 col-lg-5 col-md-6 mb-4 ">
|
|
23
|
-
{% if device %}
|
|
24
|
-
<h5>Connecting</h5><hr>
|
|
25
|
-
<form role="form" method='POST' name="init" action="{{ url_for('control.new_controller', instrument=instrument) }}">
|
|
26
|
-
<div class="form-group">
|
|
27
|
-
<div class="input-group mb-3">
|
|
28
|
-
<span class="input-group-text" >Name this device</span>
|
|
29
|
-
<input class="form-control" type="text" id="device_name" name="device_name" aria-labelledby="nameHelpBlock" placeholder="e.g. {{device.__name__}}_1" >
|
|
30
|
-
<div id="nameHelpBlock" class="form-text">
|
|
31
|
-
Name your instrument, avoid names that are defined on the right
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
{% for arg in device.__init__.__annotations__ %}
|
|
35
|
-
{% if not arg == "return" %}
|
|
36
|
-
<div class="input-group mb-3">
|
|
37
|
-
<span class="input-group-text" >{{arg}}</span>
|
|
38
|
-
<input class="form-control" type="text" id="{{arg}}" name="{{arg}}"
|
|
39
|
-
placeholder="{{device.__init__.__annotations__[arg].__name__}}"
|
|
40
|
-
value="{{args.parameters[arg].default if not args.parameters[arg].default.__name__ == '_empty' else ''}}">
|
|
41
|
-
{% if device.__init__.__annotations__[arg].__module__ is not in ["builtins", "typing"] %}
|
|
42
|
-
<a role="button" href="{{ url_for('control.new_controller', instrument=device.__init__.__annotations__[arg].__name__) }}" class="btn btn-secondary">initialize {{device.__init__.__annotations__[arg].__name__}} first</a>
|
|
43
|
-
{% endif %}
|
|
44
|
-
</div>
|
|
45
|
-
{% endif %}
|
|
46
|
-
{% endfor %}
|
|
47
|
-
<button type="submit" class="btn btn-dark">Connect</button>
|
|
48
|
-
</div>
|
|
49
|
-
</form>
|
|
50
|
-
{% endif %}
|
|
51
|
-
</div>
|
|
52
|
-
<div class="col-xl-3 col-lg-3 col-md-6 mb-4">
|
|
53
|
-
<h5>Defined Instruments</h5><hr>
|
|
54
|
-
{% if defined_variables %}
|
|
55
|
-
<ul class="list-group">
|
|
56
|
-
{% for instrument in defined_variables %}
|
|
57
|
-
<li class="list-group-item">{{instrument}}</li>
|
|
58
|
-
{% endfor %}
|
|
59
|
-
</ul>
|
|
60
|
-
{% endif %}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<div class="modal fade" id="importAPI" tabindex="-1" aria-labelledby="importModal" aria-hidden="true" >
|
|
66
|
-
<div class="modal-dialog">
|
|
67
|
-
<div class="modal-content">
|
|
68
|
-
<div class="modal-header">
|
|
69
|
-
<h1 class="modal-title fs-5" id="importModal">Import API by file path</h1>
|
|
70
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
71
|
-
</div>
|
|
72
|
-
<form method="POST" action="{{ url_for('control.import_api') }}" enctype="multipart/form-data">
|
|
73
|
-
<div class="modal-body">
|
|
74
|
-
<h5>input manually</h5>
|
|
75
|
-
<div class="input-group mb-3">
|
|
76
|
-
<label class="input-group-text" for="filepath">File Path:</label>
|
|
77
|
-
<input type="text" class="form-control" name="filepath" id="filepath">
|
|
78
|
-
</div>
|
|
79
|
-
<div class="modal-footer">
|
|
80
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button>
|
|
81
|
-
<button type="submit" class="btn btn-primary"> Save </button>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</form>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
|
|
89
|
-
{% endblock %}
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app, jsonify
|
|
2
|
-
from flask_login import login_required
|
|
3
|
-
|
|
4
|
-
from ivoryos.utils.db_models import Script, db, WorkflowRun, WorkflowStep
|
|
5
|
-
from ivoryos.utils.utils import get_script_file, post_script_file
|
|
6
|
-
|
|
7
|
-
database = Blueprint('database', __name__, template_folder='templates/database')
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@database.route("/database/scripts/edit/<script_name>")
|
|
12
|
-
@login_required
|
|
13
|
-
def edit_workflow(script_name:str):
|
|
14
|
-
"""
|
|
15
|
-
.. :quickref: Database; load workflow script to canvas
|
|
16
|
-
|
|
17
|
-
load the selected workflow to the design canvas
|
|
18
|
-
|
|
19
|
-
.. http:get:: /database/scripts/edit/<script_name>
|
|
20
|
-
|
|
21
|
-
:param script_name: script name
|
|
22
|
-
:type script_name: str
|
|
23
|
-
:status 302: redirect to :http:get:`/ivoryos/design/script/`
|
|
24
|
-
"""
|
|
25
|
-
row = Script.query.get(script_name)
|
|
26
|
-
script = Script(**row.as_dict())
|
|
27
|
-
post_script_file(script)
|
|
28
|
-
pseudo_name = session.get("pseudo_deck", "")
|
|
29
|
-
off_line = current_app.config["OFF_LINE"]
|
|
30
|
-
if off_line and pseudo_name and not script.deck == pseudo_name:
|
|
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
|
-
})
|
|
37
|
-
return redirect(url_for('design.experiment_builder'))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@database.route("/database/scripts/delete/<script_name>")
|
|
41
|
-
@login_required
|
|
42
|
-
def delete_workflow(script_name: str):
|
|
43
|
-
"""
|
|
44
|
-
.. :quickref: Database; delete workflow
|
|
45
|
-
|
|
46
|
-
delete workflow from database
|
|
47
|
-
|
|
48
|
-
.. http:get:: /database/scripts/delete/<script_name>
|
|
49
|
-
|
|
50
|
-
:param script_name: workflow name
|
|
51
|
-
:type script_name: str
|
|
52
|
-
:status 302: redirect to :http:get:`/ivoryos/database/scripts/`
|
|
53
|
-
|
|
54
|
-
"""
|
|
55
|
-
Script.query.filter(Script.name == script_name).delete()
|
|
56
|
-
db.session.commit()
|
|
57
|
-
return redirect(url_for('database.load_from_database'))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@database.route("/database/scripts/save")
|
|
61
|
-
@login_required
|
|
62
|
-
def publish():
|
|
63
|
-
"""
|
|
64
|
-
.. :quickref: Database; save workflow to database
|
|
65
|
-
|
|
66
|
-
save workflow to database
|
|
67
|
-
|
|
68
|
-
.. http:get:: /database/scripts/save
|
|
69
|
-
|
|
70
|
-
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
71
|
-
"""
|
|
72
|
-
script = get_script_file()
|
|
73
|
-
if not script.name or not script.deck:
|
|
74
|
-
flash("Deck cannot be empty, try to re-submit deck configuration on the left panel")
|
|
75
|
-
row = Script.query.get(script.name)
|
|
76
|
-
if row and row.status == "finalized":
|
|
77
|
-
flash("This is a protected script, use save as to rename.")
|
|
78
|
-
elif row and not session['user'] == row.author:
|
|
79
|
-
flash("You are not the author, use save as to rename.")
|
|
80
|
-
else:
|
|
81
|
-
db.session.merge(script)
|
|
82
|
-
db.session.commit()
|
|
83
|
-
flash("Saved!")
|
|
84
|
-
return redirect(url_for('design.experiment_builder'))
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@database.route("/database/scripts/finalize")
|
|
88
|
-
@login_required
|
|
89
|
-
def finalize():
|
|
90
|
-
"""
|
|
91
|
-
.. :quickref: Database; finalize the workflow
|
|
92
|
-
|
|
93
|
-
[protected workflow] prevent saving edited workflow to the same workflow name
|
|
94
|
-
|
|
95
|
-
.. http:get:: /finalize
|
|
96
|
-
|
|
97
|
-
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
98
|
-
|
|
99
|
-
"""
|
|
100
|
-
script = get_script_file()
|
|
101
|
-
script.finalize()
|
|
102
|
-
if script.name:
|
|
103
|
-
db.session.merge(script)
|
|
104
|
-
db.session.commit()
|
|
105
|
-
post_script_file(script)
|
|
106
|
-
return redirect(url_for('design.experiment_builder'))
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
@database.route("/database/scripts/", strict_slashes=False)
|
|
110
|
-
@database.route("/database/scripts/<deck_name>")
|
|
111
|
-
@login_required
|
|
112
|
-
def load_from_database(deck_name=None):
|
|
113
|
-
"""
|
|
114
|
-
.. :quickref: Database; database page
|
|
115
|
-
|
|
116
|
-
backend control through http requests
|
|
117
|
-
|
|
118
|
-
.. http:get:: /database/scripts/<deck_name>
|
|
119
|
-
|
|
120
|
-
:param deck_name: filter for deck name
|
|
121
|
-
:type deck_name: str
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
session.pop('edit_action', None) # reset cache
|
|
125
|
-
query = Script.query
|
|
126
|
-
search_term = request.args.get("keyword", None)
|
|
127
|
-
if search_term:
|
|
128
|
-
query = query.filter(Script.name.like(f'%{search_term}%'))
|
|
129
|
-
if deck_name is None:
|
|
130
|
-
temp = Script.query.with_entities(Script.deck).distinct().all()
|
|
131
|
-
deck_list = [i[0] for i in temp]
|
|
132
|
-
else:
|
|
133
|
-
query = query.filter(Script.deck == deck_name)
|
|
134
|
-
deck_list = ["ALL"]
|
|
135
|
-
page = request.args.get('page', default=1, type=int)
|
|
136
|
-
per_page = 10
|
|
137
|
-
|
|
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)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
@database.route("/database/scripts/rename", methods=['POST'])
|
|
151
|
-
@login_required
|
|
152
|
-
def edit_run_name():
|
|
153
|
-
"""
|
|
154
|
-
.. :quickref: Database; edit workflow name
|
|
155
|
-
|
|
156
|
-
edit the name of the current workflow, won't save to the database
|
|
157
|
-
|
|
158
|
-
.. http:post:: database/scripts/rename
|
|
159
|
-
|
|
160
|
-
: form run_name: new workflow name
|
|
161
|
-
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
162
|
-
|
|
163
|
-
"""
|
|
164
|
-
if request.method == "POST":
|
|
165
|
-
run_name = request.form.get("run_name")
|
|
166
|
-
exist_script = Script.query.get(run_name)
|
|
167
|
-
if not exist_script:
|
|
168
|
-
script = get_script_file()
|
|
169
|
-
script.save_as(run_name)
|
|
170
|
-
post_script_file(script)
|
|
171
|
-
else:
|
|
172
|
-
flash("Script name is already exist in database")
|
|
173
|
-
return redirect(url_for("design.experiment_builder"))
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
@database.route("/database/scripts/save_as", methods=['POST'])
|
|
177
|
-
@login_required
|
|
178
|
-
def save_as():
|
|
179
|
-
"""
|
|
180
|
-
.. :quickref: Database; save the run name as
|
|
181
|
-
|
|
182
|
-
save the current workflow script as
|
|
183
|
-
|
|
184
|
-
.. http:post:: /database/scripts/save_as
|
|
185
|
-
|
|
186
|
-
: form run_name: new workflow name
|
|
187
|
-
:status 302: redirect to :http:get:`/ivoryos/experiment/build/`
|
|
188
|
-
|
|
189
|
-
"""
|
|
190
|
-
if request.method == "POST":
|
|
191
|
-
run_name = request.form.get("run_name")
|
|
192
|
-
register_workflow = request.form.get("register_workflow")
|
|
193
|
-
exist_script = Script.query.get(run_name)
|
|
194
|
-
if not exist_script:
|
|
195
|
-
script = get_script_file()
|
|
196
|
-
script.save_as(run_name)
|
|
197
|
-
script.registered = register_workflow == "on"
|
|
198
|
-
script.author = session.get('user')
|
|
199
|
-
post_script_file(script)
|
|
200
|
-
publish()
|
|
201
|
-
else:
|
|
202
|
-
flash("Script name is already exist in database")
|
|
203
|
-
return redirect(url_for("design.experiment_builder"))
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
# -----------------------------------------------------------
|
|
207
|
-
# ------------------ Workflow logs -----------------------
|
|
208
|
-
# -----------------------------------------------------------
|
|
209
|
-
@database.route('/database/workflows/')
|
|
210
|
-
def list_workflows():
|
|
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())
|
|
220
|
-
search_term = request.args.get("keyword", None)
|
|
221
|
-
if search_term:
|
|
222
|
-
query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
|
|
223
|
-
page = request.args.get('page', default=1, type=int)
|
|
224
|
-
per_page = 10
|
|
225
|
-
|
|
226
|
-
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
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
|
|
241
|
-
|
|
242
|
-
list all workflow logs
|
|
243
|
-
|
|
244
|
-
.. http:get:: /database/workflows/<int:workflow_id>
|
|
245
|
-
|
|
246
|
-
"""
|
|
247
|
-
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
248
|
-
steps = WorkflowStep.query.filter_by(workflow_id=workflow_id).order_by(WorkflowStep.start_time).all()
|
|
249
|
-
|
|
250
|
-
# Use full objects for template rendering
|
|
251
|
-
grouped = {
|
|
252
|
-
"prep": [],
|
|
253
|
-
"script": {},
|
|
254
|
-
"cleanup": [],
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
# Use dicts for JSON response
|
|
258
|
-
grouped_json = {
|
|
259
|
-
"prep": [],
|
|
260
|
-
"script": {},
|
|
261
|
-
"cleanup": [],
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for step in steps:
|
|
265
|
-
step_dict = step.as_dict()
|
|
266
|
-
|
|
267
|
-
if step.phase == "prep":
|
|
268
|
-
grouped["prep"].append(step)
|
|
269
|
-
grouped_json["prep"].append(step_dict)
|
|
270
|
-
|
|
271
|
-
elif step.phase == "script":
|
|
272
|
-
grouped["script"].setdefault(step.repeat_index, []).append(step)
|
|
273
|
-
grouped_json["script"].setdefault(step.repeat_index, []).append(step_dict)
|
|
274
|
-
|
|
275
|
-
elif step.phase == "cleanup" or step.method_name == "stop":
|
|
276
|
-
grouped["cleanup"].append(step)
|
|
277
|
-
grouped_json["cleanup"].append(step_dict)
|
|
278
|
-
|
|
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)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@database.route("/database/workflows/delete/<int:workflow_id>")
|
|
289
|
-
@login_required
|
|
290
|
-
def delete_workflow_data(workflow_id: int):
|
|
291
|
-
"""
|
|
292
|
-
.. :quickref: Database; delete experiment data from database
|
|
293
|
-
|
|
294
|
-
delete workflow data from database
|
|
295
|
-
|
|
296
|
-
.. http:get:: /database/workflows/delete/<int:workflow_id>
|
|
297
|
-
|
|
298
|
-
:param workflow_id: workflow id
|
|
299
|
-
:type workflow_id: int
|
|
300
|
-
:status 302: redirect to :http:get:`/ivoryos/database/workflows/`
|
|
301
|
-
|
|
302
|
-
"""
|
|
303
|
-
run = WorkflowRun.query.get(workflow_id)
|
|
304
|
-
db.session.delete(run)
|
|
305
|
-
db.session.commit()
|
|
306
|
-
return redirect(url_for('database.list_workflows'))
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
<div class="card mb-2 {{ 'border-danger text-danger bg-light' if step.run_error else 'border-secondary' }}">
|
|
2
|
-
<div class="card-body p-2">
|
|
3
|
-
<strong>{{ step.method_name }}</strong>
|
|
4
|
-
<small>Start: {{ step.start_time }}</small>
|
|
5
|
-
<small>End: {{ step.end_time }}</small>
|
|
6
|
-
</div>
|
|
7
|
-
</div>
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
{% extends 'base.html' %}
|
|
2
|
-
|
|
3
|
-
{% block title %}IvoryOS | Experiment Results{% endblock %}
|
|
4
|
-
|
|
5
|
-
{% block body %}
|
|
6
|
-
<style>
|
|
7
|
-
.vis-time-axis .vis-text.vis-minor,
|
|
8
|
-
.vis-time-axis .vis-text.vis-major {
|
|
9
|
-
color: #666;
|
|
10
|
-
}
|
|
11
|
-
.vis-item.stop {
|
|
12
|
-
background-color: red;
|
|
13
|
-
color: white;
|
|
14
|
-
border: none;
|
|
15
|
-
font-weight: bold;
|
|
16
|
-
}
|
|
17
|
-
</style>
|
|
18
|
-
|
|
19
|
-
<div id="timeline"></div>
|
|
20
|
-
|
|
21
|
-
<script src="https://unpkg.com/vis-timeline@latest/standalone/umd/vis-timeline-graph2d.min.js"></script>
|
|
22
|
-
<link href="https://unpkg.com/vis-timeline@latest/styles/vis-timeline-graph2d.min.css" rel="stylesheet"/>
|
|
23
|
-
|
|
24
|
-
<h1>Experiment Step View</h1>
|
|
25
|
-
|
|
26
|
-
<div id="visualization"></div>
|
|
27
|
-
|
|
28
|
-
<script type="text/javascript">
|
|
29
|
-
var container = document.getElementById('visualization');
|
|
30
|
-
|
|
31
|
-
const items = [
|
|
32
|
-
{% if grouped.prep %}
|
|
33
|
-
{
|
|
34
|
-
id: 'prep',
|
|
35
|
-
content: 'Prep Phase',
|
|
36
|
-
start: '{{ grouped.prep[0].start_time }}',
|
|
37
|
-
end: '{{ grouped.prep[-1].end_time }}',
|
|
38
|
-
className: 'prep',
|
|
39
|
-
group: 'prep'
|
|
40
|
-
},
|
|
41
|
-
{% endif %}
|
|
42
|
-
|
|
43
|
-
{% for repeat_index, step_list in grouped.script.items()|sort %}
|
|
44
|
-
{
|
|
45
|
-
id: 'iter{{ repeat_index }}',
|
|
46
|
-
content: 'Iteration {{ repeat_index }}',
|
|
47
|
-
start: '{{ step_list[0].start_time }}',
|
|
48
|
-
end: '{{ step_list[-1].end_time }}',
|
|
49
|
-
className: 'script',
|
|
50
|
-
group: 'iter{{ repeat_index }}'
|
|
51
|
-
},
|
|
52
|
-
{% for step in step_list %}
|
|
53
|
-
{% if step.method_name == "stop" %}
|
|
54
|
-
{
|
|
55
|
-
id: 'stop-{{ step.id }}',
|
|
56
|
-
content: '🛑 Stop',
|
|
57
|
-
start: '{{ step.start_time }}',
|
|
58
|
-
type: 'point',
|
|
59
|
-
className: 'stop',
|
|
60
|
-
group: 'iter{{ repeat_index }}'
|
|
61
|
-
},
|
|
62
|
-
{% endif %}
|
|
63
|
-
{% endfor %}
|
|
64
|
-
{% endfor %}
|
|
65
|
-
|
|
66
|
-
{% if grouped.cleanup %}
|
|
67
|
-
{
|
|
68
|
-
id: 'cleanup',
|
|
69
|
-
content: 'Cleanup Phase',
|
|
70
|
-
start: '{{ grouped.cleanup[0].start_time }}',
|
|
71
|
-
end: '{{ grouped.cleanup[-1].end_time }}',
|
|
72
|
-
className: 'cleanup',
|
|
73
|
-
group: 'cleanup'
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
{% endif %}
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
const groups = [
|
|
80
|
-
{% if grouped.prep %}{ id: 'prep', content: 'Prep' },{% endif %}
|
|
81
|
-
{% for repeat_index in grouped.script.keys()|sort %}{ id: 'iter{{ repeat_index }}', content: 'Iteration {{ repeat_index }}' },{% endfor %}
|
|
82
|
-
{% if grouped.cleanup %}{ id: 'cleanup', content: 'Cleanup' },{% endif %}
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
var options = {
|
|
86
|
-
clickToUse: true,
|
|
87
|
-
stack: false, // important to keep point within group row
|
|
88
|
-
horizontalScroll: true,
|
|
89
|
-
zoomKey: 'ctrlKey'
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// Initialize your timeline with the sorted groups
|
|
93
|
-
const timeline = new vis.Timeline(container, items, groups, options);
|
|
94
|
-
|
|
95
|
-
timeline.on('select', function (props) {
|
|
96
|
-
const id = props.items[0];
|
|
97
|
-
if (id && id.startsWith('iter')) {
|
|
98
|
-
const card = document.getElementById('card-' + id);
|
|
99
|
-
if (card) {
|
|
100
|
-
const yOffset = -80;
|
|
101
|
-
const y = card.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|
102
|
-
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
</script>
|
|
107
|
-
|
|
108
|
-
<h2>Workflow: {{ workflow.name }}</h2>
|
|
109
|
-
|
|
110
|
-
{% if grouped.prep %}
|
|
111
|
-
<h4 class="mt-4">Prep Phase</h4>
|
|
112
|
-
{% for step in grouped.prep %}
|
|
113
|
-
{% include "step_card.html" %}
|
|
114
|
-
{% endfor %}
|
|
115
|
-
{% endif %}
|
|
116
|
-
|
|
117
|
-
{% for repeat_index, step_list in grouped.script.items()|sort %}
|
|
118
|
-
<h4 class="mt-4" id="card-iter{{ repeat_index }}">Iteration {{ repeat_index }}</h4>
|
|
119
|
-
{% for step in step_list %}
|
|
120
|
-
{% include "step_card.html" %}
|
|
121
|
-
{% endfor %}
|
|
122
|
-
{% endfor %}
|
|
123
|
-
|
|
124
|
-
{% if grouped.cleanup %}
|
|
125
|
-
<h4 class="mt-4">Cleanup Phase</h4>
|
|
126
|
-
{% for step in grouped.cleanup %}
|
|
127
|
-
{% include "step_card.html" %}
|
|
128
|
-
{% endfor %}
|
|
129
|
-
{% endif %}
|
|
130
|
-
{% endblock %}
|