🌐
SFTP.html
Back
📝 Html ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SFTP File Manager</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: rgba(255, 255, 255, 0.95); border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #2c3e50, #3498db); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; font-weight: 300; } .content { padding: 30px; } .connection-form { background: #f8f9fa; border-radius: 15px; padding: 25px; margin-bottom: 30px; border: 1px solid #e9ecef; } .form-memory { background: #e3f2fd; border-radius: 8px; padding: 12px 15px; margin-bottom: 15px; font-size: 12px; color: #0c5460; border: 1px solid #bee5eb; position: relative; } .form-memory strong { color: #0c5460; } .clear-memory-btn { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #0c5460; cursor: pointer; text-decoration: underline; font-size: 11px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; font-weight: 600; margin-bottom: 8px; color: #2c3e50; } .form-group input { width: 100%; padding: 12px 15px; border: 2px solid #e9ecef; border-radius: 8px; font-size: 14px; transition: border-color 0.3s ease; } .password-container { position: relative; } .password-container input { padding-right: 45px; } .password-toggle { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; font-size: 16px; color: #6c757d; transition: color 0.2s ease; padding: 5px; } .password-toggle:hover { color: #3498db; } .form-group input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); } .form-row { display: flex; gap: 20px; } .form-row .form-group { flex: 1; } .btn { background: linear-gradient(135deg, #3498db, #2980b9); color: white; border: none; padding: 12px 25px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; } .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3); } .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } .btn-danger { background: linear-gradient(135deg, #e74c3c, #c0392b); } .btn-danger:hover { box-shadow: 0 5px 15px rgba(231, 76, 60, 0.3); } .btn-success { background: linear-gradient(135deg, #27ae60, #219a52); } .btn-success:hover { box-shadow: 0 5px 15px rgba(39, 174, 96, 0.3); } .status { padding: 15px; border-radius: 8px; margin-bottom: 20px; font-weight: 600; } .status.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .status.info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .file-manager { display: none; background: #f8f9fa; border-radius: 15px; padding: 25px; border: 1px solid #e9ecef; } .file-manager.active { display: block; } .file-controls { display: flex; gap: 15px; margin-bottom: 20px; align-items: center; flex-wrap: wrap; } .current-path { background: white; padding: 10px 15px; border-radius: 8px; border: 1px solid #dee2e6; font-family: monospace; flex: 1; min-width: 200px; } .file-list { background: white; border-radius: 10px; border: 1px solid #dee2e6; overflow: hidden; } .file-item { display: flex; align-items: center; padding: 12px 15px; border-bottom: 1px solid #f1f3f4; transition: background-color 0.2s ease; } .file-item:hover { background: #f8f9fa; } .file-item:last-child { border-bottom: none; } .file-icon { width: 20px; height: 20px; margin-right: 10px; flex-shrink: 0; } .file-name { flex: 1; font-weight: 500; } .file-name.directory { color: #3498db; cursor: pointer; } .file-size { color: #6c757d; font-size: 12px; margin-right: 15px; min-width: 60px; } .file-date { color: #6c757d; font-size: 12px; margin-right: 15px; min-width: 120px; } .file-actions { display: flex; gap: 8px; flex-wrap: wrap; } .btn-small { padding: 6px 12px; font-size: 12px; } .upload-area { border: 2px dashed #dee2e6; border-radius: 10px; padding: 30px; text-align: center; margin-bottom: 20px; transition: all 0.3s ease; cursor: pointer; } .upload-area:hover { border-color: #3498db; background: #f8f9fa; } .upload-area.dragover { border-color: #3498db; background: #e3f2fd; } @media (max-width: 768px) { .file-controls { flex-direction: column; align-items: stretch; } .current-path { order: -1; margin-bottom: 10px; } .file-item { flex-direction: column; align-items: flex-start; gap: 8px; } .file-info { display: flex; width: 100%; justify-content: space-between; font-size: 12px; } .form-row { flex-direction: column; } .btn { margin: 5px 0; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>🗂️ SFTP File Manager</h1> <p>Secure File Transfer Protocol Connector</p> </div> <div class="content"> <div id="status-message"></div> <!-- Connection Form --> <div id="connection-form" class="connection-form"> <h3 style="margin-bottom: 20px; color: #2c3e50;">Connect to SFTP Server</h3> <div id="form-memory" class="form-memory" style="display: none;"> <strong>Last used:</strong> <span id="memory-info"></span> <button type="button" class="clear-memory-btn" onclick="clearFormMemory()">Clear</button> </div> <div class="form-row"> <div class="form-group"> <label for="host">Host/Server</label> <input type="text" id="host" placeholder="example.com" required> </div> <div class="form-group"> <label for="port">Port</label> <input type="number" id="port" value="22" required> </div> </div> <div class="form-row"> <div class="form-group"> <label for="username">Username</label> <input type="text" id="username" required> </div> <div class="form-group"> <label for="password">Password</label> <div class="password-container"> <input type="password" id="password" required> <button type="button" class="password-toggle" onclick="togglePassword()" title="Show password">👁️</button> </div> </div> </div> <div class="form-row"> <button class="btn" id="connect-btn" onclick="connect()">Connect to Server</button> <button class="btn btn-success" onclick="testConnection()">Test Server</button> <button class="btn btn-danger" onclick="clearSavedCredentials()">Clear Saved</button> </div> </div> <!-- File Manager --> <div id="file-manager" class="file-manager"> <div class="file-controls"> <input type="text" id="current-path" class="current-path" value="/" readonly> <button class="btn btn-success" onclick="refreshFiles()">↻ Refresh</button> <button class="btn" onclick="createFolder()">📁 New Folder</button> <button class="btn btn-danger" onclick="disconnect()">Disconnect</button> </div> <div class="upload-area" id="upload-area"> <p>📤 Drag and drop files here or <strong>click to select files</strong></p> <input type="file" id="file-input" multiple style="display: none;"> </div> <div id="file-list" class="file-list"> <!-- Files will be populated here --> </div> </div> </div> </div> <script> let currentPath = '/'; let isConnected = false; const API_URL = 'sftp_connector.php'; // Point this to your PHP file // Enhanced form memory management function saveFormData(host, port, username) { const formData = { host: host, port: port, username: username, lastUsed: new Date().toISOString(), timestamp: Date.now() }; localStorage.setItem('sftp_form_data', JSON.stringify(formData)); } function loadFormData() { const savedData = localStorage.getItem('sftp_form_data'); if (savedData) { try { const formData = JSON.parse(savedData); document.getElementById('host').value = formData.host || ''; document.getElementById('port').value = formData.port || 22; document.getElementById('username').value = formData.username || ''; if (formData.host) { const memoryDiv = document.getElementById('form-memory'); const memoryInfo = document.getElementById('memory-info'); const lastUsedDate = new Date(formData.lastUsed); const timeAgo = getTimeAgo(lastUsedDate); memoryInfo.textContent = `${formData.username}@${formData.host}:${formData.port} (${timeAgo})`; memoryDiv.style.display = 'block'; showStatus(`Loaded saved connection details for ${formData.host}`, 'info'); } } catch (e) { console.error('Error loading saved form data:', e); } } } function clearFormMemory() { localStorage.removeItem('sftp_form_data'); document.getElementById('form-memory').style.display = 'none'; showStatus('Form memory cleared', 'info'); } function getTimeAgo(date) { const now = new Date(); const diffMs = now - date; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return 'just now'; if (diffMins < 60) return `${diffMins} minutes ago`; if (diffHours < 24) return `${diffHours} hours ago`; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); } function togglePassword() { const passwordField = document.getElementById('password'); const toggleButton = document.querySelector('.password-toggle'); if (passwordField.type === 'password') { passwordField.type = 'text'; toggleButton.textContent = '🙈'; toggleButton.title = 'Hide password'; } else { passwordField.type = 'password'; toggleButton.textContent = '👁️'; toggleButton.title = 'Show password'; } } function saveCredentials(host, port, username) { // Use the new saveFormData function saveFormData(host, port, username); } function clearSavedCredentials() { // Clear both old and new storage formats localStorage.removeItem('sftp_credentials'); clearFormMemory(); document.getElementById('host').value = ''; document.getElementById('username').value = ''; document.getElementById('port').value = 22; document.getElementById('password').value = ''; } function showStatus(message, type = 'info') { const statusDiv = document.getElementById('status-message'); statusDiv.innerHTML = `<div class="status ${type}">${message}</div>`; const timeout = type === 'success' ? 8000 : 5000; setTimeout(() => { if (statusDiv.innerHTML.includes(message)) { statusDiv.innerHTML = ''; } }, timeout); } async function makeRequest(action, data = {}) { try { const requestData = { action, ...data }; console.log('Sending request:', requestData); const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestData) }); const text = await response.text(); console.log('Raw response:', text); let result; try { result = JSON.parse(text); } catch (parseError) { console.error('JSON parse error:', parseError); return { success: false, message: 'Invalid JSON response from server', debug: text.substring(0, 500) }; } console.log('Parsed response:', result); return result; } catch (error) { console.error('Request error:', error); return { success: false, message: 'Network error: ' + error.message }; } } async function testConnection() { showStatus('Testing SFTP connector capabilities...', 'info'); const result = await makeRequest('test'); if (result.success) { const data = result.data; let message = 'Server responding! '; if (data.ssh2_loaded) { message += 'SSH2 extension: Available ✅'; } else if (data.exec_available) { message += 'System commands: Available ✅'; } else { message += 'Limited capabilities ⚠️'; } showStatus(message, 'success'); console.log('Server capabilities:', result.data); } else { showStatus('Server test failed: ' + result.message, 'error'); if (result.debug) { console.error('Debug info:', result.debug); } } } async function connect() { const host = document.getElementById('host').value; const port = document.getElementById('port').value; const username = document.getElementById('username').value; const password = document.getElementById('password').value; if (!host || !username || !password) { showStatus('Please fill in all required fields', 'error'); return; } const connectBtn = document.getElementById('connect-btn'); connectBtn.disabled = true; connectBtn.textContent = 'Connecting...'; showStatus('Connecting to server...', 'info'); try { const result = await makeRequest('connect', { host, port: parseInt(port), username, password }); if (result.success) { isConnected = true; saveCredentials(host, port, username); document.getElementById('connection-form').style.display = 'none'; document.getElementById('file-manager').classList.add('active'); showStatus('✅ Connected successfully! Loading files...', 'success'); refreshFiles(); } else { showStatus('❌ Connection failed: ' + result.message, 'error'); } } catch (error) { showStatus('❌ Connection error: ' + error.message, 'error'); } finally { connectBtn.disabled = false; connectBtn.textContent = 'Connect to Server'; } } async function refreshFiles() { if (!isConnected) return; showStatus('Loading files...', 'info'); const result = await makeRequest('list', { path: currentPath }); if (result.success) { displayFiles(result.data); document.getElementById('current-path').value = currentPath; showStatus(`Loaded ${result.data.length} items`, 'success'); } else { showStatus('Failed to load files: ' + result.message, 'error'); } } function displayFiles(files) { const fileList = document.getElementById('file-list'); let html = ''; if (currentPath !== '/') { html += ` <div class="file-item"> <div class="file-icon">📁</div> <div class="file-name directory" onclick="navigateToParent()">.. (Parent Directory)</div> <div class="file-size"></div> <div class="file-date"></div> <div class="file-actions"></div> </div> `; } files.forEach(file => { const icon = file.is_dir ? '📁' : '📄'; const nameClass = file.is_dir ? 'directory' : ''; const onClick = file.is_dir ? `onclick="navigateToDirectory('${file.path}')"` : ''; const size = file.is_dir ? '' : formatFileSize(file.size); html += ` <div class="file-item"> <div class="file-icon">${icon}</div> <div class="file-name ${nameClass}" ${onClick}>${file.name}</div> <div class="file-size">${size}</div> <div class="file-date">${file.modified}</div> <div class="file-actions"> ${!file.is_dir ? `<button class="btn btn-small" onclick="downloadFile('${file.path}')">↓ Download</button>` : ''} <button class="btn btn-small btn-danger" onclick="deleteItem('${file.path}', ${file.is_dir})">🗑️ Delete</button> </div> </div> `; }); fileList.innerHTML = html; } function navigateToDirectory(path) { currentPath = path; refreshFiles(); } function navigateToParent() { const pathParts = currentPath.split('/').filter(p => p); pathParts.pop(); currentPath = '/' + pathParts.join('/'); if (currentPath !== '/') currentPath += '/'; refreshFiles(); } async function deleteItem(path, isDirectory) { if (!confirm(`Are you sure you want to delete ${isDirectory ? 'this directory' : 'this file'}?`)) { return; } const result = await makeRequest('delete', { path }); if (result.success) { showStatus('Item deleted successfully', 'success'); refreshFiles(); } else { showStatus('Delete failed: ' + result.message, 'error'); } } async function createFolder() { const folderName = prompt('Enter folder name:'); if (!folderName) return; const folderPath = currentPath + folderName; const result = await makeRequest('create_folder', { path: folderPath }); if (result.success) { showStatus('Folder created successfully', 'success'); refreshFiles(); } else { showStatus('Failed to create folder: ' + result.message, 'error'); } } async function disconnect() { const result = await makeRequest('disconnect'); isConnected = false; document.getElementById('connection-form').style.display = 'block'; document.getElementById('file-manager').classList.remove('active'); showStatus('Disconnected from server', 'info'); document.getElementById('password').value = ''; } function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', '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 downloadFile(remotePath) { const filename = remotePath.split('/').pop(); showStatus(`Downloading ${filename}...`, 'info'); console.log('Download requested for:', remotePath); showStatus('Download feature requires additional server-side implementation', 'error'); } // File upload handling document.addEventListener('DOMContentLoaded', function() { loadFormData(); // Load saved form data on page load const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); uploadArea.addEventListener('click', () => { fileInput.click(); }); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); handleFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', (e) => { handleFiles(e.target.files); }); document.addEventListener('keypress', function(e) { if (e.key === 'Enter' && e.target.closest('#connection-form')) { connect(); } }); // Auto-save form data when fields change ['host', 'port', 'username'].forEach(fieldId => { const field = document.getElementById(fieldId); if (field) { field.addEventListener('blur', () => { const host = document.getElementById('host').value.trim(); const port = document.getElementById('port').value.trim(); const username = document.getElementById('username').value.trim(); if (host && username) { saveFormData(host, port || 22, username); loadFormData(); // Refresh the memory display } }); } }); }); function handleFiles(files) { if (!isConnected) { showStatus('Please connect to server first', 'error'); return; } for (let file of files) { uploadFile(file); } } async function uploadFile(file) { showStatus(`Uploading ${file.name}...`, 'info'); console.log('Upload requested for:', file.name); showStatus('Upload feature requires additional server-side implementation for file handling', 'error'); } </script> </body> </html>