Refine LetsMesh status ingest and custom logo behavior

This commit is contained in:
yellowcooln
2026-02-22 11:40:12 -05:00
parent 2f40b4a730
commit 6a66eab663
9 changed files with 162 additions and 41 deletions

View File

@@ -185,33 +185,13 @@ class Subscriber:
observer_public_key, feed_type = parsed
if feed_type == "status":
status_public_key = (
payload.get("origin_id")
or payload.get("public_key")
or observer_public_key
normalized_status = self._build_letsmesh_status_advertisement_payload(
payload,
observer_public_key=observer_public_key,
)
normalized_payload = dict(payload)
normalized_payload["public_key"] = status_public_key
status_name = payload.get("origin") or payload.get("name")
if status_name and not normalized_payload.get("name"):
normalized_payload["name"] = status_name
normalized_adv_type = self._normalize_letsmesh_adv_type(normalized_payload)
if normalized_adv_type:
normalized_payload["adv_type"] = normalized_adv_type
else:
normalized_payload.pop("adv_type", None)
stats = payload.get("stats")
if (
isinstance(stats, dict)
and "flags" not in normalized_payload
and "debug_flags" in stats
):
normalized_payload["flags"] = stats["debug_flags"]
return observer_public_key, "advertisement", normalized_payload
if normalized_status:
return observer_public_key, "advertisement", normalized_status
return observer_public_key, "letsmesh_status", dict(payload)
if feed_type == "packets":
decoded_packet = self._letsmesh_decoder.decode_payload(payload)
@@ -445,6 +425,59 @@ class Subscriber:
return normalized_payload
def _build_letsmesh_status_advertisement_payload(
self,
payload: dict[str, Any],
observer_public_key: str,
) -> dict[str, Any] | None:
"""Normalize LetsMesh status feed payloads into advertisement events."""
status_public_key = self._normalize_full_public_key(
payload.get("origin_id") or payload.get("public_key") or observer_public_key
)
if not status_public_key:
return None
normalized_payload: dict[str, Any] = {"public_key": status_public_key}
status_name = payload.get("origin") or payload.get("name")
if isinstance(status_name, str) and status_name.strip():
normalized_payload["name"] = status_name.strip()
normalized_adv_type = self._normalize_letsmesh_adv_type(payload)
if normalized_adv_type:
normalized_payload["adv_type"] = normalized_adv_type
# Only trust explicit status payload flags. stats.debug_flags are observer/debug
# counters and cause false capability flags + inflated dedup churn.
explicit_flags = self._parse_int(payload.get("flags"))
if explicit_flags is not None:
normalized_payload["flags"] = explicit_flags
lat = self._parse_float(payload.get("lat"))
lon = self._parse_float(payload.get("lon"))
if lat is None:
lat = self._parse_float(payload.get("adv_lat"))
if lon is None:
lon = self._parse_float(payload.get("adv_lon"))
location = payload.get("location")
if isinstance(location, dict):
if lat is None:
lat = self._parse_float(location.get("latitude"))
if lon is None:
lon = self._parse_float(location.get("longitude"))
if lat is not None:
normalized_payload["lat"] = lat
if lon is not None:
normalized_payload["lon"] = lon
# Ignore status heartbeat/counter frames that have no node identity metadata.
if not any(
key in normalized_payload
for key in ("name", "adv_type", "flags", "lat", "lon")
):
return None
return normalized_payload
@classmethod
def _extract_letsmesh_text(
cls,

View File

@@ -50,6 +50,24 @@ def _build_channel_labels() -> dict[str, str]:
return {str(idx): label for idx, label in sorted(labels.items())}
def _resolve_logo(media_home: Path) -> tuple[str, bool, Path | None]:
"""Resolve logo URL and whether light-mode inversion should be applied.
Returns:
tuple of (logo_url, invert_in_light_mode, resolved_path)
"""
custom_logo_candidates = (("logo.svg", "/media/images/logo.svg"),)
for filename, url in custom_logo_candidates:
path = media_home / "images" / filename
if path.exists():
# Custom logos are assumed to be full-color and should not be darkened.
cache_buster = int(path.stat().st_mtime)
return f"{url}?v={cache_buster}", False, path
# Default packaged logo is monochrome and needs darkening in light mode.
return "/static/img/logo.svg", True, None
def _is_authenticated_proxy_request(request: Request) -> bool:
"""Check whether request is authenticated by an upstream auth proxy.
@@ -157,6 +175,7 @@ def _build_config_json(app: FastAPI, request: Request) -> str:
"datetime_locale": app.state.web_datetime_locale,
"auto_refresh_seconds": app.state.auto_refresh_seconds,
"channel_labels": app.state.channel_labels,
"logo_invert_light": app.state.logo_invert_light,
}
return json.dumps(config)
@@ -300,12 +319,11 @@ def create_app(
# Check for custom logo and store media path
media_home = Path(settings.effective_media_home)
custom_logo_path = media_home / "images" / "logo.svg"
if custom_logo_path.exists():
app.state.logo_url = "/media/images/logo.svg"
logger.info(f"Using custom logo from {custom_logo_path}")
else:
app.state.logo_url = "/static/img/logo.svg"
logo_url, logo_invert_light, logo_path = _resolve_logo(media_home)
app.state.logo_url = logo_url
app.state.logo_invert_light = logo_invert_light
if logo_path is not None:
logger.info("Using custom logo from %s", logo_path)
# Mount static files
if STATIC_DIR.exists():
@@ -697,6 +715,7 @@ def create_app(
"features": features,
"custom_pages": custom_pages,
"logo_url": request.app.state.logo_url,
"logo_invert_light": request.app.state.logo_invert_light,
"version": __version__,
"default_theme": request.app.state.web_theme,
"config_json": config_json,

View File

@@ -46,8 +46,8 @@
/* Spacing between horizontal nav items */
.menu-horizontal { gap: 0.125rem; }
/* Invert white logos/images to dark for light mode */
[data-theme="light"] .theme-logo {
/* Invert monochrome logos to dark for light mode */
[data-theme="light"] .theme-logo--invert-light {
filter: brightness(0.15);
}

View File

@@ -33,6 +33,7 @@ export async function render(container, params, router) {
const features = config.features || {};
const networkName = config.network_name || 'MeshCore Network';
const logoUrl = config.logo_url || '/static/img/logo.svg';
const logoInvertLight = config.logo_invert_light !== false;
const customPages = config.custom_pages || [];
const rc = config.network_radio_config;
@@ -69,7 +70,7 @@ export async function render(container, params, router) {
<div class="${showStats ? 'grid grid-cols-1 lg:grid-cols-3 gap-6' : ''} bg-base-100 rounded-box shadow-xl p-6">
<div class="${showStats ? 'lg:col-span-2' : ''} flex flex-col items-center text-center">
<div class="flex flex-col sm:flex-row items-center gap-4 sm:gap-8 mb-4">
<img src="${logoUrl}" alt="${networkName}" class="theme-logo h-24 w-24 sm:h-36 sm:w-36" />
<img src="${logoUrl}" alt="${networkName}" class="theme-logo ${logoInvertLight ? 'theme-logo--invert-light' : ''} h-24 w-24 sm:h-36 sm:w-36" />
<div class="flex flex-col justify-center">
<h1 class="hero-title text-3xl sm:text-5xl lg:text-6xl font-black tracking-tight">${networkName}</h1>
${cityCountry}
@@ -158,7 +159,7 @@ export async function render(container, params, router) {
<div class="card-body flex flex-col items-center justify-center">
<p class="text-sm opacity-70 mb-4 text-center">${t('home.meshcore_attribution')}</p>
<a href="https://meshcore.co.uk/" target="_blank" rel="noopener noreferrer" class="hover:opacity-80 transition-opacity">
<img src="/static/img/meshcore.svg" alt="MeshCore" class="theme-logo h-8" />
<img src="/static/img/meshcore.svg" alt="MeshCore" class="theme-logo theme-logo--invert-light h-8" />
</a>
<p class="text-xs opacity-50 mt-4 text-center">Connecting people and things, without using the internet</p>
<div class="flex gap-2 mt-4">

View File

@@ -30,6 +30,12 @@
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="{{ logo_url }}">
{% if not logo_invert_light %}
<style>
/* Keep custom network logos full-color in light mode */
[data-theme="light"] img[src="{{ logo_url }}"] { filter: none !important; }
</style>
{% endif %}
<!-- Tailwind CSS with DaisyUI -->
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.4.19/dist/full.min.css" rel="stylesheet" type="text/css" />
@@ -87,7 +93,7 @@
</ul>
</div>
<a href="/" class="btn btn-ghost text-xl">
<img src="{{ logo_url }}" alt="{{ network_name }}" class="theme-logo h-6 w-6 mr-2" />
<img src="{{ logo_url }}" alt="{{ network_name }}" class="theme-logo{% if logo_invert_light %} theme-logo--invert-light{% endif %} h-6 w-6 mr-2" />
{{ network_name }}
</a>
</div>