python v2 - added feartures 1 and 2 - daemon hsa errors

This commit is contained in:
2025-08-08 13:33:50 -07:00
parent a1bc9b4410
commit b718a908ce
46 changed files with 719642 additions and 12 deletions

View File

@@ -0,0 +1 @@
# Empty file to mark this directory as a Python package

24
garminsync/web/app.py Normal file
View File

@@ -0,0 +1,24 @@
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
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")
# 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()
return templates.TemplateResponse("dashboard.html", {
"request": request,
"stats": stats
})

56
garminsync/web/routes.py Normal file
View File

@@ -0,0 +1,56 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from garminsync.database import get_session, DaemonConfig, SyncLog
router = APIRouter(prefix="/api")
class ScheduleConfig(BaseModel):
enabled: bool
cron_schedule: str
@router.get("/status")
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": {
"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": [
{
"timestamp": log.timestamp,
"operation": log.operation,
"status": log.status,
"message": log.message
} for log in logs
]
}
@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"}
@router.post("/sync/trigger")
async def trigger_sync():
"""Manually trigger a sync operation"""
# TODO: Implement sync triggering
return {"message": "Sync triggered successfully"}

View File

@@ -0,0 +1,47 @@
// Auto-refresh dashboard data
setInterval(updateStatus, 30000); // Every 30 seconds
async function updateStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
// Update daemon status
document.getElementById('daemon-status').innerHTML = `
<p>Status: <span class="badge ${data.daemon.running ? 'badge-success' : 'badge-danger'}">
${data.daemon.running ? 'Running' : 'Stopped'}
</span></p>
<p>Next Run: ${data.daemon.next_run || 'Not scheduled'}</p>
<p>Schedule: ${data.daemon.schedule || 'Not configured'}</p>
`;
// Update recent logs
const logsHtml = data.recent_logs.map(log => `
<div class="log-entry">
<small class="text-muted">${log.timestamp}</small>
<span class="badge badge-${log.status === 'success' ? 'success' : 'danger'}">
${log.status}
</span>
${log.operation}: ${log.message || ''}
</div>
`).join('');
document.getElementById('recent-logs').innerHTML = logsHtml;
} catch (error) {
console.error('Failed to update status:', error);
}
}
async function triggerSync() {
try {
await fetch('/api/sync/trigger', { method: 'POST' });
alert('Sync triggered successfully');
updateStatus();
} catch (error) {
alert('Failed to trigger sync');
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', updateStatus);

View File

@@ -0,0 +1,32 @@
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
}
.card {
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.card-header {
font-weight: bold;
background-color: #f1f1f1;
}
.btn {
margin-right: 5px;
}
.log-entry {
margin-bottom: 10px;
padding: 5px;
border-left: 3px solid #ddd;
}
.log-entry .badge-success {
background-color: #28a745;
}
.log-entry .badge-error {
background-color: #dc3545;
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GarminSync Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">GarminSync</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="/">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/config">Configuration</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<h1>GarminSync Dashboard</h1>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">Statistics</div>
<div class="card-body">
<p>Total Activities: {{ stats.total }}</p>
<p>Downloaded: {{ stats.downloaded }}</p>
<p>Missing: {{ stats.missing }}</p>
<p>Last Sync: {{ stats.last_sync }}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">Daemon Status</div>
<div class="card-body" id="daemon-status">
<!-- Populated by JavaScript -->
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">Quick Actions</div>
<div class="card-body">
<button class="btn btn-primary" onclick="triggerSync()">
Sync Now
</button>
<button class="btn btn-secondary" onclick="toggleDaemon()">
Toggle Daemon
</button>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">Recent Activity</div>
<div class="card-body" id="recent-logs">
<!-- Populated by JavaScript -->
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">Schedule Configuration</div>
<div class="card-body">
<form id="schedule-form">
<div class="form-group">
<label for="schedule-enabled">Enable Scheduled Sync</label>
<input type="checkbox" id="schedule-enabled">
</div>
<div class="form-group">
<label for="cron-schedule">Cron Schedule</label>
<input type="text" class="form-control" id="cron-schedule"
placeholder="0 */6 * * *" title="Every 6 hours">
</div>
<button type="submit" class="btn btn-primary">
Update Schedule
</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}