/**
* 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');
});