210 lines
8.9 KiB
HTML
210 lines
8.9 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Fitbit-Garmin Sync Dashboard</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
</head>
|
|
<body>
|
|
<div class="container mt-5">
|
|
<h1>Fitbit-Garmin Sync Dashboard</h1>
|
|
|
|
<!-- Toast container for notifications -->
|
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
<div id="appToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
|
<div class="toast-header">
|
|
<strong class="me-auto" id="toast-title">Notification</strong>
|
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
<div class="toast-body" id="toast-body">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Activities</h5>
|
|
<p class="card-text">Total: <span id="total-activities">0</span></p>
|
|
<p class="card-text">Downloaded: <span id="downloaded-activities">0</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Sync Controls</h5>
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-primary" type="button" id="sync-activities-btn">Sync Activities</button>
|
|
<button class="btn btn-info" type="button" id="sync-metrics-btn">Sync Health Metrics</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<h3>Recent Sync Logs</h3>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped" id="sync-logs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Operation</th>
|
|
<th>Status</th>
|
|
<th>Start Time</th>
|
|
<th>End Time</th>
|
|
<th>Processed</th>
|
|
<th>Failed</th>
|
|
<th>Message</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td colspan="7">Loading logs...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-5">
|
|
<div class="col-md-12">
|
|
<h3>Actions</h5>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<a href="/setup" class="btn btn-primary me-md-2">Setup & Configuration</a>
|
|
<a href="/docs" class="btn btn-outline-secondary" target="_blank">API Documentation</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
let toastInstance = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const toastEl = document.getElementById('appToast');
|
|
toastInstance = new bootstrap.Toast(toastEl);
|
|
|
|
loadDashboardData();
|
|
|
|
document.getElementById('sync-activities-btn').addEventListener('click', syncActivities);
|
|
document.getElementById('sync-metrics-btn').addEventListener('click', syncHealthMetrics);
|
|
});
|
|
|
|
function showToast(title, body, level = 'info') {
|
|
const toastTitle = document.getElementById('toast-title');
|
|
const toastBody = document.getElementById('toast-body');
|
|
const toastHeader = document.querySelector('.toast-header');
|
|
|
|
toastTitle.textContent = title;
|
|
toastBody.textContent = body;
|
|
|
|
// Reset header color
|
|
toastHeader.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info', 'text-white');
|
|
|
|
if (level === 'success') {
|
|
toastHeader.classList.add('bg-success', 'text-white');
|
|
} else if (level === 'error') {
|
|
toastHeader.classList.add('bg-danger', 'text-white');
|
|
} else if (level === 'warning') {
|
|
toastHeader.classList.add('bg-warning');
|
|
} else {
|
|
toastHeader.classList.add('bg-info', 'text-white');
|
|
}
|
|
|
|
toastInstance.show();
|
|
}
|
|
|
|
async function loadDashboardData() {
|
|
try {
|
|
const response = await fetch('/api/status');
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const data = await response.json();
|
|
|
|
document.getElementById('total-activities').textContent = data.total_activities;
|
|
document.getElementById('downloaded-activities').textContent = data.downloaded_activities;
|
|
|
|
const logsBody = document.querySelector('#sync-logs-table tbody');
|
|
logsBody.innerHTML = '';
|
|
|
|
if (data.recent_logs.length === 0) {
|
|
logsBody.innerHTML = '<tr><td colspan="7">No recent sync logs.</td></tr>';
|
|
return;
|
|
}
|
|
|
|
data.recent_logs.forEach(log => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${log.operation}</td>
|
|
<td><span class="badge bg-${log.status === 'completed' ? 'success' : 'warning'}">${log.status}</span></td>
|
|
<td>${new Date(log.start_time).toLocaleString()}</td>
|
|
<td>${log.end_time ? new Date(log.end_time).toLocaleString() : 'N/A'}</td>
|
|
<td>${log.records_processed}</td>
|
|
<td>${log.records_failed}</td>
|
|
<td>${log.message || ''}</td>
|
|
`;
|
|
logsBody.appendChild(row);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading dashboard data:', error);
|
|
showToast('Error', 'Could not load dashboard data.', 'error');
|
|
}
|
|
}
|
|
|
|
async function syncActivities() {
|
|
showToast('Syncing...', 'Activity sync has been initiated.', 'info');
|
|
try {
|
|
const response = await fetch('/api/sync/activities', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ days_back: 30 })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
showToast('Sync Complete', data.message, 'success');
|
|
loadDashboardData(); // Refresh data after sync
|
|
} catch (error) {
|
|
console.error('Error syncing activities:', error);
|
|
showToast('Sync Error', `Activity sync failed: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async function syncHealthMetrics() {
|
|
showToast('Syncing...', 'Health metrics sync has been initiated.', 'info');
|
|
try {
|
|
const response = await fetch('/api/sync/metrics', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
showToast('Sync Complete', data.message, 'success');
|
|
loadDashboardData(); // Refresh data after sync
|
|
} catch (error) {
|
|
console.error('Error syncing health metrics:', error);
|
|
showToast('Sync Error', `Health metrics sync failed: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|