Files
agentbox-test/templates/dashboard.html
T
agentbox 3681f9dfce
CI / test-and-package (push) Successful in 37s
Add ntfy push notifications with action buttons for meal reminders
- ntfy.py: send_meal_reminder() with Home/Out HTTP action buttons, test helper
- models.py: new columns ntfy_url, ntfy_token, callback_token on users table
- meals.py: /api/ntfy-callback (public, token-authenticated), /ntfy-settings, /ntfy-test
- app.py: flask send-reminders CLI command (cron-schedulable)
- Dashboard: ntfy config form in Account Settings
- i18n: translations for all ntfy strings (en/fr/de)
- Dockerfile: include ntfy.py in COPY
- tests: 15 new tests covering callback, settings, and auth
- docs: updated AGENTS.md and doc/index.md
2026-08-01 06:17:26 +00:00

132 lines
6.7 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ t('Dashboard — Meal Tracker') }}{% endblock %}
{% block content %}
<div class="dashboard-header">
<h2>
{% if history_mode %}{{ t('History — {date}', date=viewing_date.isoformat()) }}{% else %}{{ t("Today's Meals") }}{% endif %}
</h2>
<div class="date-nav">
<a class="btn btn-sm" href="?date={{ prev_date.isoformat() }}">{{ t('◀ Prev') }}</a>
<span class="date-display">{{ viewing_date.isoformat() }}</span>
<a class="btn btn-sm" href="?date={{ next_date.isoformat() }}">{{ t('Next ▶') }}</a>
{% if viewing_date != today %}
<a class="btn btn-sm" href="{{ url_for('meals.dashboard') }}">{{ t('Today') }}</a>
{% endif %}
</div>
</div>
{% if not dashboard.users %}
<p class="empty-state">{{ t('No registered users yet. Ask household members to register!') }}</p>
{% else %}
<div class="table-wrapper">
<table class="meal-table">
<thead>
<tr>
<th>{{ t('User') }}</th>
{% for period in dashboard.periods %}
<th class="meal-col">{{ t(period.meal_type|capitalize) }}</th>
{% endfor %}
{% if is_admin %}
<th class="action-col"></th>
{% endif %}
</tr>
</thead>
<tbody>
{% for user in dashboard.users %}
{% set is_me = user.id == current_user_id %}
<tr>
<td class="user-cell">
{{ user.username }}
{% if user.is_admin %}<span class="admin-tag">{{ t('admin') }}</span>{% endif %}
{% if is_me %}<span class="you-tag">{{ t('you') }}</span>{% endif %}
</td>
{% for period in dashboard.periods %}
{% set resp = user.responses[period.meal_type] %}
<td class="response-cell status-{{ resp.status }}{% if resp.changed_at %} changed-mind{% endif %}">
{% if is_me and viewing_date >= today %}
<form method="POST" action="{{ url_for('meals.respond') }}" class="respond-form">
<input type="hidden" name="date" value="{{ viewing_date.isoformat() }}">
<input type="hidden" name="meal_type" value="{{ period.meal_type }}">
<div class="btn-row">
<button name="status" value="yes"
class="chip chip-yes {% if resp.status == 'yes' %}active{% endif %}"
>{{ t('Home') }}</button>
<button name="status" value="no"
class="chip chip-no {% if resp.status == 'no' %}active{% endif %}"
>{{ t('Out') }}</button>
</div>
</form>
{% if resp.changed_at %}
<div class="changed-badge" title="{{ t('Changed mind at {time}', time=resp.changed_at) }}">↻ {{ resp.changed_at[11:16] }}</div>
{% endif %}
{% else %}
<span class="badge badge-{{ resp.status }}">{{ t('Home') if resp.status == 'yes' else (t('Out') if resp.status == 'no' else t('—')) }}</span>
{% endif %}
</td>
{% endfor %}
{% if is_admin and not is_me and not user.is_admin %}
<td class="action-cell">
<form method="POST" action="{{ url_for('meals.admin_remove_user', user_id=user.id) }}"
onsubmit="return confirm(this.dataset.confirmMsg);"
data-confirm-msg="{{ t('Remove {name} from this household?', name=user.username) }}">
<button class="btn btn-sm btn-danger">{{ t('Remove') }}</button>
</form>
</td>
{% elif is_admin %}
<td class="action-cell"></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="manage-section">
<details class="manage-details">
<summary>{{ t('⚙ Account Settings') }}</summary>
<div class="manage-forms">
<form method="POST" action="{{ url_for('meals.delete_account') }}"
onsubmit="return confirm(this.dataset.confirmMsg);"
data-confirm-msg="{{ t('Delete YOUR account? This cannot be undone.') }}"
class="manage-form">
<h4>{{ t('Delete My Account') }}</h4>
<input type="password" name="password" placeholder="{{ t('Enter your password to confirm') }}" required>
<button class="btn btn-danger">{{ t('Delete My Account') }}</button>
</form>
<form method="POST" action="{{ url_for('meals.ntfy_settings') }}" class="manage-form">
<h4>🔔 {{ t('Ntfy Notifications') }}</h4>
<p class="hint">{{ t('Get meal reminders on your phone via ntfy.') }}</p>
<label for="ntfy_url">{{ t('Ntfy Topic URL') }}</label>
<input type="text" id="ntfy_url" name="ntfy_url"
placeholder="https://ntfy.sh/your-topic"
value="{{ ntfy.ntfy_url or '' }}">
<label for="ntfy_token">{{ t('Access Token') }} <span class="hint">{{ t('(optional)') }}</span></label>
<input type="password" id="ntfy_token" name="ntfy_token"
placeholder="tk_..."
value="{{ ntfy.ntfy_token or '' }}">
<div class="btn-row" style="margin-top: 8px;">
<button type="submit" class="btn btn-primary btn-sm">{{ t('Save') }}</button>
<button type="submit" class="btn btn-sm" formaction="{{ url_for('meals.ntfy_test') }}">{{ t('Send Test') }}</button>
</div>
</form>
{% if is_admin %}
<form method="POST" action="{{ url_for('meals.admin_delete_household') }}"
onsubmit="return confirm(this.dataset.confirmMsg);"
data-confirm-msg="{{ t('Delete the ENTIRE household and ALL members? This cannot be undone.') }}"
class="manage-form">
<h4>{{ t('Delete Household') }}</h4>
<p class="hint">{{ t('This will remove all users and all data in this household.') }}</p>
<input type="password" name="password" placeholder="{{ t('Enter your password to confirm') }}" required>
<button class="btn btn-danger">{{ t('Delete Household') }}</button>
</form>
{% endif %}
</div>
</details>
</div>
{% endblock %}