ivoryos 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ivoryos might be problematic. Click here for more details.

Files changed (46) hide show
  1. ivoryos/__init__.py +94 -0
  2. ivoryos/config.py +46 -0
  3. ivoryos/routes/__init__.py +0 -0
  4. ivoryos/routes/auth/__init__.py +0 -0
  5. ivoryos/routes/auth/auth.py +65 -0
  6. ivoryos/routes/auth/templates/auth/login.html +25 -0
  7. ivoryos/routes/auth/templates/auth/signup.html +32 -0
  8. ivoryos/routes/control/__init__.py +0 -0
  9. ivoryos/routes/control/control.py +233 -0
  10. ivoryos/routes/control/templates/control/controllers.html +71 -0
  11. ivoryos/routes/control/templates/control/controllers_home.html +50 -0
  12. ivoryos/routes/control/templates/control/controllers_new.html +89 -0
  13. ivoryos/routes/database/__init__.py +0 -0
  14. ivoryos/routes/database/database.py +122 -0
  15. ivoryos/routes/database/templates/database/experiment_database.html +72 -0
  16. ivoryos/routes/design/__init__.py +0 -0
  17. ivoryos/routes/design/design.py +396 -0
  18. ivoryos/routes/design/templates/design/experiment_builder.html +413 -0
  19. ivoryos/routes/design/templates/design/experiment_run.html +325 -0
  20. ivoryos/routes/main/__init__.py +0 -0
  21. ivoryos/routes/main/main.py +25 -0
  22. ivoryos/routes/main/templates/main/help.html +144 -0
  23. ivoryos/routes/main/templates/main/home.html +68 -0
  24. ivoryos/static/favicon.ico +0 -0
  25. ivoryos/static/gui_annotation/Slide1.png +0 -0
  26. ivoryos/static/gui_annotation/Slide2.PNG +0 -0
  27. ivoryos/static/js/overlay.js +12 -0
  28. ivoryos/static/js/socket_handler.js +25 -0
  29. ivoryos/static/js/sortable_card.js +24 -0
  30. ivoryos/static/js/sortable_design.js +36 -0
  31. ivoryos/static/logo.png +0 -0
  32. ivoryos/static/style.css +202 -0
  33. ivoryos/templates/base.html +141 -0
  34. ivoryos/utils/__init__.py +0 -0
  35. ivoryos/utils/db_models.py +501 -0
  36. ivoryos/utils/form.py +316 -0
  37. ivoryos/utils/global_config.py +68 -0
  38. ivoryos/utils/llm_agent.py +183 -0
  39. ivoryos/utils/script_runner.py +158 -0
  40. ivoryos/utils/task_manager.py +80 -0
  41. ivoryos/utils/utils.py +337 -0
  42. ivoryos-0.1.5.dist-info/LICENSE +21 -0
  43. ivoryos-0.1.5.dist-info/METADATA +96 -0
  44. ivoryos-0.1.5.dist-info/RECORD +46 -0
  45. ivoryos-0.1.5.dist-info/WHEEL +5 -0
  46. ivoryos-0.1.5.dist-info/top_level.txt +1 -0
ivoryos/__init__.py ADDED
@@ -0,0 +1,94 @@
1
+ import os
2
+ import sys
3
+ from typing import Union
4
+
5
+ from flask import Flask
6
+
7
+ from ivoryos.config import Config, get_config
8
+ from ivoryos.routes.auth.auth import auth, login_manager
9
+ from ivoryos.routes.control.control import control
10
+ from ivoryos.routes.database.database import database
11
+ from ivoryos.routes.design.design import design, socketio
12
+ from ivoryos.routes.main.main import main
13
+ from ivoryos.utils import utils
14
+ from ivoryos.utils.db_models import db
15
+ from ivoryos.utils.global_config import GlobalConfig
16
+ from ivoryos.utils.script_runner import ScriptRunner
17
+
18
+ global_config = GlobalConfig()
19
+
20
+
21
+ def create_app(config_class=None):
22
+ app = Flask(__name__)
23
+ app.config.from_object(config_class or 'config.get_config()')
24
+
25
+ # Initialize extensions
26
+ socketio.init_app(app)
27
+ login_manager.init_app(app)
28
+ login_manager.login_view = "auth.login"
29
+ db.init_app(app)
30
+
31
+ # Create database tables
32
+ with app.app_context():
33
+ db.create_all()
34
+
35
+ # Additional setup
36
+ utils.create_gui_dir(app.config['OUTPUT_FOLDER'])
37
+
38
+ # logger_list = app.config["LOGGERS"]
39
+ logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
40
+ logger = utils.start_logger(socketio, 'gui_logger', logger_path)
41
+
42
+ @app.before_request
43
+ def before_request():
44
+ from flask import g
45
+ g.logger = logger
46
+ g.socketio = socketio
47
+
48
+ app.register_blueprint(main)
49
+ app.register_blueprint(auth)
50
+ app.register_blueprint(design)
51
+ app.register_blueprint(database)
52
+ app.register_blueprint(control)
53
+
54
+ return app
55
+
56
+
57
+ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, model=None,
58
+ config: Config = None,
59
+ logger: Union[str, list] = None,
60
+ logger_output_name: str = None,
61
+ ):
62
+ app = create_app(config_class=config or get_config()) # Create app instance using factory function
63
+
64
+ port = port or int(os.environ.get("PORT", 8000))
65
+ debug = debug if debug is not None else app.config.get('DEBUG', True)
66
+
67
+ app.config["LOGGERS"] = logger
68
+ app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
69
+ logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
70
+
71
+ if module:
72
+ app.config["MODULE"] = module
73
+ app.config["OFF_LINE"] = False
74
+ global_config.deck = sys.modules[module]
75
+ global_config.deck_variables = utils.parse_deck(global_config.deck, output_path=app.config["DUMMY_DECK"], save=True)
76
+ # global_config.runner = ScriptRunner(globals())
77
+ else:
78
+ app.config["OFF_LINE"] = True
79
+ if model:
80
+ app.config["ENABLE_LLM"] = True
81
+ app.config["LLM_MODEL"] = model
82
+ app.config["LLM_SERVER"] = llm_server
83
+ utils.install_and_import('openai')
84
+ from ivoryos.utils.llm_agent import LlmAgent
85
+ global_config.agent = LlmAgent(host=llm_server, model=model,
86
+ output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
87
+ else:
88
+ app.config["ENABLE_LLM"] = False
89
+ if logger and type(logger) is str:
90
+ utils.start_logger(socketio, log_filename=logger_path, logger_name=logger)
91
+ elif type(logger) is list:
92
+ for log in logger:
93
+ utils.start_logger(socketio, log_filename=logger_path, logger_name=log)
94
+ socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
ivoryos/config.py ADDED
@@ -0,0 +1,46 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Load environment variables from .env file
5
+ load_dotenv()
6
+
7
+
8
+ class Config:
9
+ SECRET_KEY = os.getenv('SECRET_KEY', 'default_secret_key')
10
+ OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', None)
11
+
12
+ OUTPUT_FOLDER = os.path.join(os.path.abspath(os.curdir), 'ivoryos_data')
13
+ os.makedirs(OUTPUT_FOLDER, exist_ok=True)
14
+ CSV_FOLDER = os.path.join(OUTPUT_FOLDER, 'config_csv/')
15
+ SCRIPT_FOLDER = os.path.join(OUTPUT_FOLDER, 'scripts/')
16
+ DATA_FOLDER = os.path.join(OUTPUT_FOLDER, 'results/')
17
+ DUMMY_DECK = os.path.join(OUTPUT_FOLDER, 'pseudo_deck/')
18
+ DECK_HISTORY = os.path.join(OUTPUT_FOLDER, 'deck_history.txt')
19
+ LOGGERS_PATH = "default.log"
20
+
21
+ SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(OUTPUT_FOLDER, 'ivoryos.db')}"
22
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
23
+
24
+ ENABLE_LLM = True if OPENAI_API_KEY else False
25
+ OFF_LINE = True
26
+
27
+
28
+ class DevelopmentConfig(Config):
29
+ DEBUG = True
30
+
31
+
32
+ class ProductionConfig(Config):
33
+ DEBUG = False
34
+
35
+
36
+ class TestingConfig(Config):
37
+ DEBUG = True
38
+ TESTING = True
39
+
40
+
41
+ def get_config(env='dev'):
42
+ if env == 'production':
43
+ return ProductionConfig()
44
+ elif env == 'testing':
45
+ return TestingConfig()
46
+ return DevelopmentConfig()
File without changes
File without changes
@@ -0,0 +1,65 @@
1
+ from flask import Blueprint, redirect, url_for, flash, request, render_template, session
2
+ from flask_login import login_required, login_user, logout_user, LoginManager
3
+ import bcrypt
4
+
5
+ from ivoryos.utils.db_models import Script, User, db
6
+ from ivoryos.utils.utils import post_script_file
7
+ login_manager = LoginManager()
8
+
9
+ auth = Blueprint('auth', __name__, template_folder='templates/auth')
10
+
11
+
12
+ @auth.route('/login', methods=['GET', 'POST'])
13
+ def login():
14
+ if request.method == 'POST':
15
+ username = request.form.get('username')
16
+ password = request.form.get('password')
17
+
18
+ # session.query(User, User.name).all()
19
+ user = db.session.query(User).filter(User.username == username).first()
20
+ input_password = password.encode('utf-8')
21
+ # if user and bcrypt.checkpw(input_password, user.hashPassword.encode('utf-8')):
22
+ if user and bcrypt.checkpw(input_password, user.hashPassword):
23
+ # password.encode("utf-8")
24
+ # user = User(username, password.encode("utf-8"))
25
+ login_user(user)
26
+ session['user'] = username
27
+ script_file = Script(author=username)
28
+ session["script"] = script_file.as_dict()
29
+ session['hidden_functions'], session['card_order'], session['prompt'] = {}, {}, {}
30
+ session['autofill'] = False
31
+ post_script_file(script_file)
32
+ return redirect(url_for('main.index'))
33
+ else:
34
+ flash("Incorrect username or password")
35
+ return render_template('login.html')
36
+
37
+
38
+ @auth.route('/signup', methods=['GET', 'POST'])
39
+ def signup():
40
+ if request.method == 'POST':
41
+ username = request.form.get('username')
42
+ password = request.form.get('password')
43
+ salt = bcrypt.gensalt()
44
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
45
+ user = User(username, hashed)
46
+ try:
47
+ db.session.add(user)
48
+ db.session.commit()
49
+ return redirect(url_for('auth.login'))
50
+ except Exception:
51
+ flash("username exists :(")
52
+ return render_template('signup.html')
53
+
54
+
55
+ @auth.route("/logout")
56
+ @login_required
57
+ def logout():
58
+ logout_user()
59
+ session.clear()
60
+ return redirect(url_for('auth.login'))
61
+
62
+
63
+ @login_manager.user_loader
64
+ def load_user(username):
65
+ return User(username, password=None)
@@ -0,0 +1,25 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Login{% endblock %}
3
+
4
+
5
+ {% block body %}
6
+ <div class= "login">
7
+ <div class="bg-white rounded shadow-sm flex-fill">
8
+ <div class="p-4" style="align-items: center">
9
+ <h5>Log in</h5>
10
+ <form role="form" method='POST' name="login" action="{{ url_for('auth.login') }}">
11
+ <div class="input-group mb-3">
12
+ <label class="input-group-text" for="username">Username</label>
13
+ <input class="form-control" type="text" id="username" name="username">
14
+ </div>
15
+ <div class="input-group mb-3">
16
+ <label class="input-group-text" for="password">Password</label>
17
+ <input class="form-control" type="password" id="password" name="password">
18
+ </div>
19
+ <button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">login</button>
20
+ </form>
21
+ <p class="message">Not registered? <a href="{{ url_for('auth.signup') }}">Create a new account</a>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ {% endblock %}
@@ -0,0 +1,32 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Signup{% endblock %}
3
+
4
+
5
+ {% block body %}
6
+ <div class= "login">
7
+ <div class="bg-white rounded shadow-sm flex-fill">
8
+ <div class="p-4" style="align: center">
9
+ <h5>Create a new account</h5>
10
+ <form role="form" method='POST' name="signup" action="{{ url_for('auth.signup') }}">
11
+
12
+ <div class="input-group mb-3">
13
+ <label class="input-group-text" for="username">Username</label>
14
+ <input class="form-control" type="text" id="username" name="username" required>
15
+ </div>
16
+ <div class="input-group mb-3">
17
+ <label class="input-group-text" for="password">Password</label>
18
+ <input class="form-control" type="password" id="password" name="password" required>
19
+ </div>
20
+ {# <div class="input-group mb-3">#}
21
+ {# <label class="input-group-text" for="confirm_password">Confirm Password</label>#}
22
+ {# <input class="form-control" type="confirm_password" id="confirm_password" name="confirm_password">#}
23
+ {# </div>#}
24
+
25
+ <button type="submit" class="btn btn-secondary" name="login" style="width: 100%;">Sign up</button>
26
+ </form>
27
+ <p class="message" >Already registered? <a href="{{ url_for('auth.login') }}">Sign In</a></p>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ {% endblock %}
File without changes
@@ -0,0 +1,233 @@
1
+ import os
2
+ import pickle
3
+ import sys
4
+
5
+ from flask import Blueprint, redirect, url_for, flash, request, render_template, session, current_app
6
+ from flask_login import login_required
7
+
8
+ from ivoryos.utils.global_config import GlobalConfig
9
+ from ivoryos.utils import utils
10
+ from ivoryos.utils.form import create_form_from_module, format_name
11
+
12
+ global_config = GlobalConfig()
13
+
14
+ control = Blueprint('control', __name__, template_folder='templates/control')
15
+
16
+
17
+ @control.route("/my_deck")
18
+ @login_required
19
+ def deck_controllers():
20
+ deck_variables = global_config.deck_variables.keys()
21
+ deck_list = utils.import_history(os.path.join(current_app.config["OUTPUT_FOLDER"], 'deck_history.txt'))
22
+ return render_template('controllers_home.html', defined_variables=deck_variables, deck=True, history=deck_list)
23
+
24
+
25
+ @control.route("/new_controller/")
26
+ @control.route("/new_controller/<instrument>", methods=['GET', 'POST'])
27
+ @login_required
28
+ def new_controller(instrument=None):
29
+ device = None
30
+ args = None
31
+ if instrument:
32
+
33
+ device = find_instrument_by_name(instrument)
34
+ args = utils.inspect.signature(device.__init__)
35
+
36
+ if request.method == 'POST':
37
+ device_name = request.form.get("device_name", "")
38
+ if device_name and device_name in globals():
39
+ flash("Device name is defined. Try another name, or leave it as blank to auto-configure")
40
+ return render_template('controllers_new.html', instrument=instrument,
41
+ api_variables=global_config.api_variables,
42
+ device=device, args=args, defined_variables=global_config.defined_variables)
43
+ if device_name == "":
44
+ device_name = device.__name__.lower() + "_"
45
+ num = 1
46
+ while device_name + str(num) in global_config.defined_variables:
47
+ num += 1
48
+ device_name = device_name + str(num)
49
+ kwargs = request.form.to_dict()
50
+ kwargs.pop("device_name")
51
+ for i in kwargs:
52
+ if kwargs[i] in global_config.defined_variables:
53
+ kwargs[i] = global_config.defined_variables[kwargs[i]]
54
+ try:
55
+ utils.convert_config_type(kwargs, device.__init__.__annotations__, is_class=True)
56
+ except Exception as e:
57
+ flash(e)
58
+ try:
59
+ global_config.defined_variables[device_name] = device(**kwargs)
60
+ # global_config.defined_variables.add(device_name)
61
+ return redirect(url_for('control.controllers_home'))
62
+ except Exception as e:
63
+ flash(e)
64
+ return render_template('controllers_new.html', instrument=instrument, api_variables=global_config.api_variables,
65
+ device=device, args=args, defined_variables=global_config.defined_variables)
66
+
67
+
68
+ @control.route("/controllers")
69
+ @login_required
70
+ def controllers_home():
71
+ # defined_variables = parse_deck(deck)
72
+ return render_template('controllers_home.html', defined_variables=global_config.defined_variables)
73
+
74
+
75
+ @control.route("/controllers/<instrument>", methods=['GET', 'POST'])
76
+ @login_required
77
+ def controllers(instrument):
78
+ inst_object = find_instrument_by_name(instrument)
79
+ _forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
80
+ card_order = session.get('card_order')
81
+ order = card_order.get(instrument, _forms.keys())
82
+ if instrument not in card_order:
83
+ card_order[instrument] = list(order)
84
+ session['card_order'] = card_order
85
+ # print(session['card_order'])
86
+ forms = {name: _forms[name] for name in order if name in _forms}
87
+ if request.method == 'POST':
88
+ all_kwargs = request.form.copy()
89
+ method_name = all_kwargs.pop("hidden_name", None)
90
+ # if method_name is not None:
91
+ form = forms.get(method_name)
92
+ kwargs = {field.name: field.data for field in form if field.name != 'csrf_token'}
93
+ function_executable = getattr(inst_object, method_name)
94
+ if form and form.validate_on_submit():
95
+ try:
96
+ kwargs.pop("hidden_name")
97
+ output = function_executable(**kwargs)
98
+ flash(f"\nRun Success! Output value: {output}.")
99
+ except Exception as e:
100
+ flash(e.__str__())
101
+ else:
102
+ flash(form.errors)
103
+ return render_template('controllers.html', instrument=instrument, forms=forms, format_name=format_name)
104
+
105
+
106
+ @control.route("/import_api", methods=['GET', 'POST'])
107
+ def import_api():
108
+ filepath = request.form.get('filepath')
109
+ # filepath.replace('\\', '/')
110
+ name = os.path.split(filepath)[-1].split('.')[0]
111
+ try:
112
+ spec = utils.importlib.util.spec_from_file_location(name, filepath)
113
+ module = utils.importlib.util.module_from_spec(spec)
114
+ spec.loader.exec_module(module)
115
+ classes = utils.inspect.getmembers(module, utils.inspect.isclass)
116
+ if len(classes) == 0:
117
+ flash("Invalid import: no class found in the path")
118
+ return redirect(url_for("control.controllers_home"))
119
+ for i in classes:
120
+ globals()[i[0]] = i[1]
121
+ global_config.api_variables.add(i[0])
122
+ # should handle path error and file type error
123
+ except Exception as e:
124
+ flash(e.__str__())
125
+ return redirect(url_for("control.new_controller"))
126
+
127
+
128
+ # @control.route("/disconnect", methods=["GET"])
129
+ # @control.route("/disconnect/<device_name>", methods=["GET"])
130
+ # def disconnect(device_name=None):
131
+ # """TODO handle disconnect device"""
132
+ # if device_name:
133
+ # try:
134
+ # exec(device_name + ".disconnect()")
135
+ # except Exception:
136
+ # pass
137
+ # global_config.defined_variables.remove(device_name)
138
+ # globals().pop(device_name)
139
+ # return redirect(url_for('control.controllers_home'))
140
+ #
141
+ # deck_variables = ["deck." + var for var in set(dir(deck))
142
+ # if not (var.startswith("_") or var[0].isupper() or var.startswith("repackage"))
143
+ # and not type(eval("deck." + var)).__module__ == 'builtins']
144
+ # for i in deck_variables:
145
+ # try:
146
+ # exec(i + ".disconnect()")
147
+ # except Exception:
148
+ # pass
149
+ # globals()["deck"] = None
150
+ # return redirect(url_for('control.deck_controllers'))
151
+
152
+
153
+ @control.route("/import_deck", methods=['POST'])
154
+ def import_deck():
155
+ # global deck
156
+ script = utils.get_script_file()
157
+ filepath = request.form.get('filepath')
158
+ session['dismiss'] = request.form.get('dismiss')
159
+ update = request.form.get('update')
160
+ back = request.referrer
161
+ if session['dismiss']:
162
+ return redirect(back)
163
+ name = os.path.split(filepath)[-1].split('.')[0]
164
+ try:
165
+ module = utils.import_module_by_filepath(filepath=filepath, name=name)
166
+ utils.save_to_history(filepath, current_app.config["DECK_HISTORY"])
167
+ module_sigs = utils.parse_deck(module, save=update, output_path=current_app.config["DUMMY_DECK"])
168
+ if not len(module_sigs) > 0:
169
+ flash("Invalid hardware deck, connect instruments in deck script", "error")
170
+ return redirect(url_for("control.deck_controllers"))
171
+ global_config.deck = module
172
+ global_config.deck_variables = module_sigs
173
+
174
+ if script.deck is None:
175
+ script.deck = module.__name__
176
+ # file path error exception
177
+ except Exception as e:
178
+ flash(e.__str__())
179
+ return redirect(back)
180
+
181
+
182
+ @control.route('/save-order/<instrument>', methods=['POST'])
183
+ def save_order(instrument):
184
+ # Save the new order for the specified group to session
185
+ data = request.json
186
+ card_order = session.get("card_order", {})
187
+ card_order[instrument] = data['order']
188
+ session['card_order'] = card_order
189
+ return '', 204
190
+
191
+
192
+ @control.route('/hide_function/<instrument>/<function>')
193
+ def hide_function(instrument, function):
194
+ back = request.referrer
195
+ hidden_functions = session.get("hidden_functions")
196
+ functions = hidden_functions.get(instrument, [])
197
+ card_order = session.get("card_order")
198
+ order = card_order.get(instrument)
199
+ if function not in functions:
200
+ functions.append(function)
201
+ order.remove(function)
202
+ hidden_functions[instrument] = functions
203
+ card_order[instrument] = order
204
+ session['hidden_functions'] = hidden_functions
205
+ session['card_order'] = card_order
206
+ return redirect(back)
207
+
208
+
209
+ @control.route('/remove_hidden/<instrument>/<function>')
210
+ def remove_hidden(instrument, function):
211
+ back = request.referrer
212
+ hidden_functions = session.get("hidden_functions")
213
+ functions = hidden_functions.get(instrument, [])
214
+ card_order = session.get("card_order")
215
+ order = card_order.get(instrument)
216
+ if function in functions:
217
+ functions.remove(function)
218
+ order.append(function)
219
+ hidden_functions[instrument] = functions
220
+ card_order[instrument] = order
221
+ session['hidden_functions'] = hidden_functions
222
+ session['card_order'] = card_order
223
+ return redirect(back)
224
+
225
+
226
+ def find_instrument_by_name(name: str):
227
+ if name.startswith("deck"):
228
+ name = name.replace("deck.", "")
229
+ return getattr(global_config.deck, name)
230
+ elif name in global_config.defined_variables:
231
+ return global_config.defined_variables[name]
232
+ elif name in globals():
233
+ return globals()[name]
@@ -0,0 +1,71 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Controller for {{instrument}}{% endblock %}
3
+ {% block body %}
4
+ <div id="overlay" class="overlay">
5
+ <div>
6
+ <h3 id="overlay-text"></h3>
7
+ <div class="spinner-border" role="status"></div>
8
+ </div>
9
+ </div>
10
+ <h1>{{instrument}} controller</h1>
11
+ <div class="grid-container" id="sortable-grid">
12
+ {% for function, form in forms.items() %}
13
+ {% if function not in session['hidden_functions'][instrument] %}
14
+ <div class="card" id="{{function}}">
15
+ <div class="bg-white rounded shadow-sm flex-fill">
16
+ <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>
17
+ <div class="form-control" style="border: none">
18
+ <form role="form" method='POST' name="{{function}}" id="{{function}}">
19
+ <div class="form-group">
20
+ {{ form.hidden_tag() }}
21
+ {% for field in form %}
22
+ {% if field.type not in ['CSRFTokenField', 'HiddenField'] %}
23
+ <div class="input-group mb-3">
24
+ <label class="input-group-text">{{ field.label.text }}</label>
25
+ {% if field.type == "SubmitField" %}
26
+ {{ field(class="btn btn-dark") }}
27
+ {% elif field.type == "BooleanField" %}
28
+ {{ field(class="form-check-input") }}
29
+ {% else %}
30
+ {{ field(class="form-control") }}
31
+ {% endif %}
32
+ </div>
33
+ {% endif %}
34
+ {% endfor %}
35
+ </div>
36
+ <div class="input-group mb-3">
37
+ <button type="submit" name="{{ function }}" id="{{ function }}" class="form-control" style="background-color: #a5cece;">{{function}} </button>
38
+ </div>
39
+ </form>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ {% endif %}
44
+ {% endfor %}
45
+ </div>
46
+ <div class="accordion accordion-flush" id="accordionActions" >
47
+ <div class="accordion-item">
48
+ <h4 class="accordion-header">
49
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#hidden">
50
+ Hidden functions
51
+ </button>
52
+ </h4>
53
+ </div>
54
+ <div id="hidden" class="accordion-collapse collapse" data-bs-parent="#accordionActions">
55
+ <div class="accordion-body">
56
+ {% for function in session['hidden_functions'][instrument] %}
57
+ <div>
58
+ {{ function }} <a href="{{ url_for('control.remove_hidden', instrument=instrument, function=function) }}"><i class="bi bi-eye-fill"></i></a>
59
+ </div>
60
+ {% endfor %}
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <script>
66
+ const saveOrderUrl = `{{ url_for('control.save_order', instrument=instrument) }}`;
67
+ const buttonIds = {{ session['card_order'][instrument] | tojson }};
68
+ </script>
69
+ <script src="{{ url_for('static', filename='js/sortable_card.js') }}"></script>
70
+ <script src="{{ url_for('static', filename='js/overlay.js') }}"></script>
71
+ {% endblock %}
@@ -0,0 +1,50 @@
1
+ {% extends 'base.html' %}
2
+ {% block title %}IvoryOS | Devices{% endblock %}
3
+ {% block body %}
4
+ <div class="row">
5
+ {% if defined_variables %}
6
+ {% for instrument in defined_variables %}
7
+ <div class="col-xl-3 col-lg-4 col-md-6 mb-4 ">
8
+ <div class="bg-white rounded shadow-sm position-relative">
9
+ {# {% if not deck %}#}
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
+ <div class="p-4 controller-card">
12
+ <h5 class=""><a href="{{ url_for('control.controllers', instrument=instrument) }}" class="text-dark stretched-link">{{instrument}}</a></h5>
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 %}#}
19
+ </div>
20
+ </div>
21
+ {% endfor %}
22
+ {% if not deck %}
23
+ <div class="col-xl-3 col-lg-4 col-md-6 mb-4 ">
24
+ <div class="bg-white rounded shadow-sm position-relative">
25
+ <div class="p-4 controller-card" >
26
+ {% if deck %}
27
+ {# todo disconnect for imported deck #}
28
+ {# <h5> <a href="{{ url_for("disconnect") }}" class="stretched-link" style="color: orangered">Disconnect deck</a></h5>#}
29
+ {% else %}
30
+ <h5><a href="{{ url_for('control.new_controller') }}" style="color: orange" class="stretched-link">New connection</a></h5>
31
+ {% endif %}
32
+ </div>
33
+ </div>
34
+ </div>
35
+ {% endif %}
36
+ {% else %}
37
+ <div class="col-xl-3 col-lg-4 col-md-6 mb-4 ">
38
+ <div class="bg-white rounded shadow-sm position-relative">
39
+ <div class="p-4 controller-card" >
40
+ {% if deck %}
41
+ <h5><a data-bs-toggle="modal" href="#importModal" class="stretched-link"><i class="bi bi-folder-plus"></i> Import deck </a></h5>
42
+ {% else %}
43
+ <h5><a href="{{ url_for('control.new_controller') }}" style="color: orange" class="stretched-link">New connection</a></h5>
44
+ {% endif %}
45
+ </div>
46
+ </div>
47
+ </div>
48
+ {% endif %}
49
+ </div>
50
+ {% endblock %}