ivoryos 1.2.7__tar.gz → 1.3.0__tar.gz
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-1.2.7 → ivoryos-1.3.0}/PKG-INFO +1 -1
- ivoryos-1.3.0/ivoryos/__init__.py +13 -0
- ivoryos-1.3.0/ivoryos/app.py +131 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/api/api.py +2 -1
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/control.py +8 -6
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/templates/controllers.html +27 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/utils.py +2 -0
- ivoryos-1.3.0/ivoryos/routes/data/data.py +170 -0
- ivoryos-1.3.0/ivoryos/routes/data/templates/components/step_card.html +42 -0
- ivoryos-1.3.0/ivoryos/routes/data/templates/workflow_view.html +351 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/design.py +11 -4
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/action_form.html +2 -2
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/instruments_panel.html +23 -1
- ivoryos-1.2.7/ivoryos/__init__.py → ivoryos-1.3.0/ivoryos/server.py +18 -98
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/socket_handler.js +39 -4
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/sortable_design.js +28 -11
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/db_models.py +84 -13
- ivoryos-1.3.0/ivoryos/utils/decorators.py +33 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/form.py +8 -4
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/global_config.py +10 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/script_runner.py +123 -49
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/task_runner.py +7 -2
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/utils.py +25 -3
- ivoryos-1.3.0/ivoryos/version.py +1 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos.egg-info/PKG-INFO +1 -1
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos.egg-info/SOURCES.txt +3 -0
- ivoryos-1.2.7/ivoryos/routes/data/data.py +0 -131
- ivoryos-1.2.7/ivoryos/routes/data/templates/components/step_card.html +0 -13
- ivoryos-1.2.7/ivoryos/routes/data/templates/workflow_view.html +0 -130
- ivoryos-1.2.7/ivoryos/version.py +0 -1
- {ivoryos-1.2.7 → ivoryos-1.3.0}/LICENSE +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/README.md +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/config.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/optimizer/ax_optimizer.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/optimizer/base_optimizer.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/optimizer/baybe_optimizer.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/optimizer/registry.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/auth/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/auth/auth.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/auth/templates/login.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/auth/templates/signup.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/control_file.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/control_new_device.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/control/templates/controllers_new.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/data/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/data/templates/workflow_database.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/design_file.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/design_step.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/actions_panel.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/autofill_toggle.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/canvas.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/canvas_footer.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/canvas_header.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/canvas_main.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/deck_selector.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/edit_action_form.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals/drop_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals/json_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals/new_script_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals/rename_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals/saveas_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/modals.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/python_code_overlay.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/sidebar.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/components/text_to_code_panel.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/design/templates/experiment_builder.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/execute.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/execute_file.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/error_modal.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/logging_panel.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/progress_panel.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/run_panel.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/run_tabs.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/tab_bayesian.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/tab_configuration.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/components/tab_repeat.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/execute/templates/experiment_run.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/library/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/library/library.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/library/templates/library.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/main/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/main/main.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/main/templates/help.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/routes/main/templates/home.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/socket_handlers.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/favicon.ico +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/gui_annotation/Slide1.png +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/action_handlers.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/db_delete.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/overlay.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/script_metadata.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/sortable_card.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/js/ui_state.js +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/logo.webp +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/static/style.css +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/templates/base.html +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/__init__.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/bo_campaign.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/client_proxy.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/llm_agent.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/py_to_json.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos/utils/serilize.py +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos.egg-info/dependency_links.txt +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos.egg-info/requires.txt +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/ivoryos.egg-info/top_level.txt +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/pyproject.toml +0 -0
- {ivoryos-1.2.7 → ivoryos-1.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ivoryos.server import run
|
|
2
|
+
from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
|
|
3
|
+
from ivoryos.version import __version__ as ivoryos_version
|
|
4
|
+
from ivoryos.utils.decorators import block, BUILDING_BLOCKS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"block",
|
|
9
|
+
"BUILDING_BLOCKS",
|
|
10
|
+
"OPTIMIZER_REGISTRY",
|
|
11
|
+
"run",
|
|
12
|
+
"ivoryos_version",
|
|
13
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from flask import Flask, session, g, redirect, url_for
|
|
5
|
+
from flask_login import AnonymousUserMixin
|
|
6
|
+
|
|
7
|
+
from ivoryos.utils import utils
|
|
8
|
+
from ivoryos.utils.db_models import db
|
|
9
|
+
from ivoryos.config import Config, get_config
|
|
10
|
+
from ivoryos.routes.auth.auth import auth, login_manager
|
|
11
|
+
from ivoryos.routes.control.control import control
|
|
12
|
+
from ivoryos.routes.data.data import data
|
|
13
|
+
from ivoryos.routes.library.library import library
|
|
14
|
+
from ivoryos.routes.design.design import design
|
|
15
|
+
from ivoryos.routes.execute.execute import execute
|
|
16
|
+
from ivoryos.routes.api.api import api
|
|
17
|
+
from ivoryos.socket_handlers import socketio
|
|
18
|
+
from ivoryos.routes.main.main import main
|
|
19
|
+
from ivoryos.version import __version__ as ivoryos_version
|
|
20
|
+
from sqlalchemy import inspect, text
|
|
21
|
+
from flask import current_app
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def reset_old_schema(engine, db_dir):
|
|
25
|
+
inspector = inspect(engine)
|
|
26
|
+
tables = inspector.get_table_names()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Check if old tables exist (no workflow_phases table)
|
|
30
|
+
has_workflow_phase = 'workflow_phases' in tables
|
|
31
|
+
old_workflow_run = 'old_workflow_run' in tables
|
|
32
|
+
old_workflow_step = 'workflow_steps' in tables
|
|
33
|
+
|
|
34
|
+
if not has_workflow_phase:
|
|
35
|
+
print("⚠️ Old workflow database detected! All previous workflows have been reset to support the new schema.")
|
|
36
|
+
# Backup old DB
|
|
37
|
+
db_path = os.path.join(db_dir, "ivoryos.db")
|
|
38
|
+
if os.path.exists(db_path):
|
|
39
|
+
# os.makedirs(backup_dir, exist_ok=True)
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
import shutil
|
|
42
|
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
43
|
+
backup_path = os.path.join(db_dir, f"ivoryos_backup_{ts}.db")
|
|
44
|
+
shutil.copy(db_path, backup_path)
|
|
45
|
+
print(f"Backup created at {backup_path}")
|
|
46
|
+
with engine.begin() as conn:
|
|
47
|
+
# Drop old tables
|
|
48
|
+
if old_workflow_step:
|
|
49
|
+
conn.execute(text("DROP TABLE IF EXISTS workflow_steps"))
|
|
50
|
+
if old_workflow_run:
|
|
51
|
+
conn.execute(text("DROP TABLE IF EXISTS workflow_runs"))
|
|
52
|
+
|
|
53
|
+
# Recreate new schema
|
|
54
|
+
db.create_all() # creates workflow_runs, workflow_phases, workflow_steps
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_app(config_class=None):
|
|
58
|
+
"""
|
|
59
|
+
create app, init database
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
|
|
63
|
+
app = Flask(__name__, static_url_path=f'{url_prefix}/static', static_folder='static')
|
|
64
|
+
app.register_blueprint(main, url_prefix=url_prefix)
|
|
65
|
+
app.register_blueprint(auth, url_prefix=f'{url_prefix}/{auth.name}')
|
|
66
|
+
app.register_blueprint(library, url_prefix=f'{url_prefix}/{library.name}')
|
|
67
|
+
app.register_blueprint(control, url_prefix=f'{url_prefix}/instruments')
|
|
68
|
+
app.register_blueprint(design, url_prefix=f'{url_prefix}')
|
|
69
|
+
app.register_blueprint(execute, url_prefix=f'{url_prefix}')
|
|
70
|
+
app.register_blueprint(data, url_prefix=f'{url_prefix}')
|
|
71
|
+
app.register_blueprint(api, url_prefix=f'{url_prefix}/{api.name}')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
app.config.from_object(config_class or 'config.get_config()')
|
|
75
|
+
os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
|
|
76
|
+
# Initialize extensions
|
|
77
|
+
socketio.init_app(app, cors_allowed_origins="*", cookie=None)
|
|
78
|
+
login_manager.init_app(app)
|
|
79
|
+
login_manager.login_view = "auth.login"
|
|
80
|
+
db.init_app(app)
|
|
81
|
+
|
|
82
|
+
# Create database tables
|
|
83
|
+
with app.app_context():
|
|
84
|
+
# db.create_all()
|
|
85
|
+
reset_old_schema(db.engine, app.config['OUTPUT_FOLDER'])
|
|
86
|
+
|
|
87
|
+
# Additional setup
|
|
88
|
+
utils.create_gui_dir(app.config['OUTPUT_FOLDER'])
|
|
89
|
+
|
|
90
|
+
# logger_list = app.config["LOGGERS"]
|
|
91
|
+
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
92
|
+
logger = utils.start_logger(socketio, 'gui_logger', logger_path)
|
|
93
|
+
|
|
94
|
+
@app.before_request
|
|
95
|
+
def before_request():
|
|
96
|
+
"""
|
|
97
|
+
Called before
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
g.logger = logger
|
|
101
|
+
g.socketio = socketio
|
|
102
|
+
session.permanent = False
|
|
103
|
+
# DEMO_MODE: Simulate logged-in user per session
|
|
104
|
+
if app.config.get("DEMO_MODE", False):
|
|
105
|
+
if "demo_user_id" not in session:
|
|
106
|
+
session["demo_user_id"] = f"demo_{str(uuid.uuid4())[:8]}"
|
|
107
|
+
|
|
108
|
+
class SessionDemoUser(AnonymousUserMixin):
|
|
109
|
+
@property
|
|
110
|
+
def is_authenticated(self):
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
def get_id(self):
|
|
114
|
+
return session.get("demo_user_id")
|
|
115
|
+
|
|
116
|
+
login_manager.anonymous_user = SessionDemoUser
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.route('/')
|
|
121
|
+
def redirect_to_prefix():
|
|
122
|
+
return redirect(url_for('main.index', version=ivoryos_version)) # Assuming 'index' is a route in your blueprint
|
|
123
|
+
|
|
124
|
+
@app.template_filter('format_name')
|
|
125
|
+
def format_name(name):
|
|
126
|
+
name = name.split(".")[-1]
|
|
127
|
+
text = ' '.join(word for word in name.split('_'))
|
|
128
|
+
return text.capitalize()
|
|
129
|
+
|
|
130
|
+
# app.config.setdefault("DEMO_MODE", False)
|
|
131
|
+
return app
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import os
|
|
2
3
|
from flask import Blueprint, jsonify, request, current_app
|
|
3
4
|
|
|
@@ -46,7 +47,7 @@ def backend_control(instrument: str=None):
|
|
|
46
47
|
current_app=current_app._get_current_object())
|
|
47
48
|
return jsonify(output), 200
|
|
48
49
|
|
|
49
|
-
snapshot = global_config.deck_snapshot
|
|
50
|
+
snapshot = copy.deepcopy(global_config.deck_snapshot)
|
|
50
51
|
# Iterate through each instrument in the snapshot
|
|
51
52
|
for instrument_key, instrument_data in snapshot.items():
|
|
52
53
|
# Iterate through each function associated with the current instrument
|
|
@@ -5,7 +5,7 @@ from ivoryos.routes.control.control_file import control_file
|
|
|
5
5
|
from ivoryos.routes.control.control_new_device import control_temp
|
|
6
6
|
from ivoryos.routes.control.utils import post_session_by_instrument, get_session_by_instrument, find_instrument_by_name
|
|
7
7
|
from ivoryos.utils.global_config import GlobalConfig
|
|
8
|
-
from ivoryos.utils.form import create_form_from_module
|
|
8
|
+
from ivoryos.utils.form import create_form_from_module, create_form_from_pseudo
|
|
9
9
|
from ivoryos.utils.task_runner import TaskRunner
|
|
10
10
|
|
|
11
11
|
global_config = GlobalConfig()
|
|
@@ -48,7 +48,10 @@ def deck_controllers(instrument: str = None):
|
|
|
48
48
|
forms = None
|
|
49
49
|
if instrument:
|
|
50
50
|
inst_object = find_instrument_by_name(instrument)
|
|
51
|
-
|
|
51
|
+
if instrument.startswith("blocks"):
|
|
52
|
+
forms = create_form_from_pseudo(pseudo=inst_object, autofill=False, design=False)
|
|
53
|
+
else:
|
|
54
|
+
forms = create_form_from_module(sdl_module=inst_object, autofill=False, design=False)
|
|
52
55
|
order = get_session_by_instrument('card_order', instrument)
|
|
53
56
|
hidden_functions = get_session_by_instrument('hidden_functions', instrument)
|
|
54
57
|
functions = list(forms.keys())
|
|
@@ -99,12 +102,11 @@ def deck_controllers(instrument: str = None):
|
|
|
99
102
|
function_data["signature"] = str(function_data["signature"])
|
|
100
103
|
return jsonify(snapshot)
|
|
101
104
|
|
|
102
|
-
deck_variables = global_config.deck_snapshot.keys()
|
|
103
|
-
temp_variables = global_config.defined_variables.keys()
|
|
104
105
|
return render_template(
|
|
105
106
|
"controllers.html",
|
|
106
|
-
defined_variables=
|
|
107
|
-
|
|
107
|
+
defined_variables=global_config.deck_snapshot.keys(),
|
|
108
|
+
block_variables=global_config.building_blocks.keys(),
|
|
109
|
+
temp_variables=global_config.defined_variables.keys(),
|
|
108
110
|
instrument=instrument,
|
|
109
111
|
forms=forms,
|
|
110
112
|
session=session
|
|
@@ -49,6 +49,24 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
</div>
|
|
51
51
|
{% endif %}
|
|
52
|
+
|
|
53
|
+
{% if block_variables %}
|
|
54
|
+
<div class="mb-4">
|
|
55
|
+
<h6 class="fw-bold text-secondary mb-2" style="letter-spacing: 1px;">Methods</h6>
|
|
56
|
+
<div class="list-group">
|
|
57
|
+
{% for inst in block_variables %}
|
|
58
|
+
<a class="list-group-item list-group-item-action d-flex align-items-center {% if instrument == inst %}active bg-warning text-dark border-0{% else %}bg-light{% endif %}"
|
|
59
|
+
href="{{ url_for('control.deck_controllers') }}?instrument={{ inst }}"
|
|
60
|
+
style="border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background 0.2s;">
|
|
61
|
+
<span class="flex-grow-1">{{ inst | format_name }}</span>
|
|
62
|
+
{% if instrument == inst %}
|
|
63
|
+
<span class="ms-auto">></span>
|
|
64
|
+
{% endif %}
|
|
65
|
+
</a>
|
|
66
|
+
{% endfor %}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
{% endif %}
|
|
52
70
|
<!-- Action Buttons -->
|
|
53
71
|
<div class="mb-4">
|
|
54
72
|
<a href="{{ url_for('control.file.download_proxy', filetype='proxy') }}" class="btn btn-outline-primary w-100 mb-2">
|
|
@@ -89,6 +107,15 @@
|
|
|
89
107
|
{{ field(class="btn btn-dark") }}
|
|
90
108
|
{% elif field.type == "BooleanField" %}
|
|
91
109
|
{{ field(class="form-check-input") }}
|
|
110
|
+
{% elif field.type == "FlexibleEnumField" %}
|
|
111
|
+
<input type="text" id="{{ field.id }}" name="{{ field.name }}" value="{{ field.data }}"
|
|
112
|
+
list="{{ field.id }}_options" placeholder="{{ field.render_kw.placeholder if field.render_kw and field.render_kw.placeholder }}"
|
|
113
|
+
class="form-control">
|
|
114
|
+
<datalist id="{{ field.id }}_options">
|
|
115
|
+
{% for key in field.choices %}
|
|
116
|
+
<option value="{{ key }}">{{ key }}</option>
|
|
117
|
+
{% endfor %}
|
|
118
|
+
</datalist>
|
|
92
119
|
{% else %}
|
|
93
120
|
{{ field(class="form-control") }}
|
|
94
121
|
{% endif %}
|
|
@@ -13,6 +13,8 @@ def find_instrument_by_name(name: str):
|
|
|
13
13
|
if name.startswith("deck"):
|
|
14
14
|
name = name.replace("deck.", "")
|
|
15
15
|
return getattr(global_config.deck, name)
|
|
16
|
+
elif name.startswith("blocks"):
|
|
17
|
+
return global_config.building_blocks[name]
|
|
16
18
|
elif name in global_config.defined_variables:
|
|
17
19
|
return global_config.defined_variables[name]
|
|
18
20
|
elif name in globals():
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from flask import Blueprint, redirect, url_for, request, render_template, current_app, jsonify, send_file
|
|
4
|
+
from flask_login import login_required
|
|
5
|
+
|
|
6
|
+
from ivoryos.utils.db_models import db, WorkflowRun, WorkflowStep, WorkflowPhase
|
|
7
|
+
|
|
8
|
+
data = Blueprint('data', __name__, template_folder='templates')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@data.route('/executions/records')
|
|
13
|
+
@login_required
|
|
14
|
+
def list_workflows():
|
|
15
|
+
"""
|
|
16
|
+
.. :quickref: Workflow Execution Database; list all workflow execution records
|
|
17
|
+
|
|
18
|
+
list all workflow execution records
|
|
19
|
+
|
|
20
|
+
.. http:get:: /executions/records
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
query = WorkflowRun.query.order_by(WorkflowRun.id.desc())
|
|
24
|
+
search_term = request.args.get("keyword", None)
|
|
25
|
+
if search_term:
|
|
26
|
+
query = query.filter(WorkflowRun.name.like(f'%{search_term}%'))
|
|
27
|
+
page = request.args.get('page', default=1, type=int)
|
|
28
|
+
per_page = 10
|
|
29
|
+
|
|
30
|
+
workflows = query.paginate(page=page, per_page=per_page, error_out=False)
|
|
31
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
32
|
+
workflows = query.all()
|
|
33
|
+
workflow_data = {w.id:{"workflow_name":w.name, "start_time":w.start_time} for w in workflows}
|
|
34
|
+
return jsonify({
|
|
35
|
+
"workflow_data": workflow_data,
|
|
36
|
+
})
|
|
37
|
+
else:
|
|
38
|
+
return render_template('workflow_database.html', workflows=workflows)
|
|
39
|
+
|
|
40
|
+
@data.get("/executions/records/<int:workflow_id>")
|
|
41
|
+
def workflow_logs(workflow_id:int):
|
|
42
|
+
"""
|
|
43
|
+
.. :quickref: Workflow Data Database; get workflow data, steps, and logs
|
|
44
|
+
|
|
45
|
+
get workflow data logs by workflow id
|
|
46
|
+
|
|
47
|
+
.. http:get:: /executions/<int:workflow_id>
|
|
48
|
+
|
|
49
|
+
:param workflow_id: workflow id
|
|
50
|
+
:type workflow_id: int
|
|
51
|
+
"""
|
|
52
|
+
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
53
|
+
if not workflow:
|
|
54
|
+
return jsonify({"error": "Workflow not found"}), 404
|
|
55
|
+
|
|
56
|
+
# Query all phases for this run, ordered by start_time
|
|
57
|
+
phases = WorkflowPhase.query.filter_by(run_id=workflow_id).order_by(WorkflowPhase.start_time).all()
|
|
58
|
+
|
|
59
|
+
# Prepare grouped data for template (full objects)
|
|
60
|
+
grouped = {
|
|
61
|
+
"prep": [],
|
|
62
|
+
"script": {},
|
|
63
|
+
"cleanup": [],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Prepare grouped data for JSON (dicts)
|
|
67
|
+
grouped_json = {
|
|
68
|
+
"prep": [],
|
|
69
|
+
"script": {},
|
|
70
|
+
"cleanup": [],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for phase in phases:
|
|
74
|
+
phase_dict = phase.as_dict()
|
|
75
|
+
|
|
76
|
+
# Steps sorted by step_index
|
|
77
|
+
steps = sorted(phase.steps, key=lambda s: s.step_index)
|
|
78
|
+
phase_steps_dicts = [s.as_dict() for s in steps]
|
|
79
|
+
|
|
80
|
+
if phase.name == "prep":
|
|
81
|
+
grouped["prep"].append(phase)
|
|
82
|
+
grouped_json["prep"].append({
|
|
83
|
+
**phase_dict,
|
|
84
|
+
"steps": phase_steps_dicts
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
elif phase.name == "main":
|
|
88
|
+
grouped["script"].setdefault(phase.repeat_index, []).append(phase)
|
|
89
|
+
grouped_json["script"].setdefault(phase.repeat_index, []).append({
|
|
90
|
+
**phase_dict,
|
|
91
|
+
"steps": phase_steps_dicts
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
elif phase.name == "cleanup":
|
|
95
|
+
grouped["cleanup"].append(phase)
|
|
96
|
+
grouped_json["cleanup"].append({
|
|
97
|
+
**phase_dict,
|
|
98
|
+
"steps": phase_steps_dicts
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if request.accept_mimetypes.best_match(['application/json', 'text/html']) == 'application/json':
|
|
102
|
+
return jsonify({
|
|
103
|
+
"workflow_info": workflow.as_dict(),
|
|
104
|
+
"phases": grouped_json,
|
|
105
|
+
})
|
|
106
|
+
else:
|
|
107
|
+
return render_template("workflow_view.html", workflow=workflow, grouped=grouped)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@data.get("/executions/data/<int:workflow_id>")
|
|
111
|
+
def workflow_phase_data(workflow_id: int):
|
|
112
|
+
workflow = db.session.get(WorkflowRun, workflow_id)
|
|
113
|
+
if not workflow:
|
|
114
|
+
return jsonify({})
|
|
115
|
+
|
|
116
|
+
phase_data = {}
|
|
117
|
+
# Only plot 'main' phases
|
|
118
|
+
main_phases = WorkflowPhase.query.filter_by(run_id=workflow_id, name='main').order_by(
|
|
119
|
+
WorkflowPhase.repeat_index).all()
|
|
120
|
+
|
|
121
|
+
for phase in main_phases:
|
|
122
|
+
outputs = phase.outputs or {}
|
|
123
|
+
phase_index = phase.repeat_index
|
|
124
|
+
# Convert each key to list of dicts for x (phase_index) and y (value)
|
|
125
|
+
phase_data[phase_index] = {}
|
|
126
|
+
for k, v in outputs.items():
|
|
127
|
+
if isinstance(v, (int, float)):
|
|
128
|
+
phase_data[phase_index][k] = [{"x": phase_index, "y": v}]
|
|
129
|
+
elif isinstance(v, list) and all(isinstance(i, (int, float)) for i in v):
|
|
130
|
+
phase_data[phase_index][k] = v.map(lambda val, idx=0: {"x": phase_index, "y": val})
|
|
131
|
+
|
|
132
|
+
return jsonify(phase_data)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@data.delete("/executions/records/<int:workflow_id>")
|
|
136
|
+
@login_required
|
|
137
|
+
def delete_workflow_record(workflow_id: int):
|
|
138
|
+
"""
|
|
139
|
+
.. :quickref: Workflow Data Database; delete a workflow execution record
|
|
140
|
+
|
|
141
|
+
delete a workflow execution record by workflow id
|
|
142
|
+
|
|
143
|
+
.. http:delete:: /executions/records/<int:workflow_id>
|
|
144
|
+
|
|
145
|
+
:param workflow_id: workflow id
|
|
146
|
+
:type workflow_id: int
|
|
147
|
+
:status 200: return success message
|
|
148
|
+
"""
|
|
149
|
+
run = WorkflowRun.query.get(workflow_id)
|
|
150
|
+
db.session.delete(run)
|
|
151
|
+
db.session.commit()
|
|
152
|
+
return jsonify(success=True)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@data.route('/files/execution-data/<string:filename>')
|
|
156
|
+
@login_required
|
|
157
|
+
def download_results(filename:str):
|
|
158
|
+
"""
|
|
159
|
+
.. :quickref: Workflow data; download a workflow data file (.CSV)
|
|
160
|
+
|
|
161
|
+
.. http:get:: /files/execution-data/<string:filename>
|
|
162
|
+
|
|
163
|
+
:param filename: workflow data filename
|
|
164
|
+
:type filename: str
|
|
165
|
+
|
|
166
|
+
# :status 302: load pseudo deck and then redirects to :http:get:`/ivoryos/executions`
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
filepath = os.path.join(current_app.config["DATA_FOLDER"], filename)
|
|
170
|
+
return send_file(os.path.abspath(filepath), as_attachment=True)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<div class="card mb-2 {{ 'border-danger text-danger bg-light' if phase.run_error else 'border-secondary' }}">
|
|
2
|
+
<div class="card-body p-2">
|
|
3
|
+
<small class="text-muted">
|
|
4
|
+
<i class="fas fa-play-circle me-1"></i> Start: {{ phase.start_time.strftime('%H:%M:%S') if phase.start_time else 'N/A' }}
|
|
5
|
+
<i class="fas fa-stop-circle ms-2 me-1"></i> End: {{ phase.end_time.strftime('%H:%M:%S') if phase.end_time else 'N/A' }}
|
|
6
|
+
</small>
|
|
7
|
+
{% if phase.parameters %}
|
|
8
|
+
<div class="mt-2">
|
|
9
|
+
<strong>Parameters: </strong>
|
|
10
|
+
{% for key, value in phase.parameters.items() %}
|
|
11
|
+
<span class="badge bg-secondary me-1">{{ key }}: {{ value }}</span>
|
|
12
|
+
{% endfor %}
|
|
13
|
+
</div>
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if phase.steps %}
|
|
16
|
+
<div class="mt-2">
|
|
17
|
+
<strong>Steps:</strong>
|
|
18
|
+
<ul class="mb-0">
|
|
19
|
+
{% for step in phase.steps %}
|
|
20
|
+
<li class="{{ 'text-danger' if step.run_error else '' }}">
|
|
21
|
+
{{ step.method_name }}
|
|
22
|
+
<small class="text-muted">
|
|
23
|
+
({{ step.start_time.strftime('%H:%M:%S') if step.start_time else 'N/A' }} –
|
|
24
|
+
{{ step.end_time.strftime('%H:%M:%S') if step.end_time else 'N/A' }})
|
|
25
|
+
</small>
|
|
26
|
+
</li>
|
|
27
|
+
{% endfor %}
|
|
28
|
+
</ul>
|
|
29
|
+
</div>
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if phase.outputs %}
|
|
32
|
+
<div class="mt-1">
|
|
33
|
+
<strong>Outputs:</strong>
|
|
34
|
+
<ul class="mb-0">
|
|
35
|
+
{% for key, value in phase.outputs.items() %}
|
|
36
|
+
<li>{{ key }}: {{ value }}</li>
|
|
37
|
+
{% endfor %}
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|