algomancy-gui 0.3.16__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 (46) hide show
  1. algomancy_gui/__init__.py +0 -0
  2. algomancy_gui/admin_page/__init__.py +1 -0
  3. algomancy_gui/admin_page/admin.py +362 -0
  4. algomancy_gui/admin_page/sessions.py +57 -0
  5. algomancy_gui/appconfiguration.py +291 -0
  6. algomancy_gui/compare_page/__init__.py +1 -0
  7. algomancy_gui/compare_page/compare.py +360 -0
  8. algomancy_gui/compare_page/kpicard.py +236 -0
  9. algomancy_gui/compare_page/scenarioselector.py +99 -0
  10. algomancy_gui/componentids.py +177 -0
  11. algomancy_gui/contentregistry.py +167 -0
  12. algomancy_gui/cqmloader.py +58 -0
  13. algomancy_gui/data_page/__init__.py +1 -0
  14. algomancy_gui/data_page/data.py +77 -0
  15. algomancy_gui/data_page/datamanagementdeletemodal.py +260 -0
  16. algomancy_gui/data_page/datamanagementderivemodal.py +201 -0
  17. algomancy_gui/data_page/datamanagementdownloadmodal.py +193 -0
  18. algomancy_gui/data_page/datamanagementimportmodal.py +438 -0
  19. algomancy_gui/data_page/datamanagementsavemodal.py +191 -0
  20. algomancy_gui/data_page/datamanagementtopbar.py +123 -0
  21. algomancy_gui/data_page/datamanagementuploadmodal.py +366 -0
  22. algomancy_gui/data_page/dialogcallbacks.py +51 -0
  23. algomancy_gui/data_page/filenamematcher.py +109 -0
  24. algomancy_gui/defaultloader.py +36 -0
  25. algomancy_gui/gui_launcher.py +183 -0
  26. algomancy_gui/home_page/__init__.py +1 -0
  27. algomancy_gui/home_page/home.py +16 -0
  28. algomancy_gui/layout.py +199 -0
  29. algomancy_gui/layouthelpers.py +30 -0
  30. algomancy_gui/managergetters.py +28 -0
  31. algomancy_gui/overview_page/__init__.py +1 -0
  32. algomancy_gui/overview_page/overview.py +20 -0
  33. algomancy_gui/py.typed +0 -0
  34. algomancy_gui/scenario_page/__init__.py +0 -0
  35. algomancy_gui/scenario_page/delete_confirmation.py +29 -0
  36. algomancy_gui/scenario_page/new_scenario_creator.py +104 -0
  37. algomancy_gui/scenario_page/new_scenario_parameters_window.py +154 -0
  38. algomancy_gui/scenario_page/scenario_badge.py +36 -0
  39. algomancy_gui/scenario_page/scenario_cards.py +119 -0
  40. algomancy_gui/scenario_page/scenarios.py +596 -0
  41. algomancy_gui/sessionmanager.py +168 -0
  42. algomancy_gui/settingsmanager.py +43 -0
  43. algomancy_gui/stylingconfigurator.py +740 -0
  44. algomancy_gui-0.3.16.dist-info/METADATA +71 -0
  45. algomancy_gui-0.3.16.dist-info/RECORD +46 -0
  46. algomancy_gui-0.3.16.dist-info/WHEEL +4 -0
@@ -0,0 +1,51 @@
1
+ from dash import Input, Output, State, callback, get_app
2
+
3
+
4
+ from ..componentids import (
5
+ DATA_SELECTOR_DROPDOWN,
6
+ DM_DERIVE_SET_SELECTOR,
7
+ DM_DELETE_SET_SELECTOR,
8
+ DM_SAVE_SET_SELECTOR,
9
+ DM_DOWNLOAD_CHECKLIST,
10
+ DM_LIST_UPDATER_STORE,
11
+ ACTIVE_SESSION,
12
+ )
13
+ from algomancy_gui.managergetters import get_scenario_manager
14
+
15
+ """
16
+ Callback functions for data management dialogs in the dashboard application.
17
+
18
+ This module contains all the callback functions that handle interactions with
19
+ the data management modals, including deriving, deleting, loading, and saving data.
20
+ Each callback is associated with specific UI components and manages the state
21
+ and data flow between the UI and the backend ScenarioManager.
22
+ """
23
+
24
+
25
+ @callback(
26
+ [
27
+ Output(DATA_SELECTOR_DROPDOWN, "options", allow_duplicate=True),
28
+ Output(DM_DERIVE_SET_SELECTOR, "options", allow_duplicate=True),
29
+ Output(DM_DELETE_SET_SELECTOR, "options", allow_duplicate=True),
30
+ Output(DM_SAVE_SET_SELECTOR, "options", allow_duplicate=True),
31
+ Output(DM_DOWNLOAD_CHECKLIST, "options", allow_duplicate=True),
32
+ ],
33
+ [
34
+ Input(DM_LIST_UPDATER_STORE, "data"),
35
+ ],
36
+ [
37
+ State(ACTIVE_SESSION, "data"),
38
+ ],
39
+ prevent_initial_call=True,
40
+ )
41
+ def get_options_for_lists(data, session_id: str):
42
+ sm = get_scenario_manager(get_app().server, session_id)
43
+
44
+ options = [{"label": ds, "value": ds} for ds in sm.get_data_keys()]
45
+ derived_options = [
46
+ {"label": ds, "value": ds}
47
+ for ds in sm.get_data_keys()
48
+ if not sm.get_data(ds).is_master_data()
49
+ ]
50
+
51
+ return options, options, options, derived_options, options
@@ -0,0 +1,109 @@
1
+ from typing import List, Dict
2
+ from itertools import zip_longest
3
+
4
+ from algomancy_data import InputFileConfiguration
5
+
6
+
7
+ def hamming_distance(s1, s2):
8
+ """
9
+ Calculates the Hamming distance between two strings.
10
+
11
+ The Hamming distance is defined as the number of differing characters between
12
+ two strings of equal length. If the strings are of different lengths, it will
13
+ compare them up to the length of the shorter string and count differing
14
+ characters, considering any excess characters in the longer string as
15
+ additional differences.
16
+
17
+ Args:
18
+ s1: The first string to compare.
19
+ s2: The second string to compare.
20
+
21
+ Returns:
22
+ int: The Hamming distance between the two strings.
23
+ """
24
+ return sum(c1 != c2 for c1, c2 in zip_longest(s1, s2))
25
+
26
+
27
+ def find_closest_match(
28
+ file_names: List[str], file_configuration: InputFileConfiguration
29
+ ) -> str:
30
+ """
31
+ Finds the closest match to a file configuration's reference name.
32
+
33
+ Finds the file name from a list that is the closest match to a given file configuration's
34
+ reference name using the Hamming distance as the measure of similarity.
35
+
36
+ Args:
37
+ file_names (List[str]): A list of file names to evaluate.
38
+ file_configuration (InputFileConfiguration): Configuration object containing
39
+ the reference file name for comparison.
40
+
41
+ Returns:
42
+ str: The file name from the list that is the closest match to the reference
43
+ file name.
44
+ """
45
+ return min(
46
+ file_names,
47
+ key=lambda x: hamming_distance(
48
+ x.lower(), file_configuration.file_name_with_extension.lower()
49
+ ),
50
+ )
51
+
52
+
53
+ def is_bijective_mapping(mapping: Dict[str, str]) -> bool:
54
+ """
55
+ Determines if the input mapping is bijective.
56
+
57
+ A mapping is bijective if every element of the domain is mapped to a unique
58
+ element of the codomain, and every element of the codomain is uniquely
59
+ mapped back to an element in the domain. This function checks the bijection
60
+ by verifying that the lengths of the mapping's values and keys are equal
61
+ to the mapping itself.
62
+
63
+ Args:
64
+ mapping (Dict[str, str]): A dictionary representing the mapping between
65
+ two sets where keys represent the domain and values represent the
66
+ codomain.
67
+
68
+ Returns:
69
+ bool: True if the mapping is bijective, otherwise False.
70
+ """
71
+ return len(mapping) == len(set(mapping.values())) and len(mapping) == len(
72
+ set(mapping.keys())
73
+ )
74
+
75
+
76
+ def match_file_names(
77
+ file_configurations: List[InputFileConfiguration], file_names: List[str]
78
+ ) -> Dict[str, str]:
79
+ """
80
+ Matches file configurations to file names and attempts to create a bijective mapping.
81
+
82
+ This function takes a list of file configurations and file names and tries to match
83
+ each configuration to the closest corresponding name. The result will be a dictionary
84
+ representing a bijective mapping between the file configurations and the file names.
85
+ An exception is raised if such a bijective mapping cannot be established.
86
+
87
+ Args:
88
+ file_configurations: List of InputFileConfiguration objects to match.
89
+ file_names: List of available file names.
90
+
91
+ Raises:
92
+ AssertionError: If the number of file_names is less than or greater than the number
93
+ of file_configurations.
94
+ Exception: If a bijective mapping between file configurations and file names cannot
95
+ be established.
96
+
97
+ Returns:
98
+ A dictionary mapping file configuration names to file names.
99
+ """
100
+ assert len(file_names) >= len(file_configurations), "Missing input files"
101
+ assert len(file_names) <= len(file_configurations), "Too many input files"
102
+
103
+ initial_guess = {
104
+ fc.file_name: find_closest_match(file_names, fc) for fc in file_configurations
105
+ }
106
+ if is_bijective_mapping(initial_guess):
107
+ return initial_guess
108
+
109
+ raise Exception("Could not find bijective mapping.")
@@ -0,0 +1,36 @@
1
+ from dash import html
2
+ import dash_bootstrap_components as dbc
3
+
4
+
5
+ def default_loader(text: str = "Loading... ", scale: float | None = None) -> html.Div:
6
+ """
7
+ Creates a default loading with animated spinner
8
+
9
+ Args:
10
+ scale (float): float to control the size of the spinner. min: 0.5, max: 5
11
+ text (str): The text to display below the animation
12
+
13
+ Returns:
14
+ html.Div: A div containing the animated loader
15
+ """
16
+ assert (
17
+ not scale or 0.5 <= scale <= 5
18
+ ), f"Invalid scale for loader: {scale}. (min: 0.5, max: 5)"
19
+
20
+ default_font_size = 1.5
21
+ default_spinner_w = 1.5
22
+ default_spinner_h = 1.5
23
+
24
+ text_style = {}
25
+ spinner_style = {}
26
+ if scale is not None:
27
+ text_style["font-size"] = f"{default_font_size * scale}rem"
28
+ spinner_style["width"] = f"{default_spinner_w * scale}rem"
29
+ spinner_style["height"] = f"{default_spinner_h * scale}rem"
30
+ return html.Div(
31
+ html.H2(
32
+ [text, dbc.Spinner(spinner_style=spinner_style)],
33
+ style=text_style,
34
+ className="default-spinner",
35
+ ),
36
+ )
@@ -0,0 +1,183 @@
1
+ from typing import Dict, Any, Union
2
+ import importlib.metadata
3
+ import os
4
+
5
+ from waitress import serve
6
+ import dash_auth
7
+ from dash import get_app, Dash, html, dcc
8
+ from dash_bootstrap_components.themes import BOOTSTRAP
9
+
10
+ from .layout import LayoutCreator
11
+ from .contentregistry import ContentRegistry
12
+ from .settingsmanager import SettingsManager
13
+ from .sessionmanager import SessionManager
14
+ from .componentids import ACTIVE_SESSION
15
+ from .appconfiguration import AppConfiguration
16
+ from algomancy_content.librarymanager import LibraryManager as lm
17
+ from algomancy_scenario import ScenarioManager
18
+ from algomancy_utils.logger import MessageStatus
19
+
20
+
21
+ class GuiLauncher:
22
+ @staticmethod
23
+ def build(cfg: Union[AppConfiguration, Dict[str, Any]]) -> Dash:
24
+ # Normalize configuration to AppConfiguration for a single source of truth
25
+ if isinstance(cfg, dict):
26
+ cfg_obj = AppConfiguration(**cfg)
27
+ elif isinstance(cfg, AppConfiguration):
28
+ cfg_obj = cfg
29
+ else:
30
+ raise TypeError("DashLauncher.build expects AppConfiguration or dict")
31
+
32
+ if cfg_obj.use_sessions:
33
+ manager: SessionManager = SessionManager.from_config(cfg_obj)
34
+ else:
35
+ manager: ScenarioManager = ScenarioManager.from_config(cfg_obj)
36
+
37
+ # Create the app
38
+ app = GuiLauncher._construct(
39
+ cfg=cfg_obj,
40
+ manager=manager,
41
+ )
42
+
43
+ # register authentication if enabled
44
+ if cfg_obj.use_authentication:
45
+ if not os.getenv("APP_USERNAME") or not os.getenv("APP_PASSWORD"):
46
+ raise ValueError(
47
+ "Environment variables 'APP_USERNAME' and 'APP_PASSWORD' must be set"
48
+ ) # todo document where to set username and password
49
+
50
+ # add authentication to the app
51
+ dash_auth.BasicAuth(
52
+ app,
53
+ [[os.getenv("APP_USERNAME"), os.getenv("APP_PASSWORD")]],
54
+ secret_key="secret-key",
55
+ )
56
+
57
+ return app
58
+
59
+ @staticmethod
60
+ def _construct(
61
+ cfg: AppConfiguration,
62
+ manager: SessionManager | ScenarioManager,
63
+ ) -> Dash:
64
+ # Initialize the app
65
+ external_stylesheets = [
66
+ BOOTSTRAP,
67
+ "https://use.fontawesome.com/releases/v5.15.4/css/all.css",
68
+ ]
69
+
70
+ from pathlib import Path
71
+
72
+ assets_path = Path(os.getcwd()) / Path(cfg.assets_path)
73
+
74
+ app = Dash(
75
+ external_stylesheets=external_stylesheets,
76
+ suppress_callback_exceptions=True,
77
+ assets_folder=str(assets_path),
78
+ )
79
+ app.title = cfg.title
80
+
81
+ # register the scenario manager on the app object
82
+ if isinstance(manager, SessionManager):
83
+ app.server.session_manager = manager
84
+ app.server.use_sessions = True
85
+ default_session_name = app.server.session_manager.start_session_name
86
+ elif isinstance(manager, ScenarioManager):
87
+ app.server.scenario_manager = manager
88
+ app.server.use_sessions = False
89
+ default_session_name = None
90
+ else:
91
+ raise TypeError(
92
+ "DashLauncher._construct expects SessionManager or ScenarioManager"
93
+ )
94
+
95
+ # register the styling configuration on the app object
96
+ app.server.styling_config = cfg.styling_config
97
+
98
+ # register the settings manager on the app object for access in callbacks
99
+ app.server.settings = SettingsManager(cfg.as_dict())
100
+
101
+ # fetch standard pages
102
+ home_page, data_page, scenario_page, compare_page, overview_page = lm.get_pages(
103
+ cfg.as_dict()
104
+ )
105
+
106
+ # register the content register functions
107
+ content_registry = ContentRegistry()
108
+ app.server.content_registry = content_registry
109
+
110
+ # register pages
111
+ content_registry.register_pages(
112
+ home_page, data_page, scenario_page, compare_page, overview_page
113
+ )
114
+
115
+ # fill and run the app
116
+ app.layout = html.Div(
117
+ [
118
+ LayoutCreator.create_layout(cfg.styling_config),
119
+ dcc.Store(
120
+ id=ACTIVE_SESSION,
121
+ storage_type="session",
122
+ data=default_session_name,
123
+ ),
124
+ ]
125
+ )
126
+
127
+ return app
128
+
129
+ @staticmethod
130
+ def run(
131
+ app: Dash,
132
+ host: str,
133
+ port: int,
134
+ threads: int = 8,
135
+ connection_limit: int = 100,
136
+ debug: bool = False,
137
+ ) -> None:
138
+ server = get_app().server
139
+ if hasattr(server, "session_manager"):
140
+ manager = server.session_manager
141
+ elif hasattr(server, "scenario_manager"):
142
+ manager = server.scenario_manager
143
+ else:
144
+ raise Exception("No manager available")
145
+
146
+ algomancy_version = importlib.metadata.version("algomancy")
147
+ manager.log(f"Algomancy version: {algomancy_version}", MessageStatus.INFO)
148
+
149
+ if not debug:
150
+ manager.log(
151
+ "--------------------------------------------------------------------",
152
+ MessageStatus.SUCCESS,
153
+ )
154
+ manager.log(
155
+ f"Starting Dashboard server with Waitress on {host}:{port}...",
156
+ MessageStatus.SUCCESS,
157
+ )
158
+ manager.log(
159
+ f" threads:{threads}, connection limit: {connection_limit}",
160
+ MessageStatus.SUCCESS,
161
+ )
162
+ manager.log(
163
+ "--------------------------------------------------------------------",
164
+ MessageStatus.SUCCESS,
165
+ )
166
+ serve(
167
+ app.server,
168
+ host=host,
169
+ port=port,
170
+ threads=threads,
171
+ connection_limit=connection_limit,
172
+ )
173
+ else:
174
+ manager.log(
175
+ f"Starting Dashboard server in debug mode on {host}:{port}...",
176
+ MessageStatus.SUCCESS,
177
+ )
178
+ app.run(
179
+ debug=debug,
180
+ host=host,
181
+ port=port,
182
+ dev_tools_silence_routes_logging=False,
183
+ )
@@ -0,0 +1 @@
1
+ # This file is intentionally left empty to make the directory a Python package.
@@ -0,0 +1,16 @@
1
+ from dash import html, get_app
2
+
3
+ from algomancy_gui.contentregistry import ContentRegistry
4
+
5
+ from ..componentids import HOME_PAGE_CONTENT
6
+
7
+
8
+ def home_page():
9
+ """
10
+ Creates the home page layout.
11
+
12
+ Returns:
13
+ html.Div: A Dash HTML component representing the home page
14
+ """
15
+ cr: ContentRegistry = get_app().server.content_registry
16
+ return html.Div(cr.home_content(), id=HOME_PAGE_CONTENT)
@@ -0,0 +1,199 @@
1
+ from typing import Any
2
+
3
+
4
+ import dash_bootstrap_components as dbc
5
+ from dash import html, Output, callback, Input, State, dcc
6
+ from dash.html import Div
7
+
8
+ from algomancy_gui.stylingconfigurator import StylingConfigurator, LayoutSelection
9
+ from algomancy_gui.componentids import (
10
+ SIDEBAR_TOGGLE,
11
+ SIDEBAR,
12
+ PAGE_CONTENT,
13
+ SIDEBAR_COLLAPSED,
14
+ )
15
+
16
+ from algomancy_gui.home_page.home import home_page
17
+ from algomancy_gui.data_page.data import data_page
18
+ from algomancy_gui.scenario_page.scenarios import scenario_page
19
+ from algomancy_gui.compare_page.compare import compare_page
20
+ from algomancy_gui.admin_page.admin import admin_page
21
+ from algomancy_gui.overview_page.overview import overview_page
22
+
23
+
24
+ class LayoutCreator:
25
+ @staticmethod
26
+ def _create_menu_sidebar(styling: StylingConfigurator):
27
+ # Create toggle button
28
+ toggle_button = html.Button(
29
+ html.I(className="fas fa-chevron-left"),
30
+ id=SIDEBAR_TOGGLE,
31
+ className="btn toggle-sidebar-button",
32
+ )
33
+
34
+ # Create the sidebar (build children list dynamically based on available images)
35
+ sidebar_children: list[Any] = [toggle_button]
36
+ if styling.logo_url is not None or styling.button_url is not None:
37
+ logos = []
38
+ if styling.logo_url is not None:
39
+ logos.append(
40
+ html.Img(
41
+ src=styling.logo_url,
42
+ width="210px",
43
+ className="mb-2 expanded-logo sidebar-content-fade",
44
+ id="sidebar-logo",
45
+ )
46
+ )
47
+
48
+ if styling.button_url is not None:
49
+ logos.append(
50
+ html.Img(
51
+ src=styling.button_url,
52
+ width="40px",
53
+ className="mb-2 collapsed-logo",
54
+ id="sidebar-icon",
55
+ )
56
+ )
57
+ sidebar_children.extend(
58
+ [
59
+ html.Div(
60
+ logos,
61
+ className="sidebar-logo-wrapper",
62
+ id="sidebar-logo-wrapper",
63
+ ),
64
+ html.Hr(className="bg-light"),
65
+ ]
66
+ )
67
+
68
+ nav_items = [
69
+ {"icon": "fas fa-home", "label": "Home", "href": "/", "index": 1},
70
+ {"icon": "fas fa-database", "label": "Data", "href": "/data", "index": 2},
71
+ {
72
+ "icon": "fas fa-project-diagram",
73
+ "label": "Scenarios",
74
+ "href": "/scenarios",
75
+ "index": 3,
76
+ },
77
+ {
78
+ "icon": "fas fa-chart-line",
79
+ "label": "Compare",
80
+ "href": "/compare",
81
+ "index": 4,
82
+ },
83
+ {
84
+ "icon": "fas fa-table",
85
+ "label": "Overview",
86
+ "href": "/overview",
87
+ "index": 5,
88
+ },
89
+ {
90
+ "icon": "fas fa-user-shield",
91
+ "label": "Admin",
92
+ "href": "/admin",
93
+ "index": 6,
94
+ },
95
+ ]
96
+
97
+ sidebar_nav = dbc.Nav(
98
+ [
99
+ dbc.NavLink(
100
+ [
101
+ html.I(className=f"{item['icon']} me-2"),
102
+ html.Span(
103
+ item["label"],
104
+ id={"type": "sidebar-text", "index": item["index"]},
105
+ className="sidebar-content-fade",
106
+ ),
107
+ ],
108
+ href=item["href"],
109
+ className="sidebar-link",
110
+ id={"type": "sidebar-link", "index": item["index"]},
111
+ active="exact",
112
+ )
113
+ for item in nav_items
114
+ ],
115
+ vertical=True,
116
+ pills=True,
117
+ className="sidebar-nav",
118
+ )
119
+
120
+ sidebar_children.append(sidebar_nav)
121
+
122
+ # Create sidebar
123
+ sidebar = html.Div(
124
+ sidebar_children,
125
+ id=SIDEBAR,
126
+ className="expanded sidebar-layout",
127
+ )
128
+
129
+ # Routing callback
130
+ @callback(Output("page-content", "children"), Input("url", "pathname"))
131
+ def display_page(pathname):
132
+ if pathname == "/":
133
+ return home_page()
134
+ elif pathname == "/data":
135
+ return data_page()
136
+ elif pathname == "/scenarios":
137
+ return scenario_page()
138
+ elif pathname == "/compare":
139
+ return compare_page()
140
+ elif pathname == "/overview":
141
+ return overview_page()
142
+ elif pathname == "/admin":
143
+ return admin_page()
144
+ else:
145
+ return html.H1("404 - Page not found")
146
+
147
+ @callback(
148
+ Output(SIDEBAR, "className"),
149
+ Output(PAGE_CONTENT, "className"),
150
+ Input(SIDEBAR_TOGGLE, "n_clicks"),
151
+ State(SIDEBAR, "className"),
152
+ State(PAGE_CONTENT, "className"),
153
+ )
154
+ def update_sidebar_class(
155
+ n_clicks, current_sidebar_className, current_page_content_className
156
+ ):
157
+ if n_clicks is None:
158
+ return current_sidebar_className, current_page_content_className
159
+ is_expanded = "collapsed" not in current_sidebar_className
160
+ if is_expanded:
161
+ return "collapsed sidebar-layout", "collapsed page-content"
162
+ else:
163
+ return "expanded sidebar-layout", "expanded page-content"
164
+
165
+ return sidebar
166
+
167
+ @staticmethod
168
+ def _create_sidebar_layout(styling: StylingConfigurator) -> html.Div:
169
+ themed_styling = styling.initiate_theme_colors()
170
+
171
+ layout = html.Div(
172
+ [
173
+ dcc.Location(id="url", refresh=False),
174
+ dcc.Store(id=SIDEBAR_COLLAPSED, data=False),
175
+ html.Div(
176
+ [
177
+ LayoutCreator._create_menu_sidebar(styling),
178
+ html.Div(id=PAGE_CONTENT, className="expanded page-content"),
179
+ ],
180
+ className="layout-container",
181
+ ),
182
+ ],
183
+ style=themed_styling,
184
+ )
185
+
186
+ return layout
187
+
188
+ @staticmethod
189
+ def create_layout(styling_config: StylingConfigurator) -> Div | None:
190
+ match styling_config.layout_selection:
191
+ case LayoutSelection.SIDEBAR:
192
+ return LayoutCreator._create_sidebar_layout(styling_config)
193
+ case LayoutSelection.TABBED:
194
+ raise NotImplementedError
195
+ case LayoutSelection.FULLSCREEN:
196
+ raise NotImplementedError
197
+ case LayoutSelection.CUSTOM:
198
+ raise NotImplementedError
199
+ return None
@@ -0,0 +1,30 @@
1
+ from dash import html, dcc
2
+
3
+ from algomancy_gui.cqmloader import cqm_loader
4
+ from algomancy_gui.defaultloader import default_loader
5
+
6
+
7
+ def create_wrapped_content_div(
8
+ content_div: html.Div,
9
+ show_loading: bool,
10
+ cqm: bool,
11
+ spinner_scale: float = 2,
12
+ ) -> html.Div:
13
+ if show_loading:
14
+ spinner = (
15
+ cqm_loader(scale=spinner_scale)
16
+ if cqm
17
+ else default_loader(scale=spinner_scale)
18
+ )
19
+ return html.Div(
20
+ dcc.Loading(
21
+ content_div,
22
+ overlay_style={"visibility": "visible", "filter": "blur(2px)"},
23
+ custom_spinner=spinner,
24
+ delay_hide=0,
25
+ delay_show=200,
26
+ className="loading-wrapper",
27
+ ),
28
+ )
29
+ else:
30
+ return content_div
@@ -0,0 +1,28 @@
1
+ from algomancy_scenario import ScenarioManager
2
+
3
+
4
+ def get_scenario_manager(
5
+ server, active_session_name: str | None = None
6
+ ) -> ScenarioManager:
7
+ """Returns the scenario manager.
8
+ When sessions are enabled, this will return the active scenario manager via the session manager.
9
+ When sessions are disabled, this will return the scenario manager which was registered on the server object
10
+ """
11
+ if hasattr(server, "session_manager"):
12
+ sm: ScenarioManager = server.session_manager.get_scenario_manager(
13
+ active_session_name
14
+ )
15
+ elif hasattr(server, "scenario_manager"):
16
+ sm: ScenarioManager = server.scenario_manager
17
+ else:
18
+ raise Exception("No sessionmanager or scenario manager available")
19
+ return sm
20
+
21
+
22
+ def get_manager(server):
23
+ if hasattr(server, "session_manager"):
24
+ return server.session_manager
25
+ elif hasattr(server, "scenario_manager"):
26
+ return server.scenario_manager
27
+ else:
28
+ raise Exception("No sessionmanager or scenario manager available")
@@ -0,0 +1 @@
1
+ # This file is intentionally left empty to make the directory a Python package.
@@ -0,0 +1,20 @@
1
+ from dash import html, get_app
2
+
3
+ from algomancy_gui.componentids import OVERVIEW_PAGE_CONTENT
4
+ from algomancy_gui.contentregistry import ContentRegistry
5
+
6
+
7
+ def overview_page():
8
+ """
9
+ Creates the overview page layout with a table of completed scenarios and their KPIs.
10
+
11
+ This page displays a table where rows represent completed scenarios and columns represent KPIs.
12
+
13
+ Returns:
14
+ html.Div: A Dash HTML component representing the overview page
15
+ """
16
+ cr: ContentRegistry = get_app().server.content_registry
17
+
18
+ page = html.Div(cr.overview_content(), id=OVERVIEW_PAGE_CONTENT)
19
+
20
+ return page
algomancy_gui/py.typed ADDED
File without changes
File without changes