// Application Name Validation function validateAppName() { const nameField = document.getElementById('app-name'); const serverField = document.getElementById('server-id'); const feedbackElement = document.getElementById('name-feedback'); const submitButton = document.querySelector('button[type="submit"]'); if (!nameField || !serverField) return; const name = nameField.value.trim(); const serverId = serverField.value; const appId = document.getElementById('app-id')?.value; if (!name || !serverId) { clearFeedback(feedbackElement); return; } // Debounce to avoid too many requests clearTimeout(nameField.timer); nameField.timer = setTimeout(() => { fetch(`/api/validate/app-name?name=${encodeURIComponent(name)}&server_id=${serverId}${appId ? '&app_id=' + appId : ''}`) .then(response => response.json()) .then(data => { if (!data.valid) { showError(feedbackElement, data.message); if (data.edit_url) { feedbackElement.innerHTML += ` Edit this app instead?`; } submitButton.disabled = true; } else if (data.warning) { showWarning(feedbackElement, data.warning); if (data.similar_apps && data.similar_apps.length > 0) { feedbackElement.innerHTML += ''; } submitButton.disabled = false; } else { showSuccess(feedbackElement, "App name is available"); submitButton.disabled = false; } }) .catch(error => { console.error('Validation error:', error); clearFeedback(feedbackElement); }); }, 300); } // Port Validation function validatePort(portField, protocolField) { const serverField = document.getElementById('server-id'); // Create feedback element if it doesn't exist if (!portField.nextElementSibling || !portField.nextElementSibling.classList.contains('feedback')) { const feedback = document.createElement('div'); feedback.className = 'feedback'; portField.parentNode.insertBefore(feedback, portField.nextSibling); } const feedbackElement = portField.nextElementSibling; const submitButton = document.querySelector('button[type="submit"]'); if (!portField || !serverField || !protocolField) return; const port = portField.value.trim(); const protocol = protocolField.value; const serverId = serverField.value; const appId = document.getElementById('app-id')?.value; if (!port || !serverId) { clearFeedback(feedbackElement); portField.classList.remove('is-invalid'); return; } // Check for duplicate ports within the form first const allPortFields = document.querySelectorAll('input[name="port_numbers[]"]'); const allProtocolFields = document.querySelectorAll('select[name="protocols[]"]'); let duplicateFound = false; allPortFields.forEach((field, index) => { if (field !== portField && field.value === port && allProtocolFields[index].value === protocol) { duplicateFound = true; } }); if (duplicateFound) { portField.classList.add('is-invalid'); showError(feedbackElement, `Duplicate port ${port}/${protocol} in your form`); return; } // Debounce to avoid too many requests clearTimeout(portField.timer); portField.timer = setTimeout(() => { fetch(`/api/validate/app-port?port_number=${encodeURIComponent(port)}&protocol=${protocol}&server_id=${serverId}${appId ? '&app_id=' + appId : ''}`) .then(response => response.json()) .then(data => { if (!data.valid) { portField.classList.add('is-invalid'); showError(feedbackElement, data.message); if (data.edit_url) { feedbackElement.innerHTML += ` Edit conflicting app`; } } else { portField.classList.remove('is-invalid'); portField.classList.add('is-valid'); showSuccess(feedbackElement, "Port available"); } }) .catch(error => { console.error('Validation error:', error); clearFeedback(feedbackElement); portField.classList.remove('is-invalid'); portField.classList.remove('is-valid'); }); }, 300); } // Helper functions for feedback display function showError(element, message) { if (!element) return; element.className = 'invalid-feedback d-block'; element.textContent = message; } function showWarning(element, message) { if (!element) return; element.className = 'text-warning d-block'; element.textContent = message; } function showSuccess(element, message) { if (!element) return; element.className = 'valid-feedback d-block'; element.textContent = message; } function clearFeedback(element) { if (!element) return; element.className = ''; element.textContent = ''; } // Set up event listeners when the document is loaded document.addEventListener('DOMContentLoaded', function () { const nameField = document.getElementById('app-name'); const serverField = document.getElementById('server-id'); if (nameField) { nameField.addEventListener('input', validateAppName); if (serverField) { serverField.addEventListener('change', validateAppName); } } // Set up validation for existing port fields setupPortValidation(); // Make sure new port rows get validation too const addPortButton = document.getElementById('add-port-btn'); if (addPortButton) { addPortButton.addEventListener('click', function () { // Wait a moment for the new row to be added setTimeout(setupPortValidation, 100); }); } }); function setupPortValidation() { const portFields = document.querySelectorAll('input[name="port_numbers[]"]'); const protocolFields = document.querySelectorAll('select[name="protocols[]"]'); portFields.forEach((field, index) => { if (!field.hasValidationSetup) { const protocolField = protocolFields[index]; field.addEventListener('input', function () { validatePort(field, protocolField); }); if (protocolField) { protocolField.addEventListener('change', function () { validatePort(field, protocolField); }); } // Add a div for feedback if it doesn't exist if (!field.nextElementSibling || !field.nextElementSibling.classList.contains('feedback')) { const feedback = document.createElement('div'); feedback.className = 'feedback'; field.parentNode.insertBefore(feedback, field.nextSibling); } field.hasValidationSetup = true; } }); }