From deaab9b9de50587b2d38faf9da0abdba093b7a3f Mon Sep 17 00:00:00 2001 From: Louis King Date: Fri, 6 Feb 2026 22:53:36 +0000 Subject: [PATCH] Rename /network to /dashboard and add reusable icon macros - Renamed network route, template, and tests to dashboard - Added logo.svg for favicon and navbar branding - Created reusable Jinja2 icon macros for navigation and UI elements - Updated home page hero layout with centered content and larger logo - Added Map button alongside Dashboard button in hero section - Navigation menu items now display icons before labels Co-Authored-By: Claude Opus 4.5 --- src/meshcore_hub/web/app.py | 2 +- src/meshcore_hub/web/routes/__init__.py | 4 +- .../web/routes/{network.py => dashboard.py} | 10 +- src/meshcore_hub/web/static/img/logo.svg | 14 ++ src/meshcore_hub/web/templates/base.html | 39 +++-- .../{network.html => dashboard.html} | 0 src/meshcore_hub/web/templates/home.html | 134 ++++++++---------- .../web/templates/macros/icons.html | 47 ++++++ tests/e2e/test_full_flow.py | 6 +- .../{test_network.py => test_dashboard.py} | 52 +++---- 10 files changed, 176 insertions(+), 132 deletions(-) rename src/meshcore_hub/web/routes/{network.py => dashboard.py} (90%) create mode 100644 src/meshcore_hub/web/static/img/logo.svg rename src/meshcore_hub/web/templates/{network.html => dashboard.html} (100%) create mode 100644 src/meshcore_hub/web/templates/macros/icons.html rename tests/test_web/{test_network.py => test_dashboard.py} (58%) diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py index 0e70dc8..f3e9d0f 100644 --- a/src/meshcore_hub/web/app.py +++ b/src/meshcore_hub/web/app.py @@ -180,7 +180,7 @@ def create_app( # Static pages static_pages = [ ("", "daily", "1.0"), - ("/network", "hourly", "0.9"), + ("/dashboard", "hourly", "0.9"), ("/nodes", "hourly", "0.9"), ("/advertisements", "hourly", "0.8"), ("/messages", "hourly", "0.8"), diff --git a/src/meshcore_hub/web/routes/__init__.py b/src/meshcore_hub/web/routes/__init__.py index defce5c..f073b3f 100644 --- a/src/meshcore_hub/web/routes/__init__.py +++ b/src/meshcore_hub/web/routes/__init__.py @@ -3,7 +3,7 @@ from fastapi import APIRouter from meshcore_hub.web.routes.home import router as home_router -from meshcore_hub.web.routes.network import router as network_router +from meshcore_hub.web.routes.dashboard import router as dashboard_router from meshcore_hub.web.routes.nodes import router as nodes_router from meshcore_hub.web.routes.messages import router as messages_router from meshcore_hub.web.routes.advertisements import router as advertisements_router @@ -17,7 +17,7 @@ web_router = APIRouter() # Include all sub-routers web_router.include_router(home_router) -web_router.include_router(network_router) +web_router.include_router(dashboard_router) web_router.include_router(nodes_router) web_router.include_router(messages_router) web_router.include_router(advertisements_router) diff --git a/src/meshcore_hub/web/routes/network.py b/src/meshcore_hub/web/routes/dashboard.py similarity index 90% rename from src/meshcore_hub/web/routes/network.py rename to src/meshcore_hub/web/routes/dashboard.py index a52da9c..36c414e 100644 --- a/src/meshcore_hub/web/routes/network.py +++ b/src/meshcore_hub/web/routes/dashboard.py @@ -1,4 +1,4 @@ -"""Network overview page route.""" +"""Dashboard page route.""" import json import logging @@ -12,9 +12,9 @@ logger = logging.getLogger(__name__) router = APIRouter() -@router.get("/network", response_class=HTMLResponse) -async def network_overview(request: Request) -> HTMLResponse: - """Render the network overview page.""" +@router.get("/dashboard", response_class=HTMLResponse) +async def dashboard(request: Request) -> HTMLResponse: + """Render the dashboard page.""" templates = get_templates(request) context = get_network_context(request) context["request"] = request @@ -76,4 +76,4 @@ async def network_overview(request: Request) -> HTMLResponse: context["message_activity_json"] = json.dumps(message_activity) context["node_count_json"] = json.dumps(node_count) - return templates.TemplateResponse("network.html", context) + return templates.TemplateResponse("dashboard.html", context) diff --git a/src/meshcore_hub/web/static/img/logo.svg b/src/meshcore_hub/web/static/img/logo.svg new file mode 100644 index 0000000..2efdd80 --- /dev/null +++ b/src/meshcore_hub/web/static/img/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/meshcore_hub/web/templates/base.html b/src/meshcore_hub/web/templates/base.html index 98f6ebb..8136eb0 100644 --- a/src/meshcore_hub/web/templates/base.html +++ b/src/meshcore_hub/web/templates/base.html @@ -1,3 +1,4 @@ +{% from "macros/icons.html" import icon_home, icon_dashboard, icon_nodes, icon_advertisements, icon_messages, icon_map, icon_members, icon_page %} @@ -24,7 +25,7 @@ - + @@ -98,36 +99,34 @@ - - - + {{ network_name }} {{ network_name }} diff --git a/src/meshcore_hub/web/templates/network.html b/src/meshcore_hub/web/templates/dashboard.html similarity index 100% rename from src/meshcore_hub/web/templates/network.html rename to src/meshcore_hub/web/templates/dashboard.html diff --git a/src/meshcore_hub/web/templates/home.html b/src/meshcore_hub/web/templates/home.html index cb1a618..8387726 100644 --- a/src/meshcore_hub/web/templates/home.html +++ b/src/meshcore_hub/web/templates/home.html @@ -1,89 +1,73 @@ {% extends "base.html" %} +{% from "macros/icons.html" import icon_dashboard, icon_map, icon_nodes, icon_advertisements, icon_messages %} {% block title %}{{ network_name }} - Home{% endblock %} {% block content %} -
-
-
-

{{ network_name }}

- {% if network_city and network_country %} -

{{ network_city }}, {{ network_country }}

- {% endif %} - {% if network_welcome_text %} -

{{ network_welcome_text }}

- {% else %} -

- Welcome to the {{ network_name }} mesh network dashboard. - Monitor network activity, view connected nodes, and explore message history. -

- {% endif %} -
- - - - - Dashboard - - - - - - Nodes - - - - - - Advertisements - - - - - - Messages - + +
+ +
+ {{ network_name }} +

{{ network_name }}

+ {% if network_city and network_country %} +

{{ network_city }}, {{ network_country }}

+ {% endif %} + {% if network_welcome_text %} +

{{ network_welcome_text }}

+ {% else %} +

+ Welcome to the {{ network_name }} mesh network dashboard. + Monitor network activity, view connected nodes, and explore message history. +

+ {% endif %} +
+ +
+ + +
+ +
+
+ {{ icon_nodes("h-8 w-8") }}
+
Total Nodes
+
{{ stats.total_nodes }}
+
All discovered nodes
+ View Nodes
-
-
- -
- -
-
- - - + +
+
+ {{ icon_advertisements("h-8 w-8") }} +
+
Advertisements
+
{{ stats.advertisements_7d }}
+
Last 7 days
+ View Adverts
-
Total Nodes
-
{{ stats.total_nodes }}
-
All discovered nodes
-
- -
-
- - - + +
+
+ {{ icon_messages("h-8 w-8") }} +
+
Messages
+
{{ stats.messages_7d }}
+
Last 7 days
+ View Messages
-
Advertisements
-
{{ stats.advertisements_7d }}
-
Last 7 days
-
- - -
-
- - - -
-
Messages
-
{{ stats.messages_7d }}
-
Last 7 days
diff --git a/src/meshcore_hub/web/templates/macros/icons.html b/src/meshcore_hub/web/templates/macros/icons.html new file mode 100644 index 0000000..b2d4a75 --- /dev/null +++ b/src/meshcore_hub/web/templates/macros/icons.html @@ -0,0 +1,47 @@ +{% macro icon_dashboard(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_map(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_nodes(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_advertisements(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_messages(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_home(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_members(class="h-5 w-5") %} + + + +{% endmacro %} + +{% macro icon_page(class="h-5 w-5") %} + + + +{% endmacro %} diff --git a/tests/e2e/test_full_flow.py b/tests/e2e/test_full_flow.py index a044be7..e1f6821 100644 --- a/tests/e2e/test_full_flow.py +++ b/tests/e2e/test_full_flow.py @@ -122,9 +122,9 @@ class TestWebDashboard: assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") - def test_network_page(self, web_client: httpx.Client) -> None: - """Test network overview page loads.""" - response = web_client.get("/network") + def test_dashboard_page(self, web_client: httpx.Client) -> None: + """Test dashboard page loads.""" + response = web_client.get("/dashboard") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") diff --git a/tests/test_web/test_network.py b/tests/test_web/test_dashboard.py similarity index 58% rename from tests/test_web/test_network.py rename to tests/test_web/test_dashboard.py index c063a1a..572155f 100644 --- a/tests/test_web/test_network.py +++ b/tests/test_web/test_dashboard.py @@ -1,4 +1,4 @@ -"""Tests for the network overview page route.""" +"""Tests for the dashboard page route.""" from typing import Any @@ -7,29 +7,29 @@ from fastapi.testclient import TestClient from tests.test_web.conftest import MockHttpClient -class TestNetworkPage: - """Tests for the network overview page.""" +class TestDashboardPage: + """Tests for the dashboard page.""" - def test_network_returns_200(self, client: TestClient) -> None: - """Test that network page returns 200 status code.""" - response = client.get("/network") + def test_dashboard_returns_200(self, client: TestClient) -> None: + """Test that dashboard page returns 200 status code.""" + response = client.get("/dashboard") assert response.status_code == 200 - def test_network_returns_html(self, client: TestClient) -> None: - """Test that network page returns HTML content.""" - response = client.get("/network") + def test_dashboard_returns_html(self, client: TestClient) -> None: + """Test that dashboard page returns HTML content.""" + response = client.get("/dashboard") assert "text/html" in response.headers["content-type"] - def test_network_contains_network_name(self, client: TestClient) -> None: - """Test that network page contains the network name.""" - response = client.get("/network") + def test_dashboard_contains_network_name(self, client: TestClient) -> None: + """Test that dashboard page contains the network name.""" + response = client.get("/dashboard") assert "Test Network" in response.text - def test_network_displays_stats( + def test_dashboard_displays_stats( self, client: TestClient, mock_http_client: MockHttpClient ) -> None: - """Test that network page displays statistics.""" - response = client.get("/network") + """Test that dashboard page displays statistics.""" + response = client.get("/dashboard") # Check for stats from mock response assert response.status_code == 200 # The mock returns total_nodes: 10, active_nodes: 5, etc. @@ -37,24 +37,24 @@ class TestNetworkPage: assert "10" in response.text # total_nodes assert "5" in response.text # active_nodes - def test_network_displays_message_counts( + def test_dashboard_displays_message_counts( self, client: TestClient, mock_http_client: MockHttpClient ) -> None: - """Test that network page displays message counts.""" - response = client.get("/network") + """Test that dashboard page displays message counts.""" + response = client.get("/dashboard") assert response.status_code == 200 # Mock returns total_messages: 100, messages_today: 15 assert "100" in response.text assert "15" in response.text -class TestNetworkPageAPIErrors: - """Tests for network page handling API errors.""" +class TestDashboardPageAPIErrors: + """Tests for dashboard page handling API errors.""" - def test_network_handles_api_error( + def test_dashboard_handles_api_error( self, web_app: Any, mock_http_client: MockHttpClient ) -> None: - """Test that network page handles API errors gracefully.""" + """Test that dashboard page handles API errors gracefully.""" # Set error response for stats endpoint mock_http_client.set_response( "GET", "/api/v1/dashboard/stats", status_code=500, json_data=None @@ -62,15 +62,15 @@ class TestNetworkPageAPIErrors: web_app.state.http_client = mock_http_client client = TestClient(web_app, raise_server_exceptions=True) - response = client.get("/network") + response = client.get("/dashboard") # Should still return 200 (page renders with defaults) assert response.status_code == 200 - def test_network_handles_api_not_found( + def test_dashboard_handles_api_not_found( self, web_app: Any, mock_http_client: MockHttpClient ) -> None: - """Test that network page handles API 404 gracefully.""" + """Test that dashboard page handles API 404 gracefully.""" mock_http_client.set_response( "GET", "/api/v1/dashboard/stats", @@ -80,7 +80,7 @@ class TestNetworkPageAPIErrors: web_app.state.http_client = mock_http_client client = TestClient(web_app, raise_server_exceptions=True) - response = client.get("/network") + response = client.get("/dashboard") # Should still return 200 (page renders with defaults) assert response.status_code == 200