changed to db for fit streams

This commit is contained in:
2026-01-14 05:39:16 -08:00
parent 362f4cb5aa
commit 45dbc32295
99 changed files with 2118 additions and 1684 deletions

View File

@@ -4,66 +4,7 @@
<!-- Job Status Banner -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Last Sync Status</h5>
<div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
<table class="table table-sm" id="metrics-status-table">
<thead>
<tr>
<th>Type</th>
<th>Source</th>
<th>Found</th>
<th>Synced</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4">No sync data available.</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-2 text-muted small">
<span id="db-stats"></span>
</div>
</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 Latest
Activities (30d)</button>
<button class="btn btn-outline-primary" type="button" id="sync-all-activities-btn">Sync All
Historical Activities</button>
<hr>
<h6 class="text-muted">Health Metrics</h6>
<button class="btn btn-info text-white" type="button" id="scan-health-btn">Scan Health Gaps
(30d)</button>
<button class="btn btn-outline-info" type="button" id="sync-pending-health-btn">Sync Pending
Health Metrics</button>
<hr>
<h6 class="text-muted">Fitbit Sync</h6>
<button class="btn btn-success" type="button" id="sync-fitbit-btn">Sync Latest Weight
(Fitbit) (30d)</button>
<button class="btn btn-outline-success" type="button" id="sync-all-fitbit-btn">Sync All
Historical Weight (Fitbit)</button>
<button class="btn btn-warning mt-2" type="button" id="compare-fitbit-btn">Compare Fitbit vs
Garmin</button>
<hr>
<h6 class="text-muted">Debug</h6>
<button class="btn btn-outline-secondary" type="button" id="test-queue-btn"
title="Simulate 5s Job"><i class="bi bi-bug"></i> Test Queue (5s)</button>
</div>
</div>
</div>
</div>
</div>
<!-- Scheduled Jobs Row -->
<div class="row mb-4">
@@ -72,6 +13,8 @@
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0"><i class="bi bi-calendar-check"></i> Scheduled Jobs</h5>
<div>
<button class="btn btn-sm btn-warning me-2" onclick="resetJobsToDefaults()"><i
class="bi bi-arrow-counterclockwise"></i> Reset Defaults</button>
<button class="btn btn-sm btn-success me-2" onclick="createModal.show()"><i
class="bi bi-plus-lg"></i> Add Job</button>
<button class="btn btn-sm btn-light" onclick="loadJobs()"><i class="bi bi-arrow-clockwise"></i>
@@ -373,15 +316,7 @@
loadDashboardData(); // Refresh data when job finishes
});
document.getElementById('sync-activities-btn').addEventListener('click', () => syncActivities(30));
document.getElementById('sync-all-activities-btn').addEventListener('click', () => syncActivities(3650));
document.getElementById('scan-health-btn').addEventListener('click', scanHealth);
document.getElementById('sync-pending-health-btn').addEventListener('click', syncPendingHealth);
document.getElementById('sync-fitbit-btn').addEventListener('click', () => syncFitbitWeight('30d'));
document.getElementById('sync-all-fitbit-btn').addEventListener('click', () => syncFitbitWeight('all'));
document.getElementById('compare-fitbit-btn').addEventListener('click', compareWeight);
const testBtn = document.getElementById('test-queue-btn');
if (testBtn) {
@@ -448,25 +383,7 @@
dbStats.innerHTML = `<strong>DB Total Activities:</strong> ${data.total_activities} | <strong>Downloaded:</strong> ${data.downloaded_activities}`;
}
const metricsBody = document.querySelector('#metrics-status-table tbody');
if (metricsBody) {
metricsBody.innerHTML = '';
if (data.last_sync_stats && data.last_sync_stats.length > 0) {
data.last_sync_stats.forEach(stat => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${stat.type}</td>
<td>${stat.source}</td>
<td>${stat.total}</td>
<td class="${stat.synced > 0 ? 'text-success' : ''}">${stat.synced}</td>
`;
metricsBody.appendChild(row);
});
} else {
metricsBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No detailed sync stats available. Run a sync to populate.</td></tr>';
}
}
const logsBody = document.querySelector('#sync-logs-table tbody');
if (logsBody) {
@@ -671,140 +588,23 @@
});
}
async function syncActivities(daysBack = 30) {
const typeLabel = daysBack > 1000 ? 'Historical' : 'Latest';
showToast(`${typeLabel} Syncing...`, `Starting ${typeLabel} Activity sync (Scan + Download)...`, 'info');
async function resetJobsToDefaults() {
if (!confirm("Are you sure? This will delete all current jobs and restore system defaults.")) return;
try {
// Step 1: Scan
const scanRes = await fetch(`/api/activities/sync/scan?days_back=${daysBack}`, { method: 'POST' });
if (!scanRes.ok) {
const err = await scanRes.json();
throw new Error(err.detail || "Scan failed");
}
const scanData = await scanRes.json();
showToast('Scan Started', `Job ID: ${scanData.job_id} (Scanning)`, 'success');
// Helper to poll
const poll = async (jid) => {
return new Promise((resolve, reject) => {
const check = async () => {
try {
const r = await fetch(`/api/jobs/${jid}`);
if (r.status === 404) return resolve('done');
const d = await r.json();
if (d.status === 'completed') resolve('done');
else if (d.status === 'failed') reject(d.message);
else setTimeout(check, 2000);
} catch (e) { reject(e); }
};
check();
});
};
await poll(scanData.job_id);
// Step 2: Sync Pending
const syncRes = await fetch('/api/activities/sync/pending', { method: 'POST' });
if (!syncRes.ok) {
const err = await syncRes.json();
throw new Error(err.detail || "Download failed");
}
const syncData = await syncRes.json();
showToast('Download Started', `Job ID: ${syncData.job_id} (Downloading)`, 'success');
} catch (error) {
console.error('Error syncing activities:', error);
showToast('Sync Error', `Activity sync failed: ${error.message}`, 'error');
}
}
async function scanHealth() {
showToast('Scan Started', 'Scanning for health data gaps...', 'info');
try {
const response = await fetch('/api/metrics/sync/scan', { method: 'POST' });
const data = await response.json();
if (response.ok) {
showToast('Scan Started', `Job ID: ${data.job_id}`, 'success');
const res = await fetch('/api/scheduling/jobs/reset-defaults', { method: 'POST' });
const data = await res.json();
if (res.ok) {
showToast('Reset Complete', data.message, 'success');
loadJobs();
} else {
showToast('Error', 'Failed to start scan', 'error');
showToast('Error', data.detail || "Failed to reset", 'error');
}
} catch (error) {
console.error('Error scanning health:', error);
showToast('Error', error.message, 'error');
}
} catch (e) { showToast('Error', e.message, 'error'); }
}
async function syncPendingHealth() {
showToast('Sync Started', 'Syncing pending health metrics...', 'info');
try {
const response = await fetch('/api/metrics/sync/pending', { method: 'POST' });
const data = await response.json();
if (response.ok) {
showToast('Sync Started', `Job ID: ${data.job_id}`, 'success');
} else {
showToast('Error', 'Failed to start sync', 'error');
}
} catch (error) {
console.error('Error syncing health:', error);
showToast('Error', error.message, 'error');
}
}
async function syncFitbitWeight(scope) {
const typeLabel = scope === 'all' ? 'All History' : 'Latest (30d)';
showToast(`Fitbit Syncing...`, `Fitbit Weight sync initiated (${typeLabel}).`, 'info');
try {
const response = await fetch('/api/sync/fitbit/weight', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ scope: scope })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || errorData.message || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
showToast('Fitbit Sync Complete', data.message, 'success');
loadDashboardData();
} catch (error) {
console.error('Error syncing Fitbit weight:', error);
showToast('Fitbit Sync Error', `Sync failed: ${error.message}`, 'error');
}
}
async function compareWeight() {
showToast('Comparing...', 'Comparing Fitbit and Garmin weight records...', 'info');
try {
const response = await fetch('/api/sync/compare-weight', { method: 'POST' });
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
showToast('Comparison Results',
`Fitbit Total: ${data.fitbit_total}\n` +
`Garmin Total: ${data.garmin_total}\n` +
`Missing in Garmin: ${data.missing_in_garmin}\n` +
`${data.message}`,
data.missing_in_garmin > 0 ? 'warning' : 'success'
);
console.log("Comparison Data:", data);
} catch (error) {
console.error('Error comparing weight:', error);
showToast('Comparison Error', `Comparison failed: ${error.message}`, 'error');
}
}
// --- Scheduler Functions ---