diff --git a/docs/API_Documentation.md b/docs/API_Documentation.md index c6dd417..a0ba194 100644 --- a/docs/API_Documentation.md +++ b/docs/API_Documentation.md @@ -288,15 +288,46 @@ Request: `/api/lang?section=chat` --- -## 9. Version API +## 9. Health Check API + +### GET `/health` +Health check endpoint for monitoring, load balancers, and orchestration systems. + +**Response Example (Healthy)** +```json +{ + "status": "healthy", + "timestamp": "2025-11-03T14:30:00.123456Z", + "version": "2.0.8", + "git_revision": "6416978", + "database": "connected" +} +``` + +**Response Example (Unhealthy)** +Status Code: `503 Service Unavailable` +```json +{ + "status": "unhealthy", + "timestamp": "2025-11-03T14:30:00.123456Z", + "version": "2.0.8", + "git_revision": "6416978", + "database": "disconnected" +} +``` + +--- + +## 10. Version API ### GET `/version` -Returns version information including semver and git revision. +Returns detailed version information including semver, release date, and git revision. **Response Example** ```json { - "version": "2.0.8 ~ 10-22-25", + "version": "2.0.8", + "release_date": "2025-10-22", "git_revision": "6416978a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q", "git_revision_short": "6416978" } diff --git a/docs/README.md b/docs/README.md index 8263cde..552da62 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,5 +9,6 @@ These documents are intended for developers, contributors, and advanced users wh - [ALEMBIC_SETUP.md](ALEMBIC_SETUP.md) - Database migration setup and management - [TIMESTAMP_MIGRATION.md](TIMESTAMP_MIGRATION.md) - Details on timestamp schema changes - [API_Documentation.md](API_Documentation.md) - REST API endpoints and usage +- [CODE_IMPROVEMENTS.md](CODE_IMPROVEMENTS.md) - Suggested code improvements and refactoring ideas For initial setup and basic usage instructions, please see the main [README.md](../README.md) in the root directory. diff --git a/meshview/__version__.py b/meshview/__version__.py new file mode 100644 index 0000000..ae8b3f5 --- /dev/null +++ b/meshview/__version__.py @@ -0,0 +1,56 @@ +"""Version information for MeshView.""" +import subprocess +from pathlib import Path + +__version__ = "3.0.0" +__release_date__ = "2025-11-05" + + +def get_git_revision(): + """Get the current git revision hash.""" + try: + repo_dir = Path(__file__).parent.parent + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=True, + cwd=repo_dir, + ) + return result.stdout.strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return "unknown" + + +def get_git_revision_short(): + """Get the short git revision hash.""" + try: + repo_dir = Path(__file__).parent.parent + result = subprocess.run( + ["git", "rev-parse", "--short", "HEAD"], + capture_output=True, + text=True, + check=True, + cwd=repo_dir, + ) + return result.stdout.strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return "unknown" + + +def get_version_info(): + """Get complete version information.""" + return { + "version": __version__, + "release_date": __release_date__, + "git_revision": get_git_revision(), + "git_revision_short": get_git_revision_short(), + } + + +# Cache git info at import time for performance +_git_revision = get_git_revision() +_git_revision_short = get_git_revision_short() + +# Full version string for display +__version_string__ = f"{__version__} ~ {__release_date__}" diff --git a/meshview/web.py b/meshview/web.py index 9c3e416..97750cf 100644 --- a/meshview/web.py +++ b/meshview/web.py @@ -19,9 +19,17 @@ from google.protobuf.message import Message from jinja2 import Environment, PackageLoader, Undefined, select_autoescape from markupsafe import Markup from pandas import DataFrame +from sqlalchemy import text from meshtastic.protobuf.portnums_pb2 import PortNum from meshview import config, database, decode_payload, migrations, models, store +from meshview.__version__ import ( + __version__, + __version_string__, + _git_revision, + _git_revision_short, + get_version_info, +) logging.basicConfig( level=logging.INFO, @@ -31,7 +39,7 @@ logging.basicConfig( logger = logging.getLogger(__name__) SEQ_REGEX = re.compile(r"seq \d+") -SOFTWARE_RELEASE = "2.0.8 ~ 10-22-25" +SOFTWARE_RELEASE = __version_string__ # Keep for backward compatibility CONFIG = config.CONFIG env = Environment(loader=PackageLoader("meshview"), autoescape=select_autoescape()) @@ -1751,39 +1759,36 @@ async def api_lang(request): return web.json_response(translations) +@routes.get("/health") +async def health_check(request): + """Health check endpoint for monitoring and load balancers.""" + health_status = { + "status": "healthy", + "timestamp": datetime.datetime.now(datetime.UTC).isoformat(), + "version": __version__, + "git_revision": _git_revision_short, + } + + # Check database connectivity + try: + async with database.async_session() as session: + await session.execute(text("SELECT 1")) + health_status["database"] = "connected" + except Exception as e: + logger.error(f"Database health check failed: {e}") + health_status["database"] = "disconnected" + health_status["status"] = "unhealthy" + return web.json_response(health_status, status=503) + + return web.json_response(health_status) + + @routes.get("/version") async def version_endpoint(request): """Return version information including semver and git revision.""" try: - # Get git revision hash - try: - result = subprocess.run( - ["git", "rev-parse", "HEAD"], - capture_output=True, - text=True, - check=True, - cwd=os.path.dirname(__file__), - ) - git_revision = result.stdout.strip() - - # Also get short hash - result_short = subprocess.run( - ["git", "rev-parse", "--short", "HEAD"], - capture_output=True, - text=True, - check=True, - cwd=os.path.dirname(__file__), - ) - git_revision_short = result_short.stdout.strip() - except (subprocess.CalledProcessError, FileNotFoundError): - git_revision = "unknown" - git_revision_short = "unknown" - - return web.json_response({ - "version": SOFTWARE_RELEASE, - "git_revision": git_revision, - "git_revision_short": git_revision_short, - }) + version_info = get_version_info() + return web.json_response(version_info) except Exception as e: logger.error(f"Error in /version: {e}") return web.json_response({"error": "Failed to fetch version info"}, status=500)