185 lines
9.9 KiB
HTML
185 lines
9.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Settings{% endblock %}
|
|
{% block page_title %}Settings{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row g-3">
|
|
<!-- Nav cards -->
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('teller.index') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#10b981'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-bank2" style="font-size:2rem;color:#10b981;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Teller Sync</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Connect US bank accounts via Teller</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('schwab.index') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#3b82f6'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-bank" style="font-size:2rem;color:#3b82f6;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Schwab Sync</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Connect Charles Schwab directly</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('settings.profile') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#3b82f6'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-person-circle" style="font-size:2rem;color:#3b82f6;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Profile</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Name, currency, timezone, AI model</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('settings.recurring') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#10b981'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-repeat" style="font-size:2rem;color:#10b981;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Recurring</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Automate recurring income & expenses</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('settings.import_csv') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#f59e0b'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-upload" style="font-size:2rem;color:#f59e0b;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Import</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Bulk import from CSV file</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('settings.password') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#8b5cf6'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-shield-lock" style="font-size:2rem;color:#8b5cf6;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Security</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Change password</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Log nav card -->
|
|
<div class="row g-3 mt-0">
|
|
<div class="col-12 col-sm-6 col-lg-3">
|
|
<a href="{{ url_for('settings.audit_log') }}" class="text-decoration-none">
|
|
<div class="pcard text-center py-4" style="transition:all .15s;" onmouseover="this.style.borderColor='#ef4444'" onmouseout="this.style.borderColor='var(--border)'">
|
|
<i class="bi bi-clipboard-check" style="font-size:2rem;color:#ef4444;"></i>
|
|
<div style="font-size:14px;font-weight:600;margin-top:10px;">Audit Log</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:4px;">Login, 2FA, and sync events</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Two-Factor Authentication -->
|
|
<div class="row g-3 mt-1">
|
|
<div class="col-12 col-md-6">
|
|
<div class="pcard h-100">
|
|
<div class="d-flex align-items-center gap-2 mb-3">
|
|
<i class="bi bi-shield-lock-fill" style="font-size:1.3rem;color:#5b21b6;"></i>
|
|
<span class="pcard-title mb-0">Two-Factor Authentication (2FA)</span>
|
|
{% if current_user.totp_enabled %}
|
|
<span class="badge ms-auto" style="background:#d1fae5;color:#065f46;font-size:11px;">Enabled</span>
|
|
{% else %}
|
|
<span class="badge ms-auto" style="background:#fee2e2;color:#991b1b;font-size:11px;">Disabled</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if current_user.totp_enabled %}
|
|
<p style="font-size:13px;color:var(--muted);">
|
|
2FA is active. Each login requires a 6-digit code from your authenticator app.
|
|
</p>
|
|
<form method="POST" action="{{ url_for('auth.totp_disable') }}"
|
|
onsubmit="return confirm('Disable two-factor authentication?\nYou will only need a password to log in.')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="mb-2">
|
|
<input type="password" name="password" class="form-control form-control-sm"
|
|
placeholder="Confirm your password" required style="max-width:280px;">
|
|
</div>
|
|
<button type="submit" class="btn btn-sm btn-outline-danger">
|
|
<i class="bi bi-shield-x me-1"></i>Disable 2FA
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<p style="font-size:13px;color:var(--muted);">
|
|
Protect your account with a time-based one-time password (TOTP).
|
|
Use Google Authenticator, Authy, or any compatible app.
|
|
</p>
|
|
<a href="{{ url_for('auth.totp_setup') }}" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-shield-plus me-1"></i>Enable 2FA
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Maintenance + Email Alerts -->
|
|
<div class="row g-3 mt-1">
|
|
<div class="col-12 col-md-6">
|
|
<div class="pcard h-100">
|
|
<div class="pcard-title mb-3">Maintenance</div>
|
|
<p style="font-size:13px;color:var(--muted);">
|
|
Recalculate every account balance from scratch using all recorded transactions.
|
|
Run this after bulk imports or manual adjustments to make sure balances are correct.
|
|
</p>
|
|
<form method="POST" action="{{ url_for('settings.recalc_balances') }}"
|
|
onsubmit="return confirm('Recalculate all account balances now?')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-arrow-repeat me-1"></i>Recalculate All Balances
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<div class="pcard h-100">
|
|
<div class="pcard-title mb-3">Budget Alert Emails</div>
|
|
{% if smtp_ok %}
|
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
<i class="bi bi-check-circle-fill text-success"></i>
|
|
<span style="font-size:13px;">Email alerts enabled → <strong>{{ alert_email }}</strong></span>
|
|
</div>
|
|
<p style="font-size:12px;color:var(--muted);">
|
|
An email is sent automatically when a budget category hits 80% or 100% of its monthly limit.
|
|
</p>
|
|
{% else %}
|
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
<i class="bi bi-x-circle-fill text-danger"></i>
|
|
<span style="font-size:13px;color:var(--muted);">Email alerts not configured</span>
|
|
</div>
|
|
<p style="font-size:12px;color:var(--muted);">
|
|
Add these to your <code>.env</code> file to enable budget alert emails:
|
|
</p>
|
|
<pre style="font-size:11px;background:#f8fafc;padding:10px;border-radius:6px;margin:0;overflow-x:auto;">SMTP_HOST=smtp.gmail.com
|
|
SMTP_PORT=587
|
|
SMTP_USER=you@gmail.com
|
|
SMTP_PASSWORD=your-app-password
|
|
ALERT_EMAIL=you@gmail.com</pre>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upcoming recurring -->
|
|
{% if upcoming %}
|
|
<div class="pcard mt-4">
|
|
<div class="pcard-title mb-3">Upcoming Recurring (next 30 days)</div>
|
|
<table class="pfm-table">
|
|
<thead><tr><th>Date</th><th>Description</th><th>Type</th><th class="text-end">Amount</th></tr></thead>
|
|
<tbody>
|
|
{% for item in upcoming[:10] %}
|
|
<tr>
|
|
<td style="font-size:12px;color:var(--muted);white-space:nowrap;">{{ item.date.strftime('%b %d') }}</td>
|
|
<td style="font-size:13px;">{{ item.description }}</td>
|
|
<td><span class="badge {% if item.type == 'income' %}badge-income{% else %}badge-expense{% endif %}" style="font-size:11px;">{{ item.type | title }}</span></td>
|
|
<td class="text-end mono {% if item.type == 'income' %}text-income{% else %}text-expense{% endif %}" style="font-size:13px;">{{ item.amount | currency }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|