- 新增备份管理页面(备份/恢复/下载/删除) - 新增 backup_service 服务层 - 文章编辑器增加工具栏图片上传按钮 - 优化图片上传交互提示
275 lines
6.4 KiB
HTML
275 lines
6.4 KiB
HTML
{% extends 'admin/base.html' %}
|
|
|
|
{% block title %}数据备份{% endblock %}
|
|
{% block page_title %}数据备份管理{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.backup-section {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.backup-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.backup-header h2 {
|
|
margin: 0;
|
|
font-size: 1.2em;
|
|
}
|
|
|
|
.backup-stats {
|
|
display: flex;
|
|
gap: 30px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-item {
|
|
background: #f8f9fa;
|
|
padding: 15px 25px;
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-item .value {
|
|
font-size: 1.8em;
|
|
font-weight: 600;
|
|
color: var(--admin-primary);
|
|
}
|
|
|
|
.stat-item .label {
|
|
font-size: 0.85em;
|
|
color: #666;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.backup-actions {
|
|
background: #e8f4fc;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.backup-actions .action-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.backup-actions h3 {
|
|
margin: 0 0 8px 0;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
.backup-actions p {
|
|
margin: 0;
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.backup-list {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.backup-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px;
|
|
background: #fff;
|
|
border: 1px solid #eee;
|
|
border-radius: 8px;
|
|
margin-bottom: 10px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.backup-item:hover {
|
|
border-color: var(--admin-primary);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.backup-info {
|
|
display: flex;
|
|
gap: 20px;
|
|
align-items: center;
|
|
}
|
|
|
|
.backup-info .icon {
|
|
font-size: 2em;
|
|
}
|
|
|
|
.backup-info .details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.backup-info .filename {
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.backup-info .meta {
|
|
font-size: 0.85em;
|
|
color: #666;
|
|
display: flex;
|
|
gap: 15px;
|
|
}
|
|
|
|
.backup-actions-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #666;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.empty-state .icon {
|
|
font-size: 3em;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
/* 备份中状态 */
|
|
.backup-item.processing {
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
function createBackup() {
|
|
const btn = document.getElementById('create-backup-btn');
|
|
const originalText = btn.textContent;
|
|
|
|
btn.textContent = '⏳ 备份中...';
|
|
btn.disabled = true;
|
|
|
|
fetch('{{ url_for("admin.backup_create") }}', {
|
|
method: 'POST'
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
btn.textContent = originalText;
|
|
btn.disabled = false;
|
|
|
|
if (data.success) {
|
|
alert('✅ 备份成功!\n\n文件: ' + data.backup.filename + '\n大小: ' + data.backup.size);
|
|
location.reload();
|
|
} else {
|
|
alert('❌ 备份失败: ' + data.message);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
btn.textContent = originalText;
|
|
btn.disabled = false;
|
|
alert('备份失败: ' + err.message);
|
|
});
|
|
}
|
|
|
|
function deleteBackup(filename) {
|
|
if (!confirm('确定要删除备份文件 ' + filename + ' 吗?')) {
|
|
return;
|
|
}
|
|
|
|
fetch('/admin/backup/delete/' + filename, {
|
|
method: 'POST'
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('删除失败: ' + data.message);
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="backup-section">
|
|
<div class="backup-header">
|
|
<h2>📊 备份概览</h2>
|
|
</div>
|
|
|
|
<div class="backup-stats">
|
|
<div class="stat-item">
|
|
<div class="value">{{ stats.total_backups }}</div>
|
|
<div class="label">备份文件数</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="value">{{ (stats.total_size / 1024 / 1024) | round(2) }} MB</div>
|
|
<div class="label">备份总大小</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="backup-actions">
|
|
<div class="action-info">
|
|
<h3>💾 创建新备份</h3>
|
|
<p>一键备份网站数据库和所有上传的图片附件资源,打包成 ZIP 文件保存。</p>
|
|
</div>
|
|
<button id="create-backup-btn" onclick="createBackup()" class="btn btn-primary">
|
|
📦 立即备份
|
|
</button>
|
|
</div>
|
|
|
|
<div class="backup-list">
|
|
<h3 style="margin-bottom: 15px;">备份文件列表</h3>
|
|
|
|
{% if backups %}
|
|
{% for backup in backups %}
|
|
<div class="backup-item">
|
|
<div class="backup-info">
|
|
<div class="icon">📦</div>
|
|
<div class="details">
|
|
<div class="filename">{{ backup.filename }}</div>
|
|
<div class="meta">
|
|
<span>📅 {{ backup.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
|
<span>💾 {{ (backup.size / 1024) | round(1) }} KB</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="backup-actions-buttons">
|
|
<a href="{{ url_for('admin.backup_download', filename=backup.filename) }}"
|
|
class="btn btn-sm" target="_blank">📥 下载</a>
|
|
<button onclick="deleteBackup('{{ backup.filename }}')"
|
|
class="btn btn-sm btn-danger">🗑️ 删除</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<div class="icon">📭</div>
|
|
<p>暂无备份文件</p>
|
|
<p style="margin-top: 10px; font-size: 0.9em;">点击上方"立即备份"按钮创建第一个备份</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: #fff3cd; border-radius: 8px; padding: 15px; margin-top: 20px;">
|
|
<h4 style="margin: 0 0 10px 0;">⚠️ 注意事项</h4>
|
|
<ul style="margin: 0; padding-left: 20px; font-size: 0.9em; color: #856404;">
|
|
<li>备份包含数据库文件和上传的图片附件</li>
|
|
<li>备份文件保存在服务器的 <code>backups/</code> 目录</li>
|
|
<li>建议定期备份并下载到本地保存</li>
|
|
<li>恢复数据需要手动解压并替换相应文件</li>
|
|
</ul>
|
|
</div>
|
|
{% endblock %} |