Files
terra-view/app/ui/templates/partials/slm_config_form.html

289 lines
14 KiB
HTML

<form id="slm-config-form"
hx-post="/api/slm-dashboard/config/{{ unit.id }}"
hx-swap="none"
hx-on::after-request="handleConfigSave(event)">
<div class="mb-6">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Unit: {{ unit.id }}</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">Configure measurement parameters for this sound level meter</p>
</div>
<!-- Model & Serial -->
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Model</label>
<select name="slm_model" class="w-full px-3 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" {% if unit.slm_model == 'NL-43' %}selected{% endif %}>NL-43</option>
<option value="NL-53" {% if unit.slm_model == 'NL-53' %}selected{% endif %}>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" name="slm_serial_number" value="{{ unit.slm_serial_number or '' }}"
class="w-full px-3 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>
<!-- Frequency & Time Weighting -->
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Frequency Weighting</label>
<select name="slm_frequency_weighting" class="w-full px-3 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" {% if unit.slm_frequency_weighting == 'A' %}selected{% endif %}>A-weighting</option>
<option value="C" {% if unit.slm_frequency_weighting == 'C' %}selected{% endif %}>C-weighting</option>
<option value="Z" {% if unit.slm_frequency_weighting == 'Z' %}selected{% endif %}>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 name="slm_time_weighting" class="w-full px-3 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" {% if unit.slm_time_weighting == 'Fast' %}selected{% endif %}>Fast (125ms)</option>
<option value="Slow" {% if unit.slm_time_weighting == 'Slow' %}selected{% endif %}>Slow (1s)</option>
<option value="Impulse" {% if unit.slm_time_weighting == 'Impulse' %}selected{% endif %}>Impulse</option>
</select>
</div>
</div>
<!-- Measurement Range -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Measurement Range</label>
<select name="slm_measurement_range" class="w-full px-3 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 range...</option>
<option value="30-130" {% if unit.slm_measurement_range == '30-130' %}selected{% endif %}>30-130 dB</option>
<option value="40-140" {% if unit.slm_measurement_range == '40-140' %}selected{% endif %}>40-140 dB</option>
<option value="50-140" {% if unit.slm_measurement_range == '50-140' %}selected{% endif %}>50-140 dB</option>
</select>
</div>
<!-- Network Configuration -->
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-6 mb-4">
<h5 class="text-md font-semibold text-gray-900 dark:text-white mb-3">Network Configuration</h5>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Assigned Modem</label>
<div class="flex gap-2">
<select name="deployed_with_modem_id" id="config-modem-select" class="flex-1 px-3 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="">No modem (direct connection)</option>
<!-- Options loaded via JavaScript -->
</select>
<button type="button" id="test-modem-btn" onclick="testModemConnection()"
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 {% if not unit.deployed_with_modem_id %}opacity-50 cursor-not-allowed{% endif %}"
{% if not unit.deployed_with_modem_id %}disabled{% endif %}
title="Ping modem to test 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 a modem for network access, or leave blank for direct IP connection</p>
</div>
<!-- Port Configuration (always visible) -->
<div class="grid grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">TCP Port</label>
<input type="number" name="slm_tcp_port" value="{{ unit.slm_tcp_port or '2255' }}"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
placeholder="2255">
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Control port</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" name="slm_ftp_port" value="{{ unit.slm_ftp_port or '21' }}"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
placeholder="21">
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Data transfer</p>
</div>
<div id="direct-ip-field" class="{% if unit.deployed_with_modem_id %}hidden{% endif %}">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Direct IP</label>
<input type="text" name="slm_host" value="{{ unit.slm_host or '' }}"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
placeholder="192.168.1.100">
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">If no modem</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-end gap-3 mt-6">
<button type="button" onclick="closeConfigModal()"
class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors">
Cancel
</button>
<button type="button" onclick="testSLMConnection('{{ unit.id }}')"
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">
Test SLM Connection
</button>
<button type="submit"
class="px-4 py-2 text-white bg-seismo-orange hover:bg-seismo-burgundy rounded-lg transition-colors">
Save Configuration
</button>
</div>
</form>
<script>
// Load modems list for dropdown
async function loadModemsForConfig() {
try {
const response = await fetch('/api/roster/modems');
const modems = await response.json();
const select = document.getElementById('config-modem-select');
const currentValue = '{{ unit.deployed_with_modem_id or "" }}';
// Keep the "No modem" option
modems.forEach(modem => {
const option = document.createElement('option');
option.value = modem.id;
const ipText = modem.ip_address ? ' (' + modem.ip_address + ')' : '';
option.textContent = modem.id + ipText;
if (modem.id === currentValue) {
option.selected = true;
}
select.appendChild(option);
});
} catch (error) {
console.error('Failed to load modems:', error);
}
}
// Toggle direct IP field and test modem button based on modem selection
document.getElementById('config-modem-select')?.addEventListener('change', function() {
const directIpField = document.getElementById('direct-ip-field');
const testModemBtn = document.getElementById('test-modem-btn');
if (this.value === '') {
directIpField.classList.remove('hidden');
testModemBtn.disabled = true;
testModemBtn.classList.add('opacity-50', 'cursor-not-allowed');
} else {
directIpField.classList.add('hidden');
testModemBtn.disabled = false;
testModemBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
// Handle save response
function handleConfigSave(event) {
if (event.detail.successful) {
// Show success message
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
toast.textContent = 'Configuration saved successfully!';
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
closeConfigModal();
// Refresh the unit list
htmx.trigger('#slm-list', 'load');
}, 2000);
} else {
// Show error message
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
toast.textContent = 'Failed to save configuration';
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
}
// Test connection to modem (health ping)
async function testModemConnection() {
const modemSelect = document.getElementById('config-modem-select');
const modemId = modemSelect.value;
if (!modemId) {
alert('Please select a modem first');
return;
}
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
toast.textContent = 'Pinging modem...';
document.body.appendChild(toast);
try {
const response = await fetch('/api/slm-dashboard/test-modem/' + modemId);
const data = await response.json();
toast.remove();
const resultToast = document.createElement('div');
if (response.ok && data.status === 'success') {
resultToast.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
const ipAddr = data.ip_address || modemId;
const respTime = data.response_time || 'N/A';
resultToast.innerHTML = '✓ Modem responding!<br><span class="text-xs">' + ipAddr + ' - ' + respTime + 'ms</span>';
} else {
resultToast.className = 'fixed bottom-4 right-4 bg-yellow-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
resultToast.textContent = '⚠ Modem not responding: ' + (data.detail || 'Unknown error');
}
document.body.appendChild(resultToast);
setTimeout(() => {
resultToast.remove();
}, 4000);
} catch (error) {
toast.remove();
const errorToast = document.createElement('div');
errorToast.className = 'fixed bottom-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
errorToast.textContent = '✗ Failed to ping modem: ' + error.message;
document.body.appendChild(errorToast);
setTimeout(() => {
errorToast.remove();
}, 3000);
}
}
// Test connection to SLM
async function testSLMConnection(unitId) {
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
toast.textContent = 'Testing SLM connection...';
document.body.appendChild(toast);
try {
const response = await fetch('/api/slmm/' + unitId + '/status');
const data = await response.json();
toast.remove();
const resultToast = document.createElement('div');
if (response.ok && data.status === 'online') {
resultToast.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
resultToast.textContent = '✓ SLM connection successful! ' + (data.model || 'SLM') + ' responding';
} else {
resultToast.className = 'fixed bottom-4 right-4 bg-yellow-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
resultToast.textContent = '⚠ SLM connection failed or device offline';
}
document.body.appendChild(resultToast);
setTimeout(() => {
resultToast.remove();
}, 3000);
} catch (error) {
toast.remove();
const errorToast = document.createElement('div');
errorToast.className = 'fixed bottom-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50';
errorToast.textContent = '✗ SLM connection test failed: ' + error.message;
document.body.appendChild(errorToast);
setTimeout(() => {
errorToast.remove();
}, 3000);
}
}
// Load modems on page load
loadModemsForConfig();
</script>