python v2 - added feartures 1 and 2 - no errors

This commit is contained in:
2025-08-08 14:11:55 -07:00
parent 9418823915
commit 0d3a974be4
4 changed files with 302 additions and 65 deletions

View File

@@ -1,24 +1,88 @@
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import JSONResponse
import os
from pathlib import Path
from .routes import router
app = FastAPI(title="GarminSync Dashboard")
# Mount static files and templates
app.mount("/static", StaticFiles(directory="garminsync/web/static"), name="static")
templates = Jinja2Templates(directory="garminsync/web/templates")
# Get the current directory path
current_dir = Path(__file__).parent
# Mount static files and templates with error handling
static_dir = current_dir / "static"
templates_dir = current_dir / "templates"
if static_dir.exists():
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
if templates_dir.exists():
templates = Jinja2Templates(directory=str(templates_dir))
else:
templates = None
# Include API routes
app.include_router(router)
@app.get("/")
async def dashboard(request: Request):
# Get current statistics
from garminsync.database import get_offline_stats
stats = get_offline_stats()
"""Dashboard route with fallback for missing templates"""
if not templates:
# Return JSON response if templates are not available
from garminsync.database import get_offline_stats
stats = get_offline_stats()
return JSONResponse({
"message": "GarminSync Dashboard",
"stats": stats,
"note": "Web UI templates not found, showing JSON response"
})
return templates.TemplateResponse("dashboard.html", {
"request": request,
"stats": stats
try:
# Get current statistics
from garminsync.database import get_offline_stats
stats = get_offline_stats()
return templates.TemplateResponse("dashboard.html", {
"request": request,
"stats": stats
})
except Exception as e:
return JSONResponse({
"error": f"Failed to load dashboard: {str(e)}",
"message": "Dashboard unavailable, API endpoints still functional"
})
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "service": "GarminSync Dashboard"}
@app.get("/config")
async def config_page(request: Request):
"""Configuration page"""
if not templates:
return JSONResponse({
"message": "Configuration endpoint",
"note": "Use /api/schedule endpoints for configuration"
})
return templates.TemplateResponse("config.html", {
"request": request
})
# Error handlers
@app.exception_handler(404)
async def not_found_handler(request: Request, exc):
return JSONResponse(
status_code=404,
content={"error": "Not found", "path": str(request.url.path)}
)
@app.exception_handler(500)
async def server_error_handler(request: Request, exc):
return JSONResponse(
status_code=500,
content={"error": "Internal server error", "detail": str(exc)}
)

View File

@@ -12,45 +12,152 @@ class ScheduleConfig(BaseModel):
async def get_status():
"""Get current daemon status"""
session = get_session()
config = session.query(DaemonConfig).first()
# Get recent logs
logs = session.query(SyncLog).order_by(SyncLog.timestamp.desc()).limit(10).all()
return {
"daemon": {
try:
config = session.query(DaemonConfig).first()
# Get recent logs
logs = session.query(SyncLog).order_by(SyncLog.timestamp.desc()).limit(10).all()
# Convert to dictionaries to avoid session issues
daemon_data = {
"running": config.status == "running" if config else False,
"next_run": config.next_run if config else None,
"schedule": config.schedule_cron if config else None
},
"recent_logs": [
{
"schedule": config.schedule_cron if config else None,
"last_run": config.last_run if config else None,
"enabled": config.enabled if config else False
}
log_data = []
for log in logs:
log_data.append({
"timestamp": log.timestamp,
"operation": log.operation,
"status": log.status,
"message": log.message
} for log in logs
]
}
"message": log.message,
"activities_processed": log.activities_processed,
"activities_downloaded": log.activities_downloaded
})
return {
"daemon": daemon_data,
"recent_logs": log_data
}
finally:
session.close()
@router.post("/schedule")
async def update_schedule(config: ScheduleConfig):
"""Update daemon schedule configuration"""
session = get_session()
daemon_config = session.query(DaemonConfig).first()
if not daemon_config:
daemon_config = DaemonConfig()
session.add(daemon_config)
daemon_config.enabled = config.enabled
daemon_config.schedule_cron = config.cron_schedule
session.commit()
return {"message": "Configuration updated successfully"}
try:
daemon_config = session.query(DaemonConfig).first()
if not daemon_config:
daemon_config = DaemonConfig()
session.add(daemon_config)
daemon_config.enabled = config.enabled
daemon_config.schedule_cron = config.cron_schedule
session.commit()
return {"message": "Configuration updated successfully"}
except Exception as e:
session.rollback()
raise HTTPException(status_code=500, detail=f"Failed to update configuration: {str(e)}")
finally:
session.close()
@router.post("/sync/trigger")
async def trigger_sync():
"""Manually trigger a sync operation"""
# TODO: Implement sync triggering
return {"message": "Sync triggered successfully"}
try:
# Import here to avoid circular imports
from garminsync.garmin import GarminClient
from garminsync.database import sync_database, Activity
from datetime import datetime
import os
from pathlib import Path
# Create client and sync
client = GarminClient()
sync_database(client)
# Download missing activities
session = get_session()
try:
missing_activities = session.query(Activity).filter_by(downloaded=False).all()
downloaded_count = 0
data_dir = Path(os.getenv("DATA_DIR", "data"))
data_dir.mkdir(parents=True, exist_ok=True)
for activity in missing_activities:
try:
fit_data = client.download_activity_fit(activity.activity_id)
timestamp = activity.start_time.replace(":", "-").replace(" ", "_")
filename = f"activity_{activity.activity_id}_{timestamp}.fit"
filepath = data_dir / filename
with open(filepath, "wb") as f:
f.write(fit_data)
activity.filename = str(filepath)
activity.downloaded = True
activity.last_sync = datetime.now().isoformat()
downloaded_count += 1
session.commit()
except Exception as e:
print(f"Failed to download activity {activity.activity_id}: {e}")
session.rollback()
return {"message": f"Sync completed successfully. Downloaded {downloaded_count} activities."}
finally:
session.close()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Sync failed: {str(e)}")
@router.get("/activities/stats")
async def get_activity_stats():
"""Get activity statistics"""
from garminsync.database import get_offline_stats
return get_offline_stats()
@router.get("/logs")
async def get_logs(limit: int = 50):
"""Get recent sync logs"""
session = get_session()
try:
logs = session.query(SyncLog).order_by(SyncLog.timestamp.desc()).limit(limit).all()
log_data = []
for log in logs:
log_data.append({
"id": log.id,
"timestamp": log.timestamp,
"operation": log.operation,
"status": log.status,
"message": log.message,
"activities_processed": log.activities_processed,
"activities_downloaded": log.activities_downloaded
})
return {"logs": log_data}
finally:
session.close()
@router.delete("/logs")
async def clear_logs():
"""Clear all sync logs"""
session = get_session()
try:
session.query(SyncLog).delete()
session.commit()
return {"message": "Logs cleared successfully"}
except Exception as e:
session.rollback()
raise HTTPException(status_code=500, detail=f"Failed to clear logs: {str(e)}")
finally:
session.close()