|
|
@@ -11,6 +11,7 @@ const state = {
|
|
|
settings: null,
|
|
|
selectedId: null,
|
|
|
view: 'dashboard',
|
|
|
+ detailTab: 'dns',
|
|
|
dnsTestResults: {},
|
|
|
busy: false
|
|
|
};
|
|
|
@@ -125,6 +126,7 @@ function bindEvents() {
|
|
|
const item = event.target.closest('[data-domain-id]');
|
|
|
if (!item) return;
|
|
|
state.selectedId = Number(item.dataset.domainId);
|
|
|
+ state.detailTab = 'dns';
|
|
|
setView('domains');
|
|
|
render();
|
|
|
});
|
|
|
@@ -782,8 +784,107 @@ function renderDetail() {
|
|
|
</div>
|
|
|
<div class="card-body detail-body">
|
|
|
${renderSetupOverview(domain, guide, records)}
|
|
|
- ${guide.apply ? renderApplyResult(guide.apply) : ''}
|
|
|
${warnings.length ? renderWarnings(warnings) : ''}
|
|
|
+ ${renderDetailTabs()}
|
|
|
+ ${renderDetailTabContent(domain, guide, records)}
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+}
|
|
|
+
|
|
|
+function renderDetailTabs() {
|
|
|
+ const tabs = [
|
|
|
+ ['dns', 'DNS 配置', 'ti-dns'],
|
|
|
+ ['settings', '域名设置', 'ti-adjustments'],
|
|
|
+ ['send', '测试发送', 'ti-send'],
|
|
|
+ ['integrations', '接入方式', 'ti-plug-connected'],
|
|
|
+ ['events', '发送记录', 'ti-list-details']
|
|
|
+ ];
|
|
|
+ return `
|
|
|
+ <div class="detail-tabs" role="tablist">
|
|
|
+ ${tabs.map(([key, label, icon]) => `
|
|
|
+ <button class="${state.detailTab === key ? 'active' : ''}" data-action="detail-tab" data-tab="${key}" type="button">
|
|
|
+ <i class="ti ${icon}" aria-hidden="true"></i>
|
|
|
+ <span>${label}</span>
|
|
|
+ </button>
|
|
|
+ `).join('')}
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+}
|
|
|
+
|
|
|
+function renderDetailTabContent(domain, guide, records) {
|
|
|
+ if (state.detailTab === 'settings') {
|
|
|
+ return `
|
|
|
+ <section class="detail-tab-panel">
|
|
|
+ <div class="row row-cards">
|
|
|
+ <section class="col-lg-7">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">域名设置</h3></div>
|
|
|
+ <div class="card-body">${renderSettingsForm(domain)}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <section class="col-lg-5">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">增强安全记录</h3></div>
|
|
|
+ <div class="card-body">${renderOptionalRecords(guide.optionalRecords || [])}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ if (state.detailTab === 'send') {
|
|
|
+ return `
|
|
|
+ <section class="detail-tab-panel">
|
|
|
+ <div class="row row-cards">
|
|
|
+ <section class="col-lg-5">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">测试发送</h3></div>
|
|
|
+ <div class="card-body">${renderSendForm(domain)}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <section class="col-lg-7">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">最近发送</h3></div>
|
|
|
+ <div class="card-body">${renderEvents(domain)}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ if (state.detailTab === 'integrations') {
|
|
|
+ return `
|
|
|
+ <section class="detail-tab-panel">
|
|
|
+ <div class="row row-cards">
|
|
|
+ <section class="col-lg-6">
|
|
|
+ <div class="card subpanel api-box">
|
|
|
+ <div class="card-header"><h3 class="card-title">发送 API</h3></div>
|
|
|
+ <div class="card-body"><pre><code>${escapeHtml(apiExample(domain))}</code></pre></div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <section class="col-lg-6">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">SMTP 发信</h3></div>
|
|
|
+ <div class="card-body">${renderSmtpBox(domain)}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ if (state.detailTab === 'events') {
|
|
|
+ return `
|
|
|
+ <section class="detail-tab-panel">
|
|
|
+ <div class="card subpanel">
|
|
|
+ <div class="card-header"><h3 class="card-title">发送记录</h3></div>
|
|
|
+ <div class="card-body">${renderEvents(domain, 20)}</div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ return `
|
|
|
+ <section class="detail-tab-panel">
|
|
|
+ ${guide.apply ? renderApplyResult(guide.apply) : ''}
|
|
|
<div class="row row-cards">
|
|
|
<section class="col-lg-7">
|
|
|
<div class="card subpanel">
|
|
|
@@ -791,55 +892,17 @@ function renderDetail() {
|
|
|
<h3 class="card-title">配置引导</h3>
|
|
|
<div class="card-actions"><button class="btn btn-sm btn-outline-secondary" data-action="copy-all-dns" type="button">复制全部</button></div>
|
|
|
</div>
|
|
|
- <div class="card-body">
|
|
|
- ${renderDnsGuide(records)}
|
|
|
- </div>
|
|
|
+ <div class="card-body">${renderDnsGuide(records)}</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
<section class="col-lg-5">
|
|
|
- <div class="card subpanel">
|
|
|
- <div class="card-header"><h3 class="card-title">域名设置</h3></div>
|
|
|
- <div class="card-body">${renderSettingsForm(domain)}</div>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
- </div>
|
|
|
- <div class="row row-cards">
|
|
|
- <section class="col-lg-6">
|
|
|
<div class="card subpanel">
|
|
|
<div class="card-header"><h3 class="card-title">当前 DNS</h3></div>
|
|
|
<div class="card-body">${renderLiveDns(guide.live)}</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
- <section class="col-lg-6">
|
|
|
- <div class="card subpanel">
|
|
|
- <div class="card-header"><h3 class="card-title">测试发送</h3></div>
|
|
|
- <div class="card-body">${renderSendForm(domain)}</div>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
</div>
|
|
|
- <div class="row row-cards">
|
|
|
- <section class="col-lg-6">
|
|
|
- <div class="card subpanel api-box">
|
|
|
- <div class="card-header"><h3 class="card-title">发送 API</h3></div>
|
|
|
- <div class="card-body"><pre><code>${escapeHtml(apiExample(domain))}</code></pre></div>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
- <section class="col-lg-6">
|
|
|
- <div class="card subpanel">
|
|
|
- <div class="card-header"><h3 class="card-title">SMTP 发信</h3></div>
|
|
|
- <div class="card-body">${renderSmtpBox(domain)}</div>
|
|
|
- </div>
|
|
|
- </section>
|
|
|
- </div>
|
|
|
- <section class="card subpanel">
|
|
|
- <div class="card-header"><h3 class="card-title">最近发送</h3></div>
|
|
|
- <div class="card-body">${renderEvents(domain)}</div>
|
|
|
- </section>
|
|
|
- <section class="card subpanel">
|
|
|
- <div class="card-header"><h3 class="card-title">增强安全记录</h3></div>
|
|
|
- <div class="card-body">${renderOptionalRecords(guide.optionalRecords || [])}</div>
|
|
|
- </section>
|
|
|
- </div>
|
|
|
+ </section>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
@@ -1003,8 +1066,8 @@ function renderSendForm(domain) {
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
-function renderEvents(domain) {
|
|
|
- const events = state.events.filter((event) => event.domain === domain.domain).slice(0, 8);
|
|
|
+function renderEvents(domain, limit = 8) {
|
|
|
+ const events = state.events.filter((event) => event.domain === domain.domain).slice(0, limit);
|
|
|
if (!events.length) return '<p class="text-secondary">暂无发送记录</p>';
|
|
|
return `<div class="event-list">${events.map((event) => `<div class="event-row"><div class="item-line"><strong>${escapeHtml(event.subject)}</strong>${badge({ className: event.status === 'queued' ? 'ok' : 'failed', label: event.status })}</div><span class="text-secondary">${escapeHtml(event.sender)} -> ${escapeHtml((event.recipients || []).join(', '))}</span><span class="text-secondary">${escapeHtml(formatDate(event.createdAt))}</span></div>`).join('')}</div>`;
|
|
|
}
|
|
|
@@ -1161,6 +1224,7 @@ async function handleGlobalClick(event) {
|
|
|
if (viewButton && !event.target.closest('[data-domain-id]')) {
|
|
|
event.preventDefault();
|
|
|
setView(viewButton.dataset.view);
|
|
|
+ render();
|
|
|
return;
|
|
|
}
|
|
|
const copyButton = event.target.closest('[data-copy]');
|
|
|
@@ -1179,6 +1243,7 @@ async function handleGlobalClick(event) {
|
|
|
if (action === 'check') return checkSelected();
|
|
|
if (action === 'apply-dns') return applyDnsSelected();
|
|
|
if (action === 'copy-all-dns') return copyAllDns();
|
|
|
+ if (action === 'detail-tab') return selectDetailTab(actionTarget.dataset.tab);
|
|
|
if (action === 'rotate-dkim') return rotateDkim();
|
|
|
if (action === 'delete-domain') return deleteSelected();
|
|
|
if (action === 'edit-dns') return editDnsCredential(id);
|
|
|
@@ -1197,6 +1262,13 @@ async function handleDetailSubmit(event) {
|
|
|
if (form.dataset.form === 'admin-settings') return saveAdminSettings(form);
|
|
|
}
|
|
|
|
|
|
+function selectDetailTab(tab) {
|
|
|
+ const allowed = ['dns', 'settings', 'send', 'integrations', 'events'];
|
|
|
+ if (!allowed.includes(tab)) return;
|
|
|
+ state.detailTab = tab;
|
|
|
+ renderDetail();
|
|
|
+}
|
|
|
+
|
|
|
async function checkSelected() {
|
|
|
const id = state.selectedId;
|
|
|
if (!id) return;
|