核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
269 lines
9.8 KiB
HTML
269 lines
9.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>File Upload</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
max-width: 800px;
|
|
margin: 50px auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
.upload-container {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
text-align: center;
|
|
}
|
|
.upload-form {
|
|
margin-top: 20px;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}
|
|
input[type="text"], input[type="file"] {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
button {
|
|
background: #007bff;
|
|
color: white;
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
button:hover {
|
|
background: #0056b3;
|
|
}
|
|
.progress {
|
|
margin-top: 20px;
|
|
display: none;
|
|
}
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: 20px;
|
|
background: #e0e0e0;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: #007bff;
|
|
width: 0%;
|
|
transition: width 0.3s;
|
|
}
|
|
.result {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
display: none;
|
|
}
|
|
.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="upload-container">
|
|
<h1>📁 File Upload Service</h1>
|
|
|
|
<div class="upload-form">
|
|
<div class="form-group">
|
|
<label for="user_id">User ID:</label>
|
|
<input type="text" id="user_id" value="accusys" placeholder="Enter User ID">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Upload Mode:</label>
|
|
<div style="margin-top: 10px;">
|
|
<label style="margin-right: 20px;">
|
|
<input type="radio" name="upload_mode" value="folder" checked onchange="toggleUploadMode()">
|
|
📁 Folder Upload (webkitdirectory)
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="upload_mode" value="file" onchange="toggleUploadMode()">
|
|
📄 Single File Upload (supports ZIP)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" id="folder-upload-group">
|
|
<label for="folder">Select Folder:</label>
|
|
<input type="file" id="folder" multiple webkitdirectory>
|
|
<p style="color: #666; font-size: 12px; margin-top: 5px;">
|
|
Upload entire folder with subdirectories
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-group" id="file-upload-group" style="display: none;">
|
|
<label for="file">Select File:</label>
|
|
<input type="file" id="single_file" accept=".zip,.rar,.7z,.tar,.gz,.bz2,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md,.py,.rs,.js,.ts,.html,.css,.json,.xml,.yaml,.yml,.jpg,.jpeg,.png,.gif,.bmp,.svg,.mp4,.mov,.avi,.mkv,.mp3,.wav,.flac">
|
|
<p style="color: #666; font-size: 12px; margin-top: 5px;">
|
|
Supports: ZIP, RAR, 7Z, TAR, PDF, Office, Text, Code, Images, Videos, Audio files
|
|
</p>
|
|
</div>
|
|
|
|
<button onclick="uploadFiles()">Start Upload</button>
|
|
</div>
|
|
|
|
<script>
|
|
function toggleUploadMode() {
|
|
const mode = document.querySelector('input[name="upload_mode"]:checked').value;
|
|
const folderGroup = document.getElementById('folder-upload-group');
|
|
const fileGroup = document.getElementById('file-upload-group');
|
|
|
|
if (mode === 'folder') {
|
|
folderGroup.style.display = 'block';
|
|
fileGroup.style.display = 'none';
|
|
} else {
|
|
folderGroup.style.display = 'none';
|
|
fileGroup.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
async function uploadFiles() {
|
|
const userId = document.getElementById('user_id').value.trim();
|
|
if (!userId) {
|
|
alert('Please enter User ID');
|
|
return;
|
|
}
|
|
|
|
const uploadMode = document.querySelector('input[name="upload_mode"]:checked').value;
|
|
let files;
|
|
|
|
if (uploadMode === 'folder') {
|
|
files = document.getElementById('folder').files;
|
|
} else {
|
|
files = document.getElementById('single_file').files;
|
|
}
|
|
|
|
if (!files || files.length === 0) {
|
|
alert('Please select files or folder');
|
|
return;
|
|
}
|
|
const fileInput = document.getElementById('file');
|
|
const files = fileInput.files;
|
|
|
|
if (!user_id || files.length === 0) {
|
|
showError('Please enter User ID and select at least one file');
|
|
return;
|
|
}
|
|
|
|
const progressDiv = document.getElementById('progress');
|
|
const progressFill = document.getElementById('progress-fill');
|
|
const progressText = document.getElementById('progress-text');
|
|
const resultDiv = document.getElementById('result');
|
|
|
|
progressDiv.style.display = 'block';
|
|
resultDiv.style.display = 'none';
|
|
|
|
let uploaded = 0;
|
|
const total = files.length;
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
// Create AbortController with timeout (30 minutes for large files)
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => {
|
|
controller.abort();
|
|
showError(`File ${file.name} upload timeout (30 minutes limit)`);
|
|
}, 30 * 60 * 1000); // 30 minutes
|
|
|
|
try {
|
|
progressText.textContent = `Uploading: ${file.name} (${uploaded + 1}/${total})`;
|
|
|
|
const response = await fetch(`/api/v2/upload-unlimited/${user_id}`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
signal: controller.signal
|
|
});
|
|
|
|
clearTimeout(timeoutId); // Clear timeout if upload succeeds
|
|
|
|
// Check HTTP status
|
|
if (!response.ok) {
|
|
showError(`File ${file.name} upload failed: HTTP ${response.status} ${response.statusText}`);
|
|
return;
|
|
}
|
|
|
|
// Check response body
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
showError(`File ${file.name} upload failed: Server returned empty response`);
|
|
return;
|
|
}
|
|
|
|
// Parse JSON
|
|
let result;
|
|
try {
|
|
result = JSON.parse(text);
|
|
} catch (parseError) {
|
|
showError(`File ${file.name} upload failed: JSON parse error - ${parseError.message}`);
|
|
return;
|
|
}
|
|
|
|
if (result.ok) {
|
|
uploaded++;
|
|
const percent = Math.round((uploaded / total) * 100);
|
|
progressFill.style.width = percent + '%';
|
|
progressText.textContent = `Upload progress: ${percent}% (${uploaded}/${total})`;
|
|
} else {
|
|
showError(`File ${file.name} upload failed: ${result.error || 'Unknown error'}`);
|
|
return;
|
|
}
|
|
} catch (err) {
|
|
clearTimeout(timeoutId);
|
|
|
|
if (err.name === 'AbortError') {
|
|
showError(`File ${file.name} upload timeout (30 minutes limit)`);
|
|
} else {
|
|
showError(`File ${file.name} upload error: ${err.message}`);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
showSuccess(`Successfully uploaded ${uploaded} files!`);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
const resultDiv = document.getElementById('result');
|
|
resultDiv.className = 'result success';
|
|
resultDiv.textContent = message;
|
|
resultDiv.style.display = 'block';
|
|
}
|
|
|
|
function showError(message) {
|
|
const resultDiv = document.getElementById('result');
|
|
resultDiv.className = 'result error';
|
|
resultDiv.textContent = message;
|
|
resultDiv.style.display = 'block';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |