/** * PXE Server Manager - Frontend JavaScript * * Handles: * - Add/remove rows in driver paths, specialize commands, first-logon commands * - Drag-to-reorder and up/down buttons for command lists * - Switching between Form editor and Raw XML views * - AJAX and form-based save */ document.addEventListener('DOMContentLoaded', function () { // ----------------------------------------------------------------------- // Utility: renumber the "Order" column in a table body // ----------------------------------------------------------------------- function renumberRows(tbody) { var rows = tbody.querySelectorAll('tr'); rows.forEach(function (row, idx) { var orderCell = row.querySelector('.order-num'); if (orderCell) { orderCell.textContent = idx + 1; } }); } // ----------------------------------------------------------------------- // Utility: hide "empty" message if rows exist, show if none // ----------------------------------------------------------------------- function toggleEmpty(tableId, emptyId) { var tbody = document.querySelector('#' + tableId + ' tbody'); var emptyEl = document.getElementById(emptyId); if (!tbody || !emptyEl) return; emptyEl.style.display = tbody.querySelectorAll('tr').length > 0 ? 'none' : ''; } // ----------------------------------------------------------------------- // Remove row handler (delegated) // ----------------------------------------------------------------------- document.addEventListener('click', function (e) { var btn = e.target.closest('.remove-row'); if (!btn) return; var row = btn.closest('tr'); var tbody = row.parentElement; row.remove(); renumberRows(tbody); // Determine which table we are in and toggle empty message var table = tbody.closest('table'); if (table) { if (table.id === 'driverPathsTable') toggleEmpty('driverPathsTable', 'driverPathsEmpty'); if (table.id === 'specCmdTable') toggleEmpty('specCmdTable', 'specCmdEmpty'); if (table.id === 'flCmdTable') toggleEmpty('flCmdTable', 'flCmdEmpty'); } }); // ----------------------------------------------------------------------- // Move-up / Move-down handlers (delegated) // ----------------------------------------------------------------------- document.addEventListener('click', function (e) { var btn = e.target.closest('.move-up'); if (btn) { var row = btn.closest('tr'); var prev = row.previousElementSibling; if (prev) { row.parentElement.insertBefore(row, prev); renumberRows(row.parentElement); } return; } btn = e.target.closest('.move-down'); if (btn) { var row = btn.closest('tr'); var next = row.nextElementSibling; if (next) { row.parentElement.insertBefore(next, row); renumberRows(row.parentElement); } } }); // ----------------------------------------------------------------------- // Add Driver Path // ----------------------------------------------------------------------- var addDriverPathBtn = document.getElementById('addDriverPath'); if (addDriverPathBtn) { addDriverPathBtn.addEventListener('click', function () { var tbody = document.querySelector('#driverPathsTable tbody'); var idx = tbody.querySelectorAll('tr').length + 1; var tr = document.createElement('tr'); tr.innerHTML = '' + idx + '' + '' + ''; tbody.appendChild(tr); toggleEmpty('driverPathsTable', 'driverPathsEmpty'); tr.querySelector('input').focus(); }); } // ----------------------------------------------------------------------- // Add Specialize Command // ----------------------------------------------------------------------- var addSpecCmdBtn = document.getElementById('addSpecCmd'); if (addSpecCmdBtn) { addSpecCmdBtn.addEventListener('click', function () { var tbody = document.querySelector('#specCmdTable tbody'); var idx = tbody.querySelectorAll('tr').length + 1; var tr = document.createElement('tr'); tr.setAttribute('draggable', 'true'); tr.innerHTML = '' + '' + idx + '' + '' + '' + '' + ' ' + ' ' + '' + ''; tbody.appendChild(tr); initDragForRow(tr); toggleEmpty('specCmdTable', 'specCmdEmpty'); tr.querySelector('input[name="spec_cmd_path[]"]').focus(); }); } // ----------------------------------------------------------------------- // Add First Logon Command // ----------------------------------------------------------------------- var addFlCmdBtn = document.getElementById('addFlCmd'); if (addFlCmdBtn) { addFlCmdBtn.addEventListener('click', function () { var tbody = document.querySelector('#flCmdTable tbody'); var idx = tbody.querySelectorAll('tr').length + 1; var tr = document.createElement('tr'); tr.setAttribute('draggable', 'true'); tr.innerHTML = '' + '' + idx + '' + '' + '' + '' + ' ' + ' ' + '' + ''; tbody.appendChild(tr); initDragForRow(tr); toggleEmpty('flCmdTable', 'flCmdEmpty'); tr.querySelector('input[name="fl_cmd_commandline[]"]').focus(); }); } // ----------------------------------------------------------------------- // OOBE toggle switches — keep hidden input in sync // ----------------------------------------------------------------------- document.querySelectorAll('.oobe-toggle').forEach(function (cb) { cb.addEventListener('change', function () { var hiddenId = this.getAttribute('data-field') + '_val'; var hidden = document.getElementById(hiddenId); if (hidden) { hidden.value = this.checked ? 'true' : 'false'; } }); }); // ----------------------------------------------------------------------- // Drag-and-drop reorder for command tables // ----------------------------------------------------------------------- var dragSrcRow = null; function initDragForRow(row) { row.addEventListener('dragstart', function (e) { dragSrcRow = this; this.classList.add('dragging'); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', ''); // required for Firefox }); row.addEventListener('dragover', function (e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; return false; }); row.addEventListener('dragenter', function (e) { this.style.borderTop = '2px solid #0d6efd'; }); row.addEventListener('dragleave', function (e) { this.style.borderTop = ''; }); row.addEventListener('drop', function (e) { e.stopPropagation(); this.style.borderTop = ''; if (dragSrcRow !== this) { var tbody = this.parentElement; tbody.insertBefore(dragSrcRow, this); renumberRows(tbody); } return false; }); row.addEventListener('dragend', function () { this.classList.remove('dragging'); // clean up all borders this.parentElement.querySelectorAll('tr').forEach(function (r) { r.style.borderTop = ''; }); }); } // Initialize drag on existing rows document.querySelectorAll('.command-table tbody tr[draggable="true"]').forEach(initDragForRow); // ----------------------------------------------------------------------- // Save: Form view // ----------------------------------------------------------------------- var saveFormBtn = document.getElementById('saveFormBtn'); if (saveFormBtn) { saveFormBtn.addEventListener('click', function () { var activeTab = document.querySelector('.editor-tabs .nav-link.active'); var form = document.getElementById('unattendForm'); var modeInput = document.getElementById('saveMode'); if (activeTab && activeTab.id === 'raw-tab') { modeInput.value = 'raw'; } else { modeInput.value = 'form'; } form.submit(); }); } // ----------------------------------------------------------------------- // Save: Raw XML via AJAX // ----------------------------------------------------------------------- var saveRawBtn = document.getElementById('saveRawBtn'); if (saveRawBtn) { saveRawBtn.addEventListener('click', function () { var xmlContent = document.getElementById('rawXmlEditor').value; var url = window.PXE_API_URL; if (!url) { alert('API URL not configured.'); return; } saveRawBtn.disabled = true; saveRawBtn.innerHTML = ' Saving...'; var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ raw_xml: xmlContent }) }) .then(function (resp) { return resp.json(); }) .then(function (data) { if (data.error) { showToast('Error: ' + data.error, 'danger'); } else { showToast('Raw XML saved successfully.', 'success'); } }) .catch(function (err) { showToast('Network error: ' + err.message, 'danger'); }) .finally(function () { saveRawBtn.disabled = false; saveRawBtn.innerHTML = ' Save Raw XML'; }); }); } // ----------------------------------------------------------------------- // Toast notification helper // ----------------------------------------------------------------------- function showToast(message, type) { // Create a Bootstrap alert at the top of main content var container = document.querySelector('.main-content'); var alert = document.createElement('div'); alert.className = 'alert alert-' + (type || 'info') + ' alert-dismissible fade show'; alert.setAttribute('role', 'alert'); alert.innerHTML = message + ''; container.insertBefore(alert, container.firstChild); // Auto-dismiss after 5 seconds setTimeout(function () { if (alert.parentElement) { alert.classList.remove('show'); setTimeout(function () { alert.remove(); }, 200); } }, 5000); } // ----------------------------------------------------------------------- // Initial empty-state check (in case page loaded with data) // ----------------------------------------------------------------------- toggleEmpty('driverPathsTable', 'driverPathsEmpty'); toggleEmpty('specCmdTable', 'specCmdEmpty'); toggleEmpty('flCmdTable', 'flCmdEmpty'); });