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:
@@ -22,6 +22,16 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button onclick="showFTPSettings('{{ unit_item.unit.id }}')"
|
||||
id="settings-ftp-{{ unit_item.unit.id }}"
|
||||
class="px-3 py-1 text-xs bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-1"
|
||||
title="Configure FTP credentials">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
Settings
|
||||
</button>
|
||||
<button onclick="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"
|
||||
@@ -605,3 +615,6 @@ setTimeout(function() {
|
||||
{% endfor %}
|
||||
}, 100);
|
||||
</script>
|
||||
|
||||
<!-- Include the unified SLM Settings Modal -->
|
||||
{% include 'partials/slm_settings_modal.html' %}
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
534
templates/partials/slm_settings_modal.html
Normal file
534
templates/partials/slm_settings_modal.html
Normal file
@@ -0,0 +1,534 @@
|
||||
<!-- Unified SLM Settings Modal - Include this partial where SLM settings are needed -->
|
||||
<!-- Usage: include 'partials/slm_settings_modal.html' (with Jinja braces) -->
|
||||
<!-- Then call: openSLMSettingsModal(unitId) from JavaScript -->
|
||||
|
||||
<div id="slm-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">
|
||||
<!-- Header -->
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-8 h-8 text-seismo-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">SLM Configuration</h3>
|
||||
<p id="slm-settings-unit-display" class="text-sm text-gray-500 dark:text-gray-400"></p>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="closeSLMSettingsModal()" 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="slm-settings-form" onsubmit="saveSLMSettings(event)" class="p-6 space-y-6">
|
||||
<input type="hidden" id="slm-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 flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path>
|
||||
</svg>
|
||||
Network Configuration
|
||||
</h4>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Modem Selection -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Connected via Modem</label>
|
||||
<div class="flex gap-2">
|
||||
<select id="slm-settings-modem" class="flex-1 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">
|
||||
<option value="">Select a modem...</option>
|
||||
<!-- Modems loaded dynamically -->
|
||||
</select>
|
||||
<button type="button" onclick="testModemConnection()" id="slm-settings-test-modem-btn"
|
||||
class="px-4 py-2 text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900 hover:bg-blue-200 dark:hover:bg-blue-800 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled title="Test modem connectivity">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Select the modem this SLM is connected through</p>
|
||||
</div>
|
||||
|
||||
<!-- Port Configuration -->
|
||||
<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">TCP Port</label>
|
||||
<input type="number" id="slm-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" value="2255">
|
||||
<p class="text-xs text-gray-500 mt-1">Control port (default: 2255)</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="slm-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" value="21">
|
||||
<p class="text-xs text-gray-500 mt-1">File transfer (default: 21)</p>
|
||||
</div>
|
||||
</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 flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-green-500" 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>
|
||||
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="slm-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="slm-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="Leave blank to keep existing">
|
||||
<p class="text-xs text-gray-500 mt-1">Leave blank to keep existing</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Information -->
|
||||
<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 flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
|
||||
</svg>
|
||||
Device Information
|
||||
</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">Model</label>
|
||||
<select id="slm-settings-model" 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">
|
||||
<option value="">Select model...</option>
|
||||
<option value="NL-43">NL-43</option>
|
||||
<option value="NL-53">NL-53</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Serial Number</label>
|
||||
<input type="text" id="slm-settings-serial"
|
||||
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., SN123456">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 mt-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Frequency Weighting</label>
|
||||
<select id="slm-settings-freq-weighting" 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">
|
||||
<option value="">Select...</option>
|
||||
<option value="A">A-weighting</option>
|
||||
<option value="C">C-weighting</option>
|
||||
<option value="Z">Z-weighting (Linear)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Time Weighting</label>
|
||||
<select id="slm-settings-time-weighting" 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">
|
||||
<option value="">Select...</option>
|
||||
<option value="Fast">Fast (125ms)</option>
|
||||
<option value="Slow">Slow (1s)</option>
|
||||
<option value="Impulse">Impulse</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Measurement Range</label>
|
||||
<select id="slm-settings-range" 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">
|
||||
<option value="">Select...</option>
|
||||
<option value="30-130">30-130 dB</option>
|
||||
<option value="40-140">40-140 dB</option>
|
||||
<option value="50-140">50-140 dB</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FTP Enable Toggle -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-orange-500" 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-8l-4-4m0 0L8 8m4-4v12"></path>
|
||||
</svg>
|
||||
<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">Enable FTP for file browsing and downloads</p>
|
||||
</div>
|
||||
</div>
|
||||
<input type="checkbox" id="slm-settings-ftp-enabled"
|
||||
class="w-5 h-5 text-seismo-orange rounded border-gray-300 dark:border-gray-600 focus:ring-seismo-orange">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Status Messages -->
|
||||
<div id="slm-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="slm-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>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-end gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
<button type="button" onclick="closeSLMSettingsModal()"
|
||||
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="button" onclick="testSLMConnection()"
|
||||
class="px-6 py-2 text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900 hover:bg-blue-200 dark:hover:bg-blue-800 rounded-lg">
|
||||
Test SLM Connection
|
||||
</button>
|
||||
<button type="submit" id="slm-settings-save-btn"
|
||||
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>
|
||||
|
||||
<script>
|
||||
// ========================================
|
||||
// Unified SLM Settings Modal JavaScript
|
||||
// ========================================
|
||||
|
||||
let slmSettingsModems = []; // Cache modems list
|
||||
|
||||
// Open the SLM Settings Modal
|
||||
async function openSLMSettingsModal(unitId) {
|
||||
const modal = document.getElementById('slm-settings-modal');
|
||||
const errorDiv = document.getElementById('slm-settings-error');
|
||||
const successDiv = document.getElementById('slm-settings-success');
|
||||
|
||||
// Clear previous messages
|
||||
errorDiv.classList.add('hidden');
|
||||
successDiv.classList.add('hidden');
|
||||
|
||||
// Store unit ID
|
||||
document.getElementById('slm-settings-unit-id').value = unitId;
|
||||
document.getElementById('slm-settings-unit-display').textContent = unitId;
|
||||
|
||||
// Load modems list if not cached
|
||||
if (slmSettingsModems.length === 0) {
|
||||
await loadModemsForSLMSettings();
|
||||
}
|
||||
|
||||
// Load current config from both Terra-View and SLMM
|
||||
try {
|
||||
// Fetch Terra-View unit data
|
||||
const unitResponse = await fetch(`/api/roster/${unitId}`);
|
||||
const unitData = unitResponse.ok ? await unitResponse.json() : {};
|
||||
|
||||
// Fetch SLMM config
|
||||
const slmmResponse = await fetch(`/api/slmm/${unitId}/config`);
|
||||
const slmmResult = slmmResponse.ok ? await slmmResponse.json() : {};
|
||||
const slmmData = slmmResult.data || slmmResult || {};
|
||||
|
||||
// Populate form fields
|
||||
// Modem selection
|
||||
const modemSelect = document.getElementById('slm-settings-modem');
|
||||
modemSelect.value = unitData.deployed_with_modem_id || '';
|
||||
updateTestModemButton();
|
||||
|
||||
// Ports
|
||||
document.getElementById('slm-settings-tcp-port').value = unitData.slm_tcp_port || slmmData.tcp_port || 2255;
|
||||
document.getElementById('slm-settings-ftp-port').value = unitData.slm_ftp_port || slmmData.ftp_port || 21;
|
||||
|
||||
// FTP credentials from SLMM
|
||||
document.getElementById('slm-settings-ftp-username').value = slmmData.ftp_username || '';
|
||||
document.getElementById('slm-settings-ftp-password').value = ''; // Don't pre-fill
|
||||
|
||||
// Device info from Terra-View
|
||||
document.getElementById('slm-settings-model').value = unitData.slm_model || '';
|
||||
document.getElementById('slm-settings-serial').value = unitData.slm_serial_number || '';
|
||||
document.getElementById('slm-settings-freq-weighting').value = unitData.slm_frequency_weighting || '';
|
||||
document.getElementById('slm-settings-time-weighting').value = unitData.slm_time_weighting || '';
|
||||
document.getElementById('slm-settings-range').value = unitData.slm_measurement_range || '';
|
||||
|
||||
// FTP enabled from SLMM
|
||||
document.getElementById('slm-settings-ftp-enabled').checked = slmmData.ftp_enabled === true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load SLM settings:', error);
|
||||
errorDiv.textContent = 'Failed to load configuration: ' + error.message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Close the modal
|
||||
function closeSLMSettingsModal() {
|
||||
document.getElementById('slm-settings-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Alias for backwards compatibility with existing code
|
||||
function showFTPSettings(unitId) {
|
||||
openSLMSettingsModal(unitId);
|
||||
}
|
||||
function closeFTPSettings() {
|
||||
closeSLMSettingsModal();
|
||||
}
|
||||
function openSettingsModal(unitId) {
|
||||
openSLMSettingsModal(unitId);
|
||||
}
|
||||
function closeSettingsModal() {
|
||||
closeSLMSettingsModal();
|
||||
}
|
||||
function openConfigModal(unitId) {
|
||||
openSLMSettingsModal(unitId);
|
||||
}
|
||||
function closeConfigModal() {
|
||||
closeSLMSettingsModal();
|
||||
}
|
||||
function openDeviceConfigModal(unitId) {
|
||||
openSLMSettingsModal(unitId);
|
||||
}
|
||||
function closeDeviceConfigModal() {
|
||||
closeSLMSettingsModal();
|
||||
}
|
||||
|
||||
// Load modems for dropdown
|
||||
async function loadModemsForSLMSettings() {
|
||||
try {
|
||||
const response = await fetch('/api/roster/modems');
|
||||
slmSettingsModems = await response.json();
|
||||
|
||||
const select = document.getElementById('slm-settings-modem');
|
||||
// Clear existing options except first
|
||||
select.innerHTML = '<option value="">Select a modem...</option>';
|
||||
|
||||
slmSettingsModems.forEach(modem => {
|
||||
const option = document.createElement('option');
|
||||
option.value = modem.id;
|
||||
const ipText = modem.ip_address ? ` (${modem.ip_address})` : '';
|
||||
const deployedText = modem.deployed ? '' : ' [Benched]';
|
||||
option.textContent = modem.id + ipText + deployedText;
|
||||
select.appendChild(option);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load modems:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update test modem button state based on selection
|
||||
function updateTestModemButton() {
|
||||
const modemSelect = document.getElementById('slm-settings-modem');
|
||||
const testBtn = document.getElementById('slm-settings-test-modem-btn');
|
||||
testBtn.disabled = !modemSelect.value;
|
||||
}
|
||||
|
||||
// Listen for modem selection changes
|
||||
document.getElementById('slm-settings-modem')?.addEventListener('change', updateTestModemButton);
|
||||
|
||||
// Test modem connection
|
||||
async function testModemConnection() {
|
||||
const modemId = document.getElementById('slm-settings-modem').value;
|
||||
if (!modemId) return;
|
||||
|
||||
const errorDiv = document.getElementById('slm-settings-error');
|
||||
const successDiv = document.getElementById('slm-settings-success');
|
||||
|
||||
errorDiv.classList.add('hidden');
|
||||
successDiv.textContent = 'Pinging modem...';
|
||||
successDiv.classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/slm-dashboard/test-modem/${modemId}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === 'success') {
|
||||
const ipAddr = data.ip_address || modemId;
|
||||
const respTime = data.response_time || 'N/A';
|
||||
successDiv.textContent = `✓ Modem responding! ${ipAddr} - ${respTime}ms`;
|
||||
} else {
|
||||
successDiv.classList.add('hidden');
|
||||
errorDiv.textContent = '⚠ Modem not responding: ' + (data.detail || 'Unknown error');
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
successDiv.classList.add('hidden');
|
||||
errorDiv.textContent = 'Failed to ping modem: ' + error.message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Test SLM connection
|
||||
async function testSLMConnection() {
|
||||
const unitId = document.getElementById('slm-settings-unit-id').value;
|
||||
const errorDiv = document.getElementById('slm-settings-error');
|
||||
const successDiv = document.getElementById('slm-settings-success');
|
||||
|
||||
errorDiv.classList.add('hidden');
|
||||
successDiv.textContent = 'Testing SLM connection...';
|
||||
successDiv.classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/slmm/${unitId}/status`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.status === 'online') {
|
||||
successDiv.textContent = '✓ SLM connection successful! Device is responding.';
|
||||
} else {
|
||||
successDiv.classList.add('hidden');
|
||||
errorDiv.textContent = '⚠ SLM not responding or offline. Check network settings.';
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
successDiv.classList.add('hidden');
|
||||
errorDiv.textContent = 'Connection test failed: ' + error.message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Save SLM settings
|
||||
async function saveSLMSettings(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const unitId = document.getElementById('slm-settings-unit-id').value;
|
||||
const saveBtn = document.getElementById('slm-settings-save-btn');
|
||||
const errorDiv = document.getElementById('slm-settings-error');
|
||||
const successDiv = document.getElementById('slm-settings-success');
|
||||
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Saving...';
|
||||
errorDiv.classList.add('hidden');
|
||||
successDiv.classList.add('hidden');
|
||||
|
||||
// Get selected modem and resolve its IP
|
||||
const modemId = document.getElementById('slm-settings-modem').value;
|
||||
let modemIp = '';
|
||||
if (modemId) {
|
||||
const modem = slmSettingsModems.find(m => m.id === modemId);
|
||||
modemIp = modem?.ip_address || '';
|
||||
}
|
||||
|
||||
// Validation
|
||||
if (!modemId) {
|
||||
errorDiv.textContent = 'Please select a modem';
|
||||
errorDiv.classList.remove('hidden');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save Configuration';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modemIp) {
|
||||
errorDiv.textContent = 'Selected modem has no IP address configured';
|
||||
errorDiv.classList.remove('hidden');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save Configuration';
|
||||
return;
|
||||
}
|
||||
|
||||
const tcpPort = parseInt(document.getElementById('slm-settings-tcp-port').value) || 2255;
|
||||
const ftpPort = parseInt(document.getElementById('slm-settings-ftp-port').value) || 21;
|
||||
|
||||
if (tcpPort < 1 || tcpPort > 65535 || ftpPort < 1 || ftpPort > 65535) {
|
||||
errorDiv.textContent = 'Port values must be between 1 and 65535';
|
||||
errorDiv.classList.remove('hidden');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save Configuration';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Update Terra-View database (device info + modem assignment)
|
||||
const terraViewData = {
|
||||
deployed_with_modem_id: modemId,
|
||||
slm_model: document.getElementById('slm-settings-model').value || null,
|
||||
slm_serial_number: document.getElementById('slm-settings-serial').value || null,
|
||||
slm_frequency_weighting: document.getElementById('slm-settings-freq-weighting').value || null,
|
||||
slm_time_weighting: document.getElementById('slm-settings-time-weighting').value || null,
|
||||
slm_measurement_range: document.getElementById('slm-settings-range').value || null,
|
||||
slm_tcp_port: tcpPort,
|
||||
slm_ftp_port: ftpPort
|
||||
};
|
||||
|
||||
const terraResponse = await fetch(`/api/slm-dashboard/config/${unitId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams(terraViewData)
|
||||
});
|
||||
|
||||
if (!terraResponse.ok) {
|
||||
throw new Error('Failed to save Terra-View configuration');
|
||||
}
|
||||
|
||||
// 2. Update SLMM config (network + FTP credentials)
|
||||
const slmmData = {
|
||||
host: modemIp,
|
||||
tcp_port: tcpPort,
|
||||
ftp_port: ftpPort,
|
||||
ftp_username: document.getElementById('slm-settings-ftp-username').value.trim() || null,
|
||||
ftp_enabled: document.getElementById('slm-settings-ftp-enabled').checked
|
||||
};
|
||||
|
||||
// Only include password if entered
|
||||
const password = document.getElementById('slm-settings-ftp-password').value;
|
||||
if (password) {
|
||||
slmmData.ftp_password = password;
|
||||
}
|
||||
|
||||
const slmmResponse = await fetch(`/api/slmm/${unitId}/config`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(slmmData)
|
||||
});
|
||||
|
||||
if (!slmmResponse.ok) {
|
||||
const errData = await slmmResponse.json().catch(() => ({}));
|
||||
throw new Error(errData.detail || 'Failed to save SLMM configuration');
|
||||
}
|
||||
|
||||
successDiv.textContent = 'Configuration saved successfully!';
|
||||
successDiv.classList.remove('hidden');
|
||||
|
||||
// Close modal after delay and refresh if needed
|
||||
setTimeout(() => {
|
||||
closeSLMSettingsModal();
|
||||
// Try to refresh any FTP status or unit lists on the page
|
||||
if (typeof checkFTPStatus === 'function') {
|
||||
checkFTPStatus(unitId);
|
||||
}
|
||||
if (typeof htmx !== 'undefined') {
|
||||
htmx.trigger('#slm-list', 'load');
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
errorDiv.textContent = 'Error: ' + error.message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save Configuration';
|
||||
}
|
||||
}
|
||||
|
||||
// Alias for backwards compatibility
|
||||
async function saveFTPSettings(event) {
|
||||
return saveSLMSettings(event);
|
||||
}
|
||||
|
||||
// Close modal on background click
|
||||
document.getElementById('slm-settings-modal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeSLMSettingsModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user