Add local SQLite database and sync with Uptime Kuma
All checks were successful
Build and Push Container / build (push) Successful in 1m4s

Features:
- SQLite database to track monitors and hosts locally
- Uses Uptime Kuma tags to mark monitors as managed by Kuma Strapper
- Sync on startup, before each scan, and on-demand via API
- Shows existing monitors when re-scanning a host

New files:
- backend/services/database.py - SQLite database service
- backend/services/sync.py - Sync service for Uptime Kuma reconciliation

API endpoints:
- POST /api/sync - Full sync with Uptime Kuma
- POST /api/sync/host/<hostname> - Sync specific host
- GET /api/hosts - List tracked hosts
- GET /api/hosts/<hostname>/monitors - Get monitors for host
- GET /api/monitors/tracked - Get all tracked monitors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-01-06 09:10:26 +00:00
parent a65997a391
commit 1fd29e449f
11 changed files with 1044 additions and 47 deletions

View File

@@ -17,6 +17,8 @@ from services.monitors import (
parse_docker_containers_from_scan,
)
from services.kuma_client import get_kuma_client
from services.database import get_database
from services.sync import get_sync_service
from utils.approval import get_approval_queue, ApprovalStatus
@@ -90,6 +92,20 @@ def start_scan():
# Start scan in background thread
def run_scan():
# Sync this host's monitors before scanning
try:
sync = get_sync_service()
sync_result = sync.sync_host(hostname)
db = get_database()
existing_monitors = db.get_monitors_for_hostname(hostname)
socketio.emit("host_sync_complete", {
"hostname": hostname,
"sync_result": sync_result,
"existing_monitors": [m.to_dict() for m in existing_monitors],
})
except Exception as e:
print(f"Pre-scan sync failed (non-fatal): {e}")
discovery = get_discovery_service()
def on_progress(cmd_name, status):
@@ -576,6 +592,72 @@ def test_kuma_connection():
return jsonify({"connected": False, "error": str(e)})
# Sync endpoints
@app.route("/api/sync", methods=["POST"])
def trigger_sync():
"""Trigger a full sync with Uptime Kuma."""
try:
sync = get_sync_service()
result = sync.full_sync()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/sync/host/<hostname>", methods=["POST"])
def sync_host(hostname):
"""Sync monitors for a specific host."""
try:
sync = get_sync_service()
result = sync.sync_host(hostname)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/hosts", methods=["GET"])
def get_hosts():
"""Get all tracked hosts."""
try:
db = get_database()
hosts = db.get_all_hosts()
return jsonify({
"hosts": [h.to_dict() for h in hosts],
"count": len(hosts)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/hosts/<hostname>/monitors", methods=["GET"])
def get_host_monitors(hostname):
"""Get all tracked monitors for a host."""
try:
db = get_database()
monitors = db.get_monitors_for_hostname(hostname)
return jsonify({
"hostname": hostname,
"monitors": [m.to_dict() for m in monitors],
"count": len(monitors)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/monitors/tracked", methods=["GET"])
def get_tracked_monitors():
"""Get all tracked monitors across all hosts."""
try:
db = get_database()
monitors = db.get_all_monitors()
return jsonify({
"monitors": [m.to_dict() for m in monitors],
"count": len(monitors)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# WebSocket events
@socketio.on("connect")
def handle_connect():
@@ -587,6 +669,19 @@ def handle_disconnect():
pass
def startup_sync():
"""Run initial sync with Uptime Kuma on startup."""
try:
print("Running startup sync with Uptime Kuma...")
sync = get_sync_service()
result = sync.full_sync()
print(f"Startup sync complete: added={result['added']}, updated={result['updated']}, removed={result['removed']}")
if result['errors']:
print(f"Sync errors: {result['errors']}")
except Exception as e:
print(f"Startup sync failed (non-fatal): {e}")
if __name__ == "__main__":
# Validate config on startup
config = get_config()
@@ -600,4 +695,13 @@ if __name__ == "__main__":
print("Configuration OK")
print(f"Dev mode: {'enabled' if config.dev_mode else 'disabled'}")
# Run startup sync in background after short delay
def delayed_sync():
import time
time.sleep(2) # Wait for app to fully start
startup_sync()
sync_thread = threading.Thread(target=delayed_sync, daemon=True)
sync_thread.start()
socketio.run(app, host="0.0.0.0", port=5000, debug=os.environ.get("DEBUG", "false").lower() == "true")