- Shopfloor PC type menu (CMM, WaxAndTrace, Keyence, Genspect, Display, Standard) - Baseline scripts: OpenText CSF, Start Menu shortcuts, network/WinRM, power/display - Standard type: eDNC + MarkZebra with 64-bit path mirroring - CMM type: Hexagon CLM Tools, PC-DMIS 2016/2019 R2 - Display sub-type: Lobby vs Dashboard - Webapp: enrollment management, image config editor, UI refresh - Upload-Image.ps1: robocopy MCL cache to PXE server - Download-Drivers.ps1: Dell driver download pipeline - Slim Blancco GRUB EFI (10MB -> 660KB) for old hardware compat - Shopfloor display imaging guide docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
470 lines
20 KiB
JavaScript
470 lines
20 KiB
JavaScript
/**
|
|
* 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 =
|
|
'<td class="order-num">' + idx + '</td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="driver_path[]" value="" placeholder="e.g. C:\\Drivers\\Network"></td>' +
|
|
'<td><button type="button" class="btn btn-outline-danger btn-row-action remove-row"><i class="bi bi-trash"></i></button></td>';
|
|
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 =
|
|
'<td class="drag-handle"><i class="bi bi-grip-vertical"></i></td>' +
|
|
'<td class="order-num">' + idx + '</td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="spec_cmd_path[]" value="" placeholder="Command path"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="spec_cmd_desc[]" value="" placeholder="Description"></td>' +
|
|
'<td class="text-nowrap">' +
|
|
'<button type="button" class="btn btn-outline-secondary btn-row-action move-up" title="Move up"><i class="bi bi-arrow-up"></i></button> ' +
|
|
'<button type="button" class="btn btn-outline-secondary btn-row-action move-down" title="Move down"><i class="bi bi-arrow-down"></i></button> ' +
|
|
'<button type="button" class="btn btn-outline-danger btn-row-action remove-row"><i class="bi bi-trash"></i></button>' +
|
|
'</td>';
|
|
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 =
|
|
'<td class="drag-handle"><i class="bi bi-grip-vertical"></i></td>' +
|
|
'<td class="order-num">' + idx + '</td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="fl_cmd_commandline[]" value="" placeholder="Command line"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="fl_cmd_desc[]" value="" placeholder="Description"></td>' +
|
|
'<td class="text-nowrap">' +
|
|
'<button type="button" class="btn btn-outline-secondary btn-row-action move-up" title="Move up"><i class="bi bi-arrow-up"></i></button> ' +
|
|
'<button type="button" class="btn btn-outline-secondary btn-row-action move-down" title="Move down"><i class="bi bi-arrow-down"></i></button> ' +
|
|
'<button type="button" class="btn btn-outline-danger btn-row-action remove-row"><i class="bi bi-trash"></i></button>' +
|
|
'</td>';
|
|
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 =
|
|
'<td class="order-num">' + (idx + 1) + '</td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="account_name_' + idx + '" value="" placeholder="Username"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="account_password_' + idx + '" value="" placeholder="Password"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="account_group_' + idx + '" value="Administrators"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" name="account_display_' + idx + '" value="" placeholder="Display Name"></td>' +
|
|
'<td>' +
|
|
'<input type="hidden" name="account_plaintext_' + idx + '" value="true">' +
|
|
'<button type="button" class="btn btn-outline-danger btn-row-action remove-account-row">Remove</button>' +
|
|
'</td>';
|
|
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 = '<span class="spinner-border spinner-border-sm me-1"></span> 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 = '<i class="bi bi-floppy me-1"></i> 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 +
|
|
'<button type="button" class="btn-close" data-bs-dismiss="alert"></button>';
|
|
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 =
|
|
'<td class="order-num">' + idx + '</td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" data-field="Model" value="" placeholder="Model name"></td>' +
|
|
'<td><input type="text" class="form-control form-control-sm" data-field="Id" value="" placeholder="Driver Family ID"></td>' +
|
|
'<td><span class="badge bg-secondary badge-disk">New</span></td>' +
|
|
'<td><button type="button" class="btn btn-outline-danger btn-row-action remove-row"><i class="bi bi-trash"></i></button></td>';
|
|
tbody.appendChild(tr);
|
|
var empty = document.getElementById('hwModelsEmpty');
|
|
if (empty) empty.style.display = 'none';
|
|
tr.querySelector('input').focus();
|
|
});
|
|
}
|
|
|
|
});
|
|
|