feat: restyle admin UI and add per-user sync run history

Adds a self-hosted stylesheet (no CDN dependencies) with a card-based
dashboard and color-coded status badges, and shows the last 10 sync
runs per user on the detail page.
This commit is contained in:
Bastian Wagner
2026-08-15 20:19:31 +02:00
parent 990a55af14
commit 85b0d861b4
13 changed files with 597 additions and 126 deletions

View File

@@ -4,50 +4,98 @@
{% block content %}
<h1>{{ user.name }}</h1>
<p><a href="/users/{{ user.id }}/edit">Edit</a> | <a href="/">Back to dashboard</a></p>
<div class="page-actions">
<a class="btn secondary" href="/users/{{ user.id }}/edit">Edit</a>
<a class="btn secondary" href="/">Back to dashboard</a>
</div>
<dl>
<dt>Enabled</dt>
<dd>{{ "Yes" if user.enabled else "No" }}</dd>
<div class="card">
<dl class="info-grid">
<dt>Status</dt>
<dd><span class="badge badge-{{ user.health_state.value }}">{{ user.health_state.value.replace("_", " ") }}</span></dd>
<dt>Health state</dt>
<dd>{{ user.health_state.value }}</dd>
<dt>Enabled</dt>
<dd>{{ "Yes" if user.enabled else "No" }}</dd>
<dt>MyWhoosh state</dt>
<dd>{{ user.mywhoosh_state }}</dd>
<dt>MyWhoosh state</dt>
<dd>{{ user.mywhoosh_state }}</dd>
<dt>Garmin state</dt>
<dd>{{ user.garmin_state }}</dd>
<dt>Garmin state</dt>
<dd>{{ user.garmin_state }}</dd>
<dt>Action reason</dt>
<dd>{{ user.action_reason or "-" }}</dd>
<dt>Action reason</dt>
<dd>{{ user.action_reason or "-" }}</dd>
<dt>Created at</dt>
<dd>{{ user.created_at }}</dd>
<dt>Created at</dt>
<dd>{{ user.created_at }}</dd>
<dt>Updated at</dt>
<dd>{{ user.updated_at }}</dd>
</dl>
<dt>Updated at</dt>
<dd>{{ user.updated_at }}</dd>
</dl>
</div>
{% if user.action_reason == "garmin_mfa_required" %}
<h2>Garmin MFA required</h2>
{% include "fragments/mfa_form.html" %}
<div class="card">
{% include "fragments/mfa_form.html" %}
</div>
{% endif %}
<h2>Activities</h2>
<ul>
{% for activity in activities %}
<li>
{{ activity.activity_name }} &mdash; {{ activity.status.value }}
{% if activity.status.value == "failed" and activity.retryable %}
<form method="post" action="/activities/{{ activity.id }}/retry" style="display:inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit">Retry</button>
</form>
{% endif %}
</li>
<h2>Recent sync runs</h2>
<div class="card">
{% if recent_runs %}
<table>
<thead>
<tr>
<th>Started</th>
<th>Finished</th>
<th>Status</th>
<th>Discovered</th>
<th>Imported</th>
<th>Skipped</th>
<th>Failed</th>
</tr>
</thead>
<tbody>
{% for run in recent_runs %}
<tr>
<td>{{ run.started_at }}</td>
<td>{{ run.finished_at or "-" }}</td>
<td><span class="badge badge-{{ run.status.value }}">{{ run.status.value }}</span></td>
<td>{{ run.discovered_count }}</td>
<td>{{ run.imported_count }}</td>
<td>{{ run.skipped_count }}</td>
<td>{{ run.failed_count }}</td>
</tr>
{% if run.summary_error %}
<tr>
<td colspan="7" class="summary-error">{{ run.summary_error }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% else %}
<li>No pending activities.</li>
{% endfor %}
</ul>
<p class="empty-state">No sync runs yet.</p>
{% endif %}
</div>
<h2>Activities</h2>
<div class="card">
<ul class="user-list">
{% for activity in activities %}
<li>
{{ activity.activity_name }} &mdash; {{ activity.status.value }}
{% if activity.status.value == "failed" and activity.retryable %}
<form method="post" action="/activities/{{ activity.id }}/retry" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="secondary">Retry</button>
</form>
{% endif %}
</li>
{% else %}
<li class="empty-state">No pending activities.</li>
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@@ -4,37 +4,39 @@
{% block content %}
<h1>{% if user %}Edit User{% else %}New User{% endif %}</h1>
<form method="post" action="{{ form_action }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="card">
<form method="post" action="{{ form_action }}" class="stacked-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{ user.name if user else '' }}" required>
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{ user.name if user else '' }}" required>
<label for="mywhoosh_email">MyWhoosh Email</label>
<input type="email" id="mywhoosh_email" name="mywhoosh_email" value="{{ mywhoosh_email }}" required>
<label for="mywhoosh_email">MyWhoosh Email</label>
<input type="email" id="mywhoosh_email" name="mywhoosh_email" value="{{ mywhoosh_email }}" required>
<label for="mywhoosh_password">MyWhoosh Password</label>
<input type="password" id="mywhoosh_password" name="mywhoosh_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="mywhoosh_password">MyWhoosh Password</label>
<input type="password" id="mywhoosh_password" name="mywhoosh_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="garmin_email">Garmin Email</label>
<input type="email" id="garmin_email" name="garmin_email" value="{{ garmin_email }}" required>
<label for="garmin_email">Garmin Email</label>
<input type="email" id="garmin_email" name="garmin_email" value="{{ garmin_email }}" required>
<label for="garmin_password">Garmin Password</label>
<input type="password" id="garmin_password" name="garmin_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="garmin_password">Garmin Password</label>
<input type="password" id="garmin_password" name="garmin_password" autocomplete="new-password"
{% if not user %}required{% endif %}>
{% if user %}
<p class="hint">Leave blank to keep the existing password.</p>
{% endif %}
<label for="enabled">
<input type="checkbox" id="enabled" name="enabled" {% if not user or user.enabled %}checked{% endif %}>
Enabled
</label>
<label for="enabled">
<input type="checkbox" id="enabled" name="enabled" {% if not user or user.enabled %}checked{% endif %}>
Enabled
</label>
<button type="submit">{% if user %}Save{% else %}Create{% endif %}</button>
</form>
<button type="submit">{% if user %}Save{% else %}Create{% endif %}</button>
</form>
</div>
{% endblock %}