1025 lines
49 KiB
HTML
1025 lines
49 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-info" id="load-from-consul-btn">Load Config from Consul</button>
|
|
</div>
|
|
|
|
<!-- Current Status Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Current Status</h5>
|
|
<div id="status-info">
|
|
<p>Loading status...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Garmin Connect Credentials</h5>
|
|
<form id="garmin-credentials-form">
|
|
<div class="mb-3">
|
|
<label for="garmin-username" class="form-label">Username</label>
|
|
<input type="text" class="form-control" id="garmin-username" name="username" required
|
|
autocomplete="username">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="garmin-password" class="form-label">Password</label>
|
|
<input type="password" class="form-control" id="garmin-password" name="password" required
|
|
autocomplete="current-password">
|
|
</div>
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="garmin-china" name="is_china">
|
|
<label class="form-check-label" for="garmin-china">Use China domain (garmin.cn)</label>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" id="test-garmin-btn">Test Garmin
|
|
Credentials</button>
|
|
<button type="submit" class="btn btn-primary" id="save-garmin-btn" disabled>Save Garmin
|
|
Credentials</button>
|
|
<button type="button" class="btn btn-info" id="test-garmin-token-btn">Test Current Garmin
|
|
Token</button>
|
|
<button type="button" class="btn btn-danger ms-2" id="clear-garmin-btn">Clear
|
|
Credentials</button>
|
|
</form>
|
|
|
|
<!-- Garmin Authentication Status -->
|
|
<div id="garmin-auth-status-text" class="mt-3">
|
|
<p>Current auth state: <span class="badge bg-secondary">Not Tested</span></p>
|
|
</div>
|
|
|
|
<div id="garmin-token-test-result" class="mt-3"></div>
|
|
|
|
<!-- Garmin Authentication Status -->
|
|
<div id="garmin-auth-status" class="mt-3">
|
|
<p>Loading Garmin authentication status...</p>
|
|
</div>
|
|
|
|
<!-- MFA Section -->
|
|
<div id="garmin-mfa-section" class="mt-3" style="display: none;">
|
|
<h6>Multi-Factor Authentication (MFA)</h6>
|
|
<div class="mb-3">
|
|
<label for="mfa-code" class="form-label">Enter Verification Code</label>
|
|
<input type="text" class="form-control" id="mfa-code"
|
|
placeholder="Enter code from your authenticator app or SMS">
|
|
</div>
|
|
<button type="button" class="btn btn-primary" id="submit-mfa-btn">Submit Verification
|
|
Code</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Fitbit API Credentials</h5>
|
|
<form id="fitbit-credentials-form">
|
|
<div class="mb-3">
|
|
<label for="fitbit-client-id" class="form-label">Client ID</label>
|
|
<input type="text" class="form-control" id="fitbit-client-id" name="client_id" required
|
|
autocomplete="username">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="fitbit-client-secret" class="form-label">Client Secret</label>
|
|
<input type="password" class="form-control" id="fitbit-client-secret" name="client_secret"
|
|
required autocomplete="new-password">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="fitbit-redirect-uri" class="form-label">Redirect URI</label>
|
|
<input type="text" class="form-control" id="fitbit-redirect-uri" name="redirect_uri"
|
|
value="http://localhost:8000/fitbit_callback"
|
|
placeholder="http://localhost:8000/fitbit_callback">
|
|
<div class="form-text">Must match exactly what you entered in the Fitbit Developer
|
|
Dashboard. Leave blank if only one is registered.</div>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" id="test-fitbit-btn">Test Fitbit
|
|
Credentials</button>
|
|
<button type="submit" class="btn btn-primary" id="save-fitbit-btn" disabled>Save Fitbit
|
|
Credentials</button>
|
|
<button type="button" class="btn btn-info" id="test-fitbit-token-btn">Test Current Fitbit
|
|
Token</button>
|
|
</form>
|
|
|
|
<div id="fitbit-token-test-result" class="mt-3"></div>
|
|
|
|
<div class="mt-3">
|
|
<div id="auth-url-container" style="display: none;">
|
|
<p>After saving credentials, click the link below to authorize:</p>
|
|
<a id="auth-link" class="btn btn-secondary mb-3" href="#" target="_blank">Authorize with
|
|
Fitbit</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OAuth Flow Section (Moved here) -->
|
|
<div id="fitbit-oauth-flow-section"
|
|
style="display: none; border-top: 1px solid #eee; padding-top: 15px;">
|
|
<h5>Complete Fitbit OAuth Flow</h5>
|
|
<form id="fitbit-callback-form">
|
|
<div class="mb-3">
|
|
<label for="callback-url" class="form-label">Paste full callback URL from
|
|
browser</label>
|
|
<input type="url" class="form-control" id="callback-url" name="callback_url" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-success">Complete OAuth Flow</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Fitbit Authentication Status -->
|
|
<div id="fitbit-auth-status" class="mt-3">
|
|
<p>Loading Fitbit authentication status...</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
loadStatusInfo();
|
|
|
|
document.getElementById('load-from-consul-btn').addEventListener('click', loadFromConsul);
|
|
document.getElementById('test-garmin-btn').addEventListener('click', testGarminCredentials);
|
|
document.getElementById('test-garmin-token-btn').addEventListener('click', testGarminToken);
|
|
document.getElementById('clear-garmin-btn').addEventListener('click', clearGarminCredentials);
|
|
document.getElementById('garmin-credentials-form').addEventListener('submit', saveGarminCredentials);
|
|
document.getElementById('test-fitbit-btn').addEventListener('click', testFitbitCredentials);
|
|
document.getElementById('fitbit-credentials-form').addEventListener('submit', saveFitbitCredentials);
|
|
document.getElementById('fitbit-callback-form').addEventListener('submit', completeFitbitAuth);
|
|
document.getElementById('test-fitbit-token-btn').addEventListener('click', testFitbitToken);
|
|
document.getElementById('submit-mfa-btn').addEventListener('click', submitMFA);
|
|
});
|
|
|
|
async function testFitbitToken() {
|
|
const resultDiv = document.getElementById('fitbit-token-test-result');
|
|
resultDiv.innerHTML = '<p>Testing token...</p>';
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit/test-token', { method: 'POST' });
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success"><strong>Success!</strong> ${data.message}</div>`;
|
|
} else {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">${data.message || 'Failed to test token'}</div>`;
|
|
}
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function testGarminToken() {
|
|
const resultDiv = document.getElementById('garmin-token-test-result');
|
|
resultDiv.innerHTML = '<p>Testing token...</p>';
|
|
try {
|
|
const response = await fetch('/api/setup/garmin/test-token', { method: 'POST' });
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success"><pre>${JSON.stringify(data, null, 2)}</pre></div>`;
|
|
} else {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">${data.detail || data.message || 'Failed to test token'}</div>`;
|
|
}
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function loadFromConsul() {
|
|
console.log('loadFromConsul function called');
|
|
const btn = document.getElementById('load-from-consul-btn');
|
|
const originalText = btn.innerText;
|
|
btn.innerText = 'Loading...';
|
|
btn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/load-consul-config', { method: 'POST' });
|
|
const data = await response.json();
|
|
if (!response.ok) throw new Error(data.detail || 'Failed to load config from Consul');
|
|
|
|
if (data.garmin) {
|
|
if (data.garmin.username) document.getElementById('garmin-username').value = data.garmin.username;
|
|
if (data.garmin.password) document.getElementById('garmin-password').value = data.garmin.password;
|
|
if (data.garmin.is_china !== undefined) document.getElementById('garmin-china').checked = data.garmin.is_china;
|
|
}
|
|
|
|
if (data.fitbit) {
|
|
if (data.fitbit.client_id) document.getElementById('fitbit-client-id').value = data.fitbit.client_id;
|
|
if (data.fitbit.client_secret) document.getElementById('fitbit-client-secret').value = data.fitbit.client_secret;
|
|
if (data.fitbit.redirect_uri) document.getElementById('fitbit-redirect-uri').value = data.fitbit.redirect_uri;
|
|
}
|
|
|
|
alert(data.message || 'Configuration loaded. Please review and save your credentials.');
|
|
} catch (error) {
|
|
console.error('Error loading config from Consul:', error);
|
|
alert('Error loading config: ' + error.message);
|
|
} finally {
|
|
btn.innerText = originalText;
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function loadStatusInfo() {
|
|
try {
|
|
const statusResponse = await fetch('/api/status');
|
|
const statusData = await statusResponse.json();
|
|
|
|
document.getElementById('status-info').innerHTML = `
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<p><strong>Total Weight Records:</strong> ${statusData.total_weight_records}</p>
|
|
<p><strong>Synced to Garmin:</strong> ${statusData.synced_weight_records}</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<p><strong>Unsynced Records:</strong> ${statusData.unsynced_weight_records}</p>
|
|
<p><strong>Total Activities:</strong> ${statusData.total_activities}</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<p><strong>Downloaded Activities:</strong> ${statusData.downloaded_activities}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const authStatusResponse = await fetch('/api/setup/auth-status');
|
|
if (authStatusResponse.ok) {
|
|
const authData = await authStatusResponse.json();
|
|
|
|
// Update Garmin auth status
|
|
const garminStatusContainer = document.getElementById('garmin-auth-status');
|
|
if (authData.garmin) {
|
|
let garminStatusHtml = `<h6>Garmin Authentication Status</h6>`;
|
|
if (authData.garmin.token_stored) {
|
|
garminStatusHtml += `<p><strong>Tokens Stored:</strong> Yes</p>`;
|
|
garminStatusHtml += `<p><strong>Authenticated:</strong> ${authData.garmin.authenticated ? 'Yes' : 'No'}</p>`;
|
|
if (authData.garmin.last_used) garminStatusHtml += `<p><strong>Last Used:</strong> ${new Date(authData.garmin.last_used).toLocaleString()}</p>`;
|
|
} else {
|
|
garminStatusHtml += `<p><strong>Tokens Stored:</strong> No</p>`;
|
|
}
|
|
garminStatusContainer.innerHTML = `<div class="alert ${authData.garmin.authenticated ? 'alert-success' : 'alert-warning'}">${garminStatusHtml}</div>`;
|
|
}
|
|
|
|
// Update Fitbit auth status
|
|
const fitbitStatusContainer = document.getElementById('fitbit-auth-status');
|
|
if (authData.fitbit) {
|
|
fitbitStatusContainer.innerHTML = `
|
|
<div class="alert ${authData.fitbit.authenticated ? 'alert-success' : 'alert-warning'}">
|
|
<h6>Fitbit Authentication Status</h6>
|
|
<p><strong>Authenticated:</strong> ${authData.fitbit.authenticated ? 'Yes' : 'No'}</p>
|
|
${authData.fitbit.last_login ? `<p><strong>Last Login:</strong> ${new Date(authData.fitbit.last_login).toLocaleString()}</p>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading status info:', error);
|
|
}
|
|
}
|
|
|
|
async function testGarminCredentials() {
|
|
const form = document.getElementById('garmin-credentials-form');
|
|
const formData = new FormData(form);
|
|
const credentials = {
|
|
username: formData.get('username'),
|
|
password: formData.get('password'),
|
|
is_china: formData.get('is_china') === 'on' || formData.get('is_china') === 'true'
|
|
};
|
|
const statusText = document.getElementById('garmin-auth-status-text');
|
|
const saveBtn = document.getElementById('save-garmin-btn');
|
|
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-info">Testing...</span></p>`;
|
|
saveBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials)
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'mfa_required') {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-warning">MFA Required</span></p>`;
|
|
document.getElementById('garmin-mfa-section').style.display = 'block';
|
|
window.garmin_mfa_session_id = data.session_id;
|
|
alert('MFA required.');
|
|
} else if (response.ok) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-success">Success</span></p>`;
|
|
saveBtn.disabled = false;
|
|
alert('Garmin authentication successful.');
|
|
} else {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">Failed</span></p>`;
|
|
alert(data.message || 'Garmin authentication failed.');
|
|
}
|
|
} catch (error) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">Error</span></p>`;
|
|
alert('Error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function saveGarminCredentials(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
const credentials = {
|
|
username: formData.get('username'), password: formData.get('password'),
|
|
is_china: formData.get('is_china') === 'on' || formData.get('is_china') === 'true'
|
|
};
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) });
|
|
const data = await response.json();
|
|
if (response.ok) { alert('Saved successfully'); loadStatusInfo(); }
|
|
else alert(data.message || 'Error saving');
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
|
|
async function clearGarminCredentials() {
|
|
if (!confirm('Clear stored Garmin credentials?')) return;
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', { method: 'DELETE' });
|
|
if (response.ok) {
|
|
alert('Cleared.');
|
|
loadStatusInfo();
|
|
document.getElementById('garmin-auth-status-text').innerHTML = `<p>Current auth state: <span class="badge bg-secondary">Not Tested</span></p>`;
|
|
} else alert('Error clearing.');
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
|
|
async function testFitbitCredentials() {
|
|
const formData = new FormData(document.getElementById('fitbit-credentials-form'));
|
|
const credentials = { client_id: formData.get('client_id'), client_secret: formData.get('client_secret') };
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) });
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
document.getElementById('auth-link').href = data.auth_url;
|
|
document.getElementById('auth-url-container').style.display = 'block';
|
|
document.getElementById('fitbit-oauth-flow-section').style.display = 'block';
|
|
document.getElementById('save-fitbit-btn').disabled = false;
|
|
alert('Valid credentials.');
|
|
} else alert(data.message || 'Test failed.');
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
|
|
async function saveFitbitCredentials(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
const credentials = { client_id: formData.get('client_id'), client_secret: formData.get('client_secret') };
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) });
|
|
if (response.ok) { alert('Saved.'); loadStatusInfo(); }
|
|
else alert('Error saving.');
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
|
|
async function completeFitbitAuth(event) {
|
|
event.preventDefault();
|
|
const callbackUrl = new FormData(event.target).get('callback_url');
|
|
let code = callbackUrl;
|
|
try {
|
|
if (callbackUrl.includes('?')) {
|
|
const params = new URLSearchParams(new URL(callbackUrl).search);
|
|
if (params.has('code')) code = params.get('code');
|
|
}
|
|
} catch (e) { }
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit/callback', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code: code })
|
|
});
|
|
const data = await response.json();
|
|
alert(data.message || 'OAuth completed.');
|
|
loadStatusInfo();
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
|
|
async function submitMFA() {
|
|
const mfaCode = document.getElementById('mfa-code').value.trim();
|
|
if (!mfaCode) return alert('Enter code');
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin/mfa', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ verification_code: mfaCode, session_id: window.garmin_mfa_session_id })
|
|
});
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
alert('MFA Success');
|
|
document.getElementById('garmin-mfa-section').style.display = 'none';
|
|
document.getElementById('mfa-code').value = '';
|
|
document.getElementById('save-garmin-btn').disabled = false;
|
|
loadStatusInfo();
|
|
} else alert('MFA Failed: ' + data.message);
|
|
} catch (error) { alert('Error: ' + error.message); }
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-info" id="load-from-consul-btn">Load Config from Consul</button>
|
|
</div>
|
|
|
|
<!-- Current Status Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Current Status</h5>
|
|
<div id="status-info">
|
|
<p>Loading status...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Garmin Connect Credentials</h5>
|
|
<form id="garmin-credentials-form">
|
|
<div class="mb-3">
|
|
<label for="garmin-username" class="form-label">Username</label>
|
|
<input type="text" class="form-control" id="garmin-username" name="username" required
|
|
autocomplete="username">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="garmin-password" class="form-label">Password</label>
|
|
<input type="password" class="form-control" id="garmin-password" name="password" required
|
|
autocomplete="current-password">
|
|
</div>
|
|
<div class="mb-3 form-check">
|
|
<input type="checkbox" class="form-check-input" id="garmin-china" name="is_china">
|
|
<label class="form-check-label" for="garmin-china">Use China domain (garmin.cn)</label>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" id="test-garmin-btn">Test Garmin
|
|
Credentials</button>
|
|
<button type="submit" class="btn btn-primary" id="save-garmin-btn" disabled>Save Garmin
|
|
Credentials</button>
|
|
<button type="button" class="btn btn-info" id="test-garmin-token-btn">Test Current Garmin
|
|
Token</button>
|
|
<button type="button" class="btn btn-danger ms-2" id="clear-garmin-btn">Clear
|
|
Credentials</button>
|
|
</form>
|
|
|
|
<!-- Garmin Authentication Status -->
|
|
<div id="garmin-auth-status-text" class="mt-3">
|
|
<p>Current auth state: <span class="badge bg-secondary">Not Tested</span></p>
|
|
</div>
|
|
|
|
<div id="garmin-token-test-result" class="mt-3"></div>
|
|
|
|
<!-- Garmin Authentication Status -->
|
|
<div id="garmin-auth-status" class="mt-3">
|
|
<p>Loading Garmin authentication status...</p>
|
|
</div>
|
|
|
|
<!-- MFA Section -->
|
|
<div id="garmin-mfa-section" class="mt-3" style="display: none;">
|
|
<h6>Multi-Factor Authentication (MFA)</h6>
|
|
<div class="mb-3">
|
|
<label for="mfa-code" class="form-label">Enter Verification Code</label>
|
|
<input type="text" class="form-control" id="mfa-code"
|
|
placeholder="Enter code from your authenticator app or SMS">
|
|
</div>
|
|
<button type="button" class="btn btn-primary" id="submit-mfa-btn">Submit Verification
|
|
Code</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Fitbit API Credentials</h5>
|
|
<form id="fitbit-credentials-form">
|
|
<div class="mb-3">
|
|
<label for="fitbit-client-id" class="form-label">Client ID</label>
|
|
<input type="text" class="form-control" id="fitbit-client-id" name="client_id" required
|
|
autocomplete="username">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="fitbit-client-secret" class="form-label">Client Secret</label>
|
|
<input type="password" class="form-control" id="fitbit-client-secret" name="client_secret"
|
|
required autocomplete="new-password">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="fitbit-redirect-uri" class="form-label">Redirect URI</label>
|
|
<input type="text" class="form-control" id="fitbit-redirect-uri" name="redirect_uri"
|
|
value="http://localhost:8000/fitbit_callback"
|
|
placeholder="http://localhost:8000/fitbit_callback">
|
|
<div class="form-text">Must match exactly what you entered in the Fitbit Developer
|
|
Dashboard. Leave blank if only one is registered.</div>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" id="test-fitbit-btn">Test Fitbit
|
|
Credentials</button>
|
|
<button type="submit" class="btn btn-primary" id="save-fitbit-btn" disabled>Save Fitbit
|
|
Credentials</button>
|
|
<button type="button" class="btn btn-info" id="test-fitbit-token-btn">Test Current Fitbit
|
|
Token</button>
|
|
</form>
|
|
|
|
<div id="fitbit-token-test-result" class="mt-3"></div>
|
|
|
|
<div class="mt-3">
|
|
<div id="auth-url-container" style="display: none;">
|
|
<p>After saving credentials, click the link below to authorize:</p>
|
|
<a id="auth-link" class="btn btn-secondary mb-3" href="#" target="_blank">Authorize with
|
|
Fitbit</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OAuth Flow Section (Moved here) -->
|
|
<div id="fitbit-oauth-flow-section"
|
|
style="display: none; border-top: 1px solid #eee; padding-top: 15px;">
|
|
<h5>Complete Fitbit OAuth Flow</h5>
|
|
<form id="fitbit-callback-form">
|
|
<div class="mb-3">
|
|
<label for="callback-url" class="form-label">Paste full callback URL from
|
|
browser</label>
|
|
<input type="url" class="form-control" id="callback-url" name="callback_url" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-success">Complete OAuth Flow</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Fitbit Authentication Status -->
|
|
<div id="fitbit-auth-status" class="mt-3">
|
|
<p>Loading Fitbit authentication status...</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section removed here -->
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Load initial status information
|
|
loadStatusInfo();
|
|
|
|
// Setup form event listeners
|
|
document.getElementById('load-from-consul-btn').addEventListener('click', loadFromConsul);
|
|
document.getElementById('test-garmin-btn').addEventListener('click', testGarminCredentials);
|
|
document.getElementById('test-garmin-token-btn').addEventListener('click', testGarminToken);
|
|
document.getElementById('clear-garmin-btn').addEventListener('click', clearGarminCredentials);
|
|
document.getElementById('garmin-credentials-form').addEventListener('submit', saveGarminCredentials);
|
|
document.getElementById('test-fitbit-btn').addEventListener('click', testFitbitCredentials);
|
|
document.getElementById('fitbit-credentials-form').addEventListener('submit', saveFitbitCredentials);
|
|
document.getElementById('fitbit-credentials-form').addEventListener('submit', saveFitbitCredentials);
|
|
document.getElementById('fitbit-callback-form').addEventListener('submit', completeFitbitAuth);
|
|
document.getElementById('test-fitbit-token-btn').addEventListener('click', testFitbitToken);
|
|
});
|
|
|
|
async function testFitbitToken() {
|
|
const resultDiv = document.getElementById('fitbit-token-test-result');
|
|
resultDiv.innerHTML = '<p>Testing token...</p>';
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit/test-token', { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success"><strong>Success!</strong> ${data.message}</div>`;
|
|
} else {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">${data.message || 'Failed to test token'}</div>`;
|
|
}
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function testGarminToken() {
|
|
const resultDiv = document.getElementById('garmin-token-test-result');
|
|
resultDiv.innerHTML = '<p>Testing token...</p>';
|
|
try {
|
|
const response = await fetch('/api/setup/garmin/test-token', { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success"><pre>${JSON.stringify(data, null, 2)}</pre></div>`;
|
|
} else {
|
|
// Show detail (FastAPI standard) or message (our custom response)
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">${data.detail || data.message || 'Failed to test token'}</div>`;
|
|
}
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function loadFromConsul() {
|
|
// alert('Attempting to load config from Consul...');
|
|
console.log('loadFromConsul function called');
|
|
const btn = document.getElementById('load-from-consul-btn');
|
|
const originalText = btn.innerText;
|
|
btn.innerText = 'Loading...';
|
|
btn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/load-consul-config', { method: 'POST' });
|
|
console.log('Response received:', response);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || 'Failed to load config from Consul');
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log('Config data:', data);
|
|
|
|
// Populate Garmin Form
|
|
if (data.garmin) {
|
|
if (data.garmin.username) document.getElementById('garmin-username').value = data.garmin.username;
|
|
if (data.garmin.password) document.getElementById('garmin-password').value = data.garmin.password;
|
|
if (data.garmin.is_china !== undefined) document.getElementById('garmin-china').checked = data.garmin.is_china;
|
|
}
|
|
|
|
// Populate Fitbit Form
|
|
if (data.fitbit) {
|
|
if (data.fitbit.client_id) document.getElementById('fitbit-client-id').value = data.fitbit.client_id;
|
|
if (data.fitbit.client_secret) document.getElementById('fitbit-client-secret').value = data.fitbit.client_secret;
|
|
if (data.fitbit.redirect_uri) document.getElementById('fitbit-redirect-uri').value = data.fitbit.redirect_uri;
|
|
}
|
|
|
|
alert(data.message || 'Configuration loaded. Please review and save your credentials.');
|
|
|
|
} catch (error) {
|
|
console.error('Error loading config from Consul:', error);
|
|
alert('Error loading config from Consul: ' + error.message);
|
|
} finally {
|
|
btn.innerText = originalText;
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function loadStatusInfo() {
|
|
try {
|
|
// Get general status
|
|
const statusResponse = await fetch('/api/status');
|
|
const statusData = await statusResponse.json();
|
|
|
|
// Update status info
|
|
const statusContainer = document.getElementById('status-info');
|
|
statusContainer.innerHTML = `
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<p><strong>Total Weight Records:</strong> ${statusData.total_weight_records}</p>
|
|
<p><strong>Synced to Garmin:</strong> ${statusData.synced_weight_records}</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<p><strong>Unsynced Records:</strong> ${statusData.unsynced_weight_records}</p>
|
|
<p><strong>Total Activities:</strong> ${statusData.total_activities}</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<p><strong>Downloaded Activities:</strong> ${statusData.downloaded_activities}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Get authentication status from a new API endpoint
|
|
const authStatusResponse = await fetch('/api/setup/auth-status');
|
|
if (authStatusResponse.ok) {
|
|
const authData = await authStatusResponse.json();
|
|
|
|
// Update Garmin auth status
|
|
const garminStatusContainer = document.getElementById('garmin-auth-status');
|
|
if (authData.garmin) {
|
|
let garminStatusHtml = `<h6>Garmin Authentication Status</h6>`;
|
|
if (authData.garmin.token_stored) {
|
|
garminStatusHtml += `<p><strong>Tokens Stored:</strong> Yes</p>`;
|
|
garminStatusHtml += `<p><strong>Authenticated:</strong> ${authData.garmin.authenticated ? 'Yes' : 'No'}</p>`;
|
|
garminStatusHtml += `<p><strong>OAuth1 Token Exists:</strong> ${authData.garmin.garth_oauth1_token_exists ? 'Yes' : 'No'}</p>`;
|
|
garminStatusHtml += `<p><strong>OAuth2 Token Exists:</strong> ${authData.garmin.garth_oauth2_token_exists ? 'Yes' : 'No'}</p>`;
|
|
garminStatusHtml += `<p><strong>MFA State Exists:</strong> ${authData.garmin.mfa_state_exists ? 'Yes' : 'No'}</p>`;
|
|
if (authData.garmin.mfa_expires_at) {
|
|
garminStatusHtml += `<p><strong>MFA Expires:</strong> ${new Date(authData.garmin.mfa_expires_at).toLocaleString()}</p>`;
|
|
}
|
|
if (authData.garmin.last_used) {
|
|
garminStatusHtml += `<p><strong>Last Used:</strong> ${new Date(authData.garmin.last_used).toLocaleString()}</p>`;
|
|
}
|
|
if (authData.garmin.updated_at) {
|
|
garminStatusHtml += `<p><strong>Last Updated:</strong> ${new Date(authData.garmin.updated_at).toLocaleString()}</p>`;
|
|
}
|
|
} else {
|
|
garminStatusHtml += `<p><strong>Tokens Stored:</strong> No</p>`;
|
|
}
|
|
garminStatusContainer.innerHTML = `<div class="alert ${authData.garmin.authenticated ? 'alert-success' : 'alert-warning'}">${garminStatusHtml}</div>`;
|
|
}
|
|
|
|
// Update Fitbit auth status
|
|
const fitbitStatusContainer = document.getElementById('fitbit-auth-status');
|
|
if (authData.fitbit) {
|
|
fitbitStatusContainer.innerHTML = `
|
|
<div class="alert ${authData.fitbit.authenticated ? 'alert-success' : 'alert-warning'}">
|
|
<h6>Fitbit Authentication Status</h6>
|
|
<p><strong>Client ID:</strong> ${authData.fitbit.client_id ? authData.fitbit.client_id.substring(0, 10) + '...' : 'Not set'}</p>
|
|
<p><strong>Authenticated:</strong> ${authData.fitbit.authenticated ? 'Yes' : 'No'}</p>
|
|
${authData.fitbit.token_expires_at ? `<p><strong>Token Expires:</strong> ${new Date(authData.fitbit.token_expires_at).toLocaleString()}</p>` : ''}
|
|
${authData.fitbit.last_login ? `<p><strong>Last Login:</strong> ${new Date(authData.fitbit.last_login).toLocaleString()}</p>` : ''}
|
|
</div>
|
|
`;
|
|
|
|
// Show/Hide Sync Section
|
|
const syncSection = document.getElementById('fitbit-sync-section');
|
|
if (authData.fitbit.authenticated) {
|
|
syncSection.style.display = 'block';
|
|
} else {
|
|
syncSection.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading status info:', error);
|
|
}
|
|
}
|
|
|
|
async function testGarminCredentials() {
|
|
const form = document.getElementById('garmin-credentials-form');
|
|
const formData = new FormData(form);
|
|
const credentials = {
|
|
username: formData.get('username'),
|
|
password: formData.get('password'),
|
|
is_china: formData.get('is_china') === 'on' || formData.get('is_china') === 'true'
|
|
};
|
|
const statusText = document.getElementById('garmin-auth-status-text');
|
|
const saveBtn = document.getElementById('save-garmin-btn');
|
|
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-info">Testing...</span></p>`;
|
|
saveBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(credentials)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'mfa_required') {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-warning">MFA Required</span></p>`;
|
|
document.getElementById('garmin-mfa-section').style.display = 'block';
|
|
window.garmin_mfa_session_id = data.session_id;
|
|
alert('Multi-factor authentication required. Please enter the verification code.');
|
|
} else if (response.ok) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-success">Authentication Successful</span></p>`;
|
|
saveBtn.disabled = false;
|
|
alert('Garmin authentication successful. You can now save the credentials.');
|
|
} else {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">Authentication Failed</span></p>`;
|
|
alert(data.message || 'Garmin authentication failed.');
|
|
}
|
|
} catch (error) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">Error</span></p>`;
|
|
console.error('Error testing Garmin credentials:', error);
|
|
alert('Error testing Garmin credentials: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function saveGarminCredentials(event) {
|
|
event.preventDefault();
|
|
|
|
const formData = new FormData(event.target);
|
|
const credentials = {
|
|
username: formData.get('username'),
|
|
password: formData.get('password'),
|
|
is_china: formData.get('is_china') === 'on' || formData.get('is_china') === 'true'
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(credentials)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert('Garmin credentials saved successfully');
|
|
loadStatusInfo();
|
|
} else {
|
|
alert(data.message || 'Error saving Garmin credentials.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving Garmin credentials:', error);
|
|
alert('Error saving Garmin credentials: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function clearGarminCredentials() {
|
|
if (!confirm('Are you sure you want to clear stored Garmin credentials? This will require re-authentication.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin', {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
alert(data.message || 'Garmin credentials cleared.');
|
|
loadStatusInfo();
|
|
// Reset status text
|
|
document.getElementById('garmin-auth-status-text').innerHTML = `<p>Current auth state: <span class="badge bg-secondary">Not Tested</span></p>`;
|
|
} else {
|
|
alert(data.message || 'Error clearing credentials.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error clearing Garmin credentials:', error);
|
|
alert('Error clearing Garmin credentials: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function testFitbitCredentials() {
|
|
const form = document.getElementById('fitbit-credentials-form');
|
|
const formData = new FormData(form);
|
|
const credentials = {
|
|
client_id: formData.get('client_id'),
|
|
client_secret: formData.get('client_secret')
|
|
};
|
|
const saveBtn = document.getElementById('save-fitbit-btn');
|
|
|
|
saveBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(credentials)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
const authLink = document.getElementById('auth-link');
|
|
authLink.href = data.auth_url;
|
|
document.getElementById('auth-url-container').style.display = 'block';
|
|
document.getElementById('fitbit-oauth-flow-section').style.display = 'block';
|
|
saveBtn.disabled = false;
|
|
alert('Fitbit credentials seem valid. You can now save them and proceed with authorization.');
|
|
} else {
|
|
alert(data.message || 'Fitbit credentials test failed.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error testing Fitbit credentials:', error);
|
|
alert('Error testing Fitbit credentials: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function saveFitbitCredentials(event) {
|
|
event.preventDefault();
|
|
|
|
const formData = new FormData(event.target);
|
|
const credentials = {
|
|
client_id: formData.get('client_id'),
|
|
client_secret: formData.get('client_secret')
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(credentials)
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
alert('Fitbit credentials saved successfully');
|
|
loadStatusInfo();
|
|
} else {
|
|
alert(data.message || 'Error saving Fitbit credentials.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving Fitbit credentials:', error);
|
|
alert('Error saving Fitbit credentials: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function syncFitbitWeight(scope) {
|
|
const resultDiv = document.getElementById('fitbit-sync-result');
|
|
resultDiv.innerHTML = `<div class="spinner-border spinner-border-sm text-primary" role="status"></div> Syncing ${scope === '30d' ? 'latest' : 'all'} data...`;
|
|
|
|
try {
|
|
const response = await fetch('/api/sync/fitbit/weight', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ scope: scope })
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div class="alert alert-success mt-2">${data.message}</div>`;
|
|
} else {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger mt-2">${data.detail || data.message || 'Sync failed'}</div>`;
|
|
}
|
|
} catch (error) {
|
|
resultDiv.innerHTML = `<div class="alert alert-danger mt-2">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function completeFitbitAuth(event) {
|
|
event.preventDefault();
|
|
|
|
const formData = new FormData(event.target);
|
|
const callbackUrl = formData.get('callback_url');
|
|
let code = callbackUrl;
|
|
|
|
// Try to extract code parameter if it looks like a URL
|
|
try {
|
|
if (callbackUrl.includes('?')) {
|
|
const url = new URL(callbackUrl);
|
|
const params = new URLSearchParams(url.search);
|
|
if (params.has('code')) {
|
|
code = params.get('code');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("Could not parse URL, assuming input is the code itself", e);
|
|
}
|
|
|
|
const callbackData = {
|
|
code: code
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/fitbit/callback', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(callbackData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
alert(data.message || 'Fitbit OAuth flow completed successfully');
|
|
|
|
// Refresh status after completing OAuth
|
|
loadStatusInfo();
|
|
} catch (error) {
|
|
console.error('Error completing Fitbit OAuth:', error);
|
|
alert('Error completing Fitbit OAuth: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Handle MFA submission
|
|
document.getElementById('submit-mfa-btn').addEventListener('click', submitMFA);
|
|
|
|
async function submitMFA() {
|
|
const mfaCode = document.getElementById('mfa-code').value.trim();
|
|
const statusText = document.getElementById('garmin-auth-status-text');
|
|
const saveBtn = document.getElementById('save-garmin-btn');
|
|
|
|
if (!mfaCode) {
|
|
alert('Please enter the verification code');
|
|
return;
|
|
}
|
|
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-info">Verifying MFA...</span></p>`;
|
|
|
|
try {
|
|
const response = await fetch('/api/setup/garmin/mfa', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
verification_code: mfaCode,
|
|
session_id: window.garmin_mfa_session_id
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-success">MFA Verification Successful</span></p>`;
|
|
saveBtn.disabled = false;
|
|
alert(data.message || 'MFA verification successful');
|
|
document.getElementById('garmin-mfa-section').style.display = 'none';
|
|
document.getElementById('mfa-code').value = '';
|
|
loadStatusInfo();
|
|
} else {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">MFA Verification Failed</span></p>`;
|
|
alert('MFA verification failed: ' + data.message);
|
|
}
|
|
} catch (error) {
|
|
statusText.innerHTML = `<p>Current auth state: <span class="badge bg-danger">Error</span></p>`;
|
|
console.error('Error submitting MFA code:', error);
|
|
alert('Error submitting MFA code: ' + error.message);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |