🌐
Sftp2.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-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; } .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 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> <input type="password" id="password" required> </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 // Load saved connection details on page load function loadSavedCredentials() { const savedCredentials = localStorage.getItem('sftp_credentials'); if (savedCredentials) { try { const credentials = JSON.parse(savedCredentials); document.getElementById('host').value = credentials.host || ''; document.getElementById('port').value = credentials.port || 22; document.getElementById('username').value = credentials.username || ''; if (credentials.host) { showStatus(`Loaded saved connection details for ${credentials.host}`, 'info'); } } catch (e) { console.error('Error loading saved credentials:', e); } } } function saveCredentials(host, port, username) { const credentials = { host: host, port: port, username: username, savedAt: new Date().toISOString() }; localStorage.setItem('sftp_credentials', JSON.stringify(credentials)); } function clearSavedCredentials() { localStorage.removeItem('sftp_credentials'); showStatus('Saved credentials cleared', 'info'); document.getElementById('host').value = ''; document.getElementById('username').value = ''; document.getElementById('port').value = 22; } 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() { loadSavedCredentials(); 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(); } }); }); 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>