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.
- algomancy_gui/__init__.py +0 -0
- algomancy_gui/admin_page/__init__.py +1 -0
- algomancy_gui/admin_page/admin.py +362 -0
- algomancy_gui/admin_page/sessions.py +57 -0
- algomancy_gui/appconfiguration.py +291 -0
- algomancy_gui/compare_page/__init__.py +1 -0
- algomancy_gui/compare_page/compare.py +360 -0
- algomancy_gui/compare_page/kpicard.py +236 -0
- algomancy_gui/compare_page/scenarioselector.py +99 -0
- algomancy_gui/componentids.py +177 -0
- algomancy_gui/contentregistry.py +167 -0
- algomancy_gui/cqmloader.py +58 -0
- algomancy_gui/data_page/__init__.py +1 -0
- algomancy_gui/data_page/data.py +77 -0
- algomancy_gui/data_page/datamanagementdeletemodal.py +260 -0
- algomancy_gui/data_page/datamanagementderivemodal.py +201 -0
- algomancy_gui/data_page/datamanagementdownloadmodal.py +193 -0
- algomancy_gui/data_page/datamanagementimportmodal.py +438 -0
- algomancy_gui/data_page/datamanagementsavemodal.py +191 -0
- algomancy_gui/data_page/datamanagementtopbar.py +123 -0
- algomancy_gui/data_page/datamanagementuploadmodal.py +366 -0
- algomancy_gui/data_page/dialogcallbacks.py +51 -0
- algomancy_gui/data_page/filenamematcher.py +109 -0
- algomancy_gui/defaultloader.py +36 -0
- algomancy_gui/gui_launcher.py +183 -0
- algomancy_gui/home_page/__init__.py +1 -0
- algomancy_gui/home_page/home.py +16 -0
- algomancy_gui/layout.py +199 -0
- algomancy_gui/layouthelpers.py +30 -0
- algomancy_gui/managergetters.py +28 -0
- algomancy_gui/overview_page/__init__.py +1 -0
- algomancy_gui/overview_page/overview.py +20 -0
- algomancy_gui/py.typed +0 -0
- algomancy_gui/scenario_page/__init__.py +0 -0
- algomancy_gui/scenario_page/delete_confirmation.py +29 -0
- algomancy_gui/scenario_page/new_scenario_creator.py +104 -0
- algomancy_gui/scenario_page/new_scenario_parameters_window.py +154 -0
- algomancy_gui/scenario_page/scenario_badge.py +36 -0
- algomancy_gui/scenario_page/scenario_cards.py +119 -0
- algomancy_gui/scenario_page/scenarios.py +596 -0
- algomancy_gui/sessionmanager.py +168 -0
- algomancy_gui/settingsmanager.py +43 -0
- algomancy_gui/stylingconfigurator.py +740 -0
- algomancy_gui-0.3.16.dist-info/METADATA +71 -0
- algomancy_gui-0.3.16.dist-info/RECORD +46 -0
- algomancy_gui-0.3.16.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import dash
|
|
2
|
+
import dash_bootstrap_components as dbc
|
|
3
|
+
from dash import dcc, callback, Output, Input, no_update, State, get_app
|
|
4
|
+
|
|
5
|
+
from algomancy_scenario import ScenarioManager
|
|
6
|
+
|
|
7
|
+
from ..componentids import (
|
|
8
|
+
DM_SAVE_MODAL,
|
|
9
|
+
DM_SAVE_MODAL_CLOSE_BTN,
|
|
10
|
+
DM_SAVE_SET_SELECTOR,
|
|
11
|
+
DM_SAVE_SUBMIT_BUTTON,
|
|
12
|
+
DM_SAVE_OPEN_BUTTON,
|
|
13
|
+
DATA_MAN_SUCCESS_ALERT,
|
|
14
|
+
DATA_MAN_ERROR_ALERT,
|
|
15
|
+
ACTIVE_SESSION,
|
|
16
|
+
)
|
|
17
|
+
from algomancy_gui.managergetters import get_scenario_manager
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Modal component for saving derived datasets as master data.
|
|
21
|
+
|
|
22
|
+
This module provides a modal dialog that allows users to select and save
|
|
23
|
+
derived datasets as master data, which persists the data to disk.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_derived_data_selector(sm: ScenarioManager):
|
|
28
|
+
"""
|
|
29
|
+
Creates a dropdown component for selecting derived datasets.
|
|
30
|
+
|
|
31
|
+
Filters the available datasets to show only derived (non-master) datasets.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
sm: ScenarioManager instance used to retrieve and filter datasets
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
dcc.Dropdown: A Dash dropdown component populated with derived datasets
|
|
38
|
+
"""
|
|
39
|
+
derived_options = [
|
|
40
|
+
{"label": ds, "value": ds}
|
|
41
|
+
for ds in sm.get_data_keys()
|
|
42
|
+
if not sm.get_data(ds).is_master_data()
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
return dcc.Dropdown(
|
|
46
|
+
id=DM_SAVE_SET_SELECTOR,
|
|
47
|
+
value="",
|
|
48
|
+
options=derived_options,
|
|
49
|
+
placeholder="Select dataset",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def data_management_save_modal(sm: ScenarioManager, themed_styling):
|
|
54
|
+
"""
|
|
55
|
+
Creates a modal dialog component for saving derived datasets as master data.
|
|
56
|
+
|
|
57
|
+
The modal contains a dropdown to select the derived dataset to save and
|
|
58
|
+
buttons to save or cancel the operation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
sm: ScenarioManager instance used to populate the dataset dropdown
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
dbc.Modal: A Dash Bootstrap Components modal dialog
|
|
65
|
+
"""
|
|
66
|
+
return dbc.Modal(
|
|
67
|
+
[
|
|
68
|
+
dbc.ModalHeader(dbc.ModalTitle("Save"), close_button=False),
|
|
69
|
+
dbc.ModalBody(
|
|
70
|
+
["Select derived data to save.", create_derived_data_selector(sm)]
|
|
71
|
+
),
|
|
72
|
+
dbc.ModalFooter(
|
|
73
|
+
[
|
|
74
|
+
dbc.Button(
|
|
75
|
+
"Save",
|
|
76
|
+
id=DM_SAVE_SUBMIT_BUTTON,
|
|
77
|
+
class_name="dm-save-modal-confirm-btn",
|
|
78
|
+
),
|
|
79
|
+
dbc.Button(
|
|
80
|
+
"Close",
|
|
81
|
+
id=DM_SAVE_MODAL_CLOSE_BTN,
|
|
82
|
+
class_name="dm-save-modal-cancel-btn ms-auto",
|
|
83
|
+
),
|
|
84
|
+
]
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
id=DM_SAVE_MODAL,
|
|
88
|
+
is_open=False,
|
|
89
|
+
centered=True,
|
|
90
|
+
class_name="themed-modal",
|
|
91
|
+
style=themed_styling,
|
|
92
|
+
keyboard=False,
|
|
93
|
+
backdrop="static",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@callback(
|
|
98
|
+
Output(DM_SAVE_MODAL, "is_open"),
|
|
99
|
+
[
|
|
100
|
+
Input(DM_SAVE_OPEN_BUTTON, "n_clicks"),
|
|
101
|
+
Input(DM_SAVE_MODAL_CLOSE_BTN, "n_clicks"),
|
|
102
|
+
],
|
|
103
|
+
[dash.dependencies.State(DM_SAVE_MODAL, "is_open")],
|
|
104
|
+
)
|
|
105
|
+
def toggle_modal_save(open_clicks, close_clicks, is_open):
|
|
106
|
+
"""
|
|
107
|
+
Toggles the visibility of the save modal dialog.
|
|
108
|
+
|
|
109
|
+
Opens the modal when the open button is clicked and closes it when
|
|
110
|
+
the close button is clicked.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
open_clicks: Number of times the open button has been clicked
|
|
114
|
+
close_clicks: Number of times the close button has been clicked
|
|
115
|
+
is_open: Current state of the modal (open or closed)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: New state for the modal
|
|
119
|
+
"""
|
|
120
|
+
if open_clicks or close_clicks:
|
|
121
|
+
return not is_open
|
|
122
|
+
return is_open
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@callback(
|
|
126
|
+
Output(DM_SAVE_SET_SELECTOR, "value"),
|
|
127
|
+
Input(DM_SAVE_MODAL, "is_open"),
|
|
128
|
+
prevent_initial_call=True,
|
|
129
|
+
)
|
|
130
|
+
def reset_save_selection_on_close(modal_is_open: bool):
|
|
131
|
+
"""
|
|
132
|
+
Resets the save dataset selector when the save modal is closed.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
modal_is_open: Boolean indicating if the modal is open
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
None if the modal is closed, no_update otherwise
|
|
139
|
+
"""
|
|
140
|
+
if not modal_is_open:
|
|
141
|
+
return None
|
|
142
|
+
return no_update
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@callback(
|
|
146
|
+
Output(DM_SAVE_MODAL, "is_open", allow_duplicate=True),
|
|
147
|
+
Output(DATA_MAN_SUCCESS_ALERT, "children", allow_duplicate=True),
|
|
148
|
+
Output(DATA_MAN_SUCCESS_ALERT, "is_open", allow_duplicate=True),
|
|
149
|
+
Output(DATA_MAN_ERROR_ALERT, "children", allow_duplicate=True),
|
|
150
|
+
Output(DATA_MAN_ERROR_ALERT, "is_open", allow_duplicate=True),
|
|
151
|
+
Input(DM_SAVE_SUBMIT_BUTTON, "n_clicks"),
|
|
152
|
+
State(DM_SAVE_SET_SELECTOR, "value"),
|
|
153
|
+
State(ACTIVE_SESSION, "data"),
|
|
154
|
+
prevent_initial_call=True,
|
|
155
|
+
)
|
|
156
|
+
def save_derived_data(
|
|
157
|
+
n_clicks,
|
|
158
|
+
set_name: str,
|
|
159
|
+
session_id: str,
|
|
160
|
+
):
|
|
161
|
+
"""
|
|
162
|
+
Saves a derived dataset as master data when the save button is clicked.
|
|
163
|
+
|
|
164
|
+
Stores the dataset files to disk and updates the dataset's status to master data.
|
|
165
|
+
Displays success or error messages and closes the modal upon completion.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
n_clicks: Number of times the submit button has been clicked
|
|
169
|
+
set_name: Name of the dataset to save
|
|
170
|
+
session_id: ID of the active session
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Tuple containing modal state and alert messages
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
data = sm.get_data(set_name)
|
|
180
|
+
data.set_to_master_data()
|
|
181
|
+
|
|
182
|
+
if sm.save_type == "json":
|
|
183
|
+
sm.store_data_as_json(set_name)
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError(f"Unknown save type: {sm.save_type}")
|
|
186
|
+
|
|
187
|
+
return False, "Files saved successfully", True, "", False
|
|
188
|
+
except Exception as e:
|
|
189
|
+
sm.logger.error(f"Problem with saving: {str(e)}")
|
|
190
|
+
sm.logger.log_traceback(e)
|
|
191
|
+
return False, "", False, f"Problem with saving: {str(e)}", True
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from dash import html, dcc, get_app
|
|
2
|
+
import dash_bootstrap_components as dbc
|
|
3
|
+
|
|
4
|
+
from algomancy_scenario import ScenarioManager
|
|
5
|
+
|
|
6
|
+
from .datamanagementdeletemodal import data_management_delete_modal
|
|
7
|
+
from .datamanagementderivemodal import data_management_derive_modal
|
|
8
|
+
from .datamanagementdownloadmodal import data_management_download_modal
|
|
9
|
+
from .datamanagementimportmodal import data_management_import_modal
|
|
10
|
+
from .datamanagementsavemodal import data_management_save_modal
|
|
11
|
+
from .datamanagementuploadmodal import data_management_upload_modal
|
|
12
|
+
from ..componentids import (
|
|
13
|
+
DATA_SELECTOR_DROPDOWN,
|
|
14
|
+
DATA_MAN_SUCCESS_ALERT,
|
|
15
|
+
DATA_MAN_ERROR_ALERT,
|
|
16
|
+
DM_DELETE_OPEN_BUTTON,
|
|
17
|
+
DM_DERIVE_OPEN_BTN,
|
|
18
|
+
DM_SAVE_OPEN_BUTTON,
|
|
19
|
+
DM_IMPORT_OPEN_BUTTON,
|
|
20
|
+
DM_UPLOAD_OPEN_BUTTON,
|
|
21
|
+
DM_DOWNLOAD_OPEN_BUTTON,
|
|
22
|
+
DM_LIST_UPDATER_STORE,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def top_bar(sm: ScenarioManager):
|
|
27
|
+
toolbar = create_data_management_toolbar(sm)
|
|
28
|
+
active_dataset_selector = create_datasource_selector(sm)
|
|
29
|
+
|
|
30
|
+
return html.Div(
|
|
31
|
+
[
|
|
32
|
+
dbc.Row(
|
|
33
|
+
[
|
|
34
|
+
dcc.Store(DM_LIST_UPDATER_STORE, data=""),
|
|
35
|
+
dbc.Col(active_dataset_selector, width=8),
|
|
36
|
+
dbc.Col(toolbar, width=4),
|
|
37
|
+
]
|
|
38
|
+
),
|
|
39
|
+
dbc.Alert(
|
|
40
|
+
id=DATA_MAN_SUCCESS_ALERT,
|
|
41
|
+
color="success",
|
|
42
|
+
is_open=False,
|
|
43
|
+
dismissable=True,
|
|
44
|
+
duration=4000,
|
|
45
|
+
class_name="mt-2",
|
|
46
|
+
),
|
|
47
|
+
dbc.Alert(
|
|
48
|
+
id=DATA_MAN_ERROR_ALERT,
|
|
49
|
+
color="danger",
|
|
50
|
+
is_open=False,
|
|
51
|
+
dismissable=True,
|
|
52
|
+
duration=4000,
|
|
53
|
+
class_name="mt-2",
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def create_data_management_toolbar(sm: ScenarioManager):
|
|
60
|
+
themed_styling = get_app().server.styling_config.initiate_theme_colors()
|
|
61
|
+
|
|
62
|
+
return html.Div(
|
|
63
|
+
[
|
|
64
|
+
dbc.ButtonGroup(
|
|
65
|
+
[
|
|
66
|
+
dbc.Button(
|
|
67
|
+
"Derive", id=DM_DERIVE_OPEN_BTN, className="me-2 dm-derive-btn"
|
|
68
|
+
),
|
|
69
|
+
dbc.Button(
|
|
70
|
+
"Delete",
|
|
71
|
+
id=DM_DELETE_OPEN_BUTTON,
|
|
72
|
+
className="me-2 dm-delete-btn",
|
|
73
|
+
),
|
|
74
|
+
dbc.Button(
|
|
75
|
+
"Save",
|
|
76
|
+
id=DM_SAVE_OPEN_BUTTON,
|
|
77
|
+
disabled=not sm.has_persistent_state,
|
|
78
|
+
className="me-2 dm-save-btn",
|
|
79
|
+
),
|
|
80
|
+
dbc.Button(
|
|
81
|
+
"Import",
|
|
82
|
+
id=DM_IMPORT_OPEN_BUTTON, # adjust callback
|
|
83
|
+
className="me-2 dm-import-btn",
|
|
84
|
+
),
|
|
85
|
+
dbc.Button(
|
|
86
|
+
"Upload",
|
|
87
|
+
id=DM_UPLOAD_OPEN_BUTTON, # adjust callback
|
|
88
|
+
className="me-2 dm-upload-btn",
|
|
89
|
+
),
|
|
90
|
+
dbc.Button(
|
|
91
|
+
"Download",
|
|
92
|
+
id=DM_DOWNLOAD_OPEN_BUTTON, # adjust callback
|
|
93
|
+
className="me-2 dm-download-btn",
|
|
94
|
+
),
|
|
95
|
+
],
|
|
96
|
+
className="d-flex justify-content-end",
|
|
97
|
+
),
|
|
98
|
+
data_management_derive_modal(sm, themed_styling),
|
|
99
|
+
data_management_delete_modal(sm, themed_styling),
|
|
100
|
+
data_management_save_modal(sm, themed_styling),
|
|
101
|
+
data_management_import_modal(sm, themed_styling),
|
|
102
|
+
data_management_upload_modal(sm, themed_styling),
|
|
103
|
+
data_management_download_modal(sm, themed_styling),
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_datasource_selector(sm: ScenarioManager) -> html.Div:
|
|
109
|
+
dropdown = dcc.Dropdown(
|
|
110
|
+
id=DATA_SELECTOR_DROPDOWN,
|
|
111
|
+
options=[{"label": ds, "value": ds} for ds in sm.get_data_keys()],
|
|
112
|
+
placeholder="Select dataset",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return html.Div(
|
|
116
|
+
dbc.Row(
|
|
117
|
+
[
|
|
118
|
+
dbc.Col(html.P("Active dataset: "), width=2),
|
|
119
|
+
dbc.Col(dropdown, width=10),
|
|
120
|
+
]
|
|
121
|
+
),
|
|
122
|
+
className="mb-4",
|
|
123
|
+
)
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import dash_bootstrap_components as dbc
|
|
5
|
+
from dash import html, dcc, get_app, callback, Output, Input, State, no_update
|
|
6
|
+
from dash.exceptions import PreventUpdate
|
|
7
|
+
|
|
8
|
+
from algomancy_scenario import ScenarioManager
|
|
9
|
+
from ..componentids import (
|
|
10
|
+
DM_UPLOAD_MODAL_CLOSE_BTN,
|
|
11
|
+
DM_UPLOAD_MODAL_FILEVIEWER_CARD,
|
|
12
|
+
DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE,
|
|
13
|
+
DM_UPLOAD_OPEN_BUTTON,
|
|
14
|
+
DM_UPLOAD_SUBMIT_BUTTON,
|
|
15
|
+
DM_UPLOAD_UPLOADER,
|
|
16
|
+
DM_UPLOAD_MODAL,
|
|
17
|
+
ACTIVE_SESSION,
|
|
18
|
+
)
|
|
19
|
+
from ..componentids import (
|
|
20
|
+
DM_UPLOAD_SUCCESS_ALERT,
|
|
21
|
+
DM_LIST_UPDATER_STORE,
|
|
22
|
+
)
|
|
23
|
+
from ..cqmloader import cqm_loader
|
|
24
|
+
from ..defaultloader import default_loader
|
|
25
|
+
from algomancy_gui.managergetters import get_scenario_manager
|
|
26
|
+
from ..settingsmanager import SettingsManager
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Modal component for loading data files into the application.
|
|
31
|
+
|
|
32
|
+
This module provides a modal dialog that allows users to upload CSV files,
|
|
33
|
+
view file mapping information, and create new datasets from the uploaded files.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def data_management_upload_modal(sm: ScenarioManager, themed_styling):
|
|
38
|
+
"""
|
|
39
|
+
Creates a modal dialog component for loading data files.
|
|
40
|
+
|
|
41
|
+
The modal contains a file upload area, a collapsible section for displaying
|
|
42
|
+
file mapping information, an input field for naming the new dataset, and
|
|
43
|
+
an alert area for displaying messages.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
dbc.Modal: A Dash Bootstrap Components modal dialog
|
|
47
|
+
"""
|
|
48
|
+
settings: SettingsManager = get_app().server.settings
|
|
49
|
+
|
|
50
|
+
if settings.use_cqm_loader:
|
|
51
|
+
spinner = cqm_loader(
|
|
52
|
+
"Importing data..."
|
|
53
|
+
) # requires letter-c.svg, letter-q.svg and letter-m.svg
|
|
54
|
+
else:
|
|
55
|
+
spinner = default_loader("Importing data...")
|
|
56
|
+
|
|
57
|
+
return dbc.Modal(
|
|
58
|
+
[
|
|
59
|
+
dbc.ModalHeader(dbc.ModalTitle("Upload Cases"), close_button=False),
|
|
60
|
+
dbc.ModalBody(
|
|
61
|
+
dcc.Loading(
|
|
62
|
+
[
|
|
63
|
+
dbc.Label(
|
|
64
|
+
"The uploaded file will be uploaded as a new dataset."
|
|
65
|
+
"The name of the dataset will be the name of the uploaded file."
|
|
66
|
+
f"The file must be in {sm.save_type} format."
|
|
67
|
+
),
|
|
68
|
+
dcc.Upload(
|
|
69
|
+
id=DM_UPLOAD_UPLOADER,
|
|
70
|
+
children=html.Div(
|
|
71
|
+
["Drag and Drop or ", html.A("Select Files")]
|
|
72
|
+
),
|
|
73
|
+
style={
|
|
74
|
+
"width": "100%",
|
|
75
|
+
"height": "60px",
|
|
76
|
+
"lineHeight": "60px",
|
|
77
|
+
"borderWidth": "1px",
|
|
78
|
+
"borderStyle": "dashed",
|
|
79
|
+
"borderRadius": "4px",
|
|
80
|
+
"textAlign": "center",
|
|
81
|
+
},
|
|
82
|
+
multiple=True,
|
|
83
|
+
),
|
|
84
|
+
# dcc.Store(DM_UPLOAD_DATA_STORE, data=""),
|
|
85
|
+
dbc.Collapse(
|
|
86
|
+
children=[
|
|
87
|
+
dbc.Card(
|
|
88
|
+
dbc.CardBody(id=DM_UPLOAD_MODAL_FILEVIEWER_CARD),
|
|
89
|
+
className="uploaded-files-card",
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
id=DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE,
|
|
93
|
+
is_open=False,
|
|
94
|
+
class_name="mt-2 mb-2",
|
|
95
|
+
),
|
|
96
|
+
dbc.Alert(
|
|
97
|
+
children="Upload successful! Close the modal to continue.",
|
|
98
|
+
id=DM_UPLOAD_SUCCESS_ALERT,
|
|
99
|
+
color="success",
|
|
100
|
+
is_open=False,
|
|
101
|
+
),
|
|
102
|
+
dcc.Store("dm-upload-dummy-store", data=""),
|
|
103
|
+
],
|
|
104
|
+
overlay_style={
|
|
105
|
+
"visibility": "visible",
|
|
106
|
+
"opacity": 0.5,
|
|
107
|
+
"backgroundColor": "white",
|
|
108
|
+
},
|
|
109
|
+
custom_spinner=spinner,
|
|
110
|
+
delay_hide=50,
|
|
111
|
+
delay_show=50,
|
|
112
|
+
)
|
|
113
|
+
),
|
|
114
|
+
dbc.ModalFooter(
|
|
115
|
+
[
|
|
116
|
+
dbc.Button(
|
|
117
|
+
"Upload",
|
|
118
|
+
id=DM_UPLOAD_SUBMIT_BUTTON,
|
|
119
|
+
class_name="dm-upload-modal-confirm-btn",
|
|
120
|
+
),
|
|
121
|
+
dbc.Button(
|
|
122
|
+
"Close",
|
|
123
|
+
id=DM_UPLOAD_MODAL_CLOSE_BTN,
|
|
124
|
+
class_name="dm-upload-modal-cancel-btn ms-auto",
|
|
125
|
+
),
|
|
126
|
+
]
|
|
127
|
+
),
|
|
128
|
+
],
|
|
129
|
+
id=DM_UPLOAD_MODAL,
|
|
130
|
+
is_open=False,
|
|
131
|
+
centered=True,
|
|
132
|
+
class_name="themed-modal",
|
|
133
|
+
style=themed_styling,
|
|
134
|
+
keyboard=False,
|
|
135
|
+
backdrop="static",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@callback(
|
|
140
|
+
Output(DM_UPLOAD_MODAL, "is_open", allow_duplicate=True),
|
|
141
|
+
Input(DM_UPLOAD_OPEN_BUTTON, "n_clicks"),
|
|
142
|
+
Input(DM_UPLOAD_MODAL_CLOSE_BTN, "n_clicks"),
|
|
143
|
+
State(DM_UPLOAD_MODAL, "is_open"),
|
|
144
|
+
prevent_initial_call=True,
|
|
145
|
+
)
|
|
146
|
+
def open_close_modal(n_open, n_close, is_open):
|
|
147
|
+
"""
|
|
148
|
+
Callback for opening and closing the dialog modal
|
|
149
|
+
"""
|
|
150
|
+
if n_open or n_close:
|
|
151
|
+
return not is_open
|
|
152
|
+
return is_open
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@callback(
|
|
156
|
+
[
|
|
157
|
+
Output(DM_UPLOAD_MODAL_FILEVIEWER_CARD, "children", allow_duplicate=True),
|
|
158
|
+
Output(DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE, "is_open", allow_duplicate=True),
|
|
159
|
+
Output(DM_UPLOAD_UPLOADER, "disabled", allow_duplicate=True),
|
|
160
|
+
Output(DM_UPLOAD_UPLOADER, "filename", allow_duplicate=True),
|
|
161
|
+
Output(DM_UPLOAD_UPLOADER, "contents", allow_duplicate=True),
|
|
162
|
+
Output(DM_UPLOAD_SUCCESS_ALERT, "is_open", allow_duplicate=True),
|
|
163
|
+
],
|
|
164
|
+
Input(DM_UPLOAD_MODAL, "is_open"),
|
|
165
|
+
prevent_initial_call=True,
|
|
166
|
+
)
|
|
167
|
+
def reset_on_close(is_open):
|
|
168
|
+
if not is_open:
|
|
169
|
+
return [], False, False, None, None, False
|
|
170
|
+
return no_update, no_update, no_update, no_update, no_update, no_update
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _render_uploaded_files(filenames, wrong_filenames) -> html.Div:
|
|
174
|
+
"""
|
|
175
|
+
Helper function to create a rendering of uploaded files
|
|
176
|
+
"""
|
|
177
|
+
file_name_width = 8
|
|
178
|
+
status_width = 12 - file_name_width
|
|
179
|
+
|
|
180
|
+
header = html.Div(
|
|
181
|
+
dbc.Row(
|
|
182
|
+
[
|
|
183
|
+
dbc.Col(
|
|
184
|
+
[
|
|
185
|
+
html.Strong("File name"),
|
|
186
|
+
],
|
|
187
|
+
width=file_name_width,
|
|
188
|
+
),
|
|
189
|
+
dbc.Col(
|
|
190
|
+
[
|
|
191
|
+
html.Strong("Status"),
|
|
192
|
+
],
|
|
193
|
+
width=status_width,
|
|
194
|
+
),
|
|
195
|
+
]
|
|
196
|
+
),
|
|
197
|
+
className="uploaded-files-header",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
body = [
|
|
201
|
+
html.Div(
|
|
202
|
+
dbc.Row(
|
|
203
|
+
[
|
|
204
|
+
dbc.Col([f"{filename}"], width=file_name_width),
|
|
205
|
+
dbc.Col(
|
|
206
|
+
[
|
|
207
|
+
dbc.Spinner(
|
|
208
|
+
html.Div(
|
|
209
|
+
id={"type": "dm-upload-status", "index": filename}
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
],
|
|
213
|
+
width=status_width,
|
|
214
|
+
),
|
|
215
|
+
]
|
|
216
|
+
),
|
|
217
|
+
className="uploaded-file-good",
|
|
218
|
+
)
|
|
219
|
+
for filename in filenames
|
|
220
|
+
] + [
|
|
221
|
+
html.Div(
|
|
222
|
+
dbc.Row(
|
|
223
|
+
[
|
|
224
|
+
dbc.Col(
|
|
225
|
+
[f"{filename}"],
|
|
226
|
+
width=file_name_width,
|
|
227
|
+
),
|
|
228
|
+
dbc.Col(
|
|
229
|
+
[
|
|
230
|
+
dbc.Spinner(
|
|
231
|
+
html.Div(
|
|
232
|
+
id={"type": "dm-upload-status", "index": filename}
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
],
|
|
236
|
+
width=status_width,
|
|
237
|
+
),
|
|
238
|
+
]
|
|
239
|
+
),
|
|
240
|
+
className="uploaded-file-bad",
|
|
241
|
+
)
|
|
242
|
+
for filename in wrong_filenames
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
table_div = html.Div([header, *body], className="uploaded-files-table")
|
|
246
|
+
|
|
247
|
+
return table_div
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def check_files(filenames, session_id: str):
|
|
251
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
252
|
+
allowed_type = sm.save_type
|
|
253
|
+
|
|
254
|
+
filenames_with_wrong_type = [
|
|
255
|
+
file_name
|
|
256
|
+
for file_name in filenames
|
|
257
|
+
if not file_name.lower().endswith(allowed_type.lower())
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
filenames_with_allowed_type = [
|
|
261
|
+
file_name
|
|
262
|
+
for file_name in filenames
|
|
263
|
+
if file_name.lower().endswith(allowed_type.lower())
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
filenames_already_present = [
|
|
267
|
+
file_name
|
|
268
|
+
for file_name in filenames_with_allowed_type
|
|
269
|
+
if file_name.split(".")[0] in sm.get_data_keys()
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
filenames_not_present = [
|
|
273
|
+
file_name
|
|
274
|
+
for file_name in filenames_with_allowed_type
|
|
275
|
+
if file_name.split(".")[0] not in sm.get_data_keys()
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
return filenames_not_present, filenames_already_present + filenames_with_wrong_type
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@callback(
|
|
282
|
+
[
|
|
283
|
+
Output(DM_UPLOAD_MODAL_FILEVIEWER_CARD, "children", allow_duplicate=True),
|
|
284
|
+
Output(DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE, "is_open", allow_duplicate=True),
|
|
285
|
+
Output(DM_UPLOAD_UPLOADER, "disabled", allow_duplicate=True),
|
|
286
|
+
],
|
|
287
|
+
Input(DM_UPLOAD_UPLOADER, "filename"),
|
|
288
|
+
State(ACTIVE_SESSION, "data"),
|
|
289
|
+
prevent_initial_call=True,
|
|
290
|
+
)
|
|
291
|
+
def update_file_viewer(filename, session_id: str):
|
|
292
|
+
"""
|
|
293
|
+
Callback to respond to file upload events
|
|
294
|
+
"""
|
|
295
|
+
if filename is None:
|
|
296
|
+
return [], False, False
|
|
297
|
+
|
|
298
|
+
# Allow for possible list/file array
|
|
299
|
+
if isinstance(filename, list):
|
|
300
|
+
filenames = filename
|
|
301
|
+
else:
|
|
302
|
+
filenames = [filename]
|
|
303
|
+
|
|
304
|
+
good_files, bad_files = check_files(filenames, session_id)
|
|
305
|
+
|
|
306
|
+
return html.Div([_render_uploaded_files(good_files, bad_files)]), True, True
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@callback(
|
|
310
|
+
[
|
|
311
|
+
Output(DM_LIST_UPDATER_STORE, "data", allow_duplicate=True),
|
|
312
|
+
Output(DM_UPLOAD_SUBMIT_BUTTON, "disabled", allow_duplicate=True),
|
|
313
|
+
Output(DM_UPLOAD_SUCCESS_ALERT, "is_open", allow_duplicate=True),
|
|
314
|
+
Output("dm-upload-dummy-store", "data", allow_duplicate=True),
|
|
315
|
+
],
|
|
316
|
+
Input(DM_UPLOAD_SUBMIT_BUTTON, "n_clicks"),
|
|
317
|
+
State(DM_UPLOAD_UPLOADER, "contents"),
|
|
318
|
+
State(DM_UPLOAD_UPLOADER, "filename"),
|
|
319
|
+
State(ACTIVE_SESSION, "data"),
|
|
320
|
+
prevent_initial_call=True,
|
|
321
|
+
)
|
|
322
|
+
def process_uploaded_files(n_clicks, contents, filenames, session_id: str):
|
|
323
|
+
"""
|
|
324
|
+
Process uploaded files when the submit button is clicked.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
bool: Whether to close the modal
|
|
328
|
+
"""
|
|
329
|
+
if n_clicks is None or not n_clicks or contents is None or filenames is None:
|
|
330
|
+
raise PreventUpdate
|
|
331
|
+
|
|
332
|
+
sm: ScenarioManager = get_scenario_manager(get_app().server, session_id)
|
|
333
|
+
|
|
334
|
+
# Make sure we're working with lists
|
|
335
|
+
if not isinstance(filenames, list):
|
|
336
|
+
filenames = [filenames]
|
|
337
|
+
contents = [contents]
|
|
338
|
+
|
|
339
|
+
# Filter good files
|
|
340
|
+
good_files, _ = check_files(filenames, session_id)
|
|
341
|
+
files_with_content = zip(filenames, contents)
|
|
342
|
+
good_files_with_content = [
|
|
343
|
+
(filename, content)
|
|
344
|
+
for filename, content in files_with_content
|
|
345
|
+
if filename in good_files
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
for filename, content in good_files_with_content:
|
|
349
|
+
try:
|
|
350
|
+
# Process the file content
|
|
351
|
+
content_type, content_string = content.split(",", 1)
|
|
352
|
+
decoded = base64.b64decode(content_string)
|
|
353
|
+
json_string = decoded.decode("utf-8")
|
|
354
|
+
|
|
355
|
+
# Add data source to scenario manager
|
|
356
|
+
sm.add_datasource_from_json(json_string)
|
|
357
|
+
|
|
358
|
+
# Log
|
|
359
|
+
sm.logger.success(f"Successfully uploaded {filename} to data manager.")
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
sm.logger.error(f"Error processing uploaded file {filename}: {e}")
|
|
363
|
+
sm.logger.log_traceback(e)
|
|
364
|
+
|
|
365
|
+
# Close the modal
|
|
366
|
+
return datetime.now(), [True], True, ""
|