Skip to content

Commit 097d204

Browse files
author
Taois
committed
update:增加备份与恢复的插件
1 parent bd0c98e commit 097d204

File tree

7 files changed

+570
-3
lines changed

7 files changed

+570
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ nodejs作为服务端的drpy实现。全面升级异步写法
4646
* [DS时钟插件-白色时钟](/apps/clock/white_clock.html)|[日历时钟](/apps/clock/index.html)
4747
* [DS庆祝页面-完结撒花](/apps/happy/index.html)
4848
* [bookReader](/apps/book-reader)
49+
* [系统备份与恢复](/apps/backup-restore/index.html)
4950
* [代码加解密工具](/admin/encoder)
5051
* [央视点播解析工具](/proxy/央视大全[官]/index.html)
5152
* [在线猫ds源主页](/cat/index.html)

apps/backup-restore/index.html

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
<!DOCTYPE html>
2+
<html lang="zh">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>系统备份与恢复</title>
7+
<style>
8+
:root {
9+
--primary-color: #2196F3;
10+
--success-color: #4CAF50;
11+
--warning-color: #FF9800;
12+
--danger-color: #f44336;
13+
--text-color: #333;
14+
--bg-color: #f4f7f6;
15+
--card-bg: #ffffff;
16+
--code-bg: #f5f5f5;
17+
}
18+
19+
body {
20+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
21+
margin: 0;
22+
padding: 20px;
23+
display: flex;
24+
justify-content: center;
25+
align-items: center;
26+
min-height: 100vh;
27+
background-color: var(--bg-color);
28+
color: var(--text-color);
29+
box-sizing: border-box;
30+
}
31+
32+
.container {
33+
width: 100%;
34+
max-width: 600px;
35+
padding: 30px;
36+
background: var(--card-bg);
37+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
38+
border-radius: 12px;
39+
text-align: center;
40+
transition: transform 0.3s ease;
41+
}
42+
43+
h2 {
44+
margin-top: 0;
45+
margin-bottom: 25px;
46+
color: #2c3e50;
47+
font-weight: 600;
48+
}
49+
50+
.description {
51+
margin-bottom: 30px;
52+
color: #555;
53+
line-height: 1.6;
54+
text-align: left;
55+
background: #fff;
56+
padding: 0;
57+
}
58+
59+
.description p {
60+
margin: 10px 0;
61+
}
62+
63+
.info-box {
64+
background: #e3f2fd;
65+
border-left: 4px solid var(--primary-color);
66+
padding: 15px;
67+
border-radius: 4px;
68+
margin-bottom: 20px;
69+
font-size: 14px;
70+
}
71+
72+
.file-list-container {
73+
margin-top: 20px;
74+
border: 1px solid #eee;
75+
border-radius: 8px;
76+
overflow: hidden;
77+
}
78+
79+
.file-list-header {
80+
background: #f8f9fa;
81+
padding: 10px 15px;
82+
font-weight: 600;
83+
border-bottom: 1px solid #eee;
84+
font-size: 14px;
85+
display: flex;
86+
justify-content: space-between;
87+
align-items: center;
88+
}
89+
90+
.status-text {
91+
font-size: 12px;
92+
color: #777;
93+
text-align: right;
94+
margin-top: 6px;
95+
}
96+
97+
#fileList {
98+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
99+
background: #2d3436;
100+
color: #dfe6e9;
101+
padding: 15px;
102+
max-height: 150px;
103+
overflow-y: auto;
104+
text-align: left;
105+
font-size: 12px;
106+
line-height: 1.5;
107+
}
108+
109+
.button-group {
110+
display: grid;
111+
grid-template-columns: 1fr 1fr;
112+
gap: 20px;
113+
margin-bottom: 25px;
114+
}
115+
116+
button {
117+
padding: 12px 20px;
118+
font-size: 16px;
119+
border: none;
120+
border-radius: 8px;
121+
cursor: pointer;
122+
transition: all 0.2s ease;
123+
color: white;
124+
font-weight: 500;
125+
display: flex;
126+
align-items: center;
127+
justify-content: center;
128+
gap: 8px;
129+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
130+
}
131+
132+
button:active {
133+
transform: translateY(1px);
134+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
135+
}
136+
137+
button:disabled {
138+
opacity: 0.7;
139+
cursor: not-allowed;
140+
}
141+
142+
.backup-btn {
143+
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
144+
}
145+
146+
.backup-btn:hover {
147+
background: linear-gradient(135deg, #45a049 0%, #388E3C 100%);
148+
transform: translateY(-2px);
149+
box-shadow: 0 6px 12px rgba(76, 175, 80, 0.2);
150+
}
151+
152+
.restore-btn {
153+
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%);
154+
}
155+
156+
.restore-btn:hover {
157+
background: linear-gradient(135deg, #F57C00 0%, #E65100 100%);
158+
transform: translateY(-2px);
159+
box-shadow: 0 6px 12px rgba(255, 152, 0, 0.2);
160+
}
161+
162+
.log-area {
163+
width: 100%;
164+
height: 200px;
165+
overflow-y: auto;
166+
padding: 15px;
167+
border: 1px solid #eee;
168+
border-radius: 8px;
169+
background-color: #f8f9fa;
170+
box-sizing: border-box;
171+
text-align: left;
172+
font-family: monospace;
173+
font-size: 13px;
174+
box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
175+
}
176+
177+
.log-entry {
178+
margin-bottom: 6px;
179+
padding-bottom: 6px;
180+
border-bottom: 1px dashed #eee;
181+
display: flex;
182+
align-items: flex-start;
183+
}
184+
185+
.log-time {
186+
color: #999;
187+
margin-right: 10px;
188+
min-width: 70px;
189+
font-size: 12px;
190+
}
191+
192+
.log-content {
193+
flex: 1;
194+
word-break: break-all;
195+
}
196+
197+
.log-success .log-content { color: var(--success-color); }
198+
.log-error .log-content { color: var(--danger-color); }
199+
.log-info .log-content { color: var(--text-color); }
200+
.log-warning .log-content { color: var(--warning-color); }
201+
202+
.path-display {
203+
background: #e8f5e9;
204+
padding: 8px;
205+
border-radius: 4px;
206+
color: #2e7d32;
207+
font-weight: 500;
208+
}
209+
210+
/* Mobile Responsiveness */
211+
@media (max-width: 480px) {
212+
.container {
213+
padding: 20px;
214+
}
215+
216+
.button-group {
217+
grid-template-columns: 1fr;
218+
gap: 15px;
219+
}
220+
221+
h2 {
222+
font-size: 20px;
223+
}
224+
225+
.log-area {
226+
height: 150px;
227+
}
228+
}
229+
</style>
230+
</head>
231+
<body>
232+
<div class="container">
233+
<h2>📂 系统备份与恢复</h2>
234+
235+
<div class="description">
236+
<div class="info-box">
237+
<p><strong>备份说明:</strong> 点击“立即备份”将系统配置、插件及脚本备份到同级 <code>backup</code> 目录。</p>
238+
<p style="margin-bottom: 0;"><strong>恢复说明:</strong> 点击“恢复备份”将从 <code>backup</code> 目录恢复文件覆盖当前系统。</p>
239+
</div>
240+
241+
<div class="file-list-container">
242+
<div class="file-list-header">
243+
<span>涉及文件清单</span>
244+
<small>只读</small>
245+
</div>
246+
<div id="fileList">
247+
加载中...
248+
</div>
249+
<div class="status-text">
250+
<div>上次备份:<span id="lastBackupAt">--</span></div>
251+
<div>上次恢复:<span id="lastRestoreAt">--</span></div>
252+
</div>
253+
</div>
254+
</div>
255+
256+
<div class="button-group">
257+
<button class="backup-btn" onclick="performAction('backup')" id="btn-backup">
258+
<span>⬇️ 立即备份</span>
259+
</button>
260+
<button class="restore-btn" onclick="performAction('restore')" id="btn-restore">
261+
<span>⬆️ 恢复备份</span>
262+
</button>
263+
</div>
264+
265+
<div class="log-area" id="logArea">
266+
<div class="log-entry log-info"><span class="log-time">系统</span><span class="log-content">准备就绪,等待操作...</span></div>
267+
</div>
268+
</div>
269+
270+
<script>
271+
// 加载配置文件列表
272+
window.onload = async function() {
273+
try {
274+
const response = await fetch('/admin/backup/config');
275+
if (response.ok) {
276+
const data = await response.json();
277+
if (data.success && data.paths) {
278+
const fileListDiv = document.getElementById('fileList');
279+
fileListDiv.innerHTML = data.paths.join('<br>');
280+
if (data.lastBackupAt) {
281+
document.getElementById('lastBackupAt').textContent = new Date(data.lastBackupAt).toLocaleString();
282+
}
283+
if (data.lastRestoreAt) {
284+
document.getElementById('lastRestoreAt').textContent = new Date(data.lastRestoreAt).toLocaleString();
285+
}
286+
}
287+
}
288+
} catch (e) {
289+
console.error('Failed to load backup config', e);
290+
document.getElementById('fileList').textContent = '加载配置失败';
291+
}
292+
};
293+
294+
function log(message, type = 'info') {
295+
const logArea = document.getElementById('logArea');
296+
const entry = document.createElement('div');
297+
entry.className = `log-entry log-${type}`;
298+
299+
const timeSpan = document.createElement('span');
300+
timeSpan.className = 'log-time';
301+
timeSpan.textContent = new Date().toLocaleTimeString();
302+
303+
const contentSpan = document.createElement('span');
304+
contentSpan.className = 'log-content';
305+
contentSpan.textContent = message;
306+
307+
entry.appendChild(timeSpan);
308+
entry.appendChild(contentSpan);
309+
310+
logArea.prepend(entry);
311+
}
312+
313+
function displayPath(path) {
314+
const logArea = document.getElementById('logArea');
315+
const entry = document.createElement('div');
316+
entry.className = `log-entry log-info path-display`;
317+
entry.style.display = 'block'; // Override flex
318+
entry.textContent = `📁 备份路径: ${path}`;
319+
logArea.prepend(entry);
320+
}
321+
322+
async function performAction(action) {
323+
const actionName = action === 'backup' ? '备份' : '恢复';
324+
const btnId = action === 'backup' ? 'btn-backup' : 'btn-restore';
325+
const btn = document.getElementById(btnId);
326+
327+
if (!confirm(`⚠️ 确定要执行${actionName}操作吗?\n此操作不可撤销,请谨慎进行。`)) return;
328+
329+
// UI State Loading
330+
const originalText = btn.innerHTML;
331+
btn.disabled = true;
332+
btn.innerHTML = '<span>⏳ 处理中...</span>';
333+
334+
log(`开始执行${actionName}...`, 'info');
335+
336+
try {
337+
const response = await fetch(`/admin/${action}`, {
338+
method: 'POST',
339+
headers: {
340+
'Content-Type': 'application/json'
341+
},
342+
body: JSON.stringify({})
343+
});
344+
345+
let result;
346+
try {
347+
result = await response.json();
348+
} catch (e) {
349+
throw new Error(`响应解析失败: ${response.statusText}`);
350+
}
351+
352+
if (response.ok && result.success) {
353+
log(`${actionName}成功: ${result.message}`, 'success');
354+
if (result.backupDir) {
355+
displayPath(result.backupDir);
356+
}
357+
if (result.details && result.details.length > 0) {
358+
result.details.forEach(detail => log(`- ${detail}`, 'info'));
359+
}
360+
} else {
361+
log(`${actionName}失败: ${result.message || response.statusText}`, 'error');
362+
}
363+
} catch (error) {
364+
log(`${actionName}出错: ${error.message}`, 'error');
365+
} finally {
366+
// Restore UI State
367+
btn.disabled = false;
368+
btn.innerHTML = originalText;
369+
}
370+
}
371+
</script>
372+
</body>
373+
</html>

0 commit comments

Comments
 (0)