From ba18eb253ea2fdc5d46efd1fe3a2664ab9a45cdb Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Sun, 22 Mar 2026 17:50:40 +0100 Subject: [PATCH] fix: resolve empty UI issues when running with --web-conf in Dash 4.0 --- poetry.lock | 12 +++---- psa_car_controller/web/app.py | 2 +- psa_car_controller/web/dash_custom.py | 35 +++++++++------------ psa_car_controller/web/tools/utils.py | 2 +- psa_car_controller/web/view/config_views.py | 7 +++-- psa_car_controller/web/view/views.py | 16 +++++----- pyproject.toml | 2 +- 7 files changed, 37 insertions(+), 39 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2d5e287..97687fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -665,18 +665,18 @@ testing = ["beautifulsoup4 (>=4.8.2)", "cryptography", "dash_testing_stub (>=0.0 [[package]] name = "dash-bootstrap-components" -version = "1.7.1" +version = "2.0.4" description = "Bootstrap themed components for use in Plotly Dash" optional = false -python-versions = "<4,>=3.9" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "dash_bootstrap_components-1.7.1-py3-none-any.whl", hash = "sha256:5e8eae7ee1d013f69e272c68c1015b53ab71802460152088f33fffa90d245199"}, - {file = "dash_bootstrap_components-1.7.1.tar.gz", hash = "sha256:30d48340d6dc89831d6c06e400cd4236f0d5363562c05b2a922f21545695a082"}, + {file = "dash_bootstrap_components-2.0.4-py3-none-any.whl", hash = "sha256:767cf0084586c1b2b614ccf50f79fe4525fdbbf8e3a161ed60016e584a14f5d1"}, + {file = "dash_bootstrap_components-2.0.4.tar.gz", hash = "sha256:c3206c0923774bbc6a6ddaa7822b8d9aa5326b0d3c1e7cd795cc975025fe2484"}, ] [package.dependencies] -dash = ">=2.0.0" +dash = ">=3.0.4" [package.extras] pandas = ["numpy (>=2.0.2)", "pandas (>=2.2.3)"] @@ -3250,4 +3250,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11, <4.0.0" -content-hash = "406777f4f9851d08f5654943490f0407792e8e422276e91758fc76963b37a73a" +content-hash = "7490767cda5b9993fe4c8c1e9d2e3ba3b50e36826982d1a0c6737e79441e8684" diff --git a/psa_car_controller/web/app.py b/psa_car_controller/web/app.py index f10b82c..bf97007 100644 --- a/psa_car_controller/web/app.py +++ b/psa_car_controller/web/app.py @@ -73,7 +73,7 @@ def config_flask(title, base_path, debug: bool, host, port, reloader=False, app.config["DEBUG"] = debug if base_path == "/": application = DispatcherMiddleware(app) - requests_pathname_prefix = None + requests_pathname_prefix = "/" else: application = DispatcherMiddleware(Flask('dummy_app'), {base_path: app}) requests_pathname_prefix = base_path + "/" diff --git a/psa_car_controller/web/dash_custom.py b/psa_car_controller/web/dash_custom.py index fe68f04..cd6b841 100644 --- a/psa_car_controller/web/dash_custom.py +++ b/psa_car_controller/web/dash_custom.py @@ -7,24 +7,17 @@ class DashCustom(Dash): self.requests_pathname_external_prefix = self.config.requests_pathname_prefix def _config(self): - config = super()._config() - # pieces of config needed by the front end - config.update({ - "url_base_pathname": self.config.url_base_pathname, - "requests_pathname_prefix": self.requests_pathname_external_prefix, - "ui": self._dev_tools.ui, - "props_check": self._dev_tools.props_check, - "show_undo_redo": self.config.show_undo_redo, - "suppress_callback_exceptions": self.config.suppress_callback_exceptions, - "update_title": self.config.update_title, - }) - if self._dev_tools.hot_reload: - config["hot_reload"] = { - # convert from seconds to msec as used by js `setInterval` - "interval": int(self._dev_tools.hot_reload_interval * 1000), - "max_retry": self._dev_tools.hot_reload_max_retry, - } - if self.validation_layout and not self.config.suppress_callback_exceptions: - config["validation_layout"] = self.validation_layout - - return config + try: + config = super()._config() + # pieces of config needed by the front end + config.update({ + "requests_pathname_prefix": self.requests_pathname_external_prefix, + }) + if hasattr(self, "_dev_tools"): + config.update({ + "ui": getattr(self._dev_tools, "ui", True), + "props_check": getattr(self._dev_tools, "props_check", False), + }) + return config + except Exception: + return self.config diff --git a/psa_car_controller/web/tools/utils.py b/psa_car_controller/web/tools/utils.py index 27e08e6..6825300 100644 --- a/psa_car_controller/web/tools/utils.py +++ b/psa_car_controller/web/tools/utils.py @@ -31,7 +31,7 @@ def get_marks_from_start_end(start, end): date_f = '%x' marks = {} for date in result: - marks[unix_time_millis(date)] = str(date.strftime(date_f)) + marks[unix_time_millis(date)] = {"label": str(date.strftime(date_f))} return marks return None diff --git a/psa_car_controller/web/view/config_views.py b/psa_car_controller/web/view/config_views.py index 226abb4..f9f995e 100644 --- a/psa_car_controller/web/view/config_views.py +++ b/psa_car_controller/web/view/config_views.py @@ -101,8 +101,11 @@ config_otp_layout = dbc.Row(dbc.Col(className="col-md-12 col-lg-2 m-3", children def log_layout(): - with open(LOG_FILE, "r", encoding="utf-8") as f: - log_text = f.read() + try: + with open(LOG_FILE, "r", encoding="utf-8") as f: + log_text = f.read() + except Exception as e: + log_text = f"Can't read log file: {e}" return html.H3(className="m-2", children=["Log:", dbc.Container( fluid=True, style={"height": "80vh", diff --git a/psa_car_controller/web/view/views.py b/psa_car_controller/web/view/views.py index fad1777..60531be 100644 --- a/psa_car_controller/web/view/views.py +++ b/psa_car_controller/web/view/views.py @@ -71,19 +71,18 @@ def add_header(el): [Input('url', 'pathname'), Input('url', 'search')]) def display_page(pathname, search): - pathname = pathname[len(dash_app.requests_pathname_external_prefix) - 1:] + prefix = dash_app.requests_pathname_external_prefix or "/" + pathname = pathname[len(prefix) - 1:] query_params = parse_qs(urlparse(search).query) no_header = query_params.get("header", None) == ["false"] - if pathname == "/config": - page = config_layout() - elif pathname == "/config_login": + if not APP.is_good or pathname == "/config_login": page = config_layout("login") + elif pathname == "/config": + page = config_layout() elif pathname == "/config_connect": page = get_oauth_config_layout(query_params["url"][0]) elif pathname == "/log": page = log_layout() - elif not APP.is_good: - page = dcc.Location(pathname=dash_app.requests_pathname_external_prefix + "config_login", id="config_redirect") elif pathname == "/config_otp": page = config_layout("otp") elif pathname == "/control": @@ -252,6 +251,7 @@ def serve_layout(): step=step, marks=marks, value=[min_millis, max_millis], + allow_direct_input=False, ) figures.CURRENCY = APP.config.General.currency figures.EXPORT_FORMAT = APP.config.General.export_format @@ -281,7 +281,9 @@ def serve_layout(): data_div = html.Div([ *fig_filter.get_store(), html.Div([ - range_slider, + dbc.Row( + children=range_slider + ), dbc.Tabs([ dbc.Tab(label="Summary", tab_id="summary", children=summary_tab), dbc.Tab(label="Trips", tab_id="trips", id="tab_trips", diff --git a/pyproject.toml b/pyproject.toml index f17d2e7..bdea63b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ plotly = ">=5" cryptography = ">=2.6" Werkzeug = ">=1.0.0" Flask = ">=1.0.4" -dash-bootstrap-components = ">=1" +dash-bootstrap-components = ">=2" ConfigUpdater = ">=3.0" oauth2-client = "^1.3.0" requests = "^2.27.1"