upload-r2 / index.html
gewei20's picture
Add 2 files
3f88ce0 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CloudSync - S3 File Uploader</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.dropzone {
border: 2px dashed #3b82f6;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #10b981;
background-color: #f0f9ff;
}
.progress-bar {
transition: width 0.3s ease;
}
.file-item:hover {
background-color: #f8fafc;
}
.rotate {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
#folderInput, #fileInput {
display: none;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Header -->
<header class="mb-8 text-center">
<h1 class="text-4xl font-bold text-blue-600 mb-2">CloudSync</h1>
<p class="text-gray-600">Efficient S3 File Uploader with Real-time Monitoring</p>
</header>
<!-- Connection Settings Card -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-cloud-upload-alt text-blue-500 mr-2"></i>
S3 Connection Settings
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Endpoint URL</label>
<input type="text" id="endpointUrl" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" value="https://75f5ed467c14e39214f3a6f2a169f3d0.r2.cloudflarestorage.com/scrapydocs">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bucket Name</label>
<input type="text" id="bucketName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" value="scrapydocs">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Access Key</label>
<input type="password" id="accessKey" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your access key">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Secret Key</label>
<input type="password" id="secretKey" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter your secret key">
</div>
</div>
<div class="mt-6 flex justify-end">
<button id="testConnectionBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-plug mr-2"></i> Test Connection
</button>
</div>
<div id="connectionStatus" class="mt-4 hidden">
<div class="flex items-center p-3 rounded-md">
<i class="fas fa-circle mr-2 text-gray-400"></i>
<span class="text-sm">Connection status will appear here</span>
</div>
</div>
</div>
<!-- File Upload Card -->
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-folder-open text-blue-500 mr-2"></i>
Folder Upload
</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Folder Path</label>
<div class="flex">
<input type="text" id="folderPath" class="flex-1 px-3 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter folder path or drag & drop" readonly>
<button id="browseBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-r-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
<i class="fas fa-folder-open mr-1"></i> Browse
</button>
</div>
</div>
<div id="dropzone" class="dropzone p-8 rounded-md mb-4 text-center cursor-pointer">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-3"></i>
<p class="text-gray-600 mb-2">Drag & drop files here or click to select</p>
<p class="text-sm text-gray-500">Supports multiple files and folders</p>
</div>
<!-- Hidden file inputs -->
<input type="file" id="fileInput" multiple>
<input type="file" id="folderInput" webkitdirectory directory multiple>
<div class="flex justify-between">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Prefix (optional)</label>
<input type="text" id="prefix" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., docs/2023">
</div>
<div class="flex items-end">
<button id="uploadBtn" class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-upload mr-2"></i> Upload Files
</button>
</div>
</div>
</div>
<!-- Upload Progress -->
<div id="uploadProgress" class="bg-white rounded-lg shadow-md p-6 mb-6 hidden">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-tasks text-blue-500 mr-2"></i>
Upload Progress
</h2>
<div class="mb-4">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700">Overall Progress</span>
<span id="overallProgressText" class="text-sm font-medium text-gray-700">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="overallProgressBar" class="bg-blue-600 h-2.5 rounded-full progress-bar" style="width: 0%"></div>
</div>
</div>
<div class="mb-2 flex justify-between">
<span class="text-sm font-medium text-gray-700">Files Processed: <span id="filesProcessed">0</span>/<span id="totalFiles">0</span></span>
<span class="text-sm font-medium text-gray-700">Success: <span id="successCount" class="text-green-600">0</span> | Failed: <span id="failedCount" class="text-red-600">0</span></span>
</div>
<div id="fileList" class="border border-gray-200 rounded-md divide-y divide-gray-200 max-h-64 overflow-y-auto">
<!-- Files will be listed here -->
<div class="text-center py-8 text-gray-500">
<i class="fas fa-file-upload text-3xl mb-2"></i>
<p>No files selected</p>
</div>
</div>
<div class="mt-4 flex justify-end">
<button id="cancelUploadBtn" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-stop-circle mr-2"></i> Cancel Upload
</button>
</div>
</div>
<!-- Log Console -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-terminal text-blue-500 mr-2"></i>
Activity Log
</h2>
<div id="logConsole" class="bg-gray-900 text-green-400 font-mono text-sm p-4 rounded-md h-48 overflow-y-auto">
<div>> Welcome to CloudSync S3 Uploader</div>
<div>> Ready to upload files to your S3 bucket</div>
</div>
<div class="mt-4 flex justify-between">
<button id="clearLogBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-trash-alt mr-2"></i> Clear Log
</button>
<div class="flex space-x-2">
<button id="copyLogBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-copy mr-2"></i> Copy Log
</button>
<button id="exportLogBtn" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 flex items-center">
<i class="fas fa-file-export mr-2"></i> Export Log
</button>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const dropzone = document.getElementById('dropzone');
const folderPathInput = document.getElementById('folderPath');
const browseBtn = document.getElementById('browseBtn');
const uploadBtn = document.getElementById('uploadBtn');
const uploadProgress = document.getElementById('uploadProgress');
const fileList = document.getElementById('fileList');
const logConsole = document.getElementById('logConsole');
const testConnectionBtn = document.getElementById('testConnectionBtn');
const connectionStatus = document.getElementById('connectionStatus');
const fileInput = document.getElementById('fileInput');
const folderInput = document.getElementById('folderInput');
// State variables
let filesToUpload = [];
let isUploading = false;
let uploadCancelled = false;
// Event Listeners
dropzone.addEventListener('click', () => {
fileInput.click();
});
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
const files = Array.from(e.dataTransfer.files);
if (files.length > 0) {
filesToUpload = files;
folderPathInput.value = '';
updateFileList();
addLog(`Added ${files.length} file(s) from drag & drop`);
}
});
browseBtn.addEventListener('click', () => {
folderInput.click();
});
fileInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
if (files.length > 0) {
filesToUpload = files;
folderPathInput.value = '';
updateFileList();
addLog(`Added ${files.length} file(s) from file selection`);
}
fileInput.value = ''; // Reset to allow selecting same files again
});
folderInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
if (files.length > 0) {
filesToUpload = files;
const path = e.target.files[0].webkitRelativePath.split('/')[0];
folderPathInput.value = path;
updateFileList();
addLog(`Added folder "${path}" containing ${files.length} files`);
}
folderInput.value = ''; // Reset to allow selecting same folder again
});
uploadBtn.addEventListener('click', () => {
if (filesToUpload.length === 0 && !folderPathInput.value) {
addLog("✖ Error: No files or folder selected for upload", "error");
return;
}
startUpload();
});
testConnectionBtn.addEventListener('click', () => {
testConnection();
});
document.getElementById('cancelUploadBtn').addEventListener('click', () => {
if (isUploading) {
uploadCancelled = true;
isUploading = false;
addLog("Upload cancelled by user", "warning");
}
});
document.getElementById('clearLogBtn').addEventListener('click', () => {
logConsole.innerHTML = '';
addLog("Log cleared");
});
document.getElementById('copyLogBtn').addEventListener('click', () => {
navigator.clipboard.writeText(logConsole.textContent);
addLog("Log content copied to clipboard");
});
document.getElementById('exportLogBtn').addEventListener('click', () => {
const blob = new Blob([logConsole.textContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'cloudsync-log.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
addLog("Log exported as text file");
});
// Functions
function updateFileList() {
if (filesToUpload.length === 0 && !folderPathInput.value) {
fileList.innerHTML = `
<div class="text-center py-8 text-gray-500">
<i class="fas fa-file-upload text-3xl mb-2"></i>
<p>No files selected</p>
</div>
`;
return;
}
let html = '';
if (folderPathInput.value) {
html += `
<div class="file-item p-3 flex items-center bg-blue-50">
<i class="fas fa-folder text-blue-500 mr-3"></i>
<div class="flex-1">
<div class="font-medium">${folderPathInput.value.split('/').pop() || folderPathInput.value.split('\\').pop()}</div>
<div class="text-xs text-gray-500">Folder (${filesToUpload.length} files)</div>
</div>
<div class="text-sm text-gray-500">Pending</div>
</div>
`;
}
filesToUpload.forEach((file, index) => {
html += `
<div class="file-item p-3 flex items-center" data-index="${index}">
<i class="fas ${file.type.startsWith('image/') ? 'fa-image' : 'fa-file'} text-gray-500 mr-3"></i>
<div class="flex-1 truncate">
<div class="font-medium truncate">${file.name}</div>
<div class="text-xs text-gray-500">${formatFileSize(file.size)}</div>
</div>
<div class="text-sm text-gray-500">Pending</div>
</div>
`;
});
fileList.innerHTML = html;
uploadProgress.classList.remove('hidden');
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
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(2)) + ' ' + sizes[i];
}
function addLog(message, type = "info") {
const now = new Date();
const timestamp = now.toLocaleTimeString();
let icon = '>';
if (type === "error") {
icon = '<i class="fas fa-times-circle text-red-500"></i>';
} else if (type === "success") {
icon = '<i class="fas fa-check-circle text-green-500"></i>';
} else if (type === "warning") {
icon = '<i class="fas fa-exclamation-triangle text-yellow-500"></i>';
}
const logEntry = document.createElement('div');
logEntry.innerHTML = `<span class="text-gray-500">${timestamp}</span> ${icon} ${message}`;
logConsole.appendChild(logEntry);
logConsole.scrollTop = logConsole.scrollHeight;
}
function testConnection() {
const endpointUrl = document.getElementById('endpointUrl').value;
const accessKey = document.getElementById('accessKey').value;
const secretKey = document.getElementById('secretKey').value;
if (!endpointUrl || !accessKey || !secretKey) {
addLog("✖ Error: Please fill all connection fields", "error");
return;
}
testConnectionBtn.innerHTML = '<i class="fas fa-spinner rotate mr-2"></i> Testing...';
testConnectionBtn.disabled = true;
// Simulate connection test
setTimeout(() => {
connectionStatus.classList.remove('hidden');
connectionStatus.innerHTML = `
<div class="flex items-center p-3 rounded-md bg-green-50">
<i class="fas fa-check-circle mr-2 text-green-500"></i>
<span class="text-sm text-green-800">Successfully connected to S3 bucket</span>
</div>
`;
testConnectionBtn.innerHTML = '<i class="fas fa-plug mr-2"></i> Test Connection';
testConnectionBtn.disabled = false;
addLog("✔ Successfully connected to S3 bucket", "success");
}, 1500);
}
function startUpload() {
if (isUploading) return;
isUploading = true;
uploadCancelled = false;
const prefix = document.getElementById('prefix').value;
// Reset counters
document.getElementById('filesProcessed').textContent = '0';
document.getElementById('totalFiles').textContent = filesToUpload.length;
document.getElementById('successCount').textContent = '0';
document.getElementById('failedCount').textContent = '0';
document.getElementById('overallProgressBar').style.width = '0%';
document.getElementById('overallProgressText').textContent = '0%';
// Update UI
uploadBtn.innerHTML = '<i class="fas fa-spinner rotate mr-2"></i> Uploading...';
uploadBtn.disabled = true;
addLog(`Starting upload to bucket: ${document.getElementById('bucketName').value}`);
if (prefix) {
addLog(`Using prefix: ${prefix}`);
}
if (folderPathInput.value) {
addLog(`Uploading folder: ${folderPathInput.value}`);
}
// Simulate file uploads
simulateFileUpload(filesToUpload, prefix);
}
function simulateFileUpload(files, prefix) {
const totalFiles = files.length;
let processed = 0;
let success = 0;
let failed = 0;
const uploadInterval = setInterval(() => {
if (uploadCancelled || processed >= totalFiles) {
clearInterval(uploadInterval);
finishUpload(processed, success, failed);
return;
}
processed++;
const isSuccess = Math.random() > 0.1; // 90% success rate
if (isSuccess) {
success++;
document.getElementById('successCount').textContent = success;
addLog(`✔ Successfully uploaded ${prefix ? prefix + '/' : ''}${files[processed-1].name}`, "success");
// Update file item status
const fileItem = fileList.querySelector(`.file-item[data-index="${processed-1}"]`);
if (fileItem) {
const statusDiv = fileItem.querySelector('div:last-child');
statusDiv.innerHTML = '<span class="text-green-600"><i class="fas fa-check mr-1"></i>Done</span>';
}
} else {
failed++;
document.getElementById('failedCount').textContent = failed;
addLog(`✖ Failed to upload ${prefix ? prefix + '/' : ''}${files[processed-1].name}`, "error");
}
document.getElementById('filesProcessed').textContent = processed;
const progress = Math.floor((processed / totalFiles) * 100);
document.getElementById('overallProgressBar').style.width = `${progress}%`;
document.getElementById('overallProgressText').textContent = `${progress}%`;
}, 500);
}
function finishUpload(processed, success, failed) {
isUploading = false;
uploadBtn.innerHTML = '<i class="fas fa-upload mr-2"></i> Upload Files';
uploadBtn.disabled = false;
if (uploadCancelled) {
addLog(`Upload cancelled. Processed ${processed} files (${success} success, ${failed} failed)`, "warning");
} else {
addLog(`Upload complete. Processed ${processed} files (${success} success, ${failed} failed)`);
if (failed > 0) {
addLog("Some files failed to upload. You can try uploading them again.", "warning");
}
}
}
// Initialize
updateFileList();
addLog("Application initialized");
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gewei20/upload-r2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>