feat: Add report templates API for CRUD operations and implement SLM settings modal

- Implemented a new API router for managing report templates, including endpoints for listing, creating, retrieving, updating, and deleting templates.
- Added a new HTML partial for a unified SLM settings modal, allowing users to configure SLM settings with dynamic modem selection and FTP credentials.
- Created a report preview page with an editable data table using jspreadsheet, enabling users to modify report details and download the report as an Excel file.
This commit is contained in:
serversdwn
2026-01-20 21:43:50 +00:00
parent a9c9b1fd48
commit 1f3fa7a718
12 changed files with 1959 additions and 364 deletions

View File

@@ -1307,123 +1307,6 @@ window.addEventListener('beforeunload', function() {
// Timer will resume on next page load if measurement is still active
stopMeasurementTimer();
});
// ========================================
// Settings Modal
// ========================================
async function openSettingsModal(unitId) {
const modal = document.getElementById('settings-modal');
const errorDiv = document.getElementById('settings-error');
const successDiv = document.getElementById('settings-success');
// Clear previous messages
errorDiv.classList.add('hidden');
successDiv.classList.add('hidden');
// Store unit ID
document.getElementById('settings-unit-id').value = unitId;
// Load current SLMM config
try {
const response = await fetch(`/api/slmm/${unitId}/config`);
if (!response.ok) {
throw new Error('Failed to load configuration');
}
const result = await response.json();
const config = result.data || {};
// Populate form fields
document.getElementById('settings-host').value = config.host || '';
document.getElementById('settings-tcp-port').value = config.tcp_port || 2255;
document.getElementById('settings-ftp-port').value = config.ftp_port || 21;
document.getElementById('settings-ftp-username').value = config.ftp_username || '';
document.getElementById('settings-ftp-password').value = config.ftp_password || '';
document.getElementById('settings-tcp-enabled').checked = config.tcp_enabled !== false;
document.getElementById('settings-ftp-enabled').checked = config.ftp_enabled === true;
document.getElementById('settings-web-enabled').checked = config.web_enabled === true;
modal.classList.remove('hidden');
} catch (error) {
console.error('Failed to load SLMM config:', error);
errorDiv.textContent = 'Failed to load configuration: ' + error.message;
errorDiv.classList.remove('hidden');
modal.classList.remove('hidden');
}
}
function closeSettingsModal() {
document.getElementById('settings-modal').classList.add('hidden');
}
document.getElementById('settings-form').addEventListener('submit', async function(e) {
e.preventDefault();
const unitId = document.getElementById('settings-unit-id').value;
const errorDiv = document.getElementById('settings-error');
const successDiv = document.getElementById('settings-success');
errorDiv.classList.add('hidden');
successDiv.classList.add('hidden');
// Gather form data
const configData = {
host: document.getElementById('settings-host').value.trim(),
tcp_port: parseInt(document.getElementById('settings-tcp-port').value),
ftp_port: parseInt(document.getElementById('settings-ftp-port').value),
ftp_username: document.getElementById('settings-ftp-username').value.trim() || null,
ftp_password: document.getElementById('settings-ftp-password').value || null,
tcp_enabled: document.getElementById('settings-tcp-enabled').checked,
ftp_enabled: document.getElementById('settings-ftp-enabled').checked,
web_enabled: document.getElementById('settings-web-enabled').checked
};
// Validation
if (!configData.host) {
errorDiv.textContent = 'Host/IP address is required';
errorDiv.classList.remove('hidden');
return;
}
if (configData.tcp_port < 1 || configData.tcp_port > 65535) {
errorDiv.textContent = 'TCP port must be between 1 and 65535';
errorDiv.classList.remove('hidden');
return;
}
if (configData.ftp_port < 1 || configData.ftp_port > 65535) {
errorDiv.textContent = 'FTP port must be between 1 and 65535';
errorDiv.classList.remove('hidden');
return;
}
try {
const response = await fetch(`/api/slmm/${unitId}/config`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(configData)
});
if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(data.detail || 'Failed to update configuration');
}
successDiv.textContent = 'Configuration saved successfully!';
successDiv.classList.remove('hidden');
// Close modal after 1.5 seconds
setTimeout(() => {
closeSettingsModal();
// Optionally reload the page to reflect changes
// window.location.reload();
}, 1500);
} catch (error) {
errorDiv.textContent = error.message;
errorDiv.classList.remove('hidden');
}
});
// ========================================
// FTP Browser Modal
// ========================================
@@ -2201,125 +2084,6 @@ document.getElementById('preview-modal')?.addEventListener('click', function(e)
});
</script>
<!-- Settings Modal -->
<div id="settings-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center overflow-y-auto">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-2xl m-4 my-8">
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">SLM Configuration</h3>
<button onclick="closeSettingsModal()" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<form id="settings-form" class="p-6 space-y-6">
<input type="hidden" id="settings-unit-id">
<!-- Network Configuration -->
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Network Configuration</h4>
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Host / IP Address</label>
<input type="text" id="settings-host"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="e.g., 192.168.1.100" required>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">TCP Port</label>
<input type="number" id="settings-tcp-port"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="2255" min="1" max="65535" required>
<p class="text-xs text-gray-500 mt-1">Default: 2255 for NL-43/NL-53</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">FTP Port</label>
<input type="number" id="settings-ftp-port"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="21" min="1" max="65535" required>
<p class="text-xs text-gray-500 mt-1">Standard FTP port (default: 21)</p>
</div>
</div>
</div>
<!-- FTP Credentials -->
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">FTP Credentials</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Username</label>
<input type="text" id="settings-ftp-username"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="anonymous">
<p class="text-xs text-gray-500 mt-1">Leave blank for anonymous</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Password</label>
<input type="password" id="settings-ftp-password"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="••••••••">
<p class="text-xs text-gray-500 mt-1">Leave blank for anonymous</p>
</div>
</div>
</div>
<!-- Protocol Toggles -->
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<h4 class="font-semibold text-gray-900 dark:text-white mb-4">Protocol Settings</h4>
<div class="space-y-3">
<label class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
<div>
<span class="font-medium text-gray-900 dark:text-white">TCP Communication</span>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enable TCP control commands</p>
</div>
<input type="checkbox" id="settings-tcp-enabled"
class="w-5 h-5 text-seismo-orange rounded border-gray-300 dark:border-gray-600 focus:ring-seismo-orange">
</label>
<label class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
<div>
<span class="font-medium text-gray-900 dark:text-white">FTP File Transfer</span>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enable FTP file browsing and downloads</p>
</div>
<input type="checkbox" id="settings-ftp-enabled"
class="w-5 h-5 text-seismo-orange rounded border-gray-300 dark:border-gray-600 focus:ring-seismo-orange">
</label>
<label class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
<div>
<span class="font-medium text-gray-900 dark:text-white">Web Interface</span>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enable web UI access (future feature)</p>
</div>
<input type="checkbox" id="settings-web-enabled"
class="w-5 h-5 text-seismo-orange rounded border-gray-300 dark:border-gray-600 focus:ring-seismo-orange">
</label>
</div>
</div>
<div id="settings-error" class="hidden text-sm p-3 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg"></div>
<div id="settings-success" class="hidden text-sm p-3 bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 rounded-lg"></div>
<div class="flex justify-end gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
<button type="button" onclick="closeSettingsModal()"
class="px-6 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700">
Cancel
</button>
<button type="submit"
class="px-6 py-2 bg-seismo-orange hover:bg-orange-600 text-white rounded-lg font-medium">
Save Configuration
</button>
</div>
</form>
</div>
</div>
<!-- FTP Browser Modal -->
<div id="ftp-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden m-4 flex flex-col">
@@ -2407,3 +2171,6 @@ document.getElementById('preview-modal')?.addEventListener('click', function(e)
</div>
</div>
</div>
<!-- Unified SLM Settings Modal -->
{% include 'partials/slm_settings_modal.html' %}