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 %}