diff --git a/core/dashboard/routes.py b/core/dashboard/routes.py index 4de5401..7e1ccbc 100644 --- a/core/dashboard/routes.py +++ b/core/dashboard/routes.py @@ -395,8 +395,8 @@ async def get_recent_activity(limit: int = 10) -> list: audit_logger = get_audit_logger() - # Read recent entries from audit log - entries = audit_logger.get_recent_entries(limit=limit) + # Fetch extra entries because we filter some out + entries = audit_logger.get_recent_entries(limit=limit * 3) for entry in entries: # Skip internal/noise events @@ -404,16 +404,22 @@ async def get_recent_activity(limit: int = 10) -> list: if evt in ("health_metric_recorded", "health_check"): continue + project = (entry.get("metadata") or {}).get("project_id") or "-" + activity.append( { "timestamp": entry.get("timestamp") or "", "type": evt or "unknown", "message": entry.get("message") or "", - "project": (entry.get("metadata") or {}).get("project_id", "-"), + "project": project if project != "None" else "-", "level": entry.get("level") or "INFO", } ) + # Stop once we have enough + if len(activity) >= limit: + break + except Exception as e: logger.warning(f"Error getting recent activity: {e}") diff --git a/core/health.py b/core/health.py index 7c01455..ebc206e 100644 --- a/core/health.py +++ b/core/health.py @@ -553,12 +553,36 @@ class HealthMonitor: plugin_instance = plugin_registry.create_instance(plugin_type, site_id, config_dict) return await plugin_instance.health_check() except Exception as e: - logger.debug( + logger.warning( f"Could not create plugin instance for {project_id}, " - f"falling back to basic HTTP check: {e}" + f"falling back to authenticated HTTP check: {e}" ) - # Fallback: basic HTTP check if plugin instantiation fails + # Fallback: authenticated health check for WordPress-based plugins + if plugin_type in ("wordpress", "wordpress_advanced", "woocommerce"): + try: + import aiohttp + + auth = aiohttp.BasicAuth( + config.username or "", + config.app_password or "", + ) + async with aiohttp.ClientSession(auth=auth) as session: + async with session.get( + f"{config.url}/wp-json/wp/v2/users/me", + timeout=aiohttp.ClientTimeout(total=10), + ssl=False, + ) as resp: + auth_valid = resp.status == 200 + basic_result = await self._basic_http_health_check(config.url, project_id) + basic_result["auth_valid"] = auth_valid + if not auth_valid: + basic_result["auth_warning"] = "Site accessible but credentials may be invalid" + return basic_result + except Exception: + pass + + # Last resort: basic HTTP check return await self._basic_http_health_check(config.url, project_id) async def _basic_http_health_check(self, url: str | None, project_id: str) -> dict[str, Any]: diff --git a/core/templates/dashboard/index.html b/core/templates/dashboard/index.html index b465d87..8bf0981 100644 --- a/core/templates/dashboard/index.html +++ b/core/templates/dashboard/index.html @@ -137,7 +137,7 @@

{{ activity.message }}

-

{{ activity.project }}

+

{{ activity.project if activity.project and activity.project != 'None' else '-' }}

{{ activity.timestamp[:19] if activity.timestamp else '-' }} diff --git a/server.py b/server.py index 2b5ccd8..fbf98b0 100644 --- a/server.py +++ b/server.py @@ -1234,18 +1234,26 @@ if _encryption_key_raw: _key_bytes = _b64.b64decode(_encryption_key_raw) if len(_key_bytes) != 32: - logger.error( - "Invalid ENCRYPTION_KEY: must decode to 32 bytes, got %d. " - "User site management will not work.", + logger.critical( + "FATAL: ENCRYPTION_KEY must decode to exactly 32 bytes, got %d. " + 'Generate a valid key with: python -c "import os,base64; ' + 'print(base64.b64encode(os.urandom(32)).decode())"', len(_key_bytes), ) + sys.exit(1) else: from core.encryption import initialize_credential_encryption initialize_credential_encryption(_encryption_key_raw) logger.info("Credential encryption initialized") except Exception as e: - logger.error("Invalid ENCRYPTION_KEY: %s. User site management will not work.", e) + logger.critical( + "FATAL: Invalid ENCRYPTION_KEY: %s. " + 'Generate a valid key with: python -c "import os,base64; ' + 'print(base64.b64encode(os.urandom(32)).decode())"', + e, + ) + sys.exit(1) else: logger.info("ENCRYPTION_KEY not set — user site management disabled") @@ -4697,17 +4705,8 @@ def create_multi_endpoint_app(transport: str = "streamable-http"): routes.append(Mount("/static", app=StaticFiles(directory=_static_dir), name="static")) - # Main admin endpoint (must be last - catches all remaining routes) - routes.append(Mount("/", app=main_app, name="mcp_admin")) - - # Add middlewares - middleware = [ - StarletteMiddleware(OAuthRequiredMiddleware), - StarletteMiddleware(DashboardCSRFMiddleware), - ] - - # Custom 404 handler — styled HTML instead of plain text - async def not_found_handler(request: Request, exc): + # Catch-all for unmatched GET requests — serves styled 404 page + async def catch_all_404(request): _404_path = os.path.join( os.path.dirname(__file__), "core", "templates", "dashboard", "404.html" ) @@ -4722,11 +4721,21 @@ def create_multi_endpoint_app(transport: str = "streamable-http"): return PlainTextResponse("404 Not Found", status_code=404) + routes.append(Route("/{path:path}", catch_all_404, methods=["GET"])) + + # Main admin endpoint (must be last - catches all remaining non-GET routes) + routes.append(Mount("/", app=main_app, name="mcp_admin")) + + # Add middlewares + middleware = [ + StarletteMiddleware(OAuthRequiredMiddleware), + StarletteMiddleware(DashboardCSRFMiddleware), + ] + app = Starlette( routes=routes, lifespan=combined_lifespan, middleware=middleware, - exception_handlers={404: not_found_handler}, ) logger.info("=" * 60)