全面优化样式,修复注册验证码功能
This commit is contained in:
parent
1720d69be6
commit
4d802ba369
209
app.py
209
app.py
@ -14,6 +14,9 @@ import zipfile
|
||||
import shutil
|
||||
import re
|
||||
from pypinyin import pinyin, Style
|
||||
from flask import send_file
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from flask_migrate import Migrate
|
||||
|
||||
# 从配置文件中导入配置
|
||||
@ -22,6 +25,10 @@ from config import Config
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
|
||||
@app.context_processor
|
||||
def inject_now():
|
||||
return {'now': datetime.now()}
|
||||
|
||||
# 确保证书存储目录存在
|
||||
os.makedirs(Config.CERT_STORE, exist_ok=True)
|
||||
|
||||
@ -37,6 +44,89 @@ login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
from io import BytesIO
|
||||
from flask import send_file
|
||||
import random
|
||||
import string
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
def generate_captcha_image():
|
||||
# 生成4位随机验证码(字母和数字)
|
||||
captcha_code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
|
||||
|
||||
# 创建图片(120x40像素,白色背景)
|
||||
image = Image.new('RGB', (120, 40), color=(255, 255, 255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# 尝试加载字体,失败则使用默认字体
|
||||
try:
|
||||
font = ImageFont.truetype("arial.ttf", 24)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
# 绘制验证码文本(每个字符随机颜色)
|
||||
for i, char in enumerate(captcha_code):
|
||||
text_color = (
|
||||
random.randint(0, 150), # R
|
||||
random.randint(0, 150), # G
|
||||
random.randint(0, 150) # B
|
||||
)
|
||||
draw.text(
|
||||
(10 + i * 25, 5), # 位置
|
||||
char, # 文本
|
||||
font=font, # 字体
|
||||
fill=text_color # 颜色
|
||||
)
|
||||
|
||||
# 添加5条干扰线(随机位置和颜色)
|
||||
for _ in range(5):
|
||||
line_color = (
|
||||
random.randint(0, 255), # R
|
||||
random.randint(0, 255), # G
|
||||
random.randint(0, 255) # B
|
||||
)
|
||||
draw.line(
|
||||
[
|
||||
random.randint(0, 120), # x1
|
||||
random.randint(0, 40), # y1
|
||||
random.randint(0, 120), # x2
|
||||
random.randint(0, 40) # y2
|
||||
],
|
||||
fill=line_color,
|
||||
width=1
|
||||
)
|
||||
|
||||
# 保存验证码到数据库
|
||||
conn = get_db_connection()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
# 清除10分钟前的旧验证码
|
||||
cursor.execute("DELETE FROM captcha WHERE created_at < NOW() - INTERVAL 10 MINUTE")
|
||||
# 插入新验证码
|
||||
cursor.execute("INSERT INTO captcha (code) VALUES (%s)", (captcha_code,))
|
||||
conn.commit()
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
finally:
|
||||
if conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
# 返回图片字节流
|
||||
img_io = BytesIO()
|
||||
image.save(img_io, 'PNG')
|
||||
img_io.seek(0)
|
||||
return img_io, captcha_code
|
||||
|
||||
|
||||
@app.route('/captcha')
|
||||
def captcha():
|
||||
img_io, _ = generate_captcha_image()
|
||||
return send_file(img_io, mimetype='image/png')
|
||||
|
||||
|
||||
def to_pinyin(text):
|
||||
"""将中文转换为拼音"""
|
||||
if not text:
|
||||
@ -608,9 +698,120 @@ def register():
|
||||
@app.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
conn = get_db_connection()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
|
||||
# 获取CA数量
|
||||
if current_user.is_admin:
|
||||
cursor.execute("SELECT COUNT(*) as count FROM certificate_authorities")
|
||||
ca_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
cursor.execute("""
|
||||
SELECT * FROM certificate_authorities
|
||||
ORDER BY created_at DESC LIMIT 5
|
||||
""")
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificate_authorities
|
||||
WHERE created_by = %s
|
||||
""", (current_user.id,))
|
||||
ca_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
cursor.execute("""
|
||||
SELECT * FROM certificate_authorities
|
||||
WHERE created_by = %s
|
||||
ORDER BY created_at DESC LIMIT 5
|
||||
""", (current_user.id,))
|
||||
recent_cas = cursor.fetchall()
|
||||
|
||||
# 获取证书数量
|
||||
if current_user.is_admin:
|
||||
cursor.execute("SELECT COUNT(*) as count FROM certificates")
|
||||
cert_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
cursor.execute("""
|
||||
SELECT * FROM certificates
|
||||
ORDER BY created_at DESC LIMIT 5
|
||||
""")
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificates
|
||||
WHERE created_by = %s
|
||||
""", (current_user.id,))
|
||||
cert_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
cursor.execute("""
|
||||
SELECT * FROM certificates
|
||||
WHERE created_by = %s
|
||||
ORDER BY created_at DESC LIMIT 5
|
||||
""", (current_user.id,))
|
||||
recent_certs = cursor.fetchall()
|
||||
|
||||
# 获取即将过期证书数量(30天内)
|
||||
if current_user.is_admin:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificates
|
||||
WHERE expires_at BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 30 DAY)
|
||||
AND status = 'active'
|
||||
""")
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificates
|
||||
WHERE expires_at BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 30 DAY)
|
||||
AND status = 'active' AND created_by = %s
|
||||
""", (current_user.id,))
|
||||
expiring_soon_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
# 获取活跃证书数量
|
||||
if current_user.is_admin:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificates
|
||||
WHERE status = 'active'
|
||||
""")
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count FROM certificates
|
||||
WHERE status = 'active' AND created_by = %s
|
||||
""", (current_user.id,))
|
||||
active_count = cursor.fetchone()['count']
|
||||
cursor.fetchall() # 确保清空结果集
|
||||
|
||||
return render_template('index.html',
|
||||
ca_count=ca_count,
|
||||
cert_count=cert_count,
|
||||
expiring_soon_count=expiring_soon_count,
|
||||
active_count=active_count,
|
||||
recent_cas=recent_cas,
|
||||
recent_certs=recent_certs)
|
||||
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
flash('获取系统统计信息失败', 'danger')
|
||||
return render_template('index.html',
|
||||
ca_count=0,
|
||||
cert_count=0,
|
||||
expiring_soon_count=0,
|
||||
active_count=0,
|
||||
recent_cas=[],
|
||||
recent_certs=[])
|
||||
finally:
|
||||
if conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return render_template('index.html',
|
||||
ca_count=0,
|
||||
cert_count=0,
|
||||
expiring_soon_count=0,
|
||||
active_count=0,
|
||||
recent_cas=[],
|
||||
recent_certs=[])
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
@ -677,7 +878,7 @@ def ca_list():
|
||||
else:
|
||||
cursor.execute("SELECT * FROM certificate_authorities WHERE created_by = %s", (current_user.id,))
|
||||
cas = cursor.fetchall()
|
||||
return render_template('ca_list.html', cas=cas)
|
||||
return render_template('ca_list.html', cas=cas, get_username=get_username)
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
flash('获取CA列表失败', 'danger')
|
||||
@ -899,7 +1100,7 @@ def certificate_list():
|
||||
ORDER BY c.created_at DESC
|
||||
""", (current_user.id,))
|
||||
certificates = cursor.fetchall()
|
||||
return render_template('certificate_list.html', certificates=certificates)
|
||||
return render_template('certificate_list.html', certificates=certificates, get_username=get_username)
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
flash('获取证书列表失败', 'danger')
|
||||
@ -1372,4 +1573,4 @@ def download_file(filename):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=Config.DEBUG, ssl_context='adhoc', host=Config.APP_HOST, port=Config.APP_PORT)
|
||||
app.run(debug=Config.DEBUG, host=Config.APP_HOST, port=Config.APP_PORT)
|
||||
@ -2,4 +2,7 @@ Flask==2.0.1
|
||||
Flask-Login==0.5.0
|
||||
mysql-connector-python==8.0.26
|
||||
python-dotenv==0.19.0
|
||||
cryptography==2.0
|
||||
cryptography==2.0
|
||||
pypinyin
|
||||
shutil
|
||||
flask_migrate
|
||||
@ -5,51 +5,147 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>证书管理系统 - {% block title %}{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.navbar {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.1);
|
||||
background-color: rgb(124 146 157) !important;
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.footer {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px 0;
|
||||
margin-top: auto;
|
||||
}
|
||||
.nav-item.active .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333333 !important;
|
||||
}
|
||||
</style>
|
||||
{% block styles %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">证书管理系统</a>
|
||||
<a class="navbar-brand fw-bold" href="{{ url_for('index') }}">
|
||||
<i class="fas fa-shield-alt me-2"></i>证书管理系统
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('ca_list') }}">CA机构</a>
|
||||
<li class="nav-item {% if request.path.startswith('/cas') %}active{% endif %}">
|
||||
<a class="nav-link" href="{{ url_for('ca_list') }}">
|
||||
<i class="fas fa-shield-alt me-1"></i>CA机构
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('certificate_list') }}">证书</a>
|
||||
<li class="nav-item {% if request.path.startswith('/certificates') %}active{% endif %}">
|
||||
<a class="nav-link" href="{{ url_for('certificate_list') }}">
|
||||
<i class="fas fa-certificate me-1"></i>证书
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
{% if current_user.is_authenticated %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user-circle me-1"></i>{{ current_user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
{% if current_user.is_admin %}
|
||||
<li><span class="dropdown-item-text text-muted small">管理员</span></li>
|
||||
{% else %}
|
||||
<li><span class="dropdown-item-text text-muted small">普通用户</span></li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('logout') }}">
|
||||
<i class="fas fa-sign-out-alt me-2"></i>退出登录
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<span class="navbar-text me-3">欢迎, {{ current_user.username }}</span>
|
||||
<a class="nav-link" href="{{ url_for('login') }}">
|
||||
<i class="fas fa-sign-in-alt me-1"></i>登录
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('logout') }}">退出</a>
|
||||
<a class="nav-link" href="{{ url_for('register') }}">
|
||||
<i class="fas fa-user-plus me-1"></i>注册
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show mt-3" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
{% if category == 'success' %}
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
{% elif category == 'danger' %}
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
{% elif category == 'warning' %}
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
{% endif %}
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container text-center text-muted">
|
||||
<small>
|
||||
<p class="mb-1">证书管理系统 © {{ now.year }} - 基于Flask构建</p>
|
||||
<p class="mb-0">版本 1.0.0</p>
|
||||
</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
<script>
|
||||
// 高亮当前导航项
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化工具提示
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// 初始化弹出框
|
||||
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
|
||||
return new bootstrap.Popover(popoverTriggerEl);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -4,39 +4,41 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>CA机构详情: {{ ca.name }}
|
||||
<small class="text-muted fs-6">(路径: {{ ca.name|to_pinyin }})</small>
|
||||
</h2>
|
||||
<div class="btn-group">
|
||||
<div>
|
||||
<h2 class="mb-1">CA机构详情: {{ ca.name }}</h2>
|
||||
<div class="text-muted fs-6">
|
||||
<i class="fas fa-folder-open me-1"></i>路径: {{ ca.name|to_pinyin }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('export_ca_view', ca_id=ca.id) }}"
|
||||
class="btn btn-primary me-2"
|
||||
class="btn btn-primary me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="导出CA证书(管理员可导出私钥)">
|
||||
<i class="fas fa-file-export me-1"></i> 导出CA
|
||||
</a>
|
||||
<a href="{{ url_for('generate_crl_view', ca_id=ca.id) }}"
|
||||
class="btn btn-warning me-2"
|
||||
class="btn btn-warning me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="重新生成证书吊销列表">
|
||||
<i class="fas fa-sync-alt me-1"></i> 生成CRL
|
||||
</a>
|
||||
{% if crl %}
|
||||
<a href="{{ url_for('download_crl', ca_id=ca.id) }}"
|
||||
class="btn btn-success"
|
||||
class="btn btn-success me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="下载当前CRL文件(有效期至 {{ crl.next_update.strftime('%Y-%m-%d') }})">
|
||||
<i class="fas fa-download me-1"></i> 下载CRL
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('delete_ca', ca_id=ca.id) }}"
|
||||
class="btn btn-danger me-2"
|
||||
class="btn btn-danger text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="删除此CA(谨慎操作)">
|
||||
<i class="fas fa-trash-alt me-1"></i> 删除CA
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
@ -95,7 +97,7 @@
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-clock me-2"></i> 创建时间</dt>
|
||||
<dd class="col-sm-8">{{ ca.created_at.strftime('%Y-%m-%d %H:%M') }}</dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-file-certificate me-2"></i> 证书路径</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-folder-open me-1"></i> 证书路径</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code>{{ ca.cert_path|to_pinyin }}</code>
|
||||
</dd>
|
||||
|
||||
@ -4,44 +4,70 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>CA机构列表</h2>
|
||||
<a href="{{ url_for('create_ca_view') }}" class="btn btn-primary">创建CA机构</a>
|
||||
<div>
|
||||
<h2 class="mb-1">CA机构列表</h2>
|
||||
<div class="text-muted fs-6">
|
||||
<i class="fas fa-shield-alt me-1"></i>共 {{ cas|length }} 个CA机构
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url_for('create_ca_view') }}" class="btn btn-primary btn-sm text-nowrap">
|
||||
<i class="fas fa-plus me-1"></i> 创建CA机构
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>名称</th>
|
||||
<th>通用名</th>
|
||||
<th>组织</th>
|
||||
<th>有效期(天)</th>
|
||||
<th>创建者</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ca in cas %}
|
||||
<tr>
|
||||
<td>{{ ca.id }}</td>
|
||||
<td>{{ ca.name }}</td>
|
||||
<td>{{ ca.common_name }}</td>
|
||||
<td>{{ ca.organization }}</td>
|
||||
<td>{{ ca.days_valid }}</td>
|
||||
<td>{{ ca.created_by }}</td>
|
||||
<td>{{ ca.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('ca_detail', ca_id=ca.id) }}" class="btn btn-sm btn-info">详情</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">暂无CA机构</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>名称</th>
|
||||
<th>通用名</th>
|
||||
<th>组织</th>
|
||||
<th>有效期</th>
|
||||
<th>创建者</th>
|
||||
<th>创建时间</th>
|
||||
<th class="pe-4">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ca in cas %}
|
||||
<tr>
|
||||
<td class="ps-4">{{ ca.id }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('ca_detail', ca_id=ca.id) }}" class="text-decoration-none">
|
||||
{{ ca.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ ca.common_name }}</td>
|
||||
<td>{{ ca.organization }}</td>
|
||||
<td>{{ ca.days_valid }}天</td>
|
||||
<td>{{ get_username(ca.created_by) }}</td>
|
||||
<td>{{ ca.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="pe-4">
|
||||
<a href="{{ url_for('ca_detail', ca_id=ca.id) }}"
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
title="查看CA详情">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-4">
|
||||
<i class="fas fa-shield-alt fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无CA机构记录</p>
|
||||
<a href="{{ url_for('create_ca_view') }}" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-plus me-2"></i> 创建CA机构
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -4,44 +4,75 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>证书详情: {{ cert.common_name }}</h2>
|
||||
<div>
|
||||
<h2 class="mb-1">证书详情: {{ cert.common_name }}</h2>
|
||||
<div class="text-muted fs-6">
|
||||
<i class="fas fa-folder-open me-1"></i>路径: {{ cert.cert_path|to_pinyin }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
{% if cert.status == 'active' %}
|
||||
<a href="{{ url_for('revoke_certificate_view', cert_id=cert.id) }}" class="btn btn-warning me-2">吊销证书</a>
|
||||
<a href="{{ url_for('revoke_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-warning me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="吊销此证书">
|
||||
<i class="fas fa-ban me-1"></i> 吊销证书
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('renew_certificate_view', cert_id=cert.id) }}" class="btn btn-primary me-2">续期</a>
|
||||
<a href="{{ url_for('export_certificate_view', cert_id=cert.id) }}" class="btn btn-success">导出</a>
|
||||
<a href="{{ url_for('delete_certificate', cert_id=cert.id) }}" class="btn btn-danger">删除</a>
|
||||
<a href="{{ url_for('renew_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-primary me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="续期此证书">
|
||||
<i class="fas fa-sync-alt me-1"></i> 续期
|
||||
</a>
|
||||
<a href="{{ url_for('export_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-success me-1 text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="导出证书文件">
|
||||
<i class="fas fa-file-export me-1"></i> 导出
|
||||
</a>
|
||||
<a href="{{ url_for('delete_certificate', cert_id=cert.id) }}"
|
||||
class="btn btn-danger text-nowrap"
|
||||
data-bs-toggle="tooltip"
|
||||
title="删除此证书(谨慎操作)">
|
||||
<i class="fas fa-trash-alt me-1"></i> 删除
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">基本信息</h5>
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i> 基本信息
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">通用名</dt>
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-id-card me-2"></i> 通用名</dt>
|
||||
<dd class="col-sm-8">{{ cert.common_name }}</dd>
|
||||
|
||||
<dt class="col-sm-4">颁发CA</dt>
|
||||
<dd class="col-sm-8">{{ ca.name }}</dd>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-shield-alt me-2"></i> 颁发CA</dt>
|
||||
<dd class="col-sm-8">
|
||||
<a href="{{ url_for('ca_detail', ca_id=ca.id) }}" class="text-decoration-none">
|
||||
{{ ca.name }}
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4">组织</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-building me-2"></i> 组织</dt>
|
||||
<dd class="col-sm-8">{{ cert.organization }}</dd>
|
||||
|
||||
<dt class="col-sm-4">组织单位</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-users me-2"></i> 组织单位</dt>
|
||||
<dd class="col-sm-8">{{ cert.organizational_unit or 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">国家</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-globe me-2"></i> 国家</dt>
|
||||
<dd class="col-sm-8">{{ cert.country }}</dd>
|
||||
|
||||
<dt class="col-sm-4">州/省</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-map-marked me-2"></i> 州/省</dt>
|
||||
<dd class="col-sm-8">{{ cert.state or 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">城市</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-city me-2"></i> 城市</dt>
|
||||
<dd class="col-sm-8">{{ cert.locality or 'N/A' }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@ -50,44 +81,52 @@
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">技术信息</h5>
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-cogs text-primary me-2"></i> 技术信息
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">密钥长度</dt>
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-key me-2"></i> 密钥长度</dt>
|
||||
<dd class="col-sm-8">{{ cert.key_size }}位</dd>
|
||||
|
||||
<dt class="col-sm-4">有效期</dt>
|
||||
<dd class="col-sm-8">{{ cert.days_valid }}天</dd>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-calendar-check me-2"></i> 有效期</dt>
|
||||
<dd class="col-sm-8">
|
||||
{{ cert.days_valid }}天
|
||||
(至 {{ cert.expires_at.strftime('%Y-%m-%d') }})
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4">状态</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-check-circle me-2"></i> 状态</dt>
|
||||
<dd class="col-sm-8">
|
||||
{% if cert.status == 'active' %}
|
||||
<span class="badge bg-success">有效</span>
|
||||
<span class="badge bg-success rounded-pill">
|
||||
<i class="fas fa-check-circle me-1"></i> 有效
|
||||
</span>
|
||||
{% elif cert.status == 'revoked' %}
|
||||
<span class="badge bg-danger">已吊销</span>
|
||||
<span class="badge bg-danger rounded-pill">
|
||||
<i class="fas fa-ban me-1"></i> 已吊销
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">已过期</span>
|
||||
<span class="badge bg-secondary rounded-pill">
|
||||
<i class="fas fa-clock me-1"></i> 已过期
|
||||
</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if cert.status == 'revoked' %}
|
||||
<dt class="col-sm-4">吊销原因</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-comment me-2"></i> 吊销原因</dt>
|
||||
<dd class="col-sm-8">{{ cert.revocation_reason or '未指定' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">吊销时间</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-clock me-2"></i> 吊销时间</dt>
|
||||
<dd class="col-sm-8">{{ cert.revoked_at.strftime('%Y-%m-%d %H:%M') }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-4">创建者</dt>
|
||||
<dd class="col-sm-8">{{ get_username(ca.created_by) }}</dd>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-user me-2"></i> 创建者</dt>
|
||||
<dd class="col-sm-8">{{ get_username(cert.created_by) }}</dd>
|
||||
|
||||
<dt class="col-sm-4">创建时间</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-clock me-2"></i> 创建时间</dt>
|
||||
<dd class="col-sm-8">{{ cert.created_at.strftime('%Y-%m-%d %H:%M') }}</dd>
|
||||
|
||||
<dt class="col-sm-4">过期时间</dt>
|
||||
<dd class="col-sm-8">{{ cert.expires_at.strftime('%Y-%m-%d %H:%M') }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,54 +135,71 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">SAN扩展</h5>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-list-alt text-primary me-2"></i> SAN扩展
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if cert.san_dns or cert.san_ip %}
|
||||
{% if cert.san_dns %}
|
||||
<h6>DNS名称:</h6>
|
||||
<ul>
|
||||
<h6><i class="fas fa-globe me-2"></i>DNS名称:</h6>
|
||||
<ul class="list-unstyled ms-4">
|
||||
{% for dns in cert.san_dns.split(',') %}
|
||||
<li>{{ dns }}</li>
|
||||
<li><i class="fas fa-angle-right text-muted me-2"></i>{{ dns }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if cert.san_ip %}
|
||||
<h6>IP地址:</h6>
|
||||
<ul>
|
||||
<h6><i class="fas fa-network-wired me-2"></i>IP地址:</h6>
|
||||
<ul class="list-unstyled ms-4">
|
||||
{% for ip in cert.san_ip.split(',') %}
|
||||
<li>{{ ip }}</li>
|
||||
<li><i class="fas fa-angle-right text-muted me-2"></i>{{ ip }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>未配置SAN扩展</p>
|
||||
<p class="text-muted mb-0"><i class="fas fa-info-circle me-2"></i>未配置SAN扩展</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">文件路径</h5>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-folder-open text-primary me-2"></i> 文件路径
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">证书文件</dt>
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-file-certificate me-2"></i> 证书文件</dt>
|
||||
<dd class="col-sm-8"><code>{{ cert.cert_path }}</code></dd>
|
||||
|
||||
<dt class="col-sm-4">私钥文件</dt>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-key me-2"></i> 私钥文件</dt>
|
||||
<dd class="col-sm-8"><code>{{ cert.key_path }}</code></dd>
|
||||
|
||||
<dt class="col-sm-4">CSR文件</dt>
|
||||
<dd class="col-sm-8"><code>{{ cert.csr_path }}</code></dd>
|
||||
<dt class="col-sm-4 text-muted"><i class="fas fa-file-signature me-2"></i> CSR文件</dt>
|
||||
<dd class="col-sm-8"><code>{{ cert.csr_path or 'N/A' }}</code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// 启用工具提示
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -4,56 +4,111 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>证书列表</h2>
|
||||
<a href="{{ url_for('create_certificate_view') }}" class="btn btn-primary">创建证书</a>
|
||||
<div>
|
||||
<h2 class="mb-1">证书列表</h2>
|
||||
<div class="text-muted fs-6">
|
||||
<i class="fas fa-certificate me-1"></i>共 {{ certificates|length }} 个证书
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url_for('create_certificate_view') }}" class="btn btn-primary btn-sm text-nowrap">
|
||||
<i class="fas fa-plus me-1"></i> 创建证书
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>通用名</th>
|
||||
<th>CA机构</th>
|
||||
<th>状态</th>
|
||||
<th>有效期至</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cert in certificates %}
|
||||
<tr>
|
||||
<td>{{ cert.id }}</td>
|
||||
<td>{{ cert.common_name }}</td>
|
||||
<td>{{ cert.ca_name }}</td>
|
||||
<td>
|
||||
{% if cert.status == 'active' %}
|
||||
<span class="badge bg-success">有效</span>
|
||||
{% elif cert.status == 'revoked' %}
|
||||
<span class="badge bg-danger">已吊销</span>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-4">ID</th>
|
||||
<th>通用名</th>
|
||||
<th>CA机构</th>
|
||||
<th>状态</th>
|
||||
<th>有效期至</th>
|
||||
<th>创建时间</th>
|
||||
<th class="pe-4">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cert in certificates %}
|
||||
<tr>
|
||||
<td class="ps-4">{{ cert.id }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}" class="text-decoration-none">
|
||||
{{ cert.common_name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ cert.ca_name }}</td>
|
||||
<td>
|
||||
{% if cert.status == 'active' %}
|
||||
<span class="badge bg-success rounded-pill">
|
||||
<i class="fas fa-check-circle me-1"></i> 有效
|
||||
</span>
|
||||
{% elif cert.status == 'revoked' %}
|
||||
<span class="badge bg-danger rounded-pill">
|
||||
<i class="fas fa-ban me-1"></i> 已吊销
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary rounded-pill">
|
||||
<i class="fas fa-clock me-1"></i> 已过期
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ cert.expires_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>{{ cert.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="pe-4">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}"
|
||||
class="btn btn-outline-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
title="查看详情">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('export_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
title="导出证书">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
{% if cert.status == 'active' %}
|
||||
<a href="{{ url_for('revoke_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-outline-warning"
|
||||
data-bs-toggle="tooltip"
|
||||
title="吊销证书">
|
||||
<i class="fas fa-ban"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">已过期</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ cert.expires_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>{{ cert.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}" class="btn btn-info">详情</a>
|
||||
{% if cert.status == 'active' %}
|
||||
<a href="{{ url_for('revoke_certificate_view', cert_id=cert.id) }}" class="btn btn-warning">吊销</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('export_certificate_view', cert_id=cert.id) }}" class="btn btn-success">导出</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">暂无证书</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4">
|
||||
<i class="fas fa-certificate fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无证书记录</p>
|
||||
<a href="{{ url_for('create_certificate_view') }}" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-plus me-2"></i> 创建证书
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// 启用工具提示
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,40 +1,215 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}首页{% endblock %}
|
||||
{% block title %}证书管理系统 - 首页{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">系统概览</h5>
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<!-- CA机构统计卡片 -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
||||
CA机构数量</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ ca_count }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-shield-alt fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<a href="{{ url_for('ca_list') }}" class="small text-primary stretched-link">
|
||||
查看所有CA机构 <i class="fas fa-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>欢迎使用证书管理系统!</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">CA机构</h5>
|
||||
<p class="card-text">
|
||||
<a href="{{ url_for('ca_list') }}">查看所有CA机构</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书统计卡片 -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
|
||||
证书总数</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ cert_count }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-certificate fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">证书</h5>
|
||||
<p class="card-text">
|
||||
<a href="{{ url_for('certificate_list') }}">查看所有证书</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<a href="{{ url_for('certificate_list') }}" class="small text-success stretched-link">
|
||||
查看所有证书 <i class="fas fa-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活跃证书卡片 -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
||||
活跃证书</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ active_count }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-check-circle fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<a href="{{ url_for('certificate_list') }}?status=active" class="small text-info stretched-link">
|
||||
查看活跃证书 <i class="fas fa-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 即将过期证书卡片 -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-warning shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
||||
即将过期证书 (30天内)</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ expiring_soon_count }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-exclamation-triangle fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<a href="{{ url_for('certificate_list') }}?expiring=30" class="small text-warning stretched-link">
|
||||
查看即将过期证书 <i class="fas fa-arrow-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- 最近CA机构 -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-shield-alt me-2"></i> 最近创建的CA机构
|
||||
</h6>
|
||||
<a href="{{ url_for('create_ca_view') }}" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-plus me-1"></i> 新建CA
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if recent_cas %}
|
||||
<div class="list-group">
|
||||
{% for ca in recent_cas %}
|
||||
<a href="{{ url_for('ca_detail', ca_id=ca.id) }}"
|
||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-1">{{ ca.name }}</h6>
|
||||
<small class="text-muted">{{ ca.common_name }}</small>
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
{{ ca.created_at.strftime('%Y-%m-%d') }}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-shield-alt fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无CA机构记录</p>
|
||||
<a href="{{ url_for('create_ca_view') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i> 创建第一个CA机构
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近证书 -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-certificate me-2"></i> 最近创建的证书
|
||||
</h6>
|
||||
<a href="{{ url_for('create_certificate_view') }}" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-plus me-1"></i> 新建证书
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if recent_certs %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>通用名</th>
|
||||
<th>状态</th>
|
||||
<th>有效期</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cert in recent_certs %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}"
|
||||
class="text-decoration-none">
|
||||
{{ cert.common_name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if cert.status == 'active' %}
|
||||
<span class="badge bg-success rounded-pill">有效</span>
|
||||
{% elif cert.status == 'revoked' %}
|
||||
<span class="badge bg-danger rounded-pill">已吊销</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary rounded-pill">已过期</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ cert.expires_at.strftime('%Y-%m-%d') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-certificate fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">暂无证书记录</p>
|
||||
<a href="{{ url_for('create_certificate_view') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i> 创建第一个证书
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// 初始化工具提示
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,30 +3,62 @@
|
||||
{% block title %}登录{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">用户登录</h4>
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h4 class="card-title mb-0">
|
||||
<i class="fas fa-sign-in-alt text-primary me-2"></i>用户登录
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('login') }}">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">用户名</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
<label for="username" class="form-label">
|
||||
<i class="fas fa-user me-1 text-muted"></i>用户名
|
||||
</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required
|
||||
placeholder="请输入用户名">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">密码</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock me-1 text-muted"></i>密码
|
||||
</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required
|
||||
placeholder="请输入密码">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="captcha" class="form-label">验证码: <strong>{{ captcha_code }}</strong></label>
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" required>
|
||||
<div class="mb-4">
|
||||
<label for="captcha" class="form-label">
|
||||
<i class="fas fa-shield-alt me-1 text-muted"></i>验证码
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" required
|
||||
placeholder="请输入验证码">
|
||||
<img src="{{ url_for('captcha') }}" id="captcha-image"
|
||||
class="img-thumbnail bg-light"
|
||||
style="cursor: pointer; width: 100px; height: 38px;"
|
||||
onclick="refreshCaptcha()"
|
||||
title="点击刷新验证码">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-sign-in-alt me-1"></i> 登录
|
||||
</button>
|
||||
<a href="{{ url_for('register') }}" class="btn btn-link text-decoration-none">
|
||||
没有账号?立即注册
|
||||
</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function refreshCaptcha() {
|
||||
var captchaImage = document.getElementById('captcha-image');
|
||||
captchaImage.src = "{{ url_for('captcha') }}?" + new Date().getTime();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,41 +3,77 @@
|
||||
{% block title %}用户注册{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">用户注册</h4>
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h4 class="card-title mb-0">
|
||||
<i class="fas fa-user-plus text-primary me-2"></i>用户注册
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('register') }}">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">用户名</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
<div class="form-text">请输入4-20位的字母、数字或下划线</div>
|
||||
<label for="username" class="form-label">
|
||||
<i class="fas fa-user me-1 text-muted"></i>用户名
|
||||
</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required
|
||||
placeholder="4-20位字母、数字或下划线">
|
||||
<small class="form-text text-muted">请输入4-20位的字母、数字或下划线</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">密码</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
<div class="form-text">至少8位字符,包含字母和数字</div>
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock me-1 text-muted"></i>密码
|
||||
</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required
|
||||
placeholder="至少8位字符">
|
||||
<small class="form-text text-muted">至少8位字符,包含字母和数字</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">确认密码</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
<label for="confirm_password" class="form-label">
|
||||
<i class="fas fa-lock me-1 text-muted"></i>确认密码
|
||||
</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required
|
||||
placeholder="再次输入密码">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">邮箱(可选)</label>
|
||||
<input type="email" class="form-control" id="email" name="email">
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope me-1 text-muted"></i>邮箱(可选)
|
||||
</label>
|
||||
<input type="email" class="form-control" id="email" name="email"
|
||||
placeholder="example@domain.com">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="captcha" class="form-label">验证码: <strong>{{ captcha_code }}</strong></label>
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" required>
|
||||
<div class="mb-4">
|
||||
<label for="captcha" class="form-label">
|
||||
<i class="fas fa-shield-alt me-1 text-muted"></i>验证码
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" required
|
||||
placeholder="请输入验证码">
|
||||
<img src="{{ url_for('captcha') }}" id="captcha-image"
|
||||
class="img-thumbnail bg-light"
|
||||
style="cursor: pointer; width: 100px; height: 38px;"
|
||||
onclick="refreshCaptcha()"
|
||||
title="点击刷新验证码">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-user-plus me-1"></i> 注册
|
||||
</button>
|
||||
<a href="{{ url_for('login') }}" class="btn btn-link text-decoration-none">
|
||||
已有账号?去登录
|
||||
</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">注册</button>
|
||||
<a href="{{ url_for('login') }}" class="btn btn-link">已有账号?去登录</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function refreshCaptcha() {
|
||||
var captchaImage = document.getElementById('captcha-image');
|
||||
captchaImage.src = "{{ url_for('captcha') }}?" + new Date().getTime();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user