mirror of
https://github.com/sstent/GarminSync.git
synced 2026-01-25 16:42:20 +00:00
python v2 - added feartures 1 and 2 - daemon hsa errors
This commit is contained in:
1
garminsync/web/__init__.py
Normal file
1
garminsync/web/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Empty file to mark this directory as a Python package
|
||||
24
garminsync/web/app.py
Normal file
24
garminsync/web/app.py
Normal 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
56
garminsync/web/routes.py
Normal 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"}
|
||||
47
garminsync/web/static/app.js
Normal file
47
garminsync/web/static/app.js
Normal 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);
|
||||
32
garminsync/web/static/style.css
Normal file
32
garminsync/web/static/style.css
Normal 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;
|
||||
}
|
||||
37
garminsync/web/templates/base.html
Normal file
37
garminsync/web/templates/base.html
Normal 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>
|
||||
79
garminsync/web/templates/dashboard.html
Normal file
79
garminsync/web/templates/dashboard.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user