refactor(reports): namespace the FTP browser partial behind window.FtpBrowser

Proper fix superseding the fb* prefix band-aid (1801d4e): wrap ftp_browser.html's script in an IIFE and expose only window.FtpBrowser. Its ~11 helpers no longer leak to global scope, so the partial is safe to co-load with other FTP-browsing partials (e.g. slm_live_view's Command Center) without name collisions in either direction. Inline onclick handlers call FtpBrowser.*; showFTPSettings stays global (it's from the included settings modal). Behaviour unchanged — verified full Jinja render + balanced delimiters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 19:14:27 +00:00
parent 1801d4eb74
commit 694980c869
+35 -19
View File
@@ -32,19 +32,19 @@
</svg> </svg>
Settings Settings
</button> </button>
<button onclick="fbEnableFTP('{{ unit_item.unit.id }}')" <button onclick="FtpBrowser.enableFTP('{{ unit_item.unit.id }}')"
id="enable-ftp-{{ unit_item.unit.id }}" id="enable-ftp-{{ unit_item.unit.id }}"
class="px-3 py-1 text-xs bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors" class="px-3 py-1 text-xs bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
disabled> disabled>
Enable FTP Enable FTP
</button> </button>
<button onclick="disableFTP('{{ unit_item.unit.id }}')" <button onclick="FtpBrowser.disableFTP('{{ unit_item.unit.id }}')"
id="disable-ftp-{{ unit_item.unit.id }}" id="disable-ftp-{{ unit_item.unit.id }}"
class="px-3 py-1 text-xs bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors" class="px-3 py-1 text-xs bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
disabled> disabled>
Disable FTP Disable FTP
</button> </button>
<button onclick="fbLoadFTPFiles('{{ unit_item.unit.id }}', '/NL-43')" <button onclick="FtpBrowser.loadFTPFiles('{{ unit_item.unit.id }}', '/NL-43')"
id="browse-ftp-{{ unit_item.unit.id }}" 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" class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors"
disabled> disabled>
@@ -61,7 +61,7 @@
<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> <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> </svg>
<span id="current-path-{{ unit_item.unit.id }}" class="text-sm font-mono text-gray-600 dark:text-gray-400">/NL-43</span> <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="fbLoadFTPFiles('{{ unit_item.unit.id }}', '/NL-43')" <button onclick="FtpBrowser.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"> 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"> <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> <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>
@@ -87,6 +87,11 @@
</div> </div>
<script> <script>
// Self-contained namespace: this partial is reusable (project-wide Data Files
// tab AND per-NRL Data Files tab), and may be co-loaded with other FTP-browsing
// partials (e.g. slm_live_view). Wrapping in an IIFE keeps its helpers off the
// global scope; only window.FtpBrowser is exposed (see the export at the end).
(function () {
async function checkFTPStatus(unitId) { async function checkFTPStatus(unitId) {
const statusSpan = document.getElementById(`ftp-status-${unitId}`); const statusSpan = document.getElementById(`ftp-status-${unitId}`);
const enableBtn = document.getElementById(`enable-ftp-${unitId}`); const enableBtn = document.getElementById(`enable-ftp-${unitId}`);
@@ -117,7 +122,7 @@ async function checkFTPStatus(unitId) {
} }
} }
async function fbEnableFTP(unitId) { async function enableFTP(unitId) {
const enableBtn = document.getElementById(`enable-ftp-${unitId}`); const enableBtn = document.getElementById(`enable-ftp-${unitId}`);
enableBtn.disabled = true; enableBtn.disabled = true;
enableBtn.textContent = 'Enabling...'; enableBtn.textContent = 'Enabling...';
@@ -130,7 +135,7 @@ async function fbEnableFTP(unitId) {
if (response.ok) { if (response.ok) {
await checkFTPStatus(unitId); await checkFTPStatus(unitId);
// Auto-load files after enabling // Auto-load files after enabling
setTimeout(() => fbLoadFTPFiles(unitId, '/NL-43'), 1000); setTimeout(() => loadFTPFiles(unitId, '/NL-43'), 1000);
} else { } else {
alert('Failed to enable FTP'); alert('Failed to enable FTP');
} }
@@ -169,7 +174,7 @@ async function disableFTP(unitId) {
} }
} }
async function fbLoadFTPFiles(unitId, path) { async function loadFTPFiles(unitId, path) {
const fileListDiv = document.getElementById(`ftp-file-list-${unitId}`); const fileListDiv = document.getElementById(`ftp-file-list-${unitId}`);
const filesContainer = document.getElementById(`ftp-files-${unitId}`); const filesContainer = document.getElementById(`ftp-files-${unitId}`);
const currentPathSpan = document.getElementById(`current-path-${unitId}`); const currentPathSpan = document.getElementById(`current-path-${unitId}`);
@@ -220,7 +225,7 @@ async function fbLoadFTPFiles(unitId, path) {
let html = '<div class="space-y-1">'; let html = '<div class="space-y-1">';
for (const file of sorted) { for (const file of sorted) {
const icon = getFileIcon(file); const icon = getFileIcon(file);
const sizeStr = file.is_dir ? '' : fbFormatFileSize(file.size); const sizeStr = file.is_dir ? '' : formatFileSize(file.size);
const folderId = 'proj-folder-' + file.path.replace(/[^a-zA-Z0-9]/g, '-'); const folderId = 'proj-folder-' + file.path.replace(/[^a-zA-Z0-9]/g, '-');
if (file.is_dir) { if (file.is_dir) {
@@ -228,7 +233,7 @@ async function fbLoadFTPFiles(unitId, path) {
html += ` html += `
<div class="border border-gray-200 dark:border-gray-600 rounded mb-1"> <div class="border border-gray-200 dark:border-gray-600 rounded mb-1">
<div class="flex items-center justify-between p-3 hover:bg-gray-50 dark:hover:bg-gray-700 rounded transition-colors cursor-pointer" <div class="flex items-center justify-between p-3 hover:bg-gray-50 dark:hover:bg-gray-700 rounded transition-colors cursor-pointer"
onclick="toggleFTPFolderProject('${unitId}', '${escapeForAttribute(file.path)}', '${folderId}', this)"> onclick="FtpBrowser.toggleFTPFolderProject('${unitId}', '${escapeForAttribute(file.path)}', '${folderId}', this)">
<div class="flex items-center flex-1"> <div class="flex items-center flex-1">
<svg class="w-4 h-4 mr-2 text-gray-400 transition-transform folder-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 mr-2 text-gray-400 transition-transform folder-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
@@ -239,7 +244,7 @@ async function fbLoadFTPFiles(unitId, path) {
</div> </div>
<div class="flex items-center gap-2 flex-shrink-0 ml-4"> <div class="flex items-center gap-2 flex-shrink-0 ml-4">
<span class="text-xs text-gray-500 hidden sm:inline">${file.modified || ''}</span> <span class="text-xs text-gray-500 hidden sm:inline">${file.modified || ''}</span>
<button onclick="event.stopPropagation(); downloadFolderToServer('${unitId}', '${escapeForAttribute(file.path)}', '${escapeForAttribute(file.name)}')" <button onclick="event.stopPropagation(); FtpBrowser.downloadFolderToServer('${unitId}', '${escapeForAttribute(file.path)}', '${escapeForAttribute(file.name)}')"
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors flex items-center" class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors flex items-center"
title="Download folder from device to server and add to project database"> title="Download folder from device to server and add to project database">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -264,7 +269,7 @@ async function fbLoadFTPFiles(unitId, path) {
<div class="flex items-center gap-3 flex-shrink-0 ml-4"> <div class="flex items-center gap-3 flex-shrink-0 ml-4">
<span class="text-xs text-gray-500 hidden sm:inline">${sizeStr}</span> <span class="text-xs text-gray-500 hidden sm:inline">${sizeStr}</span>
<span class="text-xs text-gray-500 hidden md:inline">${file.modified || ''}</span> <span class="text-xs text-gray-500 hidden md:inline">${file.modified || ''}</span>
<button onclick="fbDownloadToServer('${unitId}', '${escapeForAttribute(file.path)}', '${escapeForAttribute(file.name)}')" <button onclick="FtpBrowser.downloadToServer('${unitId}', '${escapeForAttribute(file.path)}', '${escapeForAttribute(file.name)}')"
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors flex items-center" class="px-3 py-1 text-xs bg-seismo-orange text-white rounded hover:bg-seismo-navy transition-colors flex items-center"
title="Download file from device to server and add to project database"> title="Download file from device to server and add to project database">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -371,7 +376,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
files.forEach(file => { files.forEach(file => {
const fullPath = file.path || `${folderPath}/${file.name}`; const fullPath = file.path || `${folderPath}/${file.name}`;
const icon = getFileIcon(file); const icon = getFileIcon(file);
const sizeText = file.size ? fbFormatFileSize(file.size) : ''; const sizeText = file.size ? formatFileSize(file.size) : '';
const subFolderId = 'proj-folder-' + fullPath.replace(/[^a-zA-Z0-9]/g, '-'); const subFolderId = 'proj-folder-' + fullPath.replace(/[^a-zA-Z0-9]/g, '-');
if (file.is_dir) { if (file.is_dir) {
@@ -379,7 +384,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
html += ` html += `
<div class="border border-gray-200 dark:border-gray-600 rounded"> <div class="border border-gray-200 dark:border-gray-600 rounded">
<div class="flex items-center justify-between p-2 hover:bg-gray-50 dark:hover:bg-gray-700 rounded transition-colors cursor-pointer text-sm" <div class="flex items-center justify-between p-2 hover:bg-gray-50 dark:hover:bg-gray-700 rounded transition-colors cursor-pointer text-sm"
onclick="toggleFTPFolderProject('${unitId}', '${escapeForAttribute(fullPath)}', '${subFolderId}', this)"> onclick="FtpBrowser.toggleFTPFolderProject('${unitId}', '${escapeForAttribute(fullPath)}', '${subFolderId}', this)">
<div class="flex items-center flex-1"> <div class="flex items-center flex-1">
<svg class="w-3 h-3 mr-2 text-gray-400 transition-transform folder-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 mr-2 text-gray-400 transition-transform folder-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
@@ -389,7 +394,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
<span class="ml-2 text-xs text-gray-400 folder-status"></span> <span class="ml-2 text-xs text-gray-400 folder-status"></span>
</div> </div>
<div class="flex items-center gap-2 flex-shrink-0 ml-2"> <div class="flex items-center gap-2 flex-shrink-0 ml-2">
<button onclick="event.stopPropagation(); downloadFolderToServer('${unitId}', '${escapeForAttribute(fullPath)}', '${escapeForAttribute(file.name)}')" <button onclick="event.stopPropagation(); FtpBrowser.downloadFolderToServer('${unitId}', '${escapeForAttribute(fullPath)}', '${escapeForAttribute(file.name)}')"
class="px-2 py-1 bg-seismo-orange hover:bg-seismo-navy text-white text-xs rounded transition-colors flex items-center" class="px-2 py-1 bg-seismo-orange hover:bg-seismo-navy text-white text-xs rounded transition-colors flex items-center"
title="Download folder to server"> title="Download folder to server">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -412,7 +417,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
</div> </div>
<div class="flex items-center gap-2 flex-shrink-0 ml-2"> <div class="flex items-center gap-2 flex-shrink-0 ml-2">
<span class="text-xs text-gray-500 hidden sm:inline">${sizeText}</span> <span class="text-xs text-gray-500 hidden sm:inline">${sizeText}</span>
<button onclick="fbDownloadToServer('${unitId}', '${escapeForAttribute(fullPath)}', '${escapeForAttribute(file.name)}')" <button onclick="FtpBrowser.downloadToServer('${unitId}', '${escapeForAttribute(fullPath)}', '${escapeForAttribute(file.name)}')"
class="px-2 py-1 bg-seismo-orange hover:bg-seismo-navy text-white text-xs rounded transition-colors" class="px-2 py-1 bg-seismo-orange hover:bg-seismo-navy text-white text-xs rounded transition-colors"
title="Download to server"> title="Download to server">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -436,7 +441,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
} }
} }
async function fbDownloadFTPFile(unitId, remotePath, fileName) { async function downloadFTPFile(unitId, remotePath, fileName) {
const btn = event.target; const btn = event.target;
btn.disabled = true; btn.disabled = true;
btn.textContent = 'Downloading...'; btn.textContent = 'Downloading...';
@@ -561,7 +566,7 @@ async function downloadFolderToServer(unitId, remotePath, folderName) {
} }
} }
async function fbDownloadToServer(unitId, remotePath, fileName) { async function downloadToServer(unitId, remotePath, fileName) {
const btn = event.target; const btn = event.target;
const originalText = btn.innerHTML; const originalText = btn.innerHTML;
btn.disabled = true; btn.disabled = true;
@@ -588,7 +593,7 @@ async function fbDownloadToServer(unitId, remotePath, fileName) {
if (response.ok) { if (response.ok) {
// Show success message // Show success message
const sizeLine = `\nSize: ${fbFormatFileSize(data.file_size)}`; const sizeLine = `\nSize: ${formatFileSize(data.file_size)}`;
const msg = data.ingested const msg = data.ingested
? `${fileName} imported as measurement data!` + formatRunLength(data) + sizeLine ? `${fileName} imported as measurement data!` + formatRunLength(data) + sizeLine
: `${fileName} downloaded to server successfully!\n\nFile ID: ${data.file_id}` + sizeLine; : `${fileName} downloaded to server successfully!\n\nFile ID: ${data.file_id}` + sizeLine;
@@ -607,7 +612,7 @@ async function fbDownloadToServer(unitId, remotePath, fileName) {
} }
} }
function fbFormatFileSize(bytes) { function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B'; if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB'; if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
@@ -632,6 +637,17 @@ setTimeout(function() {
checkFTPStatus('{{ unit_item.unit.id }}'); checkFTPStatus('{{ unit_item.unit.id }}');
{% endfor %} {% endfor %}
}, 100); }, 100);
// The only global surface — the handlers referenced by inline onclick attributes.
window.FtpBrowser = {
loadFTPFiles,
enableFTP,
disableFTP,
toggleFTPFolderProject,
downloadFolderToServer,
downloadToServer,
};
})();
</script> </script>
<!-- Include the unified SLM Settings Modal --> <!-- Include the unified SLM Settings Modal -->