mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-06-30 23:11:20 +02:00
Updates
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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')"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
+37
-11
@@ -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__,
|
||||
}
|
||||
|
||||
+63
-33
@@ -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)
|
||||
|
||||
@@ -105,7 +105,11 @@
|
||||
{% endif %}
|
||||
{% if network_contact_email and network_contact_discord %} | {% endif %}
|
||||
{% if network_contact_discord %}
|
||||
<span>Discord: {{ network_contact_discord }}</span>
|
||||
<a href="{{ network_contact_discord }}" target="_blank" rel="noopener noreferrer" class="link link-hover">Discord</a>
|
||||
{% endif %}
|
||||
{% if (network_contact_email or network_contact_discord) and network_contact_github %} | {% endif %}
|
||||
{% if network_contact_github %}
|
||||
<a href="{{ network_contact_github }}" target="_blank" rel="noopener noreferrer" class="link link-hover">GitHub</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="text-xs opacity-50 mt-2">Powered by MeshCore Hub v{{ version }}</p>
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
{% if network_city and network_country %}
|
||||
<p class="py-2 text-lg opacity-70">{{ network_city }}, {{ network_country }}</p>
|
||||
{% endif %}
|
||||
{% if network_welcome_text %}
|
||||
<p class="py-6">{{ network_welcome_text }}</p>
|
||||
{% else %}
|
||||
<p class="py-6">
|
||||
Welcome to the {{ network_name }} mesh network dashboard.
|
||||
Monitor network activity, view connected nodes, and explore message history.
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="flex gap-4 justify-center flex-wrap">
|
||||
<a href="/network" class="btn btn-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -133,14 +137,22 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if network_contact_discord %}
|
||||
<div class="btn btn-outline btn-sm btn-block">
|
||||
<a href="{{ network_contact_discord }}" target="_blank" rel="noopener noreferrer" class="btn btn-outline btn-sm btn-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/>
|
||||
</svg>
|
||||
{{ network_contact_discord }}
|
||||
</div>
|
||||
Discord
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not network_contact_email and not network_contact_discord %}
|
||||
{% if network_contact_github %}
|
||||
<a href="{{ network_contact_github }}" target="_blank" rel="noopener noreferrer" class="btn btn-outline btn-sm btn-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not network_contact_email and not network_contact_discord and not network_contact_github %}
|
||||
<p class="text-sm opacity-70">No contact information configured.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user