update ftp browser, enable folder downloads (local), reimplemented timer. Enhanced project view

This commit is contained in:
serversdwn
2026-01-14 21:59:22 +00:00
parent d349af9444
commit 7971092509
4 changed files with 469 additions and 81 deletions

View File

@@ -1,4 +1,4 @@
<!-- FTP File Browser for SLMs -->
<!-- FTP File Browser for SLMs v2.0 - Folder Download Support -->
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Download Files from SLMs</h2>
@@ -34,7 +34,7 @@
disabled>
Disable FTP
</button>
<button onclick="loadFTPFiles('{{ unit_item.unit.id }}', '/NL43_DATA')"
<button onclick="loadFTPFiles('{{ unit_item.unit.id }}', '/NL-43')"
id="browse-ftp-{{ unit_item.unit.id }}"
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors"
disabled>
@@ -50,8 +50,8 @@
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
</svg>
<span id="current-path-{{ unit_item.unit.id }}" class="text-sm font-mono text-gray-600 dark:text-gray-400">/NL43_DATA</span>
<button onclick="loadFTPFiles('{{ unit_item.unit.id }}', '/NL43_DATA')"
<span id="current-path-{{ unit_item.unit.id }}" class="text-sm font-mono text-gray-600 dark:text-gray-400">/NL-43</span>
<button onclick="loadFTPFiles('{{ unit_item.unit.id }}', '/NL-43')"
class="ml-auto text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded hover:bg-gray-200 dark:hover:bg-gray-600">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
@@ -127,7 +127,7 @@ async function enableFTP(unitId) {
if (response.ok) {
await checkFTPStatus(unitId);
// Auto-load files after enabling
setTimeout(() => loadFTPFiles(unitId, '/NL43_DATA'), 1000);
setTimeout(() => loadFTPFiles(unitId, '/NL-43'), 1000);
} else {
alert('Failed to enable FTP');
}
@@ -200,18 +200,34 @@ async function loadFTPFiles(unitId, path) {
: '<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"></path></svg>';
const sizeStr = file.is_dir ? '' : formatFileSize(file.size);
const clickAction = file.is_dir
? `onclick="loadFTPFiles('${unitId}', '${file.path}')"`
: '';
html += `
<div class="flex items-center gap-3 px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-800 rounded ${file.is_dir ? 'cursor-pointer' : ''}" ${clickAction}>
<div class="flex items-center gap-3 px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-800 rounded">
${icon}
<div class="flex-1 min-w-0">
<div class="flex-1 min-w-0 ${file.is_dir ? 'cursor-pointer' : ''}" ${file.is_dir ? `onclick="loadFTPFiles('${unitId}', '${file.path}')"` : ''}>
<div class="text-sm font-medium text-gray-900 dark:text-white truncate">${file.name}</div>
<div class="text-xs text-gray-500 dark:text-gray-400">${file.modified}${sizeStr ? ' • ' + sizeStr : ''}</div>
</div>
${!file.is_dir ? `
${file.is_dir ? `
<div class="flex items-center gap-2">
<button onclick="event.stopPropagation(); downloadFolderToServer('${unitId}', '${file.path}', '${file.name}')"
class="px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
title="Download entire folder to server and add to database">
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"></path>
</svg>
To Server (ZIP)
</button>
<button onclick="event.stopPropagation(); downloadFTPFolder('${unitId}', '${file.path}', '${file.name}')"
class="px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
title="Download entire folder as ZIP to your computer">
<svg class="w-3 h-3 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
</svg>
To Browser (ZIP)
</button>
</div>
` : `
<div class="flex items-center gap-2">
<button onclick="downloadToServer('${unitId}', '${file.path}', '${file.name}')"
class="px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
@@ -230,7 +246,7 @@ async function loadFTPFiles(unitId, path) {
To Browser
</button>
</div>
` : ''}
`}
</div>
`;
}
@@ -280,6 +296,90 @@ async function downloadFTPFile(unitId, remotePath, fileName) {
}
}
async function downloadFTPFolder(unitId, remotePath, folderName) {
const btn = event.target;
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<svg class="w-3 h-3 inline mr-1 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Downloading...';
try {
const response = await fetch(`/api/slmm/${unitId}/ftp/download-folder`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
remote_path: remotePath
})
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${folderName}.zip`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
// Show success message
alert(`✓ Folder "${folderName}" downloaded successfully as ZIP file!`);
} else {
const errorData = await response.json();
alert('Folder download failed: ' + (errorData.detail || 'Unknown error'));
}
} catch (error) {
alert('Error downloading folder: ' + error);
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
}
}
async function downloadFolderToServer(unitId, remotePath, folderName) {
const btn = event.target;
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<svg class="w-3 h-3 inline mr-1 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Downloading...';
// Get location_id from the unit's data attribute
const unitContainer = btn.closest('[id^="ftp-files-"]');
const locationId = unitContainer.dataset.locationId;
try {
const response = await fetch(`/api/projects/{{ project_id }}/ftp-download-folder-to-server`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
unit_id: unitId,
remote_path: remotePath,
location_id: locationId
})
});
const data = await response.json();
if (response.ok) {
// Show success message
alert(`✓ Folder "${folderName}" downloaded to server successfully as ZIP!\n\nFile ID: ${data.file_id}\nSize: ${formatFileSize(data.file_size)}`);
// Refresh the downloaded files list
htmx.trigger('#project-files', 'refresh');
} else {
alert('Folder download to server failed: ' + (data.detail || 'Unknown error'));
}
} catch (error) {
alert('Error downloading folder to server: ' + error);
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
}
}
async function downloadToServer(unitId, remotePath, fileName) {
const btn = event.target;
const originalText = btn.innerHTML;