import { createListPageModule } from '/js/core/app-list-page-module.js'; import { escapeHtml, withCurrentListReturn } from '/js/pages/app-list-utils.js'; import { postForm } from '/js/core/app-http.js'; import { showAsyncFlash } from '/js/components/app-async-flash.js'; import { initListExport } from '/js/core/app-list-export.js'; const LEVEL_LABELS = { niedrig: 'Low', normal: 'Normal', hoch: 'High', kritisch: 'Critical', }; function resolveDialog() { const dialog = document.querySelector('[data-app-security-dialog]'); if (!(dialog instanceof HTMLDialogElement)) { return null; } return { dialog, domainEl: dialog.querySelector('#sec-dialog-domain'), levelSelect: dialog.querySelector('#sec-dialog-level'), noteTextarea: dialog.querySelector('#sec-dialog-note'), csrfInput: dialog.querySelector('#sec-dialog-csrf'), saveButton: dialog.querySelector('[data-sec-dialog-save]'), cancelButton: dialog.querySelector('[data-sec-dialog-cancel]'), closeButton: dialog.querySelector('[data-sec-dialog-close]'), }; } function openSecurityDialog(badge, securityLevelUrl, labels, onSaved) { const els = resolveDialog(); if (!els) { return; } const domainNo = badge.dataset.domainNo || ''; const currentLevel = badge.dataset.level || 'normal'; const currentNote = badge.dataset.note || ''; const domainName = badge.dataset.domainName || domainNo; els.domainEl.textContent = domainName; els.levelSelect.value = currentLevel; els.noteTextarea.value = currentNote; els.saveButton.disabled = false; const activeElement = document.activeElement; let saved = false; const cleanup = () => { els.saveButton.removeEventListener('click', onSave); els.cancelButton.removeEventListener('click', onCancel); if (els.closeButton) { els.closeButton.removeEventListener('click', onCancel); } els.dialog.removeEventListener('close', onClose); els.dialog.removeEventListener('cancel', onCancelDialog); els.dialog.removeEventListener('click', onBackdrop); }; const closeDialog = () => { saved = true; cleanup(); try { if (els.dialog.open) { els.dialog.close(); } } catch { /* no-op */ } document.body.classList.remove('modal-is-open'); if (activeElement && document.contains(activeElement)) { window.requestAnimationFrame(() => { activeElement.focus({ preventScroll: true }); }); } }; const onSave = async () => { const newLevel = els.levelSelect.value; const note = els.noteTextarea.value.trim(); const csrfKey = els.csrfInput?.name || 'csrf_token'; const csrfToken = els.csrfInput?.value || ''; const errorLabel = labels?.saveError || 'Save failed'; if (!csrfToken) { showAsyncFlash('error', errorLabel); return; } els.saveButton.disabled = true; els.saveButton.setAttribute('aria-busy', 'true'); const body = new URLSearchParams({ domain_no: domainNo, security_level: newLevel, note, [csrfKey]: csrfToken, }); try { const resp = await postForm(securityLevelUrl, body); if (resp?.ok) { closeDialog(); if (typeof onSaved === 'function') { onSaved(); } return; } showAsyncFlash('error', resp?.error || errorLabel); } catch (error) { showAsyncFlash('error', error?.message || errorLabel); } finally { els.saveButton.disabled = false; els.saveButton.removeAttribute('aria-busy'); } }; const onCancel = (event) => { event.preventDefault(); closeDialog(); }; const onCancelDialog = (event) => { event.preventDefault(); closeDialog(); }; const onClose = () => { if (!saved) { saved = true; cleanup(); document.body.classList.remove('modal-is-open'); } }; const onBackdrop = (event) => { if (event.target === els.dialog) { closeDialog(); } }; els.saveButton.addEventListener('click', onSave); els.cancelButton.addEventListener('click', onCancel); if (els.closeButton) { els.closeButton.addEventListener('click', onCancel); } els.dialog.addEventListener('cancel', onCancelDialog); els.dialog.addEventListener('close', onClose); els.dialog.addEventListener('click', onBackdrop); document.body.classList.add('modal-is-open'); try { els.dialog.showModal(); } catch { cleanup(); document.body.classList.remove('modal-is-open'); return; } window.requestAnimationFrame(() => { els.levelSelect.focus(); }); } createListPageModule({ configId: 'helpdesk-domains', moduleId: 'helpdesk-domains', missingGridMessage: 'Helpdesk domains grid init failed: Grid.js missing', initErrorMessage: 'Helpdesk domains grid init failed', setup: ({ config, gridjs, appBase, initListPage }) => { const labels = config.labels || {}; const domainBaseUrl = config.domainBaseUrl || ''; const securityLevelUrl = (config.securityLevelDataUrl || '').trim(); const domainUrlIndex = 8; const gridContainer = document.querySelector('#helpdesk-domains-grid'); const gridRef = { grid: null }; const refreshGrid = () => { gridRef.grid?.forceRender(); }; gridContainer.addEventListener('click', (event) => { const badge = event.target.closest('td .badge[data-domain-no][role="button"]'); if (!badge) { return; } event.preventDefault(); event.stopPropagation(); openSecurityDialog(badge, securityLevelUrl, labels, refreshGrid); }); gridContainer.addEventListener('keydown', (event) => { if (event.key !== 'Enter' && event.key !== ' ') { return; } const badge = event.target.closest('.badge[data-domain-no][role="button"]'); if (!badge) { return; } event.preventDefault(); event.stopPropagation(); openSecurityDialog(badge, securityLevelUrl, labels, refreshGrid); }); const { gridConfig } = initListPage({ grid: { gridjs, container: '#helpdesk-domains-grid', dataUrl: config.dataUrl || 'helpdesk/domains-data', appBase, columns: [ { name: labels.no || 'No.', sort: true, formatter: (cell) => { const no = escapeHtml(cell?.no || ''); const detailUrl = String(cell?.url || '').trim(); if (detailUrl === '') { return gridjs.html(no); } const href = escapeHtml(detailUrl); return gridjs.html(`${no}`); }, }, { name: labels.customerName || 'Customer Name', sort: true, formatter: (cell) => { const name = escapeHtml(cell?.name || ''); const detailUrl = String(cell?.url || '').trim(); if (detailUrl === '') { return gridjs.html(name); } const href = escapeHtml(withCurrentListReturn(detailUrl)); return gridjs.html(`${name}`); }, }, { name: labels.url || 'URL', sort: true }, { name: labels.contractType || 'Contract Type', sort: true }, { name: labels.state || 'State', sort: true, formatter: (cell) => { const state = escapeHtml(cell?.state || ''); const variant = cell?.variant || 'neutral'; return gridjs.html(`${state}`); }, }, { name: labels.administration || 'Administration', sort: true }, { name: labels.securityLevel || 'Security level', sort: true, formatter: (cell) => { const level = cell?.level || 'normal'; const variant = cell?.variant || 'neutral'; const domainNo = cell?.domainNo || ''; const note = cell?.note || ''; const domainName = cell?.domainName || domainNo; const levelLabel = labels?.[level] ?? LEVEL_LABELS[level] ?? level; const noteAttr = note ? ` data-note="${escapeHtml(note)}"` : ''; return gridjs.html(`${escapeHtml(levelLabel)}`); }, }, { name: labels.note || 'Note', sort: true, formatter: (cell) => { const note = String(cell || '').trim(); if (note === '') { return gridjs.html(''); } const escaped = escapeHtml(note); return gridjs.html(`${escaped}`); }, }, { name: 'debitor_url', hidden: true }, { name: 'domain_detail_url', hidden: true }, ], sortColumns: ['No', 'Customer_Name', 'URL', 'contract_type', 'State', 'Administration', 'security_level', 'security_level_note', null, null], paginationLimit: 10, language: config.gridLang || {}, mapData: (data) => (data.data || []).map((row) => { const domainDetailUrl = domainBaseUrl && row.No ? domainBaseUrl + encodeURIComponent(row.No) : ''; return [ { no: row.No || '', url: domainDetailUrl }, { name: row.Customer_Name || '', url: row.debitor_url || '' }, row.URL || '', row.contract_type || '', { state: row.State || '', variant: row.state_variant || 'neutral' }, row.Administration || '', { level: row.security_level || 'normal', variant: row.security_level_variant || 'neutral', domainNo: row.No || '', note: row.security_level_note || '', domainName: row.Customer_Name || row.No || '' }, row.security_level_note || '', row.debitor_url || '', domainDetailUrl, ]; }), search: config.gridSearch || null, filterSchema: config.filterSchema || [], urlSync: true, rowInteraction: { linkColumn: 0 }, rowDblClick: { getUrl: (rowData) => rowData?.cells?.[domainUrlIndex]?.data || '', exclude: '.badge[role="button"], .grid-actions, button, a, input, select, textarea', }, }, filters: { mode: 'drawer', chipMeta: config.filterChipMeta || {}, watchInputs: ['#helpdesk-domains-search-input'], }, }) || {}; gridRef.grid = gridConfig?.grid || null; if (gridConfig && config.exportUrl) { initListExport({ gridConfig, exportUrl: config.exportUrl }); } return gridConfig; }, });