Teammasik

Index.html

May 22nd, 2026 (edited)
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 12.53 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="ru">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>S3 Upload Service - Yandex Cloud</title>
  7.     <style>
  8.         * { margin: 0; padding: 0; box-sizing: border-box; }
  9.         body {
  10.             font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  11.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  12.             min-height: 100vh;
  13.             padding: 20px;
  14.         }
  15.         .container { max-width: 900px; margin: 0 auto; }
  16.         .card {
  17.             background: white;
  18.             border-radius: 16px;
  19.             padding: 30px;
  20.             margin-bottom: 30px;
  21.             box-shadow: 0 10px 40px rgba(0,0,0,0.1);
  22.         }
  23.         h2 {
  24.             color: #333;
  25.             margin-bottom: 20px;
  26.             font-size: 24px;
  27.         }
  28.         .form-group {
  29.             margin-bottom: 20px;
  30.         }
  31.         label {
  32.             display: block;
  33.             margin-bottom: 8px;
  34.             color: #555;
  35.             font-weight: 500;
  36.         }
  37.         input[type="text"], input[type="file"] {
  38.             width: 100%;
  39.             padding: 12px;
  40.             border: 2px solid #e0e0e0;
  41.             border-radius: 8px;
  42.             font-size: 16px;
  43.         }
  44.         input[type="text"]:focus, input[type="file"]:focus {
  45.             outline: none;
  46.             border-color: #667eea;
  47.         }
  48.         button {
  49.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  50.             color: white;
  51.             border: none;
  52.             padding: 12px 24px;
  53.             border-radius: 8px;
  54.             font-size: 16px;
  55.             font-weight: 600;
  56.             cursor: pointer;
  57.             transition: transform 0.2s;
  58.         }
  59.         button:hover { transform: translateY(-2px); }
  60.         .status {
  61.             margin-top: 20px;
  62.             padding: 12px;
  63.             border-radius: 8px;
  64.             display: none;
  65.         }
  66.         .status.success {
  67.             background: #d4edda;
  68.             color: #155724;
  69.             border: 1px solid #c3e6cb;
  70.             display: block;
  71.         }
  72.         .status.error {
  73.             background: #f8d7da;
  74.             color: #721c24;
  75.             border: 1px solid #f5c6cb;
  76.             display: block;
  77.         }
  78.         .file-list {
  79.             margin-top: 20px;
  80.         }
  81.         .file-item {
  82.             background: #f8f9fa;
  83.             padding: 15px;
  84.             margin-bottom: 10px;
  85.             border-radius: 8px;
  86.             display: flex;
  87.             justify-content: space-between;
  88.             align-items: center;
  89.             flex-wrap: wrap;
  90.             gap: 10px;
  91.         }
  92.         .file-name {
  93.             font-weight: 500;
  94.             color: #333;
  95.             word-break: break-all;
  96.             flex: 1;
  97.         }
  98.         .download-btn {
  99.             background: #28a745;
  100.             padding: 8px 16px;
  101.             font-size: 14px;
  102.         }
  103.         .download-btn:hover { background: #218838; }
  104.         .refresh-btn {
  105.             background: #17a2b8;
  106.             margin-bottom: 15px;
  107.         }
  108.         .progress {
  109.             width: 100%;
  110.             height: 6px;
  111.             background: #e0e0e0;
  112.             border-radius: 3px;
  113.             overflow: hidden;
  114.             margin-top: 10px;
  115.             display: none;
  116.         }
  117.         .progress-bar {
  118.             height: 100%;
  119.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  120.             width: 0%;
  121.             transition: width 0.3s;
  122.         }
  123.         .progress.active { display: block; }
  124.         .info {
  125.             background: #e7f3ff;
  126.             border-left: 4px solid #2196F3;
  127.             padding: 12px;
  128.             margin-top: 15px;
  129.             font-size: 14px;
  130.         }
  131.         code {
  132.             background: #f4f4f4;
  133.             padding: 2px 6px;
  134.             border-radius: 4px;
  135.             font-family: monospace;
  136.         }
  137.         .bucket-name {
  138.             background: #e0e7ff;
  139.             padding: 4px 8px;
  140.             border-radius: 6px;
  141.             font-family: monospace;
  142.             font-size: 14px;
  143.         }
  144.     </style>
  145. </head>
  146. <body>
  147.     <div class="container">
  148.         <div class="card">
  149.             <h2>📤 Загрузка файла в Yandex Cloud Object Storage</h2>
  150.             <div class="form-group">
  151.                 <label>Имя файла в хранилище:</label>
  152.                 <input type="text" id="fileName" placeholder="example.jpg или оставьте пустым для авто-имени">
  153.             </div>
  154.             <div class="form-group">
  155.                 <label>Выберите файл:</label>
  156.                 <input type="file" id="fileInput">
  157.             </div>
  158.             <button onclick="uploadFile()">🚀 Загрузить файл</button>
  159.             <div id="uploadStatus" class="status"></div>
  160.             <div id="progress" class="progress">
  161.                 <div class="progress-bar"></div>
  162.             </div>
  163.             <div class="info">
  164.                 📦 Бакет: <span class="bucket-name">test-bucket-supa-unique</span><br>
  165.                 🔗 Подписанный URL действителен <strong>2 минуты</strong>
  166.             </div>
  167.         </div>
  168.  
  169.         <div class="card">
  170.             <h2>📥 Скачивание файлов</h2>
  171.             <button onclick="loadFiles()" class="refresh-btn">🔄 Обновить список файлов</button>
  172.             <div id="fileList" class="file-list">
  173.                 <p style="color: #666;">Нажмите "Обновить список" для загрузки файлов из бакета</p>
  174.             </div>
  175.             <div class="info">
  176.                 💡 При нажатии на "Скачать" сервер делает редирект на подписанный URL<br>
  177.                 ⏱️ Подписанный URL действителен <strong>1 минуту</strong>
  178.             </div>
  179.         </div>
  180.     </div>
  181.  
  182.     <script>
  183.         const API_URL = '';
  184.  
  185.         async function uploadFile() {
  186.             const fileInput = document.getElementById('fileInput');
  187.             const fileNameInput = document.getElementById('fileName');
  188.             const statusDiv = document.getElementById('uploadStatus');
  189.             const progressDiv = document.getElementById('progress');
  190.             const progressBar = document.querySelector('.progress-bar');
  191.            
  192.             if (!fileInput.files[0]) {
  193.                 showStatus('❌ Пожалуйста, выберите файл', 'error');
  194.                 return;
  195.             }
  196.  
  197.             const file = fileInput.files[0];
  198.             let fileName = fileNameInput.value || file.name;
  199.             fileName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
  200.            
  201.             progressDiv.classList.add('active');
  202.             statusDiv.className = 'status';
  203.             statusDiv.style.display = 'none';
  204.            
  205.             // Анимация прогресса
  206.             let width = 0;
  207.             const progressInterval = setInterval(() => {
  208.                 width += 10;
  209.                 if (width >= 90) clearInterval(progressInterval);
  210.                 progressBar.style.width = width + '%';
  211.             }, 100);
  212.  
  213.             try {
  214.                 // 1. Получаем подписанный URL для загрузки
  215.                 const response = await fetch('/upload', {
  216.                     method: 'POST',
  217.                     headers: { 'Content-Type': 'application/json' },
  218.                     body: JSON.stringify({ fileName })
  219.                 });
  220.  
  221.                 if (!response.ok) {
  222.                     const err = await response.json();
  223.                     throw new Error(err.error || 'Failed to get upload URL');
  224.                 }
  225.                
  226.                 const { uploadUrl, fileName: uploadedFileName } = await response.json();
  227.                
  228.                 // 2. Загружаем файл напрямую в S3
  229.                 const uploadResponse = await fetch(uploadUrl, {
  230.                     method: 'PUT',
  231.                     body: file,
  232.                     headers: { 'Content-Type': file.type || 'application/octet-stream' }
  233.                 });
  234.  
  235.                 clearInterval(progressInterval);
  236.                
  237.                 if (uploadResponse.ok) {
  238.                     progressBar.style.width = '100%';
  239.                     showStatus('✅ Файл "' + uploadedFileName + '" успешно загружен в бакет!', 'success');
  240.                     document.getElementById('fileName').value = '';
  241.                     fileInput.value = '';
  242.                     loadFiles(); // Обновляем список файлов
  243.                     setTimeout(() => {
  244.                         progressDiv.classList.remove('active');
  245.                         progressBar.style.width = '0%';
  246.                     }, 1000);
  247.                 } else {
  248.                     throw new Error('Upload failed with status: ' + uploadResponse.status);
  249.                 }
  250.             } catch (error) {
  251.                 console.error('Upload error:', error);
  252.                 showStatus('❌ Ошибка загрузки: ' + error.message, 'error');
  253.                 clearInterval(progressInterval);
  254.                 progressDiv.classList.remove('active');
  255.                 progressBar.style.width = '0%';
  256.             }
  257.         }
  258.  
  259.         async function downloadFile(fileName) {
  260.             try {
  261.                 // Открываем в новой вкладке - сервер сделает редирект на подписанный URL
  262.                 window.open('/download/' + encodeURIComponent(fileName), '_blank');
  263.             } catch (error) {
  264.                 console.error('Download error:', error);
  265.                 alert('Ошибка при скачивании файла: ' + error.message);
  266.             }
  267.         }
  268.  
  269.         async function loadFiles() {
  270.             const fileListDiv = document.getElementById('fileList');
  271.             fileListDiv.innerHTML = '<p>🔄 Загрузка списка файлов...</p>';
  272.            
  273.             try {
  274.                 const response = await fetch('/list');
  275.                
  276.                 if (!response.ok) {
  277.                     throw new Error('Failed to load files');
  278.                 }
  279.                
  280.                 const { files } = await response.json();
  281.                
  282.                 if (files.length === 0) {
  283.                     fileListDiv.innerHTML = '<p>📭 В бакете пока нет файлов. Загрузите что-нибудь!</p>';
  284.                     return;
  285.                 }
  286.                
  287.                 fileListDiv.innerHTML = '';
  288.                 files.forEach(file => {
  289.                     const fileSize = formatFileSize(file.size);
  290.                     const fileItem = document.createElement('div');
  291.                     fileItem.className = 'file-item';
  292.                     fileItem.innerHTML = `
  293.                         <span class="file-name">📄 ${escapeHtml(file.name)} (${fileSize})</span>
  294.                         <button class="download-btn" onclick="downloadFile('${escapeHtml(file.name)}')">📥 Скачать</button>
  295.                     `;
  296.                     fileListDiv.appendChild(fileItem);
  297.                 });
  298.             } catch (error) {
  299.                 console.error('Load files error:', error);
  300.                 fileListDiv.innerHTML = '<p style="color: red;">❌ Ошибка загрузки списка файлов</p>';
  301.             }
  302.         }
  303.  
  304.         function showStatus(message, type) {
  305.             const statusDiv = document.getElementById('uploadStatus');
  306.             statusDiv.textContent = message;
  307.             statusDiv.className = 'status ' + type;
  308.             setTimeout(() => {
  309.                 if (statusDiv.className === 'status ' + type) {
  310.                     statusDiv.style.display = 'none';
  311.                 }
  312.             }, 5000);
  313.         }
  314.  
  315.         function formatFileSize(bytes) {
  316.             if (bytes === 0) return '0 Bytes';
  317.             const k = 1024;
  318.             const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  319.             const i = Math.floor(Math.log(bytes) / Math.log(k));
  320.             return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  321.         }
  322.  
  323.         function escapeHtml(str) {
  324.             const div = document.createElement('div');
  325.             div.textContent = str;
  326.             return div.innerHTML;
  327.         }
  328.  
  329.         loadFiles();
  330.     </script>
  331. </body>
  332. </html>
Advertisement
Add Comment
Please, Sign In to add comment