DockFlare/dockflare/app/templates/email.html
2026-04-14 10:27:10 +02:00

285 lines
16 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
<div>
<h1 class="text-3xl font-bold">{{ t('email.title') }}</h1>
<p class="text-sm opacity-60 mt-1">{{ t('email.title_description') }}</p>
</div>
{% if email_enabled %}
<div class="flex gap-2 shrink-0">
<button class="btn btn-outline btn-sm" onclick="emailRedeployWorkers()">Redeploy Workers</button>
<button class="btn btn-primary btn-sm" onclick="emailOpenWebmail()">{{ t('email.webmail_link') }}</button>
</div>
{% endif %}
</div>
<div id="emailPermissionsBanner" class="alert alert-warning shadow-lg mb-8 hidden">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
<div>
<h3 class="font-bold">{{ t('email.permissions_title') }}</h3>
<div class="text-sm mt-1">
<span id="permEmailRouting"></span> {{ t('email.permission_email_routing') }}<br>
<span id="permWorkers"></span> {{ t('email.permission_workers') }}<br>
<span id="permR2"></span> {{ t('email.permission_r2') }}
</div>
</div>
<button class="btn btn-sm btn-ghost" onclick="emailCheckPermissions()">{{ t('email.recheck_permissions') }}</button>
</div>
<section class="card bg-base-100 shadow-xl mb-8 sm:mb-12 transition-all duration-300 hover:shadow-2xl">
<div class="card-body overflow-visible">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b border-base-300 pb-4 mb-6 gap-4">
<div>
<h2 class="card-title text-2xl sm:text-3xl">{{ t('email.domain_setup') }}</h2>
<p class="text-sm opacity-60 mt-1">{{ t('email.domain_setup_description') }}</p>
</div>
<div class="flex items-center gap-2 w-full sm:w-auto">
<select id="emailZoneSelect" class="select select-bordered select-sm flex-1 sm:w-56">
<option disabled selected>{{ t('email.choose_domain') }}</option>
{% for zone in zones %}
<option value="{{ zone.id }}">{{ zone.name }}</option>
{% endfor %}
</select>
<button id="emailSetupBtn" class="btn btn-primary btn-sm shrink-0" onclick="emailSetupDomain(event)" disabled>{{ t('email.setup_email') }}</button>
</div>
</div>
<table class="table table-zebra w-full">
<colgroup>
<col>
<col class="w-36">
<col class="w-64">
</colgroup>
<thead>
<tr>
<th class="px-4 py-3">{{ t('email.domain') }}</th>
<th class="px-4 py-3">{{ t('email.status') }}</th>
<th class="px-4 py-3 text-right">{{ t('email.actions') }}</th>
</tr>
</thead>
<tbody>
{% if email_config.domains %}
{% for domain, cfg in email_config.domains.items() %}
<tr>
<td class="px-4 py-3">{{ domain }}</td>
<td class="px-4 py-3"><div class="badge badge-success badge-sm">{{ t('email.setup_complete') }}</div></td>
<td class="px-4 py-3">
<div class="flex items-center justify-end gap-1">
<button class="btn btn-sm btn-ghost" onclick="emailVerifyDns('{{ domain }}', event)">{{ t('email.dns_verify') }}</button>
<button class="btn btn-sm btn-ghost" onclick="emailRepairDns('{{ domain }}', event)">Repair DNS</button>
<div class="dropdown dropdown-end">
<button tabindex="0" class="btn btn-sm btn-ghost px-2"></button>
<ul tabindex="0" class="dropdown-content z-50 menu menu-sm p-2 shadow-lg bg-base-100 border border-base-200 rounded-box w-52">
<li><button onclick="emailUpdateR2('{{ domain }}', event)">R2 Credentials</button></li>
<li class="menu-title">Danger zone</li>
<li><button onclick="emailTeardownPartial('{{ domain }}', event)" class="text-warning">{{ t('email.teardown_partial') }}</button></li>
<li><button onclick="emailTeardownComplete('{{ domain }}', event)" class="text-error">{{ t('email.teardown_complete') }}</button></li>
</ul>
</div>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="3" class="px-4 py-8 text-center opacity-50">{{ t('email.no_domains') }}</td></tr>
{% endif %}
</tbody>
</table>
</div>
</section>
{% if email_enabled %}
<section class="card bg-base-100 shadow-xl mb-8 sm:mb-12 transition-all duration-300 hover:shadow-2xl">
<div class="card-body">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b border-base-300 pb-4 mb-6 gap-4">
<div>
<h2 class="card-title text-2xl sm:text-3xl">{{ t('email.mailbox_management') }}</h2>
<p class="text-sm opacity-60 mt-1">{{ t('email.mailbox_description') }}</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-2 mb-6">
<div class="flex items-center gap-1 flex-1 min-w-48">
<input type="text" id="newMailboxAddress" placeholder="{{ t('email.address') }}" class="input input-bordered input-sm w-32 flex-1" />
<span class="text-sm opacity-40 select-none px-1">@</span>
<select id="newMailboxDomain" class="select select-bordered select-sm">
{% if email_config and email_config.domains %}
{% for domain in email_config.domains %}
<option value="{{ domain }}">{{ domain }}</option>
{% endfor %}
{% endif %}
</select>
</div>
<input type="text" id="newMailboxName" placeholder="{{ t('email.display_name') }}" class="input input-bordered input-sm flex-1 min-w-32" />
<button class="btn btn-primary btn-sm shrink-0" onclick="emailCreateMailbox(event)">{{ t('email.add_mailbox') }}</button>
</div>
<div class="overflow-x-auto">
<table class="table table-zebra w-full">
<colgroup>
<col>
<col class="w-36">
<col class="w-32">
<col class="w-40">
</colgroup>
<thead>
<tr>
<th class="px-4 py-3">{{ t('email.address') }}</th>
<th class="px-4 py-3">{{ t('email.display_name') }}</th>
<th class="px-4 py-3">{{ t('email.domain') }}</th>
<th class="px-4 py-3 text-right">{{ t('email.actions') }}</th>
</tr>
</thead>
<tbody>
{% if email_config and email_config.domains %}
{% for domain, cfg in email_config.domains.items() %}
{% for addr, mb in cfg.mailboxes.items() %}
<tr>
<td class="px-4 py-3 font-mono text-sm">{{ addr }}</td>
<td class="px-4 py-3">{{ mb.display_name }}</td>
<td class="px-4 py-3 opacity-50 text-sm">{{ domain }}</td>
<td class="px-4 py-3">
<div class="flex items-center justify-end gap-1">
<button class="btn btn-sm btn-outline" onclick="emailSetPassword('{{ addr }}', '{{ domain }}')">Set Password</button>
<button class="btn btn-sm btn-error btn-outline" onclick="emailDeleteMailbox('{{ addr }}', '{{ domain }}', event)">{{ t('email.delete') }}</button>
</div>
</td>
</tr>
{% endfor %}
{% endfor %}
{% else %}
<tr><td colspan="4" class="px-4 py-8 text-center opacity-50">{{ t('email.no_mailboxes') }}</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</section>
<section class="card bg-base-100 shadow-xl mb-8 sm:mb-12 transition-all duration-300 hover:shadow-2xl">
<div class="card-body">
<div class="border-b border-base-300 pb-4 mb-6">
<h2 class="card-title text-2xl sm:text-3xl">{{ t('email.statistics') }}</h2>
<p class="text-sm opacity-60 mt-1">{{ t('email.statistics_description') }}</p>
</div>
<div class="stats stats-horizontal shadow w-full">
<div class="stat">
<div class="stat-title">{{ t('email.stats_received') }}</div>
<div class="stat-value text-primary" id="statReceived">0</div>
</div>
<div class="stat">
<div class="stat-title">{{ t('email.stats_sent') }}</div>
<div class="stat-value text-secondary" id="statSent">0</div>
</div>
<div class="stat">
<div class="stat-title">{{ t('email.stats_storage') }}</div>
<div class="stat-value text-accent" id="statStorage">0</div>
</div>
<div class="stat">
<div class="stat-title">{{ t('email.stats_mailboxes') }}</div>
<div class="stat-value text-info" id="statMailboxes">0</div>
</div>
</div>
</div>
</section>
<section class="card bg-base-100 shadow-xl mb-8 sm:mb-12 transition-all duration-300 hover:shadow-2xl">
<div class="card-body">
<div class="border-b border-base-300 pb-4 mb-6">
<h2 class="card-title text-2xl sm:text-3xl">{{ t('email.backup_restore') }}</h2>
<p class="text-sm opacity-60 mt-1">{{ t('email.backup_restore_description') }}</p>
</div>
<div class="grid sm:grid-cols-2 gap-8">
<div>
<h3 class="font-semibold mb-1">{{ t('email.backup_title') }}</h3>
<p class="text-sm opacity-60 mb-3">{{ t('email.backup_description') }}</p>
<p class="text-xs text-warning font-medium mb-4">⚠ {{ t('email.backup_security_warning') }}</p>
<a href="{{ url_for('email.email_backup') }}" class="btn btn-primary btn-sm">{{ t('email.download_backup') }}</a>
</div>
<div>
<h3 class="font-semibold mb-1">{{ t('email.restore_title') }}</h3>
<p class="text-sm opacity-60 mb-3">{{ t('email.restore_description') }}</p>
<p class="text-xs text-error font-medium mb-4">⚠ {{ t('email.restore_warning') }}</p>
<div class="flex gap-2 items-center">
<label class="btn btn-outline btn-sm" for="emailRestoreFile">Choose file</label>
<span id="emailRestoreFileName" class="text-sm opacity-50 truncate flex-1">No file chosen</span>
<input type="file" id="emailRestoreFile" accept=".zip" class="hidden" onchange="document.getElementById('emailRestoreFileName').textContent = this.files[0]?.name || 'No file chosen'" />
<button class="btn btn-error btn-sm shrink-0" id="emailRestoreBtn" onclick="emailRestoreBackup()">{{ t('email.restore_backup') }}</button>
</div>
<p id="emailRestoreFeedback" class="text-sm mt-2 font-semibold hidden"></p>
</div>
</div>
<div id="emailOrphanedSection" class="hidden mt-6">
<div class="divider"></div>
<h3 class="font-semibold mb-1">{{ t('email.orphaned_data_title') }}</h3>
<p class="text-sm opacity-60 mb-3">{{ t('email.orphaned_data_description') }}</p>
<div id="emailOrphanedList"></div>
</div>
</div>
</section>
<section class="card bg-base-100 shadow-xl border border-error mb-8 sm:mb-12 transition-all duration-300 hover:shadow-2xl">
<div class="card-body">
<div class="border-b border-error border-opacity-30 pb-4 mb-6">
<h2 class="card-title text-2xl sm:text-3xl text-error">{{ t('email.nuke_all_title') }}</h2>
<p class="text-sm opacity-60 mt-1">{{ t('email.nuke_all_description') }}</p>
</div>
<div class="flex gap-2">
<button class="btn btn-warning btn-sm" onclick="emailNukeAll(false, event)">{{ t('email.nuke_all_partial') }}</button>
<button class="btn btn-error btn-sm" onclick="emailNukeAll(true, event)">{{ t('email.nuke_all_complete') }}</button>
</div>
<p id="emailNukeFeedback" class="text-sm mt-2 font-semibold hidden"></p>
</div>
</section>
{% else %}
<section class="card bg-base-100 shadow-xl mb-8">
<div class="card-body">
<div class="alert alert-info">
<div>
<span>{{ t('email.container_stopped') }}</span><br>
<code class="text-sm mt-2 block">docker compose --profile email up -d</code>
</div>
</div>
</div>
</section>
{% endif %}
</div>
<dialog id="emailSetPasswordModal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-1">Set Password</h3>
<p class="text-sm opacity-60 mb-4" id="emailSetPasswordTarget"></p>
<div class="space-y-3">
<input type="password" id="emailSetPasswordNew" placeholder="New password (min 8 characters)" class="input input-bordered w-full" autocomplete="new-password" />
<input type="password" id="emailSetPasswordConfirm" placeholder="Confirm password" class="input input-bordered w-full" autocomplete="new-password" />
<p id="emailSetPasswordError" class="text-error text-sm hidden"></p>
</div>
<div class="modal-action">
<form method="dialog"><button class="btn btn-ghost">Cancel</button></form>
<button class="btn btn-primary" id="emailSetPasswordSubmitBtn">Set Password</button>
</div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<div id="emailProgressPanel" class="fixed bottom-4 right-4 w-80 z-50 transition-all duration-300 translate-y-4 opacity-0 pointer-events-none">
<div class="card bg-base-200 shadow-xl border border-base-300">
<div class="card-body p-4 gap-2">
<div class="flex items-center justify-between">
<h3 class="font-bold text-sm" id="emailProgressTitle"></h3>
<button class="btn btn-ghost btn-xs btn-circle" id="emailProgressClose"></button>
</div>
<ul id="emailProgressSteps" class="space-y-1 text-sm"></ul>
</div>
</div>
</div>
{% block scripts %}
<script>document.addEventListener('DOMContentLoaded', () => { emailCheckPermissions(); emailLoadOrphanedDomains(); });</script>
{% endblock %}
{% endblock %}