二阶段差不多

This commit is contained in:
2026-06-01 19:46:51 +08:00
parent 9e50d05e71
commit 4e233c82b4
34 changed files with 1631 additions and 67 deletions
+80
View File
@@ -0,0 +1,80 @@
{{define "admin_bans"}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IP黑名单 - MailGo</title>
{{template "styles" .}}
</head>
<body>
{{template "navbar" .}}
<div class="container">
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<h2>IP 黑名单</h2>
<form method="POST" action="/admin/bans/cleanup" style="display:inline;">
<button type="submit" class="btn btn-primary">清理过期记录</button>
</form>
</div>
{{if not .bans}}
<p style="color:#7f8c8d;text-align:center;padding:40px 0;">暂无被封禁的 IP</p>
{{else}}
<table>
<thead>
<tr>
<th>ID</th>
<th>IP 地址</th>
<th>失败次数</th>
<th>原因</th>
<th>到期时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{range .bans}}
<tr>
<td>{{.ID}}</td>
<td>{{.IPAddress}}</td>
<td>{{.FailCount}}</td>
<td>{{.Reason}}</td>
<td>{{.ExpiresAt.Format "2006-01-02 15:04:05"}}</td>
<td>
<form method="POST" action="/admin/bans/{{.ID}}/unban" style="display:inline;"
onsubmit="return confirm('确定要解封 IP {{.IPAddress}} 吗?');">
<button type="submit" class="btn btn-primary btn-sm">解封</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
</div>
{{if .totalPages}}
<div class="pagination">
{{if gt .page 1}}
<a href="/admin/bans?page={{sub .page 1}}">上一页</a>
{{end}}
<span>第 {{.page}} / {{.totalPages}} 页</span>
{{if lt .page .totalPages}}
<a href="/admin/bans?page={{add .page 1}}">下一页</a>
{{end}}
</div>
{{end}}
</div>
</div>
</div>
</body>
</html>
{{end}}
+46 -3
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin" class="active">控制面板</a>
<a href="/admin/domains">域名管理</a>
<a href="/admin/users">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<h2 style="margin-bottom:24px;">管理后台</h2>
@@ -28,12 +30,53 @@
<h3>{{.userCount}}</h3>
<p>用户数</p>
</div>
<div class="stat-card">
<h3>{{.totalMails}}</h3>
<p>邮件总数</p>
</div>
<div class="stat-card">
<h3>{{.banCount}}</h3>
<p>被封IP</p>
</div>
</div>
<div class="card">
<h3>邮件分布</h3>
<table style="margin-top:12px;">
<thead>
<tr>
<th>文件夹</th>
<th>邮件数</th>
<th>占用空间</th>
</tr>
</thead>
<tbody>
<tr><td>收件箱 (INBOX)</td><td>{{.inboxCount}}</td><td>{{formatBytes .inboxSize}}</td></tr>
<tr><td>发件箱 (Sent)</td><td>{{.sentCount}}</td><td>{{formatBytes .sentSize}}</td></tr>
<tr><td>草稿箱 (Drafts)</td><td>{{.draftsCount}}</td><td></td></tr>
<tr><td>垃圾箱 (Trash)</td><td>{{.trashCount}}</td><td></td></tr>
<tr style="font-weight:bold;"><td>合计</td><td>{{.totalMails}}</td><td>{{formatBytes .totalSize}}</td></tr>
</tbody>
</table>
</div>
<div class="card">
<h3>收发统计</h3>
<table style="margin-top:12px;">
<thead>
<tr><th>时间段</th><th>收件</th><th>发件</th></tr>
</thead>
<tbody>
<tr><td>今日</td><td>{{.todayReceived}}</td><td>{{.todaySent}}</td></tr>
<tr><td>近 7 天</td><td>{{.weekReceived}}</td><td>{{.weekSent}}</td></tr>
</tbody>
</table>
</div>
<div class="card">
<h3>快捷操作</h3>
<p style="margin-top:12px;">
<a href="/admin/domains/new" class="btn btn-primary">新增域名</a>
<a href="/admin/users/new" class="btn btn-primary" style="margin-left:8px;">新增用户</a>
<a href="/admin/mails" class="btn btn-primary" style="margin-left:8px;">查看所有邮件</a>
<a href="/admin/bans" class="btn btn-primary" style="margin-left:8px;">IP黑名单</a>
</p>
</div>
</div>
+11 -5
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin">控制面板</a>
<a href="/admin/domains" class="active">域名管理</a>
<a href="/admin/users">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
@@ -28,8 +30,12 @@
<h4 style="margin-bottom:8px;">SPF 记录 (TXT)</h4>
<div class="dns-record">@ IN TXT "v=spf1 mx -all"</div>
<h4 style="margin-bottom:8px;">DKIM 记录</h4>
<div class="dns-record" style="color:#e67e22;">⚠️ 请在域名配置页配置 DKIM 私钥路径后,将自动生成 DKIM 公钥记录。</div>
<h4 style="margin-bottom:8px;">DKIM 记录 (TXT)</h4>
{{if .domain.DkimPublicKey}}
<div class="dns-record">default._domainkey.{{.domain.Name}}. IN TXT "{{.dkimRecord}}"</div>
{{else}}
<div class="dns-record" style="color:#e67e22;">⚠️ DKIM 密钥尚未生成,请编辑域名重新生成。</div>
{{end}}
<h4 style="margin-bottom:8px;">DMARC 记录</h4>
<div class="dns-record">_dmarc.{{.domain.Name}}. IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@{{.domain.Name}}"</div>
+22 -3
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin">控制面板</a>
<a href="/admin/domains" class="active">域名管理</a>
<a href="/admin/users">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
@@ -24,7 +26,11 @@
<form method="POST" action="{{if .isEdit}}/admin/domains/{{.domain.ID}}{{else}}/admin/domains{{end}}">
<div class="form-group">
<label>域名</label>
{{if .isEdit}}
<input type="text" name="name" value="{{.domain.Name}}" disabled style="background:#f5f5f5;">
{{else}}
<input type="text" name="name" required value="{{.domain.Name}}" placeholder="example.com">
{{end}}
</div>
<div class="form-group">
<label>SMTP 端口</label>
@@ -44,6 +50,19 @@
启用 TLS
</label>
</div>
{{if .isEdit}}
<div class="form-group">
<label>
<input type="checkbox" name="regenerate_dkim">
重新生成 DKIM 密钥
</label>
{{if .domain.DkimPublicKey}}
<p style="color:#27ae60;font-size:12px;margin-top:4px;">✅ DKIM 密钥已配置</p>
{{else}}
<p style="color:#e67e22;font-size:12px;margin-top:4px;">⚠️ DKIM 密钥未配置</p>
{{end}}
</div>
{{end}}
<button type="submit" class="btn btn-primary">{{if .isEdit}}保存更改{{else}}创建域名{{end}}</button>
<a href="/admin/domains" class="btn" style="margin-left:8px;background:#bdc3c7;color:#fff;">取消</a>
</form>
+6 -3
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin">控制面板</a>
<a href="/admin/domains" class="active">域名管理</a>
<a href="/admin/users">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
@@ -48,6 +50,7 @@
<td>{{.Pop3Port}}</td>
<td>{{if .TlsEnabled}}✅{{else}}❌{{end}}</td>
<td>
<a href="/admin/domains/{{.ID}}/edit" class="btn btn-primary btn-sm">编辑</a>
<a href="/admin/domains/{{.ID}}/dns" class="btn btn-primary btn-sm">DNS</a>
<form method="POST" action="/admin/domains/{{.ID}}/delete" style="display:inline;"
onsubmit="return confirm('确定要删除域名 {{.Name}} 吗?删除域名将导致关联用户无法收发邮件!');">
+77
View File
@@ -0,0 +1,77 @@
{{define "admin_mails"}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>所有邮件 - MailGo</title>
{{template "styles" .}}
</head>
<body>
{{template "navbar" .}}
<div class="container">
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
<h2 style="margin-bottom:16px;">所有邮件</h2>
<div style="margin-bottom:16px;">
<a href="/admin/mails" {{if eq .folder ""}}class="btn btn-primary"{{else}}class="btn" style="background:#ecf0f1;color:#333;"{{end}}>全部</a>
<a href="/admin/mails?folder=INBOX" {{if eq .folder "INBOX"}}class="btn btn-primary"{{else}}class="btn" style="background:#ecf0f1;color:#333;"{{end}}>INBOX</a>
<a href="/admin/mails?folder=Sent" {{if eq .folder "Sent"}}class="btn btn-primary"{{else}}class="btn" style="background:#ecf0f1;color:#333;"{{end}}>Sent</a>
<a href="/admin/mails?folder=Drafts" {{if eq .folder "Drafts"}}class="btn btn-primary"{{else}}class="btn" style="background:#ecf0f1;color:#333;"{{end}}>Drafts</a>
<a href="/admin/mails?folder=Trash" {{if eq .folder "Trash"}}class="btn btn-primary"{{else}}class="btn" style="background:#ecf0f1;color:#333;"{{end}}>Trash</a>
</div>
{{if not .messages}}
<p style="color:#7f8c8d;text-align:center;padding:40px 0;">暂无邮件</p>
{{else}}
<table>
<thead>
<tr>
<th>发件人</th>
<th>收件人</th>
<th>主题</th>
<th>所属用户</th>
<th>文件夹</th>
<th>日期</th>
</tr>
</thead>
<tbody>
{{range .messages}}
<tr>
<td>{{.FromAddr}}</td>
<td>{{.ToAddr}}</td>
<td>{{.Subject}}</td>
<td>{{if .User.ID}}{{.User.Username}}{{else}}—{{end}}</td>
<td>{{.Folder}}</td>
<td>{{.Date.Format "2006-01-02 15:04"}}</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
</div>
{{if .totalPages}}
<div class="pagination">
{{if gt .page 1}}
<a href="/admin/mails?page={{sub .page 1}}{{if .folder}}&folder={{.folder}}{{end}}">上一页</a>
{{end}}
<span>第 {{.page}} / {{.totalPages}} 页</span>
{{if lt .page .totalPages}}
<a href="/admin/mails?page={{add .page 1}}{{if .folder}}&folder={{.folder}}{{end}}">下一页</a>
{{end}}
</div>
{{end}}
</div>
</div>
</div>
</body>
</html>
{{end}}
+5 -3
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin">控制面板</a>
<a href="/admin/domains">域名管理</a>
<a href="/admin/users" class="active">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
+5 -3
View File
@@ -13,9 +13,11 @@
<div class="clearfix">
<div class="sidebar">
<a href="/inbox">返回邮箱</a>
<a href="/admin">控制面板</a>
<a href="/admin/domains">域名管理</a>
<a href="/admin/users" class="active">用户管理</a>
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
</div>
<div class="content">
<div class="card">
+54
View File
@@ -0,0 +1,54 @@
{{define "banned"}}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>访问被禁止 - MailGo</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f5; color: #333; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.banned-card { background: #fff; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); padding: 48px; text-align: center; max-width: 480px; width: 100%; }
.banned-icon { font-size: 64px; margin-bottom: 16px; }
h1 { font-size: 24px; color: #c0392b; margin-bottom: 12px; }
p { color: #7f8c8d; line-height: 1.6; margin-bottom: 8px; }
.detail { background: #f8f9fa; border-radius: 6px; padding: 16px; margin: 16px 0; text-align: left; }
.detail-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #eee; }
.detail-row:last-child { border-bottom: none; }
.detail-label { color: #7f8c8d; font-size: 13px; }
.detail-value { color: #2c3e50; font-weight: 600; font-size: 13px; }
.back-link { display: inline-block; margin-top: 20px; color: #3498db; text-decoration: none; }
.back-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="banned-card">
<div class="banned-icon">🚫</div>
<h1>您的 IP 已被暂时禁止访问</h1>
<p>由于登录失败次数过多,您的 IP 地址已被暂时封禁。</p>
{{if .entry}}
<div class="detail">
<div class="detail-row">
<span class="detail-label">IP 地址</span>
<span class="detail-value">{{.entry.IPAddress}}</span>
</div>
<div class="detail-row">
<span class="detail-label">原因</span>
<span class="detail-value">{{.entry.Reason}}</span>
</div>
<div class="detail-row">
<span class="detail-label">失败次数</span>
<span class="detail-value">{{.entry.FailCount}} 次</span>
</div>
<div class="detail-row">
<span class="detail-label">解封时间</span>
<span class="detail-value">{{.entry.ExpiresAt.Format "2006-01-02 15:04:05"}}</span>
</div>
</div>
{{end}}
<p style="font-size:13px;">请在解封时间后重试,或联系管理员。</p>
<a href="/login" class="back-link">返回登录页</a>
</div>
</body>
</html>
{{end}}
+31 -1
View File
@@ -5,6 +5,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>撰写邮件 - MailGo</title>
<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
{{template "styles" .}}
</head>
<body>
@@ -37,12 +38,17 @@
</div>
<div class="form-group">
<label>正文</label>
<textarea name="body" rows="12" placeholder="请输入邮件内容..."></textarea>
<div id="editor" style="height:300px;"></div>
<input type="hidden" name="body" id="body-hidden">
<input type="hidden" name="html_body" id="html-body-hidden">
</div>
<div class="form-group">
<label>附件</label>
<input type="file" name="attachments" multiple>
</div>
<div class="form-group" style="color:#7f8c8d;font-size:12px;">
配额: {{formatBytes .usedBytes}} / {{formatBytes .quotaBytes}}
</div>
<button type="submit" class="btn btn-primary">发送邮件</button>
<a href="/inbox" class="btn" style="margin-left:8px;">取消</a>
</form>
@@ -50,6 +56,30 @@
</div>
</div>
</div>
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
<script>
var quill = new Quill('#editor', {
theme: 'snow',
placeholder: '请输入邮件内容...',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link', 'image'],
['clean']
]
}
});
document.querySelector('form').addEventListener('submit', function() {
document.getElementById('body-hidden').value = quill.getText();
document.getElementById('html-body-hidden').value = quill.root.innerHTML;
});
{{if .bodyContent}}
quill.root.innerHTML = {{.bodyContent | safeJS}};
{{end}}
</script>
</body>
</html>
{{end}}
+24
View File
@@ -25,6 +25,30 @@
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">登录</button>
</form>
{{if or .oauth2Enabled .ldapEnabled}}
<div style="text-align:center;margin:16px 0;color:#7f8c8d;">─── 或 ───</div>
{{end}}
{{if .ldapEnabled}}
<form method="POST" action="/login/ldap">
<div class="form-group">
<label>LDAP 用户名</label>
<input type="text" name="username" placeholder="LDAP 用户名">
</div>
<div class="form-group">
<label>LDAP 密码</label>
<input type="password" name="password" placeholder="LDAP 密码">
</div>
<button type="submit" class="btn" style="width:100%;background:#8e44ad;color:#fff;">LDAP 登录</button>
</form>
{{end}}
{{if .oauth2Enabled}}
<a href="/auth/oauth2" class="btn" style="width:100%;background:#3498db;color:#fff;text-align:center;display:block;margin-top:8px;">
{{if eq .oauth2Provider "google"}}Google{{else if eq .oauth2Provider "github"}}GitHub{{else}}OAuth2{{end}} 登录
</a>
{{end}}
</div>
</div>
</div>