diff --git a/meshinfo/config.py b/meshinfo/config.py index 1f7b0df..081658c 100644 --- a/meshinfo/config.py +++ b/meshinfo/config.py @@ -152,7 +152,7 @@ def configure( filters = settings.setdefault("jinja2.filters", {}) filters.setdefault("duration", "meshinfo.filters.duration") filters.setdefault("in_tz", "meshinfo.filters.in_tz") - filters.setdefault("local_tz", "meshinfo.filters.local_tz") + filters.setdefault("client_tz", "meshinfo.filters.client_tz") if app_config.env == Environment.DEV: settings["pyramid.reload_all"] = True @@ -185,23 +185,20 @@ def configure( if app_config.env == Environment.DEV: config.include("pyramid_debugtoolbar") - server_timezone = pendulum.tz.local_timezone() - def client_timezone(request): - if "local_tz" in request.cookies: - try: - client_tz = pendulum.timezone(request.cookies["local_tz"]) - except Exception as exc: - # TODO: identify client? - logger.warning( - "Invalid timezone specified: {} ({!r})", - request.cookies["local_tz"], - exc, - ) - client_tz = server_timezone - return client_tz.name - else: - return server_timezone.name + if "client_tz" not in request.cookies: + return pendulum.timezone("UTC") + try: + client_tz = pendulum.timezone(request.cookies["client_tz"]) + except Exception as exc: + # TODO: identify client? + logger.warning( + "Invalid timezone specified: {} ({!r})", + request.cookies["client_tz"], + exc, + ) + client_tz = pendulum.timezone("UTC") + return client_tz config.add_request_method(lambda r: client_timezone(r), "timezone", reify=True) diff --git a/meshinfo/filters.py b/meshinfo/filters.py index 7cd5687..efc57f2 100644 --- a/meshinfo/filters.py +++ b/meshinfo/filters.py @@ -18,6 +18,6 @@ def in_tz(dt, tz="utc"): @jinja2.pass_context -def local_tz(ctx, dt): +def client_tz(ctx, dt): request = ctx.get("request") or get_current_request() return dt.in_tz(request.timezone).format("YYYY-MM-DD HH:mm:ss zz") diff --git a/meshinfo/historical.py b/meshinfo/historical.py index 3cf6d18..b100185 100644 --- a/meshinfo/historical.py +++ b/meshinfo/historical.py @@ -3,6 +3,7 @@ from __future__ import annotations import enum +import os from itertools import cycle from pathlib import Path from typing import Any, Iterable, List, Optional @@ -132,6 +133,7 @@ def graph_network_stats( self, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph network poller statistics.""" rrd_file = self._network_filename() @@ -150,9 +152,9 @@ def graph_network_stats( legend=ds.replace("_count", "s"), ) - return graph.render() + return graph.render(timezone=timezone) - def graph_poller_stats(self, *, params: GraphParams) -> bytes: + def graph_poller_stats(self, *, params: GraphParams, timezone: str) -> bytes: """Graph network info.""" rrd_file = self._network_filename() colors = cycle(COLORS) @@ -170,13 +172,14 @@ def graph_poller_stats(self, *, params: GraphParams) -> bytes: style="LINE1", legend=ds.replace("_time", ""), ) - return graph.render() + return graph.render(timezone=timezone) def graph_node_uptime( self, node: Node, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph node uptime.""" rrd_file = self._node_filename(node) @@ -193,13 +196,14 @@ def graph_node_uptime( style="AREA", legend="uptime", ) - return graph.render() + return graph.render(timezone=timezone) def graph_node_load( self, node: Node, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph node uptime.""" rrd_file = self._node_filename(node) @@ -216,13 +220,14 @@ def graph_node_load( style="LINE1", legend="load", ) - return graph.render() + return graph.render(timezone=timezone) def graph_node_links( self, node: Node, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph node links.""" rrd_file = self._node_filename(node) @@ -256,13 +261,14 @@ def graph_node_links( style="LINE1", ) - return graph.render() + return graph.render(timezone=timezone) def graph_link_cost( self, link: Link, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph link routing cost.""" @@ -278,13 +284,14 @@ def graph_link_cost( style="LINE1", legend="route cost", ) - return graph.render() + return graph.render(timezone=timezone) def graph_link_snr( self, link: Link, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph node uptime.""" # TODO: add a black line at 0 so it stands out @@ -316,13 +323,14 @@ def graph_link_snr( style="LINE1", legend="noise", ) - return graph.render() + return graph.render(timezone=timezone) def graph_link_quality( self, link: Link, *, params: GraphParams, + timezone: str, ) -> bytes: """Graph link quality and neighbor quality.""" @@ -347,7 +355,7 @@ def graph_link_quality( style="LINE1", legend="neighbor", ) - return graph.render() + return graph.render(timezone=timezone) def update_link_stats(self, link: Link) -> bool: # switch to async after testing! @@ -590,8 +598,8 @@ def add_summarized_ds( ) self.elements[-1] += r"\l" - def render(self) -> bytes: - """Draw the graph via RRDtool.""" + def render(self, *, timezone: str) -> bytes: + """Draw the graph via RRDtool in a particular timezone.""" self.options.extend(("--start", self.start)) if self.end: @@ -616,5 +624,11 @@ def render(self) -> bytes: ) logger.trace("Rendering graph: {}", graphv_args) + current_timezone = os.getenv("TZ") + os.environ["TZ"] = timezone graph_info = rrdtool.graphv(*graphv_args) + if current_timezone is None: + del os.environ["TZ"] + else: + os.environ["TZ"] = current_timezone return graph_info["image"] diff --git a/meshinfo/templates/home.jinja2 b/meshinfo/templates/home.jinja2 index 0a53629..5279450 100644 --- a/meshinfo/templates/home.jinja2 +++ b/meshinfo/templates/home.jinja2 @@ -18,7 +18,7 @@