ivoryos 0.1.12__py3-none-any.whl → 0.1.18__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 +46 -40
- ivoryos/routes/design/templates/design/experiment_builder.html +23 -10
- ivoryos/routes/main/templates/main/home.html +19 -17
- ivoryos/templates/base.html +20 -10
- ivoryos/utils/db_models.py +157 -61
- ivoryos/utils/form.py +192 -81
- ivoryos/utils/script_runner.py +11 -8
- ivoryos/utils/utils.py +13 -41
- ivoryos/version.py +1 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/METADATA +4 -1
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/RECORD +16 -17
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/WHEEL +1 -1
- ivoryos/static/.DS_Store +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.dist-info}/LICENSE +0 -0
- {ivoryos-0.1.12.dist-info → ivoryos-0.1.18.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
|
|
|
@@ -99,25 +100,29 @@ def experiment_builder(instrument=None):
|
|
|
99
100
|
|
|
100
101
|
deck_list = utils.available_pseudo_deck(current_app.config["DUMMY_DECK"])
|
|
101
102
|
|
|
102
|
-
functions =
|
|
103
|
+
functions = {}
|
|
103
104
|
if deck:
|
|
104
105
|
deck_variables = global_config.deck_snapshot.keys()
|
|
105
106
|
else:
|
|
106
107
|
deck_variables = list(pseudo_deck.keys()) if pseudo_deck else []
|
|
107
108
|
deck_variables.remove("deck_name") if len(deck_variables) > 0 else deck_variables
|
|
108
|
-
|
|
109
|
+
edit_action_info = session.get("edit_action")
|
|
110
|
+
if edit_action_info:
|
|
111
|
+
forms = create_form_from_action(edit_action_info, script=script)
|
|
112
|
+
elif instrument:
|
|
109
113
|
if instrument in ['if', 'while', 'variable', 'wait', 'repeat']:
|
|
110
|
-
forms = create_builtin_form(instrument,
|
|
114
|
+
forms = create_builtin_form(instrument, script=script)
|
|
111
115
|
else:
|
|
112
116
|
if deck:
|
|
113
|
-
|
|
117
|
+
functions = global_config.deck_snapshot.get(instrument, {})
|
|
114
118
|
elif pseudo_deck:
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
functions = pseudo_deck.get(instrument, {})
|
|
120
|
+
# print(function_metadata)
|
|
121
|
+
# functions = {key: data.get('signature', {}) for key, data in function_metadata.items()}
|
|
117
122
|
forms = create_form_from_pseudo(pseudo=functions, autofill=autofill, script=script)
|
|
118
123
|
if request.method == 'POST' and "hidden_name" in request.form:
|
|
119
|
-
all_kwargs = request.form.copy()
|
|
120
|
-
method_name =
|
|
124
|
+
# all_kwargs = request.form.copy()
|
|
125
|
+
method_name = request.form.get("hidden_name", None)
|
|
121
126
|
# if method_name is not None:
|
|
122
127
|
form = forms.get(method_name)
|
|
123
128
|
kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
|
|
@@ -125,29 +130,15 @@ def experiment_builder(instrument=None):
|
|
|
125
130
|
if form and form.validate_on_submit():
|
|
126
131
|
function_name = kwargs.pop("hidden_name")
|
|
127
132
|
save_data = kwargs.pop('return', '')
|
|
128
|
-
variable_kwargs = {}
|
|
129
|
-
variable_kwargs_types = {}
|
|
130
133
|
|
|
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)
|
|
134
|
+
primitive_arg_types = utils.get_arg_type(kwargs, functions[function_name])
|
|
146
135
|
|
|
136
|
+
script.eval_list(kwargs, primitive_arg_types)
|
|
137
|
+
kwargs = script.validate_variables(kwargs)
|
|
147
138
|
action = {"instrument": instrument, "action": function_name,
|
|
148
|
-
"args":
|
|
139
|
+
"args": kwargs,
|
|
149
140
|
"return": save_data,
|
|
150
|
-
'arg_types':
|
|
141
|
+
'arg_types': primitive_arg_types}
|
|
151
142
|
script.add_action(action=action)
|
|
152
143
|
else:
|
|
153
144
|
flash(form.errors)
|
|
@@ -155,9 +146,13 @@ def experiment_builder(instrument=None):
|
|
|
155
146
|
elif request.method == 'POST' and "builtin_name" in request.form:
|
|
156
147
|
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
157
148
|
if forms.validate_on_submit():
|
|
149
|
+
# print(kwargs)
|
|
158
150
|
logic_type = kwargs.pop('builtin_name')
|
|
159
151
|
if 'variable' in kwargs:
|
|
160
|
-
|
|
152
|
+
try:
|
|
153
|
+
script.add_variable(**kwargs)
|
|
154
|
+
except ValueError:
|
|
155
|
+
flash("Invalid variable type")
|
|
161
156
|
else:
|
|
162
157
|
script.add_logic_action(logic_type=logic_type, **kwargs)
|
|
163
158
|
else:
|
|
@@ -168,12 +163,13 @@ def experiment_builder(instrument=None):
|
|
|
168
163
|
autofill = not autofill
|
|
169
164
|
forms = create_form_from_pseudo(functions, autofill=autofill, script=script)
|
|
170
165
|
session['autofill'] = autofill
|
|
166
|
+
|
|
171
167
|
utils.post_script_file(script)
|
|
172
|
-
design_buttons =
|
|
168
|
+
design_buttons = create_action_button(script)
|
|
173
169
|
return render_template('experiment_builder.html', off_line=off_line, instrument=instrument, history=deck_list,
|
|
174
170
|
script=script, defined_variables=deck_variables,
|
|
175
171
|
local_variables=global_config.defined_variables,
|
|
176
|
-
|
|
172
|
+
forms=forms, buttons=design_buttons, format_name=format_name,
|
|
177
173
|
use_llm=enable_llm)
|
|
178
174
|
|
|
179
175
|
|
|
@@ -250,10 +246,14 @@ def experiment_run():
|
|
|
250
246
|
# module = current_app.config.get('MODULE', '')
|
|
251
247
|
# deck = sys.modules[module] if module else None
|
|
252
248
|
# script.deck = os.path.splitext(os.path.basename(deck.__file__))[0]
|
|
253
|
-
design_buttons = {stype:
|
|
249
|
+
design_buttons = {stype: create_action_button(script, stype) for stype in script.stypes}
|
|
254
250
|
config_preview = []
|
|
255
251
|
config_file_list = [i for i in os.listdir(current_app.config["CSV_FOLDER"]) if not i == ".gitkeep"]
|
|
256
|
-
|
|
252
|
+
try:
|
|
253
|
+
exec_string = script.compile(current_app.config['SCRIPT_FOLDER'])
|
|
254
|
+
except ValueError as e:
|
|
255
|
+
flash(e.__str__())
|
|
256
|
+
return redirect(url_for("design.experiment_builder"))
|
|
257
257
|
# print(exec_string)
|
|
258
258
|
config_file = request.args.get("filename")
|
|
259
259
|
config = []
|
|
@@ -492,14 +492,20 @@ def edit_action(uuid: str):
|
|
|
492
492
|
script = utils.get_script_file()
|
|
493
493
|
action = script.find_by_uuid(uuid)
|
|
494
494
|
session['edit_action'] = action
|
|
495
|
-
|
|
495
|
+
|
|
496
|
+
if request.method == "POST" and action is not None:
|
|
497
|
+
forms = create_form_from_action(action, script=script)
|
|
496
498
|
if "back" not in request.form:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
499
|
+
kwargs = {field.name: field.data for field in forms if field.name != 'csrf_token'}
|
|
500
|
+
# print(kwargs)
|
|
501
|
+
if forms and forms.validate_on_submit():
|
|
502
|
+
save_as = kwargs.pop('return', '')
|
|
503
|
+
kwargs = script.validate_variables(kwargs)
|
|
504
|
+
# try:
|
|
505
|
+
script.update_by_uuid(uuid=uuid, args=kwargs, output=save_as)
|
|
506
|
+
# except Exception as e:
|
|
507
|
+
else:
|
|
508
|
+
flash(forms.errors)
|
|
503
509
|
session.pop('edit_action')
|
|
504
510
|
return redirect(url_for('design.experiment_builder'))
|
|
505
511
|
|
|
@@ -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
|
|
|
@@ -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">
|
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
|
|