From cf2c3350cc0780358d81842e7ee73bf570550da0 Mon Sep 17 00:00:00 2001
From: Louis King
Date: Thu, 4 Dec 2025 18:10:29 +0000
Subject: [PATCH] Updates
---
.env.example | 4 +
docker-compose.yml | 2 +
src/meshcore_hub/common/config.py | 6 ++
src/meshcore_hub/web/app.py | 48 +++++++++---
src/meshcore_hub/web/cli.py | 96 ++++++++++++++++--------
src/meshcore_hub/web/templates/base.html | 6 +-
src/meshcore_hub/web/templates/home.html | 20 ++++-
7 files changed, 133 insertions(+), 49 deletions(-)
diff --git a/.env.example b/.env.example
index 7399be4..2248e07 100644
--- a/.env.example
+++ b/.env.example
@@ -94,6 +94,10 @@ NETWORK_RADIO_CONFIG=
NETWORK_CONTACT_EMAIL=
NETWORK_CONTACT_DISCORD=
+# Welcome text displayed on the homepage (plain text, optional)
+# If not set, a default welcome message is shown
+NETWORK_WELCOME_TEXT=
+
# Members file location (optional override)
# Default: ${DATA_HOME}/web/members.json
# Only set this if you want to use a different location
diff --git a/docker-compose.yml b/docker-compose.yml
index 4f4c784..35fd21b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -241,6 +241,8 @@ services:
- NETWORK_RADIO_CONFIG=${NETWORK_RADIO_CONFIG:-}
- NETWORK_CONTACT_EMAIL=${NETWORK_CONTACT_EMAIL:-}
- NETWORK_CONTACT_DISCORD=${NETWORK_CONTACT_DISCORD:-}
+ - NETWORK_CONTACT_GITHUB=${NETWORK_CONTACT_GITHUB:-}
+ - NETWORK_WELCOME_TEXT=${NETWORK_WELCOME_TEXT:-}
command: ["web"]
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
diff --git a/src/meshcore_hub/common/config.py b/src/meshcore_hub/common/config.py
index 6f19623..77542e3 100644
--- a/src/meshcore_hub/common/config.py
+++ b/src/meshcore_hub/common/config.py
@@ -243,6 +243,12 @@ class WebSettings(CommonSettings):
network_contact_discord: Optional[str] = Field(
default=None, description="Discord server link"
)
+ network_contact_github: Optional[str] = Field(
+ default=None, description="GitHub repository URL"
+ )
+ network_welcome_text: Optional[str] = Field(
+ default=None, description="Welcome text for homepage"
+ )
@property
def web_data_dir(self) -> str:
diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py
index f27ca3e..5758a19 100644
--- a/src/meshcore_hub/web/app.py
+++ b/src/meshcore_hub/web/app.py
@@ -48,18 +48,23 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
def create_app(
- api_url: str = "http://localhost:8000",
+ api_url: str | None = None,
api_key: str | None = None,
- network_name: str = "MeshCore Network",
+ network_name: str | None = None,
network_city: str | None = None,
network_country: str | None = None,
network_location: tuple[float, float] | None = None,
network_radio_config: str | None = None,
network_contact_email: str | None = None,
network_contact_discord: str | None = None,
+ network_contact_github: str | None = None,
+ network_welcome_text: str | None = None,
) -> FastAPI:
"""Create and configure the web dashboard application.
+ When called without arguments (e.g., in reload mode), settings are loaded
+ from environment variables via the WebSettings class.
+
Args:
api_url: Base URL of the MeshCore Hub API
api_key: API key for authentication
@@ -70,10 +75,17 @@ def create_app(
network_radio_config: Radio configuration description
network_contact_email: Contact email address
network_contact_discord: Discord invite/server info
+ network_contact_github: GitHub repository URL
+ network_welcome_text: Welcome text for homepage
Returns:
Configured FastAPI application
"""
+ # Load settings from environment if not provided
+ from meshcore_hub.common.config import get_web_settings
+
+ settings = get_web_settings()
+
app = FastAPI(
title="MeshCore Hub Dashboard",
description="Web dashboard for MeshCore network visualization",
@@ -83,16 +95,28 @@ def create_app(
redoc_url=None,
)
- # Store configuration in app state
- app.state.api_url = api_url
- app.state.api_key = api_key
- app.state.network_name = network_name
- app.state.network_city = network_city
- app.state.network_country = network_country
+ # Store configuration in app state (use args if provided, else settings)
+ app.state.api_url = api_url or settings.api_base_url
+ app.state.api_key = api_key or settings.api_key
+ app.state.network_name = network_name or settings.network_name
+ app.state.network_city = network_city or settings.network_city
+ app.state.network_country = network_country or settings.network_country
app.state.network_location = network_location or (0.0, 0.0)
- app.state.network_radio_config = network_radio_config
- app.state.network_contact_email = network_contact_email
- app.state.network_contact_discord = network_contact_discord
+ app.state.network_radio_config = (
+ network_radio_config or settings.network_radio_config
+ )
+ app.state.network_contact_email = (
+ network_contact_email or settings.network_contact_email
+ )
+ app.state.network_contact_discord = (
+ network_contact_discord or settings.network_contact_discord
+ )
+ app.state.network_contact_github = (
+ network_contact_github or settings.network_contact_github
+ )
+ app.state.network_welcome_text = (
+ network_welcome_text or settings.network_welcome_text
+ )
# Set up templates
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
@@ -148,5 +172,7 @@ def get_network_context(request: Request) -> dict:
"network_radio_config": radio_config,
"network_contact_email": request.app.state.network_contact_email,
"network_contact_discord": request.app.state.network_contact_discord,
+ "network_contact_github": request.app.state.network_contact_github,
+ "network_welcome_text": request.app.state.network_welcome_text,
"version": __version__,
}
diff --git a/src/meshcore_hub/web/cli.py b/src/meshcore_hub/web/cli.py
index 412de85..4eeeb8f 100644
--- a/src/meshcore_hub/web/cli.py
+++ b/src/meshcore_hub/web/cli.py
@@ -7,21 +7,21 @@ import click
@click.option(
"--host",
type=str,
- default="0.0.0.0",
+ default=None,
envvar="WEB_HOST",
- help="Web server host",
+ help="Web server host (default: 0.0.0.0)",
)
@click.option(
"--port",
type=int,
- default=8080,
+ default=None,
envvar="WEB_PORT",
- help="Web server port",
+ help="Web server port (default: 8080)",
)
@click.option(
"--api-url",
type=str,
- default="http://localhost:8000",
+ default=None,
envvar="API_BASE_URL",
help="API server base URL",
)
@@ -42,7 +42,7 @@ import click
@click.option(
"--network-name",
type=str,
- default="MeshCore Network",
+ default=None,
envvar="NETWORK_NAME",
help="Network display name",
)
@@ -63,14 +63,14 @@ import click
@click.option(
"--network-lat",
type=float,
- default=0.0,
+ default=None,
envvar="NETWORK_LAT",
help="Network center latitude",
)
@click.option(
"--network-lon",
type=float,
- default=0.0,
+ default=None,
envvar="NETWORK_LON",
help="Network center longitude",
)
@@ -95,6 +95,20 @@ import click
envvar="NETWORK_CONTACT_DISCORD",
help="Discord server info",
)
+@click.option(
+ "--network-contact-github",
+ type=str,
+ default=None,
+ envvar="NETWORK_CONTACT_GITHUB",
+ help="GitHub repository URL",
+)
+@click.option(
+ "--network-welcome-text",
+ type=str,
+ default=None,
+ envvar="NETWORK_WELCOME_TEXT",
+ help="Welcome text for homepage",
+)
@click.option(
"--reload",
is_flag=True,
@@ -104,19 +118,21 @@ import click
@click.pass_context
def web(
ctx: click.Context,
- host: str,
- port: int,
- api_url: str,
+ host: str | None,
+ port: int | None,
+ api_url: str | None,
api_key: str | None,
data_home: str | None,
- network_name: str,
+ network_name: str | None,
network_city: str | None,
network_country: str | None,
- network_lat: float,
- network_lon: float,
+ network_lat: float | None,
+ network_lon: float | None,
network_radio_config: str | None,
network_contact_email: str | None,
network_contact_discord: str | None,
+ network_contact_github: str | None,
+ network_welcome_text: str | None,
reload: bool,
) -> None:
"""Run the web dashboard.
@@ -146,46 +162,58 @@ def web(
from meshcore_hub.common.config import get_web_settings
from meshcore_hub.web.app import create_app
- # Get settings to compute effective values
+ # Get settings for defaults and display
settings = get_web_settings()
- # Override data_home if provided
- if data_home:
- settings = settings.model_copy(update={"data_home": data_home})
-
+ # Use CLI args or fall back to settings
+ effective_host = host or settings.web_host
+ effective_port = port or settings.web_port
effective_data_home = data_home or settings.data_home
# Ensure web data directory exists
web_data_dir = Path(effective_data_home) / "web"
web_data_dir.mkdir(parents=True, exist_ok=True)
+ # Display effective settings
+ effective_network_name = network_name or settings.network_name
+
click.echo("=" * 50)
click.echo("MeshCore Hub Web Dashboard")
click.echo("=" * 50)
- click.echo(f"Host: {host}")
- click.echo(f"Port: {port}")
+ click.echo(f"Host: {effective_host}")
+ click.echo(f"Port: {effective_port}")
click.echo(f"Data home: {effective_data_home}")
- click.echo(f"API URL: {api_url}")
- click.echo(f"API key configured: {api_key is not None}")
- click.echo(f"Network: {network_name}")
- if network_city and network_country:
- click.echo(f"Location: {network_city}, {network_country}")
- if network_lat != 0.0 or network_lon != 0.0:
- click.echo(f"Map center: {network_lat}, {network_lon}")
+ click.echo(f"API URL: {api_url or settings.api_base_url}")
+ click.echo(f"API key configured: {(api_key or settings.api_key) is not None}")
+ click.echo(f"Network: {effective_network_name}")
+ effective_city = network_city or settings.network_city
+ effective_country = network_country or settings.network_country
+ if effective_city and effective_country:
+ click.echo(f"Location: {effective_city}, {effective_country}")
+ effective_lat = network_lat if network_lat is not None else 0.0
+ effective_lon = network_lon if network_lon is not None else 0.0
+ if effective_lat != 0.0 or effective_lon != 0.0:
+ click.echo(f"Map center: {effective_lat}, {effective_lon}")
click.echo(f"Reload mode: {reload}")
click.echo("=" * 50)
- network_location = (network_lat, network_lon)
+ # Build network_location tuple only if explicitly provided
+ network_location: tuple[float, float] | None = None
+ if network_lat is not None or network_lon is not None:
+ network_location = (
+ network_lat if network_lat is not None else 0.0,
+ network_lon if network_lon is not None else 0.0,
+ )
if reload:
# For development, use uvicorn's reload feature
click.echo("\nStarting in development mode with auto-reload...")
- click.echo("Note: Using default settings for reload mode.")
+ click.echo("Note: Settings loaded from environment/config.")
uvicorn.run(
"meshcore_hub.web.app:create_app",
- host=host,
- port=port,
+ host=effective_host,
+ port=effective_port,
reload=True,
factory=True,
)
@@ -201,7 +229,9 @@ def web(
network_radio_config=network_radio_config,
network_contact_email=network_contact_email,
network_contact_discord=network_contact_discord,
+ network_contact_github=network_contact_github,
+ network_welcome_text=network_welcome_text,
)
click.echo("\nStarting web dashboard...")
- uvicorn.run(app, host=host, port=port)
+ uvicorn.run(app, host=effective_host, port=effective_port)
diff --git a/src/meshcore_hub/web/templates/base.html b/src/meshcore_hub/web/templates/base.html
index 816cc3e..8711287 100644
--- a/src/meshcore_hub/web/templates/base.html
+++ b/src/meshcore_hub/web/templates/base.html
@@ -105,7 +105,11 @@
{% endif %}
{% if network_contact_email and network_contact_discord %} | {% endif %}
{% if network_contact_discord %}
- Discord: {{ network_contact_discord }}
+ Discord
+ {% endif %}
+ {% if (network_contact_email or network_contact_discord) and network_contact_github %} | {% endif %}
+ {% if network_contact_github %}
+ GitHub
{% endif %}
Powered by MeshCore Hub v{{ version }}
diff --git a/src/meshcore_hub/web/templates/home.html b/src/meshcore_hub/web/templates/home.html
index 7bdf43f..4fea129 100644
--- a/src/meshcore_hub/web/templates/home.html
+++ b/src/meshcore_hub/web/templates/home.html
@@ -10,10 +10,14 @@
{% 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 %}
@@ -133,14 +137,22 @@
{% endif %}
{% if network_contact_discord %}
-
+ Discord
+
{% endif %}
- {% if not network_contact_email and not network_contact_discord %}
+ {% if network_contact_github %}
+
+
+
+
+ GitHub
+
+ {% endif %}
+ {% if not network_contact_email and not network_contact_discord and not network_contact_github %}
No contact information configured.
{% endif %}