mail/app/templates/admin/domains.html
deeily 5024bf9a8d init: full mail stack — phases 0..8 (web client, admin, IMAP/SMTP,
sieve, search, sessions, dramatiq, deploy/install, ELK, monitoring)
2026-04-29 16:30:43 +03:00

174 lines
8.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'layout/base.html' %}
{% block title %}Домены · Админка{% endblock %}
{% block body %}
<div class="content">
{% include 'admin/_flash.html' %}
<div class="card">
<div class="card-head" style="display:flex;align-items:center">
<div class="card-title">Домены ({{ domains|length }})</div>
<span style="flex:1"></span>
<button type="button" class="btn btn-primary" data-action="domain-wizard-open">+ Добавить домен</button>
</div>
<div class="card-body" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
{% for d in domains %}
<a class="btn{% if d == selected %} btn-primary{% endif %}" href="?d={{ d }}">{{ d }}</a>
{% else %}
<span style="color:var(--color-text-tertiary)">Доменов пока нет — нажмите «Добавить домен», чтобы пройти настройку по шагам.</span>
{% endfor %}
</div>
</div>
{% if selected %}
<div class="card" style="margin-top:14px">
<div class="card-head" style="display:flex;align-items:center;gap:10px">
<div class="card-title">DNS-записи для <code>{{ selected }}</code></div>
<span style="flex:1"></span>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="dkim">
<input type="hidden" name="domain" value="{{ selected }}">
<button class="btn" type="submit" title="Создать DKIM-ключ для этого домена">Создать DKIM</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="reload">
<button class="btn" type="submit" title="Перезапустить Postfix">Reload Postfix</button>
</form>
</div>
<div class="card-body" style="padding:0">
<table class="tbl">
<thead>
<tr><th>Тип</th><th>Имя</th><th>Значение</th><th>Назначение</th></tr>
</thead>
<tbody>
{% for r in records %}
<tr>
<td><span class="dns-type">{{ r.type }}</span></td>
<td><code>{{ r.name }}</code></td>
<td style="max-width:480px"><code style="word-break:break-all;white-space:pre-wrap">{{ r.value }}</code></td>
<td style="color:var(--color-text-secondary)">{{ r.hint }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-body" style="border-top:0.5px solid var(--color-border-tertiary);color:var(--color-text-secondary);font-size:12px">
Скопируйте записи в свой DNS-регистратор. После применения проверьте через
<code>dig MX {{ selected }}</code>, <code>dig TXT mail._domainkey.{{ selected }}</code>.
Для отправки наружу нужен также правильный rDNS на IP-адресе сервера.
</div>
</div>
{% endif %}
</div>
<div class="overlay" id="domain-wizard-modal">
<div class="modal wiz-modal">
<div class="compose-head">
<div class="compose-title">Новый домен</div>
<div class="compose-head-actions">
<button class="compose-head-btn" data-action="modal-close" title="Закрыть">×</button>
</div>
</div>
<div class="wiz-content" id="domain-wizard-content">
<div class="wiz-loading">Загрузка…</div>
</div>
</div>
</div>
<style>
.wiz-modal{width:min(960px,96vw);max-width:96vw;display:flex;flex-direction:column;max-height:90vh}
.wiz-content{padding:18px 22px;overflow-y:auto}
.wiz-loading{text-align:center;color:var(--color-text-tertiary);padding:40px}
.wiz-body .wiz-steps{display:flex;align-items:center;gap:14px;margin-bottom:18px;padding-bottom:14px;border-bottom:0.5px solid var(--color-border-tertiary)}
.wiz-body .wiz-step{display:flex;align-items:center;gap:8px;color:var(--color-text-tertiary);font-size:13px}
.wiz-body .wiz-step.active{color:var(--color-text-primary);font-weight:500}
.wiz-body .wiz-step.done{color:var(--color-text-secondary)}
.wiz-num{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:var(--color-background-secondary);border:0.5px solid var(--color-border-tertiary);font-size:12px}
.wiz-step.active .wiz-num{background:var(--color-text-primary);color:var(--color-background-primary);border-color:transparent}
.wiz-step.done .wiz-num{background:#2e7d32;color:#fff;border-color:transparent;font-size:0}
.wiz-step.done .wiz-num::before{content:'✓';font-size:13px}
.wiz-line{flex:1;height:0.5px;background:var(--color-border-tertiary);max-width:80px}
.wiz-title{font-size:14px;font-weight:500;margin-bottom:14px;display:flex;align-items:center;gap:10px}
.wiz-recheck{margin-left:auto;font-size:12px;color:var(--color-text-secondary);text-decoration:none;padding:3px 8px;border:0.5px solid var(--color-border-tertiary);border-radius:4px}
.wiz-recheck:hover{background:var(--color-background-secondary)}
.wiz-form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.wiz-form-row label{color:var(--color-text-secondary);font-size:12px}
.wiz-input{flex:1;min-width:200px;border:0.5px solid var(--color-border-tertiary);border-radius:6px;padding:6px 10px;font-size:13px;font-family:inherit;background:var(--color-background-primary);color:var(--color-text-primary)}
.wiz-hint{color:var(--color-text-secondary);font-size:12px;margin-bottom:10px;line-height:1.5}
.wiz-table-wrap{border:0.5px solid var(--color-border-tertiary);border-radius:6px;overflow-x:auto;margin-bottom:14px}
.wiz-table-wrap .tbl td{padding:8px 12px}
.wiz-actions{display:flex;gap:8px;align-items:center;margin-top:18px;padding-top:14px;border-top:0.5px solid var(--color-border-tertiary)}
.wiz-actions .spacer{flex:1}
.dns-ok{color:#2e7d32;font-weight:600;font-size:14px}
.dns-fail{color:var(--color-text-danger);font-weight:600;font-size:14px}
.dns-type{display:inline-block;padding:1px 6px;background:var(--color-background-secondary);border:0.5px solid var(--color-border-tertiary);border-radius:4px;font-size:11px;font-family:monospace}
code{font-family:'SF Mono',Menlo,monospace;font-size:12px}
.break-all{word-break:break-all;white-space:pre-wrap}
.small{font-size:11px}
.muted{color:var(--color-text-secondary);font-size:12px}
.wiz-section{margin-top:18px;padding-top:14px;border-top:0.5px solid var(--color-border-tertiary)}
.wiz-subtitle{font-size:13px;font-weight:500;margin-bottom:6px}
.wiz-mb-list{display:flex;flex-direction:column;gap:4px}
.wiz-mb-item{font-size:12px;color:#2e7d32;padding:4px 0}
.flash-stack{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.flash{padding:8px 12px;border-radius:6px;border:0.5px solid var(--color-border-tertiary);font-size:12px}
.flash-ok{background:#e8f5e9;color:#1b5e20;border-color:#c8e6c9}
.flash-err{background:#fce8e6;color:#9b2c2c;border-color:#f5c0c0}
body.dark .flash-ok{background:#0f3a18;color:#a8e8b9;border-color:#1f5a2d}
body.dark .flash-err{background:#3a1212;color:#f0a8a8;border-color:#5c2020}
</style>
<script>
(function(){
const root = document.getElementById('domain-wizard-content');
const modal = document.getElementById('domain-wizard-modal');
if(!root || !modal) return;
async function loadFrag(url, opts){
root.innerHTML = '<div class="wiz-loading">Загрузка…</div>';
try {
const r = await fetch(url, Object.assign({headers: {'X-Wizard-Frag': '1'}, redirect: 'follow'}, opts || {}));
const text = await r.text();
if(text.trim() === '__DONE__'){
modal.classList.remove('open');
location.reload();
return;
}
root.innerHTML = text;
} catch(e) {
root.innerHTML = '<div class="wiz-loading">Ошибка загрузки</div>';
}
}
document.addEventListener('click', e => {
const open = e.target.closest('[data-action="domain-wizard-open"]');
if(open){
e.preventDefault();
modal.classList.add('open');
loadFrag('/admin/domains/new?frag=1');
return;
}
const a = e.target.closest('#domain-wizard-content a[href]');
if(a){
e.preventDefault();
loadFrag(a.getAttribute('href'));
}
});
document.addEventListener('submit', e => {
if(!root.contains(e.target)) return;
e.preventDefault();
const f = e.target;
const fd = new FormData(f);
const params = new URLSearchParams();
for(const [k,v] of fd.entries()) params.append(k, v);
const url = f.getAttribute('action') || '/admin/domains/new';
loadFrag(url, {
method: 'POST',
body: params,
headers: {'X-Wizard-Frag': '1', 'Content-Type': 'application/x-www-form-urlencoded'},
});
});
})();
</script>
{% endblock %}