satdetect-dev / static /js /dda /admin_menu.js
coderuday21's picture
Cursor
Deploy satdetect-dev with Priyanka audit/exception logs.
d70361b
Raw
History Blame Contribute Delete
8.02 kB
/** Menu Management admin page — hierarchical menu item list + up/down
* reorder within each sibling group, icon + parent (one level of nesting). */
let allMenuItems = [];
let allMenuModules = [];
async function loadMenuModules() {
try {
const data = await ddaApi('GET', '/api/dda/rbac/modules');
allMenuModules = data.modules || [];
const sel = document.getElementById('menu-item-module');
if (sel) {
sel.innerHTML = '<option value="">— No module (always visible) —</option>' +
allMenuModules.map((m) => `<option value="${m.id}">${escapeHtml(m.name)}</option>`).join('');
}
const keysList = document.getElementById('module-keys-list');
if (keysList) {
keysList.innerHTML = allMenuModules.length
? allMenuModules.map((m) => `<code>${escapeHtml(m.key)}</code>`).join('')
: '<p class="dim">No modules yet.</p>';
}
} catch (_) {
// Module select / keys list just stay at their defaults if this fails.
}
}
async function loadMenuItems() {
const el = document.getElementById('menu-item-list');
try {
const data = await ddaApi('GET', '/api/dda/rbac/menu-items');
allMenuItems = (data.menuItems || []).sort((a, b) => a.sortOrder - b.sortOrder);
renderMenuItemList();
} catch (err) {
el.innerHTML = `<p class="dim">Could not load menu items: ${escapeHtml(err.message || 'error')}</p>`;
}
}
function moduleNameFor(moduleId) {
const m = allMenuModules.find((x) => x.id === moduleId);
return m ? m.name : null;
}
function siblingGroup(parentId) {
return allMenuItems.filter((i) => i.parentId === parentId).sort((a, b) => a.sortOrder - b.sortOrder);
}
function renderItemRow(item, isChild) {
const group = siblingGroup(item.parentId);
const idx = group.findIndex((i) => i.id === item.id);
return `
<div class="dda-menu-item-row${isChild ? ' is-child' : ''}" data-item-id="${item.id}">
<div class="dda-menu-item-order">
<button type="button" data-move="up" ${idx === 0 ? 'disabled' : ''} title="Move up">▲</button>
<button type="button" data-move="down" ${idx === group.length - 1 ? 'disabled' : ''} title="Move down">▼</button>
</div>
<div class="dda-menu-item-info">
<div class="dda-menu-item-label">
${item.icon ? `<span class="dda-menu-item-icon">${escapeHtml(item.icon)}</span>` : ''}
${escapeHtml(item.label)}
<span class="dda-status-badge ${item.isActive ? 'is-active' : 'is-inactive'}">${item.isActive ? 'Active' : 'Hidden'}</span>
</div>
<div class="dda-menu-item-meta">
<code>${escapeHtml(item.url)}</code>
${moduleNameFor(item.moduleId) ? ' · module: ' + escapeHtml(moduleNameFor(item.moduleId)) : ' · no module (always visible)'}
</div>
</div>
<div class="dda-menu-item-actions">
<button type="button" class="btn btn-secondary btn-sm" data-edit-item="${item.id}">Edit</button>
<button type="button" class="btn btn-danger btn-sm" data-del-item="${item.id}">Delete</button>
</div>
</div>`;
}
function renderMenuItemList() {
const el = document.getElementById('menu-item-list');
if (!allMenuItems.length) {
el.innerHTML = '<p class="dim">No menu items yet.</p>';
return;
}
const topLevel = siblingGroup(null);
el.innerHTML = topLevel.map((item) => {
const children = siblingGroup(item.id);
return renderItemRow(item, false) + children.map((c) => renderItemRow(c, true)).join('');
}).join('');
el.querySelectorAll('[data-move]').forEach((btn) => {
btn.addEventListener('click', () => moveMenuItem(Number(btn.closest('.dda-menu-item-row').dataset.itemId), btn.dataset.move));
});
el.querySelectorAll('[data-edit-item]').forEach((btn) => {
btn.addEventListener('click', () => openMenuItemModal(Number(btn.dataset.editItem)));
});
el.querySelectorAll('[data-del-item]').forEach((btn) => {
btn.addEventListener('click', () => deleteMenuItem(Number(btn.dataset.delItem)));
});
}
async function moveMenuItem(itemId, direction) {
const item = allMenuItems.find((i) => i.id === itemId);
const group = siblingGroup(item.parentId);
const idx = group.findIndex((i) => i.id === itemId);
const swapWith = direction === 'up' ? idx - 1 : idx + 1;
if (swapWith < 0 || swapWith >= group.length) return;
[group[idx], group[swapWith]] = [group[swapWith], group[idx]];
renderMenuItemList();
try {
await ddaApi('POST', '/api/dda/rbac/menu-items/reorder', {
body: JSON.stringify({ orderedIds: group.map((i) => i.id) }),
});
group.forEach((i, order) => { i.sortOrder = order; });
} catch (err) {
showDdaError(err.message);
await loadMenuItems();
}
}
function populateParentSelect(excludeId) {
const sel = document.getElementById('menu-item-parent');
if (!sel) return;
const topLevel = allMenuItems.filter((i) => i.parentId === null && i.id !== excludeId);
sel.innerHTML = '<option value="">— Top Level —</option>' +
topLevel.map((i) => `<option value="${i.id}">${escapeHtml(i.label)}</option>`).join('');
}
function openMenuItemModal(itemId) {
const modal = document.getElementById('menu-item-modal');
const title = document.getElementById('menu-item-modal-title');
const item = allMenuItems.find((i) => i.id === itemId);
populateParentSelect(itemId);
document.getElementById('menu-item-id').value = item ? item.id : '';
document.getElementById('menu-item-label').value = item ? item.label : '';
document.getElementById('menu-item-icon').value = item ? item.icon || '' : '';
document.getElementById('menu-item-url').value = item ? item.url : '';
document.getElementById('menu-item-module').value = item && item.moduleId ? item.moduleId : '';
document.getElementById('menu-item-parent').value = item && item.parentId ? item.parentId : '';
document.getElementById('menu-item-active').checked = item ? item.isActive : true;
title.textContent = item ? 'Edit Menu Item' : 'Add Menu Item';
modal.classList.remove('hidden');
}
document.getElementById('btn-new-menu-item')?.addEventListener('click', () => openMenuItemModal(null));
document.getElementById('menu-item-modal-close')?.addEventListener('click', () => document.getElementById('menu-item-modal').classList.add('hidden'));
document.getElementById('menu-item-modal')?.addEventListener('click', (e) => {
if (e.target.id === 'menu-item-modal') document.getElementById('menu-item-modal').classList.add('hidden');
});
document.getElementById('menu-item-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
hideDdaError();
const id = document.getElementById('menu-item-id').value;
const moduleVal = document.getElementById('menu-item-module').value;
const parentVal = document.getElementById('menu-item-parent').value;
const body = {
label: document.getElementById('menu-item-label').value.trim(),
icon: document.getElementById('menu-item-icon').value.trim(),
url: document.getElementById('menu-item-url').value.trim(),
moduleId: moduleVal ? Number(moduleVal) : null,
parentId: parentVal ? Number(parentVal) : null,
isActive: document.getElementById('menu-item-active').checked,
};
try {
if (id) {
await ddaApi('PUT', `/api/dda/rbac/menu-items/${id}`, { body: JSON.stringify(body) });
} else {
await ddaApi('POST', '/api/dda/rbac/menu-items', { body: JSON.stringify(body) });
}
document.getElementById('menu-item-modal').classList.add('hidden');
showDdaSuccess('Menu item saved.');
await loadMenuItems();
} catch (err) {
showDdaError(err.message);
}
});
async function deleteMenuItem(itemId) {
if (!confirm('Delete this menu item?')) return;
try {
await ddaApi('DELETE', `/api/dda/rbac/menu-items/${itemId}`);
showDdaSuccess('Menu item deleted.');
await loadMenuItems();
} catch (err) {
showDdaError(err.message);
}
}
document.addEventListener('DOMContentLoaded', async () => {
await loadMenuModules();
await loadMenuItems();
});