ivoryos 0.1.12__py3-none-any.whl → 0.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +50 -14
- ivoryos/routes/control/templates/control/controllers.html +3 -0
- ivoryos/routes/design/design.py +67 -47
- ivoryos/routes/design/templates/design/experiment_builder.html +23 -10
- ivoryos/routes/design/templates/design/experiment_run.html +21 -5
- ivoryos/routes/main/templates/main/home.html +19 -17
- ivoryos/static/js/socket_handler.js +31 -3
- ivoryos/templates/base.html +20 -10
- ivoryos/utils/db_models.py +132 -69
- ivoryos/utils/form.py +192 -81
- ivoryos/utils/script_runner.py +86 -37
- ivoryos/utils/utils.py +13 -41
- ivoryos/version.py +1 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/METADATA +13 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/RECORD +18 -19
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/WHEEL +1 -1
- ivoryos/static/.DS_Store +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.19.dist-info}/top_level.txt +0 -0
ivoryos/__init__.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
1
3
|
import os
|
|
2
4
|
import sys
|
|
3
5
|
from typing import Union
|
|
4
6
|
|
|
5
|
-
from flask import Flask, redirect, url_for
|
|
7
|
+
from flask import Flask, redirect, url_for, Blueprint, g
|
|
6
8
|
|
|
7
9
|
from ivoryos.config import Config, get_config
|
|
8
10
|
from ivoryos.routes.auth.auth import auth, login_manager
|
|
@@ -10,22 +12,18 @@ from ivoryos.routes.control.control import control
|
|
|
10
12
|
from ivoryos.routes.database.database import database
|
|
11
13
|
from ivoryos.routes.design.design import design, socketio
|
|
12
14
|
from ivoryos.routes.main.main import main
|
|
15
|
+
# from ivoryos.routes.monitor.monitor import monitor
|
|
13
16
|
from ivoryos.utils import utils
|
|
14
17
|
from ivoryos.utils.db_models import db
|
|
15
18
|
from ivoryos.utils.global_config import GlobalConfig
|
|
16
19
|
from ivoryos.utils.script_runner import ScriptRunner
|
|
17
20
|
from ivoryos.version import __version__ as ivoryos_version
|
|
18
|
-
|
|
21
|
+
from importlib.metadata import entry_points
|
|
19
22
|
global_config = GlobalConfig()
|
|
20
23
|
|
|
21
|
-
|
|
22
24
|
url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
|
|
23
25
|
app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
|
|
24
|
-
|
|
25
|
-
app.register_blueprint(auth, url_prefix=url_prefix)
|
|
26
|
-
app.register_blueprint(design, url_prefix=url_prefix)
|
|
27
|
-
app.register_blueprint(database, url_prefix=url_prefix)
|
|
28
|
-
app.register_blueprint(control, url_prefix=url_prefix)
|
|
26
|
+
|
|
29
27
|
|
|
30
28
|
def create_app(config_class=None):
|
|
31
29
|
# url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
|
|
@@ -55,12 +53,9 @@ def create_app(config_class=None):
|
|
|
55
53
|
Called before
|
|
56
54
|
|
|
57
55
|
"""
|
|
58
|
-
from flask import g
|
|
59
56
|
g.logger = logger
|
|
60
57
|
g.socketio = socketio
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
59
|
@app.route('/')
|
|
65
60
|
def redirect_to_prefix():
|
|
66
61
|
return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
|
|
@@ -72,6 +67,7 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
72
67
|
config: Config = None,
|
|
73
68
|
logger: Union[str, list] = None,
|
|
74
69
|
logger_output_name: str = None,
|
|
70
|
+
enable_design=True
|
|
75
71
|
):
|
|
76
72
|
"""
|
|
77
73
|
Start ivoryOS app server.
|
|
@@ -85,22 +81,43 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
85
81
|
:param config: config class, defaults to None
|
|
86
82
|
:param logger: logger name of list of logger names, defaults to None
|
|
87
83
|
:param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
|
|
88
|
-
|
|
84
|
+
:param enable_design: enable design canvas, database and workflow execution
|
|
85
|
+
:param stream_address:
|
|
89
86
|
"""
|
|
90
87
|
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
91
88
|
|
|
89
|
+
app.register_blueprint(main, url_prefix=url_prefix)
|
|
90
|
+
app.register_blueprint(auth, url_prefix=url_prefix)
|
|
91
|
+
app.register_blueprint(control, url_prefix=url_prefix)
|
|
92
|
+
|
|
93
|
+
if enable_design:
|
|
94
|
+
app.register_blueprint(design, url_prefix=url_prefix)
|
|
95
|
+
app.register_blueprint(database, url_prefix=url_prefix)
|
|
96
|
+
|
|
97
|
+
plugins = load_plugins(app, socketio)
|
|
98
|
+
|
|
99
|
+
def inject_nav_config():
|
|
100
|
+
"""Make NAV_CONFIG available globally to all templates."""
|
|
101
|
+
return dict(
|
|
102
|
+
enable_design=enable_design,
|
|
103
|
+
plugins=plugins,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
app.context_processor(inject_nav_config)
|
|
92
107
|
port = port or int(os.environ.get("PORT", 8000))
|
|
93
108
|
debug = debug if debug is not None else app.config.get('DEBUG', True)
|
|
94
109
|
|
|
95
110
|
app.config["LOGGERS"] = logger
|
|
96
|
-
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"]
|
|
111
|
+
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
|
|
97
112
|
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
98
113
|
|
|
99
114
|
if module:
|
|
100
115
|
app.config["MODULE"] = module
|
|
101
116
|
app.config["OFF_LINE"] = False
|
|
102
117
|
global_config.deck = sys.modules[module]
|
|
103
|
-
global_config.
|
|
118
|
+
# global_config.heinsight = HeinsightAPI("http://127.0.0.1:8080")
|
|
119
|
+
global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
|
|
120
|
+
output_path=app.config["DUMMY_DECK"], save=True)
|
|
104
121
|
# global_config.runner = ScriptRunner(globals())
|
|
105
122
|
else:
|
|
106
123
|
app.config["OFF_LINE"] = True
|
|
@@ -120,3 +137,22 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
120
137
|
for log in logger:
|
|
121
138
|
utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
|
|
122
139
|
socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
|
|
140
|
+
# return app
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_plugins(app, socketio):
|
|
144
|
+
"""
|
|
145
|
+
Dynamically load installed plugins and attach Flask-SocketIO.
|
|
146
|
+
"""
|
|
147
|
+
plugin_names = []
|
|
148
|
+
for entry_point in entry_points().get("ivoryos.plugins", []):
|
|
149
|
+
plugin = entry_point.load()
|
|
150
|
+
|
|
151
|
+
# If the plugin has an `init_socketio()` function, pass socketio
|
|
152
|
+
if hasattr(plugin, 'init_socketio'):
|
|
153
|
+
plugin.init_socketio(socketio)
|
|
154
|
+
|
|
155
|
+
plugin_names.append(entry_point.name)
|
|
156
|
+
app.register_blueprint(getattr(plugin, entry_point.name), url_prefix=f"{url_prefix}/{entry_point.name}")
|
|
157
|
+
|
|
158
|
+
return plugin_names
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
{% if function not in hidden_instrument %}
|
|
17
17
|
<div class="card" id="{{function}}">
|
|
18
18
|
<div class="bg-white rounded shadow-sm flex-fill">
|
|
19
|
+
<i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top" title='{{ form.hidden_name.description or "Docstring is not available" }}' ></i>
|
|
19
20
|
<a style="float: right" aria-label="Close" href="{{ url_for('control.hide_function', instrument=instrument, function=function) }}"><i class="bi bi-eye-slash-fill"></i></a>
|
|
20
21
|
<div class="form-control" style="border: none">
|
|
21
22
|
<form role="form" method='POST' name="{{function}}" id="{{function}}">
|
|
@@ -38,7 +39,9 @@
|
|
|
38
39
|
</div>
|
|
39
40
|
<div class="input-group mb-3">
|
|
40
41
|
<button type="submit" name="{{ function }}" id="{{ function }}" class="form-control" style="background-color: #a5cece;">{{format_name(function)}} </button>
|
|
42
|
+
|
|
41
43
|
</div>
|
|
44
|
+
|
|
42
45
|
</form>
|
|
43
46
|
</div>
|
|
44
47
|
</div>
|
ivoryos/routes/design/design.py
CHANGED
|
@@ -12,7 +12,8 @@ from werkzeug.utils import secure_filename
|
|
|
12
12
|
|
|
13
13
|
from ivoryos.utils import utils
|
|
14
14
|
from ivoryos.utils.global_config import GlobalConfig
|
|
15
|
-
from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo
|
|
15
|
+
from ivoryos.utils.form import create_builtin_form, create_action_button, format_name, create_form_from_pseudo, \
|
|
16
|
+
create_form_from_action
|
|
16
17
|
from ivoryos.utils.db_models import Script
|
|
17
18
|
from ivoryos.utils.script_runner import ScriptRunner
|
|
18
19
|
|
|
@@ -23,10 +24,22 @@ global_config = GlobalConfig()
|
|
|
23
24
|
runner = ScriptRunner()
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
@socketio.on('
|
|
27
|
-
def
|
|
27
|
+
@socketio.on('abort_pending')
|
|
28
|
+
def handle_abort_pending():
|
|
29
|
+
runner.abort_pending()
|
|
30
|
+
socketio.emit('log', {'message': "aborted pending iterations"})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@socketio.on('abort_current')
|
|
34
|
+
def handle_abort_current():
|
|
28
35
|
runner.stop_execution()
|
|
29
|
-
socketio.emit('log', {'message': "
|
|
36
|
+
socketio.emit('log', {'message': "stopped next task"})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@socketio.on('pause')
|
|
40
|
+
def handle_pause():
|
|
41
|
+
msg = runner.toggle_pause()
|
|
42
|
+
socketio.emit('log', {'message': msg})
|
|
30
43
|
|
|
31
44
|
|
|
32
45
|
@socketio.on('connect')
|
|
@@ -99,25 +112,29 @@ def experiment_builder(instrument=None):
|
|
|
99
112
|
|
|
100
113
|
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
101
114
|
|
|
102
|
-
functions =
|
|
115
|
+
functions = {}
|
|
103
116
|
if deck:
|
|
104
117
|
deck_variables = global_config.deck_snapshot.keys()
|
|
105
118
|
else:
|
|
106
119
|
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
107
120
|
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
108
|
-
|
|
121
|
+
edit_action_info = session.get("edit_action")
|
|
122
|
+
if edit_action_info:
|
|
123
|
+
forms = create_form_from_action(edit_action_info, script=script)
|
|
124
|
+
elif instrument:
|
|
109
125
|
if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
110
|
-
forms = create_builtin_form(instrument,
|
|
126
|
+
forms = create_builtin_form(instrument, script=script)
|
|
111
127
|
else:
|
|
112
128
|
if deck:
|
|
113
|
-
|
|
129
|
+
functions = global_config.deck_snapshot.get(instrument, {})
|
|
114
130
|
elif pseudo_deck:
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
functions = pseudo_deck.get(instrument, {})
|
|
132
|
+
# print(function_metadata)
|
|
133
|
+
# functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
|
|
117
134
|
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
118
135
|
if request.method == 'POST' and "hidden_name" in request.form:
|
|
119
|
-
all_kwargs = request.form.copy()
|
|
120
|
-
method_name =
|
|
136
|
+
# all_kwargs = request.form.copy()
|
|
137
|
+
method_name = request.form.get("hidden_name", None)
|
|
121
138
|
# if method_name is not None:
|
|
122
139
|
form = forms.get(method_name)
|
|
123
140
|
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
@@ -125,29 +142,15 @@ def experiment_builder(instrument=None):
|
|
|
125
142
|
if form and form.validate_on_submit():
|
|
126
143
|
function_name = kwargs.pop("hidden_name")
|
|
127
144
|
save_data = kwargs.pop('return', '')
|
|
128
|
-
variable_kwargs = {}
|
|
129
|
-
variable_kwargs_types = {}
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
variable_kwargs, variable_kwargs_types = utils.find_variable_in_script(script, kwargs)
|
|
133
|
-
|
|
134
|
-
for name in variable_kwargs.keys():
|
|
135
|
-
del kwargs[name]
|
|
136
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
137
|
-
|
|
138
|
-
except:
|
|
139
|
-
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
140
|
-
|
|
141
|
-
kwargs.update(variable_kwargs)
|
|
142
|
-
arg_types = {}
|
|
143
|
-
arg_types.update(variable_kwargs_types)
|
|
144
|
-
arg_types.update(primitive_arg_types)
|
|
145
|
-
all_kwargs.update(variable_kwargs)
|
|
146
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
146
147
|
|
|
148
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
149
|
+
kwargs = script.validate_variables(kwargs)
|
|
147
150
|
action = {"instrument": instrument, "action": function_name,
|
|
148
|
-
"args":
|
|
151
|
+
"args": kwargs,
|
|
149
152
|
"return": save_data,
|
|
150
|
-
'arg_types':
|
|
153
|
+
'arg_types': primitive_arg_types}
|
|
151
154
|
script.add_action(action=action)
|
|
152
155
|
else:
|
|
153
156
|
flash(form.errors)
|
|
@@ -155,9 +158,13 @@ def experiment_builder(instrument=None):
|
|
|
155
158
|
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
156
159
|
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
157
160
|
if forms.validate_on_submit():
|
|
161
|
+
# print(kwargs)
|
|
158
162
|
logic_type = kwargs.pop('builtin_name')
|
|
159
163
|
if 'variable' in kwargs:
|
|
160
|
-
|
|
164
|
+
try:
|
|
165
|
+
script.add_variable(**kwargs)
|
|
166
|
+
except ValueError:
|
|
167
|
+
flash("Invalid variable type")
|
|
161
168
|
else:
|
|
162
169
|
script.add_logic_action(logic_type=logic_type, **kwargs)
|
|
163
170
|
else:
|
|
@@ -168,12 +175,13 @@ def experiment_builder(instrument=None):
|
|
|
168
175
|
autofill = not autofill
|
|
169
176
|
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
170
177
|
session['autofill'] = autofill
|
|
178
|
+
|
|
171
179
|
utils.post_script_file(script)
|
|
172
|
-
design_buttons =
|
|
180
|
+
design_buttons = create_action_button(script)
|
|
173
181
|
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
174
182
|
script=script, defined_variables=deck_variables,
|
|
175
183
|
local_variables=global_config.defined_variables,
|
|
176
|
-
|
|
184
|
+
forms=forms, buttons=design_buttons, format_name=format_name,
|
|
177
185
|
use_llm=enable_llm)
|
|
178
186
|
|
|
179
187
|
|
|
@@ -250,10 +258,14 @@ def experiment_run():
|
|
|
250
258
|
# module = current_app.config.get('MODULE', '')
|
|
251
259
|
# deck = sys.modules[module] if module else None
|
|
252
260
|
# script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
253
|
-
design_buttons = {stype:
|
|
261
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
254
262
|
config_preview = []
|
|
255
263
|
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
256
|
-
|
|
264
|
+
try:
|
|
265
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
266
|
+
except ValueError as e:
|
|
267
|
+
flash(e.__str__())
|
|
268
|
+
return redirect(url_for("design.experiment_builder"))
|
|
257
269
|
# print(exec_string)
|
|
258
270
|
config_file = request.args.get("filename")
|
|
259
271
|
config = []
|
|
@@ -266,9 +278,10 @@ def experiment_run():
|
|
|
266
278
|
config_preview = config[1:6]
|
|
267
279
|
arg_type = config.pop(0) # first entry is types
|
|
268
280
|
try:
|
|
269
|
-
|
|
281
|
+
for key, func_str in exec_string.items():
|
|
282
|
+
exec(func_str)
|
|
270
283
|
except Exception:
|
|
271
|
-
flash("Please check syntax!!")
|
|
284
|
+
flash(f"Please check {key} syntax!!")
|
|
272
285
|
return redirect(url_for("design.experiment_builder"))
|
|
273
286
|
# runner.globals_dict.update(globals())
|
|
274
287
|
run_name = script.name if script.name else "untitled"
|
|
@@ -313,7 +326,8 @@ def experiment_run():
|
|
|
313
326
|
return render_template('experiment_run.html', script=script.script_dict, filename=filename, dot_py=exec_string,
|
|
314
327
|
return_list=return_list, config_list=config_list, config_file_list=config_file_list,
|
|
315
328
|
config_preview=config_preview, data_list=data_list, config_type_list=config_type_list,
|
|
316
|
-
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
329
|
+
no_deck_warning=no_deck_warning, dismiss=dismiss, design_buttons=design_buttons,
|
|
330
|
+
history=deck_list)
|
|
317
331
|
|
|
318
332
|
|
|
319
333
|
@design.route("/toggle_script_type/<stype>")
|
|
@@ -492,14 +506,20 @@ def edit_action(uuid: str):
|
|
|
492
506
|
script = utils.get_script_file()
|
|
493
507
|
action = script.find_by_uuid(uuid)
|
|
494
508
|
session['edit_action'] = action
|
|
495
|
-
|
|
509
|
+
|
|
510
|
+
if request.method == "POST" and action is not None:
|
|
511
|
+
forms = create_form_from_action(action, script=script)
|
|
496
512
|
if "back" not in request.form:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
513
|
+
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
514
|
+
# print(kwargs)
|
|
515
|
+
if forms and forms.validate_on_submit():
|
|
516
|
+
save_as = kwargs.pop('return', '')
|
|
517
|
+
kwargs = script.validate_variables(kwargs)
|
|
518
|
+
# try:
|
|
519
|
+
script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
|
|
520
|
+
# except Exception as e:
|
|
521
|
+
else:
|
|
522
|
+
flash(forms.errors)
|
|
503
523
|
session.pop('edit_action')
|
|
504
524
|
return redirect(url_for('design.experiment_builder'))
|
|
505
525
|
|
|
@@ -541,4 +561,4 @@ def duplicate_action(id: int):
|
|
|
541
561
|
script = utils.get_script_file()
|
|
542
562
|
script.duplicate_action(id)
|
|
543
563
|
utils.post_script_file(script)
|
|
544
|
-
return redirect(back)
|
|
564
|
+
return redirect(back)
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
{# edit action #}
|
|
32
32
|
{% if session["edit_action"] %}
|
|
33
|
+
{# {{ session["edit_action"] }}#}
|
|
33
34
|
{% with action = session["edit_action"] %}
|
|
34
35
|
<h5> {{ format_name(action['action']) }} </h5>
|
|
35
36
|
<form role="form" method='POST' name="{{instrument}}" action="{{ url_for('design.edit_action', uuid=session["edit_action"]['uuid']) }}">
|
|
@@ -41,16 +42,26 @@
|
|
|
41
42
|
<input class="form-control" type="text" id="arg" name="arg" placeholder="{{ action['arg_types']}}" value="{{ action['args'] }}" aria-labelledby="variableHelpBlock">
|
|
42
43
|
</div>
|
|
43
44
|
{% else %}
|
|
44
|
-
{% for arg in action['args'] %}
|
|
45
|
-
<div class="input-group mb-3"
|
|
46
|
-
<label class="input-group-text">{{ format_name(arg) }}</label
|
|
47
|
-
<input class="form-control" type="text" id="{{ arg }}" name="{{ arg }}" placeholder="{{ action['arg_types'][arg] }}" value="{{ action['args'][arg] }}" aria-labelledby="variableHelpBlock"
|
|
48
|
-
</div
|
|
45
|
+
{# {% for arg in action['args'] %}#}
|
|
46
|
+
{# <div class="input-group mb-3">#}
|
|
47
|
+
{# <label class="input-group-text">{{ format_name(arg) }}</label>#}
|
|
48
|
+
{# <input class="form-control" type="text" id="{{ arg }}" name="{{ arg }}" placeholder="{{ action['arg_types'][arg] }}" value="{{ action['args'][arg] }}" aria-labelledby="variableHelpBlock">#}
|
|
49
|
+
{# </div>#}
|
|
50
|
+
{# {% endfor %}#}
|
|
51
|
+
{# <div class="input-group mb-3">#}
|
|
52
|
+
{# <label class="input-group-text">Save Output?</label>#}
|
|
53
|
+
{# <input class="form-control" type="text" id="return" name="return" value="{{ action['return'] }}" aria-labelledby="variableHelpBlock">#}
|
|
54
|
+
{# </div>#}
|
|
55
|
+
{{ forms.hidden_tag() }}
|
|
56
|
+
{% for field in forms %}
|
|
57
|
+
{% if field.type not in ['CSRFTokenField'] %}
|
|
58
|
+
<div class="input-group mb-3">
|
|
59
|
+
<label class="input-group-text">{{ field.label.text }}</label>
|
|
60
|
+
{{ field(class="form-control") }}
|
|
61
|
+
<div class="form-text">{{ field.description }} </div>
|
|
62
|
+
</div>
|
|
63
|
+
{% endif %}
|
|
49
64
|
{% endfor %}
|
|
50
|
-
<div class="input-group mb-3">
|
|
51
|
-
<label class="input-group-text">Save Output?</label>
|
|
52
|
-
<input class="form-control" type="text" id="return" name="return" value="{{ action['return'] }}" aria-labelledby="variableHelpBlock">
|
|
53
|
-
</div>
|
|
54
65
|
{% endif %}
|
|
55
66
|
</div>
|
|
56
67
|
{% endif %}
|
|
@@ -139,12 +150,12 @@
|
|
|
139
150
|
{{ format_name(name) }}
|
|
140
151
|
</button>
|
|
141
152
|
</h2>
|
|
153
|
+
|
|
142
154
|
<div id="{{name}}" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
|
|
143
155
|
<div class="accordion-body">
|
|
144
156
|
<form role="form" method='POST' name="add" id="add">
|
|
145
157
|
<div class="form-group">
|
|
146
158
|
{{ form.hidden_tag() }}
|
|
147
|
-
{# {{ form.hidden_name() }}#}
|
|
148
159
|
{% for field in form %}
|
|
149
160
|
{% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
|
|
150
161
|
<div class="input-group mb-3">
|
|
@@ -161,6 +172,8 @@
|
|
|
161
172
|
{% endfor %}
|
|
162
173
|
</div>
|
|
163
174
|
<button type="submit" class="btn btn-dark">Add </button>
|
|
175
|
+
<i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" data-bs-placement="top" title='{{ form.hidden_name.description or "Docstring is not available" }}' ></i>
|
|
176
|
+
|
|
164
177
|
</form>
|
|
165
178
|
|
|
166
179
|
|
|
@@ -219,10 +219,24 @@
|
|
|
219
219
|
</div>
|
|
220
220
|
<div class="col-lg-6 col-sm-12 logging-panel">
|
|
221
221
|
<p>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
<div class="p d-flex justify-content-between align-items-center">
|
|
223
|
+
<h5>Progress:</h5>
|
|
224
|
+
<div class="d-flex gap-2 ms-auto">
|
|
225
|
+
<button id="pause-resume" class="btn btn-info text-white" data-bs-toggle="tooltip" title="Pause execution">
|
|
226
|
+
<i class="bi bi-pause-circle"></i> <!-- Icon for Pause -->
|
|
227
|
+
</button>
|
|
228
|
+
<button id="abort-current" class="btn btn-danger text-white" data-bs-toggle="tooltip" title="Stop execution after current step">
|
|
229
|
+
<i class="bi bi-stop-circle"></i> <!-- Icon for Stop After This Step -->
|
|
230
|
+
</button>
|
|
231
|
+
<button id="abort-pending" class="btn btn-warning text-white" data-bs-toggle="tooltip" title="Stop execution after current iteration">
|
|
232
|
+
<i class="bi bi-hourglass-split"></i> <!-- Icon for Stop After This Iteration -->
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="text-muted mt-2">
|
|
237
|
+
<small><strong>Note:</strong> The current step cannot be paused or stopped until it completes. </small>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
226
240
|
</p>
|
|
227
241
|
<div class="progress" role="progressbar" aria-label="Animated striped example" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100">
|
|
228
242
|
<div id="progress-bar-inner" class="progress-bar progress-bar-striped progress-bar-animated"></div>
|
|
@@ -295,7 +309,9 @@
|
|
|
295
309
|
</h2>
|
|
296
310
|
<div id="python" class="accordion-collapse collapse">
|
|
297
311
|
<div class="accordion-body">
|
|
298
|
-
|
|
312
|
+
{% for stype, script in dot_py.items() %}
|
|
313
|
+
<pre><code class="python" >{{script}}</code></pre>
|
|
314
|
+
{% endfor %}
|
|
299
315
|
<a href="{{ url_for('design.download', filetype='python') }}">Download <i class="bi bi-download"></i></a>
|
|
300
316
|
</div>
|
|
301
317
|
</div>
|
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
</h1>
|
|
10
10
|
<p>Version: {{ version }}</p>
|
|
11
11
|
<div class="row">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
{% if enable_design %}
|
|
13
|
+
<div class="col-lg-6 mb-4 d-flex align-items-stretch">
|
|
14
|
+
<div class="card rounded shadow-sm flex-fill">
|
|
15
|
+
<div class="card-body">
|
|
16
|
+
<h5 class="card-title">Browse designs</h5>
|
|
17
|
+
<p class="card-text">Browse all workflows saved in the database.</p>
|
|
18
|
+
<a href="{{ url_for('database.load_from_database') }}" class="stretched-link"></a>
|
|
19
|
+
</div>
|
|
19
20
|
</div>
|
|
20
21
|
</div>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
<div class="col-lg-6 mb-4 d-flex align-items-stretch">
|
|
23
|
+
<div class="card rounded shadow-sm flex-fill">
|
|
24
|
+
<div class="card-body">
|
|
25
|
+
<h5 class="card-title">Edit designs</h5>
|
|
26
|
+
<p class="card-text">Build your workflow from current deck functions.</p>
|
|
27
|
+
<a href="{{ url_for('design.experiment_builder') }}" class="stretched-link"></a>
|
|
28
|
+
</div>
|
|
28
29
|
</div>
|
|
29
30
|
</div>
|
|
30
|
-
|
|
31
|
+
{% endif %}
|
|
31
32
|
</div>
|
|
32
33
|
|
|
33
34
|
<br><br><br>
|
|
34
35
|
{% if not off_line %}
|
|
35
|
-
{# <h5>Only available in online mode</h5>#}
|
|
36
|
-
{# <hr>#}
|
|
37
36
|
<div class="row">
|
|
37
|
+
{% if enable_design %}
|
|
38
38
|
<div class="col-lg-6 mb-4 d-flex align-items-stretch">
|
|
39
39
|
<div class="card rounded shadow-sm flex-fill">
|
|
40
40
|
<div class="card-body">
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
47
49
|
<div class="col-lg-6 mb-4 d-flex align-items-stretch">
|
|
48
50
|
<div class="card rounded shadow-sm flex-fill">
|
|
49
51
|
<div class="card-body">
|
|
@@ -24,11 +24,39 @@ document.addEventListener("DOMContentLoaded", function() {
|
|
|
24
24
|
$('#logging-panel').scrollTop($('#logging-panel')[0].scrollHeight);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
document.getElementById('abort').addEventListener('click', function() {
|
|
28
|
-
var confirmation = confirm("Are you sure you want to
|
|
27
|
+
document.getElementById('abort-pending').addEventListener('click', function() {
|
|
28
|
+
var confirmation = confirm("Are you sure you want to stop after this iteration?");
|
|
29
29
|
if (confirmation) {
|
|
30
|
-
socket.emit('
|
|
30
|
+
socket.emit('abort_pending');
|
|
31
31
|
console.log('Abort action sent to server.');
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
+
document.getElementById('abort-current').addEventListener('click', function() {
|
|
35
|
+
var confirmation = confirm("Are you sure you want to stop after this step?");
|
|
36
|
+
if (confirmation) {
|
|
37
|
+
socket.emit('abort_current');
|
|
38
|
+
console.log('Stop action sent to server.');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
document.getElementById('pause-resume').addEventListener('click', function() {
|
|
43
|
+
|
|
44
|
+
socket.emit('pause');
|
|
45
|
+
console.log('Pause/Resume is toggled.');
|
|
46
|
+
var button = this;
|
|
47
|
+
var icon = button.querySelector("i");
|
|
48
|
+
|
|
49
|
+
// Toggle between Pause and Resume
|
|
50
|
+
if (icon.classList.contains("bi-pause-circle")) {
|
|
51
|
+
icon.classList.remove("bi-pause-circle");
|
|
52
|
+
icon.classList.add("bi-play-circle");
|
|
53
|
+
button.innerHTML = '<i class="bi bi-play-circle"></i>';
|
|
54
|
+
button.setAttribute("title", "Resume execution");
|
|
55
|
+
} else {
|
|
56
|
+
icon.classList.remove("bi-play-circle");
|
|
57
|
+
icon.classList.add("bi-pause-circle");
|
|
58
|
+
button.innerHTML = '<i class="bi bi-pause-circle"></i>';
|
|
59
|
+
button.setAttribute("title", "Pause execution");
|
|
60
|
+
}
|
|
61
|
+
});
|
|
34
62
|
});
|
ivoryos/templates/base.html
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<body>
|
|
24
24
|
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
|
|
25
25
|
<div class= "container">
|
|
26
|
-
|
|
26
|
+
{# {{ module_config }}#}
|
|
27
27
|
<a class="navbar-brand" href="{{ url_for('main.index') }}">
|
|
28
28
|
<img src="{{url_for('static', filename='logo.webp')}}" alt="Logo" height="60" class="d-inline-block align-text-bottom">
|
|
29
29
|
</a>
|
|
@@ -36,15 +36,18 @@
|
|
|
36
36
|
<li class="nav-item">
|
|
37
37
|
<a class="nav-link" href="{{ url_for('main.index') }}" aria-current="page">Home</a>
|
|
38
38
|
</li>
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
39
|
+
{% if enable_design %}
|
|
40
|
+
<li class="nav-item">
|
|
41
|
+
<a class="nav-link" href="{{ url_for('database.load_from_database') }}" aria-current="page">Library</a>
|
|
42
|
+
</li>
|
|
43
|
+
<li class="nav-item">
|
|
44
|
+
<a class="nav-link" href="{{ url_for('design.experiment_builder') }}">Design</a>
|
|
45
|
+
</li>
|
|
46
|
+
<li class="nav-item">
|
|
47
|
+
<a class="nav-link" href="{{ url_for('design.experiment_run') }}">Compile/Run</a>
|
|
48
|
+
</li>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
48
51
|
<li class="nav-item">
|
|
49
52
|
<a class="nav-link" href="{{ url_for('control.deck_controllers') }}">Devices</a></li>
|
|
50
53
|
</li>
|
|
@@ -54,6 +57,13 @@
|
|
|
54
57
|
<li class="nav-item">
|
|
55
58
|
<a class="nav-link" href="{{ url_for('main.help_info') }}">About</a>
|
|
56
59
|
</li>
|
|
60
|
+
{% if plugins %}
|
|
61
|
+
{% for plugin in plugins %}
|
|
62
|
+
<li class="nav-item">
|
|
63
|
+
<a class="nav-link" href="{{ url_for(plugin+'.main') }}">{{ plugin.capitalize() }}</a></li>
|
|
64
|
+
</li>
|
|
65
|
+
{% endfor %}
|
|
66
|
+
{% endif %}
|
|
57
67
|
</ul>
|
|
58
68
|
<ul class="navbar-nav ms-auto">
|
|
59
69
|
|