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>
Settings
</button>
<button onclick="fbEnableFTP('{{ unit_item.unit.id }}')"
<button onclick="FtpBrowser.enableFTP('{{ 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"
disabled>
Enable FTP
</button>
<button onclick="disableFTP('{{ unit_item.unit.id }}')"
<button onclick="FtpBrowser.disableFTP('{{ 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"
disabled>
Disable FTP
</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 }}"
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors"
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>
</svg>
<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">
<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>
@@ -87,6 +87,11 @@
</div>
<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) {
const statusSpan = document.getElementById(`ftp-status-${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}`);
enableBtn.disabled = true;
enableBtn.textContent = 'Enabling...';
@@ -130,7 +135,7 @@ async function fbEnableFTP(unitId) {
if (response.ok) {
await checkFTPStatus(unitId);
// Auto-load files after enabling
setTimeout(() => fbLoadFTPFiles(unitId, '/NL-43'), 1000);
setTimeout(() => loadFTPFiles(unitId, '/NL-43'), 1000);
} else {
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 filesContainer = document.getElementById(`ftp-files-${unitId}`);
const currentPathSpan = document.getElementById(`current-path-${unitId}`);
@@ -220,7 +225,7 @@ async function fbLoadFTPFiles(unitId, path) {
let html = '<div class="space-y-1">';
for (const file of sorted) {
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, '-');
if (file.is_dir) {
@@ -228,7 +233,7 @@ async function fbLoadFTPFiles(unitId, path) {
html += `
<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"
onclick="toggleFTPFolderProject('${unitId}', '${escapeForAttribute(file.path)}', '${folderId}', this)">
onclick="FtpBrowser.toggleFTPFolderProject('${unitId}', '${escapeForAttribute(file.path)}', '${folderId}', this)">
<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">
<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 class="flex items-center gap-2 flex-shrink-0 ml-4">
<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"
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">
@@ -264,7 +269,7 @@ async function fbLoadFTPFiles(unitId, path) {
<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 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"
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">
@@ -371,7 +376,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
files.forEach(file => {
const fullPath = file.path || `${folderPath}/${file.name}`;
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, '-');
if (file.is_dir) {
@@ -379,7 +384,7 @@ async function toggleFTPFolderProject(unitId, folderPath, folderId, headerElemen
html += `
<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"
onclick="toggleFTPFolderProject('${unitId}', '${escapeForAttribute(fullPath)}', '${subFolderId}', this)">
onclick="FtpBrowser.toggleFTPFolderProject('${unitId}', '${escapeForAttribute(fullPath)}', '${subFolderId}', this)">
<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">
<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>
</div>
<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"
title="Download folder to server">
<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 class="flex items-center gap-2 flex-shrink-0 ml-2">
<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"
title="Download to server">
<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;
btn.disabled = true;
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 originalText = btn.innerHTML;
btn.disabled = true;
@@ -588,7 +593,7 @@ async function fbDownloadToServer(unitId, remotePath, fileName) {
if (response.ok) {
// Show success message
const sizeLine = `\nSize: ${fbFormatFileSize(data.file_size)}`;
const sizeLine = `\nSize: ${formatFileSize(data.file_size)}`;
const msg = data.ingested
? `${fileName} imported as measurement data!` + formatRunLength(data) + 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 < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + ' MB';
@@ -632,6 +637,17 @@ setTimeout(function() {
checkFTPStatus('{{ unit_item.unit.id }}');
{% endfor %}
}, 100);
// The only global surface — the handlers referenced by inline onclick attributes.
window.FtpBrowser = {
loadFTPFiles,
enableFTP,
disableFTP,
toggleFTPFolderProject,
downloadFolderToServer,
downloadToServer,
};
})();
</script>
<!-- Include the unified SLM Settings Modal -->