ivoryos 1.0.9__py3-none-any.whl → 1.4.4__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.
Files changed (107) hide show
  1. docs/source/conf.py +84 -0
  2. ivoryos/__init__.py +17 -207
  3. ivoryos/app.py +154 -0
  4. ivoryos/config.py +1 -0
  5. ivoryos/optimizer/ax_optimizer.py +191 -0
  6. ivoryos/optimizer/base_optimizer.py +84 -0
  7. ivoryos/optimizer/baybe_optimizer.py +193 -0
  8. ivoryos/optimizer/nimo_optimizer.py +173 -0
  9. ivoryos/optimizer/registry.py +11 -0
  10. ivoryos/routes/auth/auth.py +43 -14
  11. ivoryos/routes/auth/templates/change_password.html +32 -0
  12. ivoryos/routes/control/control.py +101 -366
  13. ivoryos/routes/control/control_file.py +33 -0
  14. ivoryos/routes/control/control_new_device.py +152 -0
  15. ivoryos/routes/control/templates/controllers.html +193 -0
  16. ivoryos/routes/control/templates/controllers_new.html +112 -0
  17. ivoryos/routes/control/utils.py +40 -0
  18. ivoryos/routes/data/data.py +197 -0
  19. ivoryos/routes/data/templates/components/step_card.html +78 -0
  20. ivoryos/routes/{database/templates/database → data/templates}/workflow_database.html +14 -8
  21. ivoryos/routes/data/templates/workflow_view.html +360 -0
  22. ivoryos/routes/design/__init__.py +4 -0
  23. ivoryos/routes/design/design.py +348 -657
  24. ivoryos/routes/design/design_file.py +68 -0
  25. ivoryos/routes/design/design_step.py +171 -0
  26. ivoryos/routes/design/templates/components/action_form.html +53 -0
  27. ivoryos/routes/design/templates/components/actions_panel.html +25 -0
  28. ivoryos/routes/design/templates/components/autofill_toggle.html +10 -0
  29. ivoryos/routes/design/templates/components/canvas.html +5 -0
  30. ivoryos/routes/design/templates/components/canvas_footer.html +9 -0
  31. ivoryos/routes/design/templates/components/canvas_header.html +75 -0
  32. ivoryos/routes/design/templates/components/canvas_main.html +39 -0
  33. ivoryos/routes/design/templates/components/deck_selector.html +10 -0
  34. ivoryos/routes/design/templates/components/edit_action_form.html +53 -0
  35. ivoryos/routes/design/templates/components/info_modal.html +318 -0
  36. ivoryos/routes/design/templates/components/instruments_panel.html +88 -0
  37. ivoryos/routes/design/templates/components/modals/drop_modal.html +17 -0
  38. ivoryos/routes/design/templates/components/modals/json_modal.html +22 -0
  39. ivoryos/routes/design/templates/components/modals/new_script_modal.html +17 -0
  40. ivoryos/routes/design/templates/components/modals/rename_modal.html +23 -0
  41. ivoryos/routes/design/templates/components/modals/saveas_modal.html +27 -0
  42. ivoryos/routes/design/templates/components/modals.html +6 -0
  43. ivoryos/routes/design/templates/components/python_code_overlay.html +56 -0
  44. ivoryos/routes/design/templates/components/sidebar.html +15 -0
  45. ivoryos/routes/design/templates/components/text_to_code_panel.html +20 -0
  46. ivoryos/routes/design/templates/experiment_builder.html +44 -0
  47. ivoryos/routes/execute/__init__.py +0 -0
  48. ivoryos/routes/execute/execute.py +377 -0
  49. ivoryos/routes/execute/execute_file.py +78 -0
  50. ivoryos/routes/execute/templates/components/error_modal.html +20 -0
  51. ivoryos/routes/execute/templates/components/logging_panel.html +56 -0
  52. ivoryos/routes/execute/templates/components/progress_panel.html +27 -0
  53. ivoryos/routes/execute/templates/components/run_panel.html +9 -0
  54. ivoryos/routes/execute/templates/components/run_tabs.html +60 -0
  55. ivoryos/routes/execute/templates/components/tab_bayesian.html +520 -0
  56. ivoryos/routes/execute/templates/components/tab_configuration.html +383 -0
  57. ivoryos/routes/execute/templates/components/tab_repeat.html +18 -0
  58. ivoryos/routes/execute/templates/experiment_run.html +30 -0
  59. ivoryos/routes/library/__init__.py +0 -0
  60. ivoryos/routes/library/library.py +157 -0
  61. ivoryos/routes/{database/templates/database/scripts_database.html → library/templates/library.html} +32 -23
  62. ivoryos/routes/main/main.py +31 -3
  63. ivoryos/routes/main/templates/{main/home.html → home.html} +4 -4
  64. ivoryos/server.py +180 -0
  65. ivoryos/socket_handlers.py +52 -0
  66. ivoryos/static/ivoryos_logo.png +0 -0
  67. ivoryos/static/js/action_handlers.js +384 -0
  68. ivoryos/static/js/db_delete.js +23 -0
  69. ivoryos/static/js/script_metadata.js +39 -0
  70. ivoryos/static/js/socket_handler.js +40 -5
  71. ivoryos/static/js/sortable_design.js +107 -56
  72. ivoryos/static/js/ui_state.js +114 -0
  73. ivoryos/templates/base.html +67 -8
  74. ivoryos/utils/bo_campaign.py +180 -3
  75. ivoryos/utils/client_proxy.py +267 -36
  76. ivoryos/utils/db_models.py +300 -65
  77. ivoryos/utils/decorators.py +34 -0
  78. ivoryos/utils/form.py +63 -29
  79. ivoryos/utils/global_config.py +34 -1
  80. ivoryos/utils/nest_script.py +314 -0
  81. ivoryos/utils/py_to_json.py +295 -0
  82. ivoryos/utils/script_runner.py +599 -165
  83. ivoryos/utils/serilize.py +201 -0
  84. ivoryos/utils/task_runner.py +71 -21
  85. ivoryos/utils/utils.py +50 -6
  86. ivoryos/version.py +1 -1
  87. ivoryos-1.4.4.dist-info/METADATA +263 -0
  88. ivoryos-1.4.4.dist-info/RECORD +119 -0
  89. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/WHEEL +1 -1
  90. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info}/top_level.txt +1 -0
  91. tests/unit/test_type_conversion.py +42 -0
  92. tests/unit/test_util.py +3 -0
  93. ivoryos/routes/control/templates/control/controllers.html +0 -78
  94. ivoryos/routes/control/templates/control/controllers_home.html +0 -55
  95. ivoryos/routes/control/templates/control/controllers_new.html +0 -89
  96. ivoryos/routes/database/database.py +0 -306
  97. ivoryos/routes/database/templates/database/step_card.html +0 -7
  98. ivoryos/routes/database/templates/database/workflow_view.html +0 -130
  99. ivoryos/routes/design/templates/design/experiment_builder.html +0 -521
  100. ivoryos/routes/design/templates/design/experiment_run.html +0 -558
  101. ivoryos-1.0.9.dist-info/METADATA +0 -218
  102. ivoryos-1.0.9.dist-info/RECORD +0 -61
  103. /ivoryos/routes/auth/templates/{auth/login.html → login.html} +0 -0
  104. /ivoryos/routes/auth/templates/{auth/signup.html → signup.html} +0 -0
  105. /ivoryos/routes/{database → data}/__init__.py +0 -0
  106. /ivoryos/routes/main/templates/{main/help.html → help.html} +0 -0
  107. {ivoryos-1.0.9.dist-info → ivoryos-1.4.4.dist-info/licenses}/LICENSE +0 -0
@@ -2,25 +2,28 @@
2
2
 
3
3
  {% block title %}IvoryOS | Design Database{% endblock %}
4
4
  {% block body %}
5
- <div class="database-filter">
6
- {% for deck_name in deck_list %}
7
- {% if deck_name == "ALL" %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database')}}">Back</a>
8
- {% else %}<a class="btn btn-secondary" href="{{url_for('database.load_from_database',deck_name=deck_name)}}">{{deck_name}}</a>
9
- {% endif %}
5
+ <!-- Deck Filter Buttons -->
6
+ <div class="btn-group" role="group">
7
+ {% for deck in deck_list %}
8
+ <a class="btn {% if deck == current_deck_name %}btn-primary{% else %}btn-secondary{% endif %}"
9
+ href="{{ url_for('library.load_from_database', deck_name=deck) }}">
10
+ {{ deck }}
11
+ </a>
10
12
  {% endfor %}
11
-
12
- <form id="search" style="display: inline-block;float: right;" action="{{url_for('database.load_from_database',deck_name=deck_name)}}" method="GET">
13
- <div class="input-group">
14
- <div class="form-outline">
15
- <input type="search" name="keyword" id="keyword" class="form-control" placeholder="Search workflows...">
16
- </div>
17
- <button type="submit" class="btn btn-primary">
18
- <i class="bi bi-search"></i>
19
- </button>
20
- </div>
21
- </form>
22
13
  </div>
23
14
 
15
+ <!-- Search Form -->
16
+ <form id="search" class="d-flex " method="GET" style="display: inline-block;float: right;"
17
+ action="{{ url_for('library.load_from_database', deck_name=current_deck_name or 'ALL') }}">
18
+ <div class="input-group">
19
+ <input type="search" name="keyword" id="keyword" class="form-control"
20
+ placeholder="Search workflows..." value="{{ request.args.get('keyword', '') }}">
21
+ <button type="submit" class="btn btn-primary">
22
+ <i class="bi bi-search"></i>
23
+ </button>
24
+ </div>
25
+ </form>
26
+
24
27
  <table class="table table-hover" id="workflowLibrary">
25
28
  <thead>
26
29
  <tr>
@@ -37,7 +40,7 @@
37
40
  <tbody>
38
41
  {% for script in scripts %}
39
42
  <tr>
40
- <td><a href="{{ url_for('database.edit_workflow', script_name=script.name) }}">{{ script.name }}</a></td>
43
+ <td><a href="{{ url_for('library.workflow_script', script_name=script.name) }}">{{ script.name }}</a></td>
41
44
  <td>{{ script.deck }}</td>
42
45
  <td>{{ script.status }}</td>
43
46
  <td>{{ script.time_created }}</td>
@@ -46,8 +49,14 @@
46
49
  {# <td>{{ workflow.registered }}</td>#}
47
50
  <td>
48
51
  {#not workflow.status == "finalized" or#}
49
- {% if session['user'] == 'admin' or session['user'] == script.author %}
50
- <a href="{{ url_for('database.delete_workflow', script_name=script.name) }}">delete</a>
52
+ {% set username = current_user.get_id() %}
53
+ {% if username == 'admin' or username == script.author %}
54
+ <a href="#"
55
+ class="text-danger"
56
+ data-delete-url="{{ url_for('library.workflow_script', script_name=script.name) }}"
57
+ onclick="deleteWorkflow(this); return false;">
58
+ Delete
59
+ </a>
51
60
  {% else %}
52
61
  <a class="disabled-link">delete</a>
53
62
  {% endif %}
@@ -60,13 +69,13 @@
60
69
  {# paging#}
61
70
  <div class="pagination justify-content-center">
62
71
  <div class="page-item {{ 'disabled' if not scripts.has_prev else '' }}">
63
- <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.prev_num) }}">Previous</a>
72
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.prev_num) }}">Previous</a>
64
73
  </div>
65
74
 
66
75
  {% for num in scripts.iter_pages() %}
67
76
  {% if num %}
68
77
  <div class="page-item {{ 'active' if num == scripts.page else '' }}">
69
- <a class="page-link" href="{{ url_for('database.load_from_database', page=num) }}">{{ num }}</a>
78
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=num) }}">{{ num }}</a>
70
79
  </div>
71
80
  {% else %}
72
81
  <div class="page-item disabled">
@@ -76,8 +85,8 @@
76
85
  {% endfor %}
77
86
 
78
87
  <div class="page-item {{ 'disabled' if not scripts.has_next else '' }}">
79
- <a class="page-link" href="{{ url_for('database.load_from_database', page=scripts.next_num) }}">Next</a>
88
+ <a class="page-link" href="{{ url_for('library.load_from_database', page=scripts.next_num) }}">Next</a>
80
89
  </div>
81
90
  </div>
82
-
91
+ <script src="{{ url_for('static', filename='js/db_delete.js') }}"></script>
83
92
  {% endblock %}
@@ -1,8 +1,12 @@
1
- from flask import Blueprint, render_template, current_app
2
- from flask_login import login_required
1
+ import os
2
+
3
+ from flask import Blueprint, render_template, current_app, request, url_for
4
+ from flask_login import login_required, current_user
5
+ from werkzeug.utils import secure_filename, redirect
6
+ from ivoryos.utils.db_models import db
3
7
  from ivoryos.version import __version__ as ivoryos_version
4
8
 
5
- main = Blueprint('main', __name__, template_folder='templates/main')
9
+ main = Blueprint('main', __name__, template_folder='templates')
6
10
 
7
11
  @main.route("/")
8
12
  @login_required
@@ -40,3 +44,27 @@ def help_info():
40
44
  ivoryos(__name__)
41
45
  """
42
46
  return render_template('help.html', sample_deck=sample_deck)
47
+
48
+
49
+ @main.route('/customize-logo', methods=['POST'])
50
+ @login_required
51
+ def customize_logo():
52
+ if request.method == 'POST':
53
+ file = request.files.get('logo')
54
+ mode = request.form.get('mode')
55
+
56
+ if file and file.filename != '':
57
+ filename = secure_filename(file.filename)
58
+
59
+ USER_LOGO_DIR = os.path.join(current_app.static_folder, "user_logos")
60
+ os.makedirs(USER_LOGO_DIR, exist_ok=True)
61
+ filepath = os.path.join(USER_LOGO_DIR, filename)
62
+ file.save(filepath)
63
+
64
+ # Save to database
65
+ current_user.settings = {"logo_filename": filename, "logo_mode": mode}
66
+ # current_user.logo_mode = mode
67
+ db.session.commit()
68
+
69
+ return redirect(url_for('main.index'))
70
+
@@ -19,7 +19,7 @@
19
19
  <i class="bi bi-folder2-open me-2"></i>Browse designs
20
20
  </h5>
21
21
  <p class="card-text">View all saved workflows from the database.</p>
22
- <a href="{{ url_for('database.load_from_database') }}" class="stretched-link"></a>
22
+ <a href="{{ url_for('library.load_from_database') }}" class="stretched-link"></a>
23
23
  </div>
24
24
  </div>
25
25
  </div>
@@ -48,7 +48,7 @@
48
48
  <i class="bi bi-graph-up-arrow me-2"></i>Experiment data
49
49
  </h5>
50
50
  <p class="card-text">Browse workflow logs and output data.</p>
51
- <a href="{{ url_for('database.list_workflows') }}" class="stretched-link"></a>
51
+ <a href="{{ url_for('data.list_workflows') }}" class="stretched-link"></a>
52
52
  </div>
53
53
  </div>
54
54
  </div>
@@ -62,7 +62,7 @@
62
62
  <i class="bi bi-play-circle me-2"></i>Run current workflow
63
63
  </h5>
64
64
  <p class="card-text">Execute workflows with configurable parameters.</p>
65
- <a href="{{ url_for('design.experiment_run') }}" class="stretched-link"></a>
65
+ <a href="{{ url_for('execute.experiment_run') }}" class="stretched-link"></a>
66
66
  </div>
67
67
  </div>
68
68
  </div>
@@ -93,7 +93,7 @@
93
93
  <i class="bi bi-usb-plug me-2"></i>Connect a new device
94
94
  </h5>
95
95
  <p class="card-text">Add new hardware temporarily or for testing purposes.</p>
96
- <a href="{{ url_for('control.controllers_home') }}" class="stretched-link"></a>
96
+ <a href="{{ url_for('control.temp.new_controller') }}" class="stretched-link"></a>
97
97
  </div>
98
98
  </div>
99
99
  </div>
ivoryos/server.py ADDED
@@ -0,0 +1,180 @@
1
+ import os
2
+ import sqlite3
3
+ import sys
4
+ from typing import Union
5
+
6
+ from flask import Blueprint
7
+
8
+ from sqlalchemy import Engine, event
9
+
10
+ # from ivoryos import BUILDING_BLOCKS
11
+ from ivoryos.app import create_app
12
+ from ivoryos.config import Config, get_config
13
+ from ivoryos.optimizer.registry import OPTIMIZER_REGISTRY
14
+ from ivoryos.routes.auth.auth import login_manager
15
+ from ivoryos.routes.control.control import global_config
16
+ from ivoryos.socket_handlers import socketio
17
+ from ivoryos.utils import utils
18
+ from ivoryos.utils.db_models import db, User
19
+
20
+
21
+ url_prefix = os.getenv('URL_PREFIX', "/ivoryos")
22
+
23
+ @event.listens_for(Engine, "connect")
24
+ def enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
25
+ if isinstance(dbapi_connection, sqlite3.Connection):
26
+ cursor = dbapi_connection.cursor()
27
+ cursor.execute("PRAGMA foreign_keys=ON")
28
+ cursor.close()
29
+
30
+
31
+
32
+ @login_manager.user_loader
33
+ def load_user(user_id):
34
+ """
35
+ This function is called by Flask-Login on every request to get the
36
+ current user object from the user ID stored in the session.
37
+ """
38
+ # The correct implementation is to fetch the user from the database.
39
+ return db.session.get(User, user_id)
40
+
41
+
42
+
43
+
44
+
45
+ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, model=None,
46
+ config: Config = None,
47
+ logger: Union[str, list] = None,
48
+ logger_output_name: str = None,
49
+ enable_design: bool = True,
50
+ blueprint_plugins: Union[list, Blueprint] = [],
51
+ exclude_names: list = [],
52
+ notification_handler=None,
53
+ optimizer_registry: dict = None,
54
+ ):
55
+ """
56
+ Start ivoryOS app server.
57
+
58
+ :param module: module name, __name__ for current module
59
+ :param host: host address, defaults to 0.0.0.0
60
+ :param port: port, defaults to None, and will use 8000
61
+ :param debug: debug mode, defaults to None (True)
62
+ :param llm_server: llm server, defaults to None.
63
+ :param model: llm model, defaults to None. If None, app will run without text-to-code feature
64
+ :param config: config class, defaults to None
65
+ :param logger: logger name of list of logger names, defaults to None
66
+ :param logger_output_name: log file save name of logger, defaults to None, and will use "default.log"
67
+ :param enable_design: enable design canvas, database and workflow execution
68
+ :param blueprint_plugins: Union[list[Blueprint], Blueprint] custom Blueprint pages
69
+ :param exclude_names: list[str] module names to exclude from parsing
70
+ :param notification_handler: notification handler function
71
+ """
72
+ app = create_app(config_class=config or get_config()) # Create app instance using factory function
73
+
74
+ # plugins = load_installed_plugins(app, socketio)
75
+ plugins = []
76
+ if blueprint_plugins:
77
+ config_plugins = load_plugins(blueprint_plugins, app, socketio)
78
+ plugins.extend(config_plugins)
79
+
80
+ def inject_nav_config():
81
+ """Make NAV_CONFIG available globally to all templates."""
82
+ return dict(
83
+ enable_design=enable_design,
84
+ plugins=plugins,
85
+ )
86
+
87
+ app.context_processor(inject_nav_config)
88
+ port = port or int(os.environ.get("PORT", 8000))
89
+ debug = debug if debug is not None else app.config.get('DEBUG', True)
90
+
91
+ app.config["LOGGERS"] = logger
92
+ app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
93
+ logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
94
+ dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
95
+ if optimizer_registry:
96
+ global_config.optimizers = optimizer_registry
97
+ else:
98
+ global_config.optimizers = OPTIMIZER_REGISTRY
99
+ if module:
100
+ app.config["MODULE"] = module
101
+ app.config["OFF_LINE"] = False
102
+ global_config.deck = sys.modules[module]
103
+ global_config.building_blocks = utils.create_block_snapshot()
104
+ global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
105
+ output_path=dummy_deck_path,
106
+ save=True,
107
+ exclude_names=exclude_names
108
+ )
109
+ global_config.api_variables = utils.create_module_snapshot(global_config.deck)
110
+
111
+ else:
112
+ app.config["OFF_LINE"] = True
113
+ if model:
114
+ app.config["ENABLE_LLM"] = True
115
+ app.config["LLM_MODEL"] = model
116
+ app.config["LLM_SERVER"] = llm_server
117
+ utils.install_and_import('openai')
118
+ from ivoryos.utils.llm_agent import LlmAgent
119
+ global_config.agent = LlmAgent(host=llm_server, model=model,
120
+ output_path=app.config["OUTPUT_FOLDER"] if module is not None else None)
121
+ else:
122
+ app.config["ENABLE_LLM"] = False
123
+
124
+
125
+ # --- Logger registration ---
126
+ if logger:
127
+ if isinstance(logger, str):
128
+ logger = [logger] # convert single logger to list
129
+ elif not isinstance(logger, list):
130
+ raise TypeError("logger must be a string or a list of strings.")
131
+
132
+ for log_name in logger:
133
+ utils.start_logger(socketio, log_filename=logger_path, logger_name=log_name)
134
+
135
+ # --- Notification handler registration ---
136
+ if notification_handler:
137
+
138
+ # make it a list if a single function is passed
139
+ if callable(notification_handler):
140
+ notification_handler = [notification_handler]
141
+
142
+ if not isinstance(notification_handler, list):
143
+ raise ValueError("notification_handlers must be a callable or a list of callables.")
144
+
145
+ # validate all items are callable
146
+ for handler in notification_handler:
147
+ if not callable(handler):
148
+ raise TypeError(f"Handler {handler} is not callable.")
149
+ global_config.register_notification(handler)
150
+
151
+ # TODO in case Python 3.12 or higher doesn't log URL
152
+ # if sys.version_info >= (3, 12):
153
+ # ip = utils.get_local_ip()
154
+ # print(f"Server running at http://localhost:{port}")
155
+ # if not ip == "127.0.0.1":
156
+ # print(f"Server running at http://{ip}:{port}")
157
+ socketio.run(app, host=host, port=port, debug=debug, use_reloader=False, allow_unsafe_werkzeug=True)
158
+ # return app
159
+
160
+
161
+
162
+ def load_plugins(blueprints: Union[list, Blueprint], app, socketio):
163
+ """
164
+ Dynamically load installed plugins and attach Flask-SocketIO.
165
+ :param blueprints: Union[list, Blueprint] list of Blueprint objects or a single Blueprint object
166
+ :param app: Flask application instance
167
+ :param socketio: Flask-SocketIO instance
168
+ :return: list of plugin names
169
+ """
170
+ plugin_names = []
171
+ if not isinstance(blueprints, list):
172
+ blueprints = [blueprints]
173
+ for blueprint in blueprints:
174
+ # If the plugin has an `init_socketio()` function, pass socketio
175
+ if hasattr(blueprint, 'init_socketio'):
176
+ blueprint.init_socketio(socketio)
177
+ plugin_names.append(blueprint.name)
178
+ app.register_blueprint(blueprint, url_prefix=f"{url_prefix}/{blueprint.name}")
179
+ return plugin_names
180
+
@@ -0,0 +1,52 @@
1
+ import os
2
+ from flask import current_app
3
+ from flask_socketio import SocketIO
4
+ from ivoryos.utils.script_runner import ScriptRunner
5
+
6
+ socketio = SocketIO(cors_allowed_origins="*")
7
+ runner = ScriptRunner()
8
+
9
+ def abort_pending():
10
+ runner.abort_pending()
11
+ socketio.emit('log', {'message': "aborted pending iterations"})
12
+
13
+ def abort_current():
14
+ runner.stop_execution()
15
+ socketio.emit('log', {'message': "stopped next task"})
16
+
17
+ def pause():
18
+ runner.retry = False
19
+ msg = runner.toggle_pause()
20
+ socketio.emit('log', {'message': msg})
21
+ return msg
22
+
23
+ def retry():
24
+ runner.retry = True
25
+ msg = runner.toggle_pause()
26
+ socketio.emit('log', {'message': msg})
27
+
28
+ # Socket.IO Event Handlers
29
+ @socketio.on('abort_pending')
30
+ def handle_abort_pending():
31
+ abort_pending()
32
+
33
+ @socketio.on('abort_current')
34
+ def handle_abort_current():
35
+ abort_current()
36
+
37
+ @socketio.on('pause')
38
+ def handle_pause():
39
+ pause()
40
+
41
+ @socketio.on('retry')
42
+ def handle_retry():
43
+ retry()
44
+
45
+ @socketio.on('connect')
46
+ def handle_connect():
47
+ # Fetch log messages from local file
48
+ filename = os.path.join(current_app.config["OUTPUT_FOLDER"], current_app.config["LOGGERS_PATH"])
49
+ with open(filename, 'r') as log_file:
50
+ log_history = log_file.readlines()
51
+ for message in log_history[-10:]:
52
+ socketio.emit('log', {'message': message})
Binary file