Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="ru">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>S3 Upload Service - Yandex Cloud</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
- .container { max-width: 900px; margin: 0 auto; }
- .card {
- background: white;
- border-radius: 16px;
- padding: 30px;
- margin-bottom: 30px;
- box-shadow: 0 10px 40px rgba(0,0,0,0.1);
- }
- h2 {
- color: #333;
- margin-bottom: 20px;
- font-size: 24px;
- }
- .form-group {
- margin-bottom: 20px;
- }
- label {
- display: block;
- margin-bottom: 8px;
- color: #555;
- font-weight: 500;
- }
- input[type="text"], input[type="file"] {
- width: 100%;
- padding: 12px;
- border: 2px solid #e0e0e0;
- border-radius: 8px;
- font-size: 16px;
- }
- input[type="text"]:focus, input[type="file"]:focus {
- outline: none;
- border-color: #667eea;
- }
- button {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border: none;
- padding: 12px 24px;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- transition: transform 0.2s;
- }
- button:hover { transform: translateY(-2px); }
- .status {
- margin-top: 20px;
- padding: 12px;
- border-radius: 8px;
- display: none;
- }
- .status.success {
- background: #d4edda;
- color: #155724;
- border: 1px solid #c3e6cb;
- display: block;
- }
- .status.error {
- background: #f8d7da;
- color: #721c24;
- border: 1px solid #f5c6cb;
- display: block;
- }
- .file-list {
- margin-top: 20px;
- }
- .file-item {
- background: #f8f9fa;
- padding: 15px;
- margin-bottom: 10px;
- border-radius: 8px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 10px;
- }
- .file-name {
- font-weight: 500;
- color: #333;
- word-break: break-all;
- flex: 1;
- }
- .download-btn {
- background: #28a745;
- padding: 8px 16px;
- font-size: 14px;
- }
- .download-btn:hover { background: #218838; }
- .refresh-btn {
- background: #17a2b8;
- margin-bottom: 15px;
- }
- .progress {
- width: 100%;
- height: 6px;
- background: #e0e0e0;
- border-radius: 3px;
- overflow: hidden;
- margin-top: 10px;
- display: none;
- }
- .progress-bar {
- height: 100%;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- width: 0%;
- transition: width 0.3s;
- }
- .progress.active { display: block; }
- .info {
- background: #e7f3ff;
- border-left: 4px solid #2196F3;
- padding: 12px;
- margin-top: 15px;
- font-size: 14px;
- }
- code {
- background: #f4f4f4;
- padding: 2px 6px;
- border-radius: 4px;
- font-family: monospace;
- }
- .bucket-name {
- background: #e0e7ff;
- padding: 4px 8px;
- border-radius: 6px;
- font-family: monospace;
- font-size: 14px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="card">
- <h2>📤 Загрузка файла в Yandex Cloud Object Storage</h2>
- <div class="form-group">
- <label>Имя файла в хранилище:</label>
- <input type="text" id="fileName" placeholder="example.jpg или оставьте пустым для авто-имени">
- </div>
- <div class="form-group">
- <label>Выберите файл:</label>
- <input type="file" id="fileInput">
- </div>
- <button onclick="uploadFile()">🚀 Загрузить файл</button>
- <div id="uploadStatus" class="status"></div>
- <div id="progress" class="progress">
- <div class="progress-bar"></div>
- </div>
- <div class="info">
- 📦 Бакет: <span class="bucket-name">test-bucket-supa-unique</span><br>
- 🔗 Подписанный URL действителен <strong>2 минуты</strong>
- </div>
- </div>
- <div class="card">
- <h2>📥 Скачивание файлов</h2>
- <button onclick="loadFiles()" class="refresh-btn">🔄 Обновить список файлов</button>
- <div id="fileList" class="file-list">
- <p style="color: #666;">Нажмите "Обновить список" для загрузки файлов из бакета</p>
- </div>
- <div class="info">
- 💡 При нажатии на "Скачать" сервер делает редирект на подписанный URL<br>
- ⏱️ Подписанный URL действителен <strong>1 минуту</strong>
- </div>
- </div>
- </div>
- <script>
- const API_URL = '';
- async function uploadFile() {
- const fileInput = document.getElementById('fileInput');
- const fileNameInput = document.getElementById('fileName');
- const statusDiv = document.getElementById('uploadStatus');
- const progressDiv = document.getElementById('progress');
- const progressBar = document.querySelector('.progress-bar');
- if (!fileInput.files[0]) {
- showStatus('❌ Пожалуйста, выберите файл', 'error');
- return;
- }
- const file = fileInput.files[0];
- let fileName = fileNameInput.value || file.name;
- fileName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
- progressDiv.classList.add('active');
- statusDiv.className = 'status';
- statusDiv.style.display = 'none';
- // Анимация прогресса
- let width = 0;
- const progressInterval = setInterval(() => {
- width += 10;
- if (width >= 90) clearInterval(progressInterval);
- progressBar.style.width = width + '%';
- }, 100);
- try {
- // 1. Получаем подписанный URL для загрузки
- const response = await fetch('/upload', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ fileName })
- });
- if (!response.ok) {
- const err = await response.json();
- throw new Error(err.error || 'Failed to get upload URL');
- }
- const { uploadUrl, fileName: uploadedFileName } = await response.json();
- // 2. Загружаем файл напрямую в S3
- const uploadResponse = await fetch(uploadUrl, {
- method: 'PUT',
- body: file,
- headers: { 'Content-Type': file.type || 'application/octet-stream' }
- });
- clearInterval(progressInterval);
- if (uploadResponse.ok) {
- progressBar.style.width = '100%';
- showStatus('✅ Файл "' + uploadedFileName + '" успешно загружен в бакет!', 'success');
- document.getElementById('fileName').value = '';
- fileInput.value = '';
- loadFiles(); // Обновляем список файлов
- setTimeout(() => {
- progressDiv.classList.remove('active');
- progressBar.style.width = '0%';
- }, 1000);
- } else {
- throw new Error('Upload failed with status: ' + uploadResponse.status);
- }
- } catch (error) {
- console.error('Upload error:', error);
- showStatus('❌ Ошибка загрузки: ' + error.message, 'error');
- clearInterval(progressInterval);
- progressDiv.classList.remove('active');
- progressBar.style.width = '0%';
- }
- }
- async function downloadFile(fileName) {
- try {
- // Открываем в новой вкладке - сервер сделает редирект на подписанный URL
- window.open('/download/' + encodeURIComponent(fileName), '_blank');
- } catch (error) {
- console.error('Download error:', error);
- alert('Ошибка при скачивании файла: ' + error.message);
- }
- }
- async function loadFiles() {
- const fileListDiv = document.getElementById('fileList');
- fileListDiv.innerHTML = '<p>🔄 Загрузка списка файлов...</p>';
- try {
- const response = await fetch('/list');
- if (!response.ok) {
- throw new Error('Failed to load files');
- }
- const { files } = await response.json();
- if (files.length === 0) {
- fileListDiv.innerHTML = '<p>📭 В бакете пока нет файлов. Загрузите что-нибудь!</p>';
- return;
- }
- fileListDiv.innerHTML = '';
- files.forEach(file => {
- const fileSize = formatFileSize(file.size);
- const fileItem = document.createElement('div');
- fileItem.className = 'file-item';
- fileItem.innerHTML = `
- <span class="file-name">📄 ${escapeHtml(file.name)} (${fileSize})</span>
- <button class="download-btn" onclick="downloadFile('${escapeHtml(file.name)}')">📥 Скачать</button>
- `;
- fileListDiv.appendChild(fileItem);
- });
- } catch (error) {
- console.error('Load files error:', error);
- fileListDiv.innerHTML = '<p style="color: red;">❌ Ошибка загрузки списка файлов</p>';
- }
- }
- function showStatus(message, type) {
- const statusDiv = document.getElementById('uploadStatus');
- statusDiv.textContent = message;
- statusDiv.className = 'status ' + type;
- setTimeout(() => {
- if (statusDiv.className === 'status ' + type) {
- statusDiv.style.display = 'none';
- }
- }, 5000);
- }
- function formatFileSize(bytes) {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- }
- function escapeHtml(str) {
- const div = document.createElement('div');
- div.textContent = str;
- return div.innerHTML;
- }
- loadFiles();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment