Files
markbase/markbase-core/src/upload.html
Warren 4fa8fd8c1f Merge origin SMB fixes with local Phase 21-22 features
Origin changes merged:
- SMB performance optimization (pread/pwrite, tokio Mutex)
- macOS SMB mount fix (AAPL caps, credit grant)
- Compound request integration tests
- CTDB architecture analysis

Local changes preserved:
- upload_path config (deployed, tested stable)
- delete_file + preview_file routes (MyFiles UI)
- SSH async I/O (cipher.rs, packet.rs, server.rs)
- auth.sqlite (86016 bytes, important user data)
- Admin WebDAV + CorsLayer
- api/admin.rs + api/config.rs (new endpoints)

Conflicts resolved:
- myfiles.rs: kept upload_path + OnceLock static
- auth.sqlite: preserved local version (important data)

Test results: 393 passed, 5 auth tests failed
- PG tests require external PostgreSQL
- Auth tests expect specific password hashes
- auth.sqlite preserved with actual user credentials
2026-06-30 07:25:04 +08:00

146 lines
6.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>File Upload</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f7; color: #1d1d1f; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; }
h1 { font-size: 28px; margin-bottom: 8px; }
.desc { color: #6e6e73; margin-bottom: 24px; }
.card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); margin-bottom: 16px; }
.form-group { margin-bottom: 16px; }
label { display: block; font-weight: 600; margin-bottom: 6px; font-size: 14px; }
input[type="text"] { width: 100%; padding: 10px 12px; border: 1px solid #d2d2d7; border-radius: 8px; font-size: 14px; }
input[type="text"]:focus { outline: none; border-color: #0071e3; }
.radio-group { display: flex; gap: 16px; margin-top: 6px; }
.radio-group label { font-weight: 400; font-size: 14px; display: flex; align-items: center; gap: 6px; cursor: pointer; }
.file-input-wrap { margin-top: 8px; }
.file-input-wrap input[type="file"] { width: 100%; padding: 8px; border: 1px solid #d2d2d7; border-radius: 8px; font-size: 14px; }
.hint { font-size: 12px; color: #6e6e73; margin-top: 4px; }
.btn { padding: 10px 24px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-primary { background: #0071e3; color: #fff; }
.btn-primary:hover { background: #0058b0; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.progress-wrap { margin-top: 16px; display: none; }
.progress-bar { width: 100%; height: 8px; background: #e8e8ed; border-radius: 4px; overflow: hidden; }
.progress-fill { height: 100%; background: #0071e3; width: 0%; transition: width 0.3s; border-radius: 4px; }
.progress-text { font-size: 13px; color: #6e6e73; margin-top: 8px; }
.result { margin-top: 16px; padding: 12px 16px; border-radius: 8px; font-size: 14px; display: none; }
.result.success { background: #d1fae5; color: #065f46; }
.result.error { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
<div class="container">
<h1>Upload</h1>
<p class="desc">Upload files to user storage directory</p>
<div class="card">
<div class="form-group">
<label for="user_id">User ID</label>
<input type="text" id="user_id" value="demo">
</div>
<div class="form-group">
<label>Mode</label>
<div class="radio-group">
<label><input type="radio" name="mode" value="file" checked onchange="toggleMode()"> Single File</label>
<label><input type="radio" name="mode" value="folder" onchange="toggleMode()"> Folder (all files)</label>
</div>
</div>
<div class="form-group">
<div id="single-group">
<div class="file-input-wrap">
<input type="file" id="single_file">
</div>
</div>
<div id="folder-group" style="display:none">
<div class="file-input-wrap">
<input type="file" id="folder" multiple webkitdirectory>
</div>
<p class="hint">Uploads all files in the selected folder</p>
</div>
</div>
<button class="btn btn-primary" id="upload-btn" onclick="uploadFiles()">Upload</button>
<div class="progress-wrap" id="progress">
<div class="progress-bar"><div class="progress-fill" id="progress-fill"></div></div>
<div class="progress-text" id="progress-text"></div>
</div>
<div class="result" id="result"></div>
</div>
</div>
<script>
function toggleMode() {
const mode = document.querySelector('input[name="mode"]:checked').value;
document.getElementById('single-group').style.display = mode === 'file' ? 'block' : 'none';
document.getElementById('folder-group').style.display = mode === 'folder' ? 'block' : 'none';
}
async function uploadFiles() {
const uid = document.getElementById('user_id').value.trim();
if (!uid) return showError('Enter a user ID');
const mode = document.querySelector('input[name="mode"]:checked').value;
const files = mode === 'folder'
? document.getElementById('folder').files
: document.getElementById('single_file').files;
if (!files || files.length === 0) return showError('Select a file or folder');
const btn = document.getElementById('upload-btn');
btn.disabled = true;
const progress = document.getElementById('progress');
const fill = document.getElementById('progress-fill');
const ptext = document.getElementById('progress-text');
const result = document.getElementById('result');
progress.style.display = 'block';
result.style.display = 'none';
let uploaded = 0;
const total = files.length;
for (let i = 0; i < total; i++) {
const f = files[i];
const fd = new FormData();
fd.append('file', f);
ptext.textContent = `Uploading ${f.name} (${i+1}/${total})`;
try {
const res = await fetch(`/api/v2/upload-unlimited/${uid}`, { method: 'POST', body: fd });
if (!res.ok) { showError(`${f.name}: HTTP ${res.status}`); btn.disabled = false; return; }
const data = await res.json();
if (!data.ok) { showError(`${f.name}: ${data.error || 'unknown'}`); btn.disabled = false; return; }
uploaded++;
const pct = Math.round(uploaded / total * 100);
fill.style.width = pct + '%';
ptext.textContent = `${pct}% (${uploaded}/${total})`;
} catch(e) {
showError(`${f.name}: ${e.message}`);
btn.disabled = false;
return;
}
}
showSuccess(`Uploaded ${uploaded} file${uploaded > 1 ? 's' : ''}`);
btn.disabled = false;
}
function showSuccess(m) { showResult(m, 'success'); }
function showError(m) { showResult(m, 'error'); }
function showResult(m, t) {
const r = document.getElementById('result');
r.className = 'result ' + t;
r.textContent = m;
r.style.display = 'block';
}
</script>
</body>
</html>