/** * 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(); }); } // ----------------------------------------------------------------------- // Add User Account // ----------------------------------------------------------------------- var addUserAccountBtn = document.getElementById('addUserAccount'); if (addUserAccountBtn) { addUserAccountBtn.addEventListener('click', function () { var tbody = document.querySelector('#userAccountsTable tbody'); var idx = tbody.querySelectorAll('tr').length; var tr = document.createElement('tr'); tr.innerHTML = '' + (idx + 1) + '' + '' + '' + '' + '' + '' + '' + '' + ''; tbody.appendChild(tr); var emptyEl = document.getElementById('userAccountsEmpty'); if (emptyEl) emptyEl.style.display = 'none'; tr.querySelector('input[name^="account_name_"]').focus(); }); } // ----------------------------------------------------------------------- // Remove User Account row + renumber indices (delegated) // ----------------------------------------------------------------------- document.addEventListener('click', function (e) { var btn = e.target.closest('.remove-account-row'); if (!btn) return; var row = btn.closest('tr'); var tbody = row.parentElement; row.remove(); // Renumber order and field name indices var rows = tbody.querySelectorAll('tr'); rows.forEach(function (r, i) { var orderCell = r.querySelector('.order-num'); if (orderCell) orderCell.textContent = i + 1; // Rename inputs to match new index r.querySelectorAll('input').forEach(function (inp) { var name = inp.getAttribute('name'); if (name) { inp.setAttribute('name', name.replace(/_\d+$/, '_' + i)); } }); }); var emptyEl = document.getElementById('userAccountsEmpty'); if (emptyEl) emptyEl.style.display = rows.length > 0 ? 'none' : ''; }); // ----------------------------------------------------------------------- // AutoLogon Enabled toggle — keep hidden input in sync // ----------------------------------------------------------------------- var autologonToggle = document.getElementById('autologonEnabledToggle'); if (autologonToggle) { autologonToggle.addEventListener('change', function () { var hidden = document.getElementById('autologon_enabled_val'); if (hidden) { hidden.value = this.checked ? 'true' : 'false'; } }); } // ----------------------------------------------------------------------- // 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'); toggleEmpty('userAccountsTable', 'userAccountsEmpty'); // ======================================================================= // Image Configuration Editor handlers // ======================================================================= // ----------------------------------------------------------------------- // Generic: collect table rows into JSON and submit form // ----------------------------------------------------------------------- function collectAndSubmit(tableId, hiddenId, formId, extractor) { var tbody = document.querySelector('#' + tableId + ' tbody'); if (!tbody) return; var rows = tbody.querySelectorAll('tr'); var data = []; rows.forEach(function (row) { var item = extractor(row); if (item) data.push(item); }); document.getElementById(hiddenId).value = JSON.stringify(data); document.getElementById(formId).submit(); } // Helper: extract JSON from data-json attr, stripping internal _fields function extractDataJson(row) { var raw = row.getAttribute('data-json'); if (!raw) return null; try { var obj = JSON.parse(raw); Object.keys(obj).forEach(function (k) { if (k.charAt(0) === '_') delete obj[k]; }); return obj; } catch (e) { return null; } } // ----------------------------------------------------------------------- // Save: Hardware Models // ----------------------------------------------------------------------- var saveHwModelsBtn = document.getElementById('saveHwModels'); if (saveHwModelsBtn) { saveHwModelsBtn.addEventListener('click', function () { collectAndSubmit('hwModelsTable', 'hwModelsData', 'hwModelsForm', function (row) { var m = row.querySelector('[data-field="Model"]'); var f = row.querySelector('[data-field="Id"]'); if (!m || !f) return null; return { Model: m.value, Id: f.value }; }); }); } // ----------------------------------------------------------------------- // Save: Drivers // ----------------------------------------------------------------------- var saveDriversBtn = document.getElementById('saveDrivers'); if (saveDriversBtn) { saveDriversBtn.addEventListener('click', function () { collectAndSubmit('driversTable', 'driversData', 'driversForm', extractDataJson); }); } // ----------------------------------------------------------------------- // Save: Operating Systems // ----------------------------------------------------------------------- var saveOsBtn = document.getElementById('saveOs'); if (saveOsBtn) { saveOsBtn.addEventListener('click', function () { collectAndSubmit('osTable', 'osData', 'osForm', extractDataJson); }); } // ----------------------------------------------------------------------- // Save: Packages // ----------------------------------------------------------------------- var savePackagesBtn = document.getElementById('savePackages'); if (savePackagesBtn) { savePackagesBtn.addEventListener('click', function () { collectAndSubmit('packagesTable', 'packagesData', 'packagesForm', extractDataJson); }); } // ----------------------------------------------------------------------- // Add Hardware Model row // ----------------------------------------------------------------------- var addHwModelBtn = document.getElementById('addHwModel'); if (addHwModelBtn) { addHwModelBtn.addEventListener('click', function () { var tbody = document.querySelector('#hwModelsTable tbody'); var idx = tbody.querySelectorAll('tr').length + 1; var tr = document.createElement('tr'); tr.innerHTML = '' + idx + '' + '' + '' + 'New' + ''; tbody.appendChild(tr); var empty = document.getElementById('hwModelsEmpty'); if (empty) empty.style.display = 'none'; tr.querySelector('input').focus(); }); } });