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.
- ivoryos/__init__.py +94 -0
- ivoryos/config.py +46 -0
- ivoryos/routes/__init__.py +0 -0
- ivoryos/routes/auth/__init__.py +0 -0
- ivoryos/routes/auth/auth.py +65 -0
- ivoryos/routes/auth/templates/auth/login.html +25 -0
- ivoryos/routes/auth/templates/auth/signup.html +32 -0
- ivoryos/routes/control/__init__.py +0 -0
- ivoryos/routes/control/control.py +233 -0
- ivoryos/routes/control/templates/control/controllers.html +71 -0
- ivoryos/routes/control/templates/control/controllers_home.html +50 -0
- ivoryos/routes/control/templates/control/controllers_new.html +89 -0
- ivoryos/routes/database/__init__.py +0 -0
- ivoryos/routes/database/database.py +122 -0
- ivoryos/routes/database/templates/database/experiment_database.html +72 -0
- ivoryos/routes/design/__init__.py +0 -0
- ivoryos/routes/design/design.py +396 -0
- ivoryos/routes/design/templates/design/experiment_builder.html +413 -0
- ivoryos/routes/design/templates/design/experiment_run.html +325 -0
- ivoryos/routes/main/__init__.py +0 -0
- ivoryos/routes/main/main.py +25 -0
- ivoryos/routes/main/templates/main/help.html +144 -0
- ivoryos/routes/main/templates/main/home.html +68 -0
- ivoryos/static/favicon.ico +0 -0
- ivoryos/static/gui_annotation/Slide1.png +0 -0
- ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- ivoryos/static/js/overlay.js +12 -0
- ivoryos/static/js/socket_handler.js +25 -0
- ivoryos/static/js/sortable_card.js +24 -0
- ivoryos/static/js/sortable_design.js +36 -0
- ivoryos/static/logo.png +0 -0
- ivoryos/static/style.css +202 -0
- ivoryos/templates/base.html +141 -0
- ivoryos/utils/__init__.py +0 -0
- ivoryos/utils/db_models.py +501 -0
- ivoryos/utils/form.py +316 -0
- ivoryos/utils/global_config.py +68 -0
- ivoryos/utils/llm_agent.py +183 -0
- ivoryos/utils/script_runner.py +158 -0
- ivoryos/utils/task_manager.py +80 -0
- ivoryos/utils/utils.py +337 -0
- ivoryos-0.1.5.dist-info/LICENSE +21 -0
- ivoryos-0.1.5.dist-info/METADATA +96 -0
- ivoryos-0.1.5.dist-info/RECORD +46 -0
- ivoryos-0.1.5.dist-info/WHEEL +5 -0
- 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 %}
|