<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件备份系统 - 增强版</title>
<style>
/* 保留原有样式,新增样式在下面 */
.api-info {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.api-info h3 {
margin: 0;
font-size: 1.2em;
display: flex;
align-items: center;
gap: 10px;
}
.api-info .method-badge {
background: rgba(255,255,255,0.2);
padding: 3px 10px;
border-radius: 20px;
font-size: 0.9em;
font-weight: bold;
}
.api-method-selector {
display: flex;
gap: 10px;
align-items: center;
}
.api-method-selector select {
padding: 8px 15px;
border-radius: 6px;
border: none;
background: rgba(255,255,255,0.9);
font-size: 0.9em;
cursor: pointer;
min-width: 150px;
}
.api-method-selector select:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(255,255,255,0.3);
}
.method-description {
font-size: 0.85em;
opacity: 0.9;
margin-top: 5px;
}
.connection-test {
margin-left: 10px;
}
.connection-test button {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: all 0.3s;
}
.connection-test button:hover {
background: rgba(255,255,255,0.3);
}
.method-details {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #4facfe;
}
.method-details h4 {
margin-top: 0;
color: #333;
}
.method-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 10px;
}
.feature-item {
background: white;
padding: 10px;
border-radius: 6px;
border: 1px solid #e9ecef;
font-size: 0.9em;
}
.feature-item strong {
color: #4facfe;
}
.retry-config {
display: flex;
gap: 15px;
margin-top: 10px;
font-size: 0.9em;
}
.config-item {
display: flex;
align-items: center;
gap: 5px;
}
.config-item label {
font-weight: bold;
color: #666;
}
.config-item input {
width: 60px;
padding: 3px 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}
.debug-info {
background: #f8f9fa;
padding: 10px;
border-radius: 6px;
margin-top: 10px;
font-size: 0.85em;
color: #666;
border: 1px solid #e9ecef;
}
.debug-info pre {
margin: 5px 0;
padding: 8px;
background: #f1f3f4;
border-radius: 4px;
overflow-x: auto;
font-size: 0.8em;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.status-online {
background-color: #28a745;
animation: pulse 2s infinite;
}
.status-offline {
background-color: #dc3545;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.advanced-config {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #ddd;
}
.advanced-config h4 {
margin-top: 0;
color: #333;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.advanced-config-content {
display: none;
}
.advanced-config.active .advanced-config-content {
display: block;
}
.config-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-top: 15px;
}
.config-field {
margin-bottom: 10px;
}
.config-field label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
font-size: 0.9em;
}
.config-field input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>📁 文件备份系统 - 增强版</h1>
<p>支持多种API连接方式,更稳定可靠的备份解决方案</p>
</header>
<div class="content">
<div class="api-info" id="apiInfo">
<!-- API信息将通过JavaScript动态加载 -->
</div>
<div class="method-details" id="methodDetails">
<!-- 方法详情将通过JavaScript动态加载 -->
</div>
<div class="advanced-config" id="advancedConfig">
<h4 onclick="toggleAdvancedConfig()">
⚙️ 高级配置
<span id="advancedToggle">▼</span>
</h4>
<div class="advanced-config-content">
<div class="config-grid">
<div class="config-field">
<label>API超时时间 (秒):</label>
<input type="number" id="configTimeout" value="300" min="30" max="3600">
</div>
<div class="config-field">
<label>连接超时时间 (秒):</label>
<input type="number" id="configConnectTimeout" value="30" min="5" max="300">
</div>
<div class="config-field">
<label>重试次数:</label>
<input type="number" id="configRetryTimes" value="3" min="0" max="10">
</div>
<div class="config-field">
<label>重试延迟 (毫秒):</label>
<input type="number" id="configRetryDelay" value="1000" min="0" max="10000">
</div>
</div>
<button class="btn" onclick="saveAdvancedConfig()" style="margin-top: 15px; padding: 8px 20px; font-size: 0.9em;">
保存高级配置
</button>
</div>
</div>
<div class="stats-grid" id="stats">
<!-- 统计数据将通过JavaScript动态加载 -->
</div>
<div class="exclude-panel" id="excludePanel">
<!-- 排除规则信息将通过JavaScript动态加载 -->
</div>
<div class="backup-controls">
<button class="btn" onclick="startBackup()" id="startBtn">开始备份</button>
<button class="btn btn-stop" onclick="stopBackup()" id="stopBtn" style="display:none">停止备份</button>
<button class="btn" onclick="resetBackup()" id="resetBtn">重置备份</button>
<button class="btn" onclick="testConnection()" id="testBtn">测试连接</button>
<button class="btn" onclick="location.reload()">刷新状态</button>
<div class="auto-backup">
<label>
<input type="checkbox" id="autoBackup" onchange="toggleAutoBackup()">
自动备份
</label>
<input type="number" id="interval" value="2" min="1" max="60" style="display:none">
<span style="display:none" id="intervalText">秒间隔</span>
</div>
</div>
<div class="progress-section">
<h2>备份进度</h2>
<div class="progress-container">
<div class="progress-bar" id="progressBar">
<div class="progress-text" id="progressText">0%</div>
</div>
</div>
<div id="progressInfo">等待开始备份...</div>
</div>
<div class="file-info" id="currentFile">
<!-- 当前文件信息将通过JavaScript动态加载 -->
</div>
<div class="log-container" id="log">
<!-- 日志将通过JavaScript动态加载 -->
</div>
<div class="debug-info" id="debugInfo" style="display:none">
<!-- 调试信息 -->
</div>
<div class="spinner" id="spinner"></div>
</div>
<footer>
<p>© 2023 文件备份系统 - 增强版 | 兼容 PHP 5.3 - PHP 8.x</p>
<p>支持API连接方式: cURL, Socket, Stream | 当前目录: /mnt/sda1/www/2026</p>
</footer>
</div>
<script>
let isBackupRunning = false;
let autoBackupEnabled = false;
let backupInterval;
let logEntries = [];
let currentApiMethod = 'auto';
// 加载API方法信息
function loadApiInfo() {
fetch('baksend.php?action=api_methods')
.then(response => response.json())
.then(data => {
const methods = data.methods;
const current = data.current;
currentApiMethod = current;
// 更新API信息显示
const apiInfo = document.getElementById('apiInfo');
apiInfo.innerHTML = `
<div>
<h3>
<span class="status-indicator status-online"></span>
API连接状态
<span class="method-badge">${methods[current] || current}</span>
</h3>
<div class="method-description">
当前使用: ${methods[current] || '自动选择'}
</div>
</div>
<div class="api-method-selector">
<select id="apiMethodSelect" onchange="changeApiMethod(this.value)">
<option value="auto" ${current == 'auto' ? 'selected' : ''}>自动选择</option>
${Object.keys(methods).map(key => `
<option value="${key}" ${current == key ? 'selected' : ''}>
${methods[key]}
</option>
`).join('')}
</select>
<div class="connection-test">
<button onclick="testConnection()">测试连接</button>
</div>
</div>
`;
// 更新方法详情
updateMethodDetails(current, methods);
});
}
// 更新方法详情
function updateMethodDetails(method, methods) {
const details = document.getElementById('methodDetails');
let description = '';
switch (method) {
case 'curl':
description = '使用cURL库进行HTTP请求,支持HTTPS、代理、压缩等高级功能,性能最好。';
break;
case 'socket':
description = '使用原生Socket连接,不依赖外部库,兼容性最好。';
break;
case 'stream':
description = '使用PHP流上下文,简单易用,适合基本需求。';
break;
default:
description = '自动选择可用的最佳连接方式。';
}
details.innerHTML = `
<h4>🔧 当前连接方式: ${methods[method] || '自动选择'}</h4>
<p>${description}</p>
<div class="method-features">
<div class="feature-item">
<strong>cURL</strong>: ${methods['curl'] ? '✅ 可用' : '❌ 不可用'}
</div>
<div class="feature-item">
<strong>Socket</strong>: ${methods['socket'] ? '✅ 可用' : '❌ 不可用'}
</div>
<div class="feature-item">
<strong>Stream</strong>: ${methods['stream'] ? '✅ 可用' : '❌ 不可用'}
</div>
</div>
<div class="retry-config">
<div class="config-item">
<label>超时:</label>
<input type="number" id="timeoutInput" value="300" onchange="updateTimeout(this.value)">
<span>秒</span>
</div>
<div class="config-item">
<label>重试:</label>
<input type="number" id="retryInput" value="3" onchange="updateRetry(this.value)">
<span>次</span>
</div>
</div>
`;
}
// 更改API方法
function changeApiMethod(method) {
fetch('baksend.php?action=set_api_method', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'method=' + encodeURIComponent(method)
})
.then(response => response.json())
.then(data => {
if (data.success) {
addLogEntry(`API方法已切换为: ${method}`, 'success');
loadApiInfo();
} else {
addLogEntry(`切换API方法失败: ${data.message}`, 'error');
}
})
.catch(error => {
addLogEntry(`切换API方法出错: ${error.message}`, 'error');
});
}
// 测试连接
function testConnection() {
const testBtn = document.getElementById('testBtn');
const originalText = testBtn.textContent;
testBtn.textContent = '测试中...';
testBtn.disabled = true;
fetch('baksend.php?action=stats')
.then(response => response.json())
.then(data => {
if (data.api_method) {
addLogEntry(`连接测试成功,当前API方法: ${data.api_method}`, 'success');
loadApiInfo();
} else {
addLogEntry('连接测试失败,无法获取API方法信息', 'error');
}
testBtn.textContent = originalText;
testBtn.disabled = false;
})
.catch(error => {
addLogEntry(`连接测试出错: ${error.message}`, 'error');
testBtn.textContent = originalText;
testBtn.disabled = false;
});
}
// 切换高级配置显示
function toggleAdvancedConfig() {
const config = document.getElementById('advancedConfig');
const toggle = document.getElementById('advancedToggle');
config.classList.toggle('active');
toggle.textContent = config.classList.contains('active') ? '▲' : '▼';
}
// 保存高级配置
function saveAdvancedConfig() {
const timeout = document.getElementById('configTimeout').value;
const connectTimeout = document.getElementById('configConnectTimeout').value;
const retryTimes = document.getElementById('configRetryTimes').value;
const retryDelay = document.getElementById('configRetryDelay').value;
// 这里应该将配置保存到服务器
// 由于是演示,我们只更新本地显示
addLogEntry(`高级配置已更新: 超时=${timeout}s, 重试=${retryTimes}次, 延迟=${retryDelay}ms`, 'info');
// 在实际应用中,应该发送到服务器保存
// fetch('save_config.php', { ... })
}
// 更新超时时间
function updateTimeout(value) {
addLogEntry(`超时时间已更新为: ${value}秒`, 'info');
}
// 更新重试次数
function updateRetry(value) {
addLogEntry(`重试次数已更新为: ${value}次`, 'info');
}
// 加载统计信息
function loadStats() {
fetch('baksend.php?action=stats')
.then(response => response.json())
.then(data => {
const statsHtml = `
<div class="stat-card">
<h3>📊 总文件数</h3>
<div class="number">${data.total_files}</div>
</div>
<div class="stat-card">
<h3>✅ 已备份</h3>
<div class="number">${data.backed_up}</div>
</div>
<div class="stat-card">
<h3>⏳ 待备份</h3>
<div class="number">${data.pending}</div>
</div>
<div class="stat-card">
<h3>❌ 失败数</h3>
<div class="number">${data.errors}</div>
</div>
<div class="stat-card">
<h3>🚫 已排除</h3>
<div class="number">${data.excluded_count}</div>
</div>
`;
document.getElementById('stats').innerHTML = statsHtml;
// 加载排除规则
loadExcludeRules(data);
});
}
// 加载排除规则
function loadExcludeRules(data) {
fetch('baksend.php?action=exclude_rules')
.then(response => response.json())
.then(rules => {
// ... 原有代码 ...
});
}
// 添加日志条目
function addLogEntry(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const entry = {
time: timestamp,
message: message,
type: type
};
logEntries.unshift(entry);
if (logEntries.length > 50) {
logEntries = logEntries.slice(0, 50);
}
updateLogDisplay();
}
// 更新日志显示
function updateLogDisplay() {
const logContainer = document.getElementById('log');
let logHtml = '';
logEntries.forEach(entry => {
logHtml += `
<div class="log-entry ${entry.type}">
<strong>[${entry.time}]</strong> ${entry.message}
</div>
`;
});
logContainer.innerHTML = logHtml;
}
// 开始备份
function startBackup() {
if (isBackupRunning) return;
isBackupRunning = true;
document.getElementById('startBtn').style.display = 'none';
document.getElementById('stopBtn').style.display = 'inline-block';
document.getElementById('spinner').style.display = 'block';
addLogEntry('开始备份进程...', 'info');
addLogEntry(`当前API方法: ${currentApiMethod}`, 'info');
// 执行备份
performBackup();
}
// 执行备份
function performBackup() {
if (!isBackupRunning) return;
fetch('baksend.php?action=backup')
.then(response => response.json())
.then(data => {
if (data.completed) {
// 备份完成
backupCompleted(data);
return;
}
// 更新进度
updateProgress(data);
// 显示当前文件信息
updateCurrentFile(data.current_file, data.api_method);
// 添加日志
if (data.result.success) {
addLogEntry(`✅ ${data.result.file} - ${data.result.message} (${data.result.size})`, 'success');
} else {
addLogEntry(`❌ ${data.result.file} - ${data.result.message}`, 'error');
}
// 如果启用了自动备份,继续下一个文件
if (autoBackupEnabled) {
setTimeout(performBackup, document.getElementById('interval').value * 1000);
}
})
.catch(error => {
addLogEntry(`请求错误: ${error.message}`, 'error');
if (autoBackupEnabled) {
setTimeout(performBackup, 5000);
}
});
}
// 更新进度
function updateProgress(data) {
const progress = data.progress;
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const progressInfo = document.getElementById('progressInfo');
progressBar.style.width = progress.percent + '%';
progressText.textContent = progress.percent + '%';
const stats = data.stats;
progressInfo.innerHTML = `
进度: ${progress.current}/${progress.total} 文件 |
成功: ${stats.success} |
失败: ${stats.failed} |
方法: ${data.api_method || 'auto'} |
耗时: ${Math.round((Date.now()/1000 - stats.start_time))}秒
`;
}
// 更新当前文件信息
function updateCurrentFile(file, apiMethod) {
if (!file) return;
const fileSize = formatBytes(file.size);
const fileInfo = document.getElementById('currentFile');
fileInfo.innerHTML = `
<h3>📄 当前备份文件</h3>
<p><strong>文件路径:</strong> ${file.path}</p>
<p><strong>文件大小:</strong> ${fileSize}</p>
<p><strong>修改时间:</strong> ${new Date(file.mtime * 1000).toLocaleString()}</p>
<p><strong>MD5:</strong> ${file.md5.substring(0, 16)}...</p>
<p><strong>API方法:</strong> ${apiMethod}</p>
`;
}
// 备份完成
function backupCompleted(data) {
isBackupRunning = false;
document.getElementById('startBtn').style.display = 'inline-block';
document.getElementById('stopBtn').style.display = 'none';
document.getElementById('spinner').style.display = 'none';
addLogEntry(`🎉 备份完成!总计: ${data.stats.success} 成功, ${data.stats.failed} 失败`, 'success');
// 更新统计信息
loadStats();
// 显示完成信息
document.getElementById('currentFile').innerHTML = `
<h3>✅ 备份完成</h3>
<p><strong>总文件数:</strong> ${data.stats.total}</p>
<p><strong>成功:</strong> ${data.stats.success}</p>
<p><strong>失败:</strong> ${data.stats.failed}</p>
<p><strong>API方法:</strong> ${data.api_method}</p>
<p><strong>开始时间:</strong> ${new Date(data.stats.start_time * 1000).toLocaleString()}</p>
<p><strong>完成时间:</strong> ${new Date().toLocaleString()}</p>
`;
// 重置进度条
document.getElementById('progressBar').style.width = '100%';
document.getElementById('progressText').textContent = '100%';
}
// 停止备份
function stopBackup() {
isBackupRunning = false;
document.getElementById('startBtn').style.display = 'inline-block';
document.getElementById('stopBtn').style.display = 'none';
document.getElementById('spinner').style.display = 'none';
addLogEntry('备份已停止', 'info');
}
// 重置备份
function resetBackup() {
if (isBackupRunning) {
if (!confirm('备份正在进行中,确定要重置吗?')) {
return;
}
stopBackup();
}
fetch('baksend.php?action=reset')
.then(() => {
location.reload();
});
}
// 切换自动备份
function toggleAutoBackup() {
autoBackupEnabled = document.getElementById('autoBackup').checked;
const intervalInput = document.getElementById('interval');
const intervalText = document.getElementById('intervalText');
if (autoBackupEnabled) {
intervalInput.style.display = 'inline-block';
intervalText.style.display = 'inline-block';
// 如果备份没有运行,自动开始
if (!isBackupRunning) {
startBackup();
}
} else {
intervalInput.style.display = 'none';
intervalText.style.display = 'none';
}
}
// 格式化字节大小
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// 页面加载时初始化
window.onload = function() {
loadApiInfo();
loadStats();
addLogEntry('系统就绪,可以开始备份', 'info');
// 检查是否有待备份文件
fetch('baksend.php?action=stats')
.then(response => response.json())
.then(data => {
if (data.pending > 0) {
addLogEntry(`检测到 ${data.pending} 个待备份文件`, 'info');
}
});
};
</script>
</body>
</html>