Deploy Stride with encrypted manual and scheduled Garmin sync

This commit is contained in:
Peter Stockings
2026-10-01 20:42:28 +10:00
parent 24c7fc2c6a
commit 8555695c5e
28 changed files with 900 additions and 56 deletions
+14 -4
View File
@@ -2,8 +2,18 @@
{% block title %}Settings & imports · Stride{% endblock %}
{% block breadcrumb %}SETTINGS & IMPORTS{% endblock %}
{% block content %}
<div class="page-heading"><div><span class="eyebrow">MAKE IT YOUR OWN</span><h1>Your space, your way.</h1><p>Manage your profile and bring your activities home.</p></div></div>
<div class="settings-grid"><section class="panel settings-card"><span class="eyebrow">THE PERSON BEHIND THE MILES</span><h2>Your profile</h2><form method="post" class="stack"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label>Display name<input name="display_name" value="{{ current_user.display_name }}" required maxlength="80"></label><label>Time zone<input name="timezone" value="{{ current_user.timezone }}" list="timezones" required><datalist id="timezones"><option>Australia/Sydney</option><option>Australia/Melbourne</option><option>Australia/Brisbane</option><option>UTC</option><option>Europe/London</option><option>America/New_York</option></datalist></label><p class="field-help">Your <a href="{{ url_for('web.profile',username=current_user.username) }}">shared profile</a> is visible only to accepted friends and only includes activities you share.</p><button class="button primary">Save profile ↗</button></form></section>
<section class="panel settings-card"><span class="eyebrow">BRING YOUR RUNS WITH YOU</span><h2>Import a FIT file</h2><p class="muted">Upload an original activity exported from your watch or Garmin Connect.</p><form action="{{ url_for('web.upload') }}" method="post" enctype="multipart/form-data" class="stack"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label class="upload-zone"><span class="upload-icon">↑</span><strong>Choose an activity file</strong><small>FIT format · up to 16 MB · imported privately</small><input type="file" name="file" accept=".fit" required></label><button class="button primary">Import activity ↗</button></form></section>
<section class="panel settings-card connection-card"><div><span class="eyebrow">GARMIN CONNECT</span><h2>Your local sync</h2><p class="muted">{% if current_user.local_account_key %}Your account is linked to the activities downloaded by your local Garmin sync command.{% else %}You can import FIT files now. Web-based Garmin token connections are coming in the next development stage.{% endif %}</p></div>{% if current_user.local_account_key %}<div class="connection-actions"><span class="pill public">Local imports linked</span><p class="field-help">After running the sync command, load any new downloads here. This button reads local files; it does not contact Garmin.</p><form method="post" action="{{ url_for('web.import_local') }}"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="button secondary">Load new downloads ↻</button></form></div>{% endif %}</section></div>
<div class="page-heading"><div><span class="eyebrow">ACCOUNT</span><h1>Settings & imports</h1><p>Manage your profile and bring your activities home.</p></div></div>
<div class="settings-grid"><section class="panel settings-card"><span class="eyebrow">PROFILE</span><h2>Your profile</h2><form method="post" class="stack"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label>Display name<input name="display_name" value="{{ current_user.display_name }}" required maxlength="80"></label><label>Time zone<input name="timezone" value="{{ current_user.timezone }}" list="timezones" required><datalist id="timezones"><option>Australia/Sydney</option><option>Australia/Melbourne</option><option>Australia/Brisbane</option><option>UTC</option><option>Europe/London</option><option>America/New_York</option></datalist></label><p class="field-help">Your <a href="{{ url_for('web.profile',username=current_user.username) }}">shared profile</a> is visible only to accepted friends and only includes activities you share.</p><button class="button primary">Save profile {{ icon('arrow') }}</button></form></section>
<section class="panel settings-card"><span class="eyebrow">ACTIVITY IMPORT</span><h2>Import a FIT file</h2><p class="muted">Upload an original activity exported from your watch or Garmin Connect.</p><form action="{{ url_for('web.upload') }}" method="post" enctype="multipart/form-data" class="stack"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label class="upload-zone"><span class="upload-icon">{{ icon('upload') }}</span><strong>Choose an activity file</strong><small>FIT format · up to 16 MB · imported privately</small><input type="file" name="file" accept=".fit" required></label><button class="button primary">Import activity {{ icon('arrow') }}</button></form></section>
<section class="panel settings-card connection-card"><div><span class="eyebrow">GARMIN CONNECT</span><h2>Garmin sync</h2>
{% if connection %}
<p>{{ connection.message }}</p><p class="field-help">{% if connection.last_success %}Last successful sync: {{ connection.last_success|localdate(current_user.timezone, '%d %b %Y %H:%M') }}.{% endif %} {% if connection.pending %}A sync is queued.{% endif %} Automatic sync is {{ 'on (hourly)' if connection.scheduled else 'off' }}.</p>
<form method="post" action="{{ url_for('web.garmin_action', action='sync') }}"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="button primary">Sync now</button></form>
<form method="post" action="{{ url_for('web.garmin_action', action='schedule') }}"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><input type="hidden" name="enabled" value="{{ 'no' if connection.scheduled else 'yes' }}"><button class="button secondary">{{ 'Pause' if connection.scheduled else 'Enable' }} hourly sync</button></form>
<form method="post" action="{{ url_for('web.garmin_action', action='disconnect') }}"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="button secondary">Disconnect Garmin</button></form>
{% endif %}
<p class="muted">Connect or reconnect by uploading the session JSON exported by the local Garmin login helper (garminconnect 0.3.17). Sessions are encrypted on the server. New imports are private. Sync checks your latest 100 activities.</p>
<form method="post" enctype="multipart/form-data" action="{{ url_for('web.garmin_action', action='connect') }}" class="stack"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label>Garmin session<input type="file" name="tokens" accept=".json" required></label><button class="button primary">Connect Garmin</button></form>
<p class="field-help">Disconnect deletes the server session and stops scheduled sync. It keeps imported activities and does not revoke other session copies.</p>
</div></section></div>
{% endblock %}