/* ITSM Frontend-Logik -- CSP-konform: kein Inline-JS, keine onclick-Attribute. Alle zustandsaendernden Requests senden das CSRF-Token als Header. */ (function () { "use strict"; var CSRF = (document.querySelector('meta[name="csrf"]') || {}).content || ""; function esc(s) { return String(s == null ? "" : s) .replace(/&/g, "&").replace(//g, ">") .replace(/"/g, """).replace(/'/g, "'"); } function post(url, params) { var body = new URLSearchParams(params || {}).toString(); return fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "X-CSRF-Token": CSRF }, body: body }).then(function (r) { return r.json().catch(function () { return { ok: r.ok }; }); }); } function initials(label) { var local = String(label || "?").split("@")[0]; var parts = local.split(/[._\- ]+/).filter(Boolean); var s = parts.slice(0, 2).map(function (p) { return p[0].toUpperCase(); }).join(""); return s || "?"; } // ── Zeilen-Navigation + Ticket-Detail ──────────────────────────────────────── document.addEventListener("click", function (ev) { var row = ev.target.closest(".row"); if (!row) return; if (ev.target.closest("a, button, form, select, input, textarea")) return; if (row.dataset.ticketId) openDetail(parseInt(row.dataset.ticketId, 10)); else if (row.dataset.href) location.href = row.dataset.href; }); // Formulare mit Bestaetigung + Auto-Submit-Selects + Change-Typ-Anzeige document.addEventListener("submit", function (ev) { var f = ev.target; if (f.dataset && f.dataset.confirm && !window.confirm(f.dataset.confirm)) { ev.preventDefault(); } }); document.addEventListener("change", function (ev) { if (ev.target.matches("select[data-autosubmit]")) ev.target.form.submit(); if (ev.target.id === "new-kategorie") { var row = document.getElementById("change-typ-row"); if (row) row.hidden = ev.target.value !== "Change"; } if (ev.target.id === "check-all") { document.querySelectorAll(".row-check").forEach(function (c) { c.checked = ev.target.checked; }); } }); // ── Modal "Neues Ticket" ───────────────────────────────────────────────────── var modal = document.getElementById("new-ticket-modal"); var openBtn = document.getElementById("open-new-ticket"); var closeBtn = document.getElementById("close-new-ticket"); if (openBtn) openBtn.addEventListener("click", function () { modal.hidden = false; }); if (closeBtn) closeBtn.addEventListener("click", function () { modal.hidden = true; }); if (modal) modal.addEventListener("click", function (ev) { if (ev.target === modal) modal.hidden = true; }); // ── Aktionen-Dropdown + Bulk-Status ────────────────────────────────────────── var bulkBtn = document.getElementById("bulk-actions-btn"); var bulkMenu = document.getElementById("bulk-menu"); if (bulkBtn) { bulkBtn.addEventListener("click", function (ev) { ev.stopPropagation(); bulkMenu.hidden = !bulkMenu.hidden; }); document.addEventListener("click", function () { bulkMenu.hidden = true; }); bulkMenu.querySelectorAll("[data-bulk-status]").forEach(function (b) { b.addEventListener("click", function () { var ids = Array.prototype.map.call(document.querySelectorAll(".row-check:checked"), function (c) { return c.value; }); if (!ids.length) { alert("Keine Tickets ausgewaehlt."); return; } var status = b.dataset.bulkStatus; var done = 0, errors = []; var next = function () { if (!ids.length) { if (errors.length) alert("Teilweise fehlgeschlagen (ITIL-Statusmodell):\n" + errors.join("\n")); location.reload(); return; } var id = ids.shift(); post("/tickets/" + id + "/status", { status: status }).then(function (d) { if (!d.ok) errors.push("#" + id + ": " + (d.error || "Fehler")); done++; next(); }); }; next(); }); }); } // ── Detail-Panel ───────────────────────────────────────────────────────────── var overlay = document.getElementById("detail-overlay"); var panel = document.getElementById("detail-panel"); if (overlay) overlay.addEventListener("click", closeDetail); function openDetail(id, tab) { fetch("/api/tickets/" + id, { headers: { "Accept": "application/json" } }) .then(function (r) { return r.json(); }) .then(function (t) { if (!t || t.ok === false) return; panel.innerHTML = renderDetail(t, tab || "details"); bindDetail(t, tab || "details"); panel.classList.add("open"); overlay.classList.add("open"); }); } function closeDetail() { panel.classList.remove("open"); overlay.classList.remove("open"); } function pill(text, cls) { return '' + esc(text) + ""; } function statusCls(s) { return { "In Bearbeitung": "inbearbeitung", "Warten": "warten", "Geloest": "geloest", "Geschlossen": "geschlossen" }[s] || "offen"; } function prioCls(p) { return { "Kritisch": "krit", "Hoch": "hoch", "Niedrig": "niedrig" }[p] || "mittel"; } function renderDetail(t, tab) { var h = ""; h += '

' + esc(t.ticket_nr) + '

'; h += '
' + esc(initials(t.service_name || t.titel)) + ''; h += '
' + esc(t.service_name || "(kein Service)") + '
' + esc(t.titel) + '
'; h += '
'; h += 'Status' + pill(t.status, statusCls(t.status)) + ''; h += 'Prioritaet' + pill(t.prioritaet, prioCls(t.prioritaet)) + ''; h += 'Kategorie' + pill(t.kategorie) + (t.kategorie === "Change" && t.change_typ ? " " + pill(t.change_typ) + " " + pill("Freigabe: " + (t.approval_status || "-")) : "") + (t.known_error ? " " + pill("Known Error", "ueberfaellig") : "") + ''; h += 'Zugewiesen an' + esc(initials(t.zugewiesen_an)) + ' ' + esc(t.zugewiesen_an || "--") + ''; h += 'Impact / Urgency' + esc(t.impact) + ' / ' + esc(t.urgency) + ''; h += 'Fortschritt' + t.fortschritt + '%
'; if (t.problem_nr) h += 'Problem' + esc(t.problem_nr) + ''; h += '
'; var tabs = [["details", "Details"], ["akt", "Aktivitaeten"], ["anh", "Anhaenge"], ["auf", "Aufgaben"]]; h += '
' + tabs.map(function (x) { return '' + x[1] + ''; }).join("") + '
'; if (tab === "details") { h += '
Beschreibung
' + esc(t.beschreibung || "--") + '
'; h += '
Betroffene Services
' + pill(t.service_name || "(kein Service)") + '
'; if (t.operative) { h += '
CI / Betroffene Konfigurationselemente
'; (t.cis || []).forEach(function (c) { h += '' + esc(c.name) + ' '; }); if (!(t.cis || []).length) h += 'keine'; if (t.cis_available && t.cis_available.length) { h += '
'; } h += '
'; } if (t.sla) { h += '
SLAs
'; h += 'Antwortzeit' + esc(t.sla.response_label) + ''; h += 'Loesungszeit' + esc(t.sla.resolve_label) + ''; h += '
'; } if (t.operative && t.kategorie !== "Problem" && t.problems && t.problems.length) { h += '
Mit Problem verknuepfen
'; h += '
'; } if (t.operative && t.kategorie === "Problem") { h += '
Problem Management
'; h += '
"; } if (t.can_approve) { h += '
Change-Freigabe (CAB)
'; h += ' '; h += '
'; } if (t.can_repo_edit) { h += '
Repo-Datei bearbeiten (Forge, dokumentationspflichtig)
'; h += '
'; h += '
'; h += '
'; h += '
'; h += ''; h += '
'; h += ''; h += ''; h += ''; h += ''; h += '
'; } } else if (tab === "akt") { h += '
Zeitleiste
'; h += ((t.timeline || []).map(function (e) { return '
' + esc(e.akteur || "") + '
' + esc(e.zeit) + "
" + esc(e.text || "") + "
"; }).join("")) || '
Keine Ereignisse.
'; } else { h += '
' + (tab === "anh" ? "Anhaenge werden in einer spaeteren Ausbaustufe unterstuetzt." : "Aufgaben werden in einer spaeteren Ausbaustufe unterstuetzt.") + '
'; } h += ''; h += ''; return h; } function bindDetail(t, tab) { var id = t.id; panel.querySelectorAll("[data-dt-tab]").forEach(function (el) { el.addEventListener("click", function () { openDetail(id, el.dataset.dtTab); }); }); var statusSel = panel.querySelector("#dt-status"); if (statusSel) statusSel.addEventListener("change", function () { if (!statusSel.value) return; post("/tickets/" + id + "/status", { status: statusSel.value }).then(function (d) { if (d.ok) openDetail(id, tab); else alert(d.error || "Fehler"); }); }); panel.querySelectorAll("[data-approve]").forEach(function (b) { b.addEventListener("click", function () { post("/tickets/" + id + "/approval", { decision: b.dataset.approve }).then(function (d) { if (d.ok) openDetail(id, tab); else alert(d.error || "Fehler"); }); }); }); panel.querySelectorAll("[data-known-error]").forEach(function (b) { b.addEventListener("click", function () { post("/tickets/" + id + "/known-error", { known_error: b.dataset.knownError }).then(function (d) { if (d.ok) openDetail(id, tab); else alert(d.error || "Fehler"); }); }); }); panel.querySelectorAll("[data-ci-unlink]").forEach(function (b) { b.addEventListener("click", function () { post("/tickets/" + id + "/ci-unlink", { ci_id: b.dataset.ciUnlink }).then(function () { openDetail(id, tab); }); }); }); var ciAdd = panel.querySelector("#dt-ci-add"); if (ciAdd) ciAdd.addEventListener("click", function () { post("/tickets/" + id + "/ci-link", { ci_id: panel.querySelector("#dt-ci").value }) .then(function () { openDetail(id, tab); }); }); var probSave = panel.querySelector("#dt-problem-save"); if (probSave) probSave.addEventListener("click", function () { post("/tickets/" + id + "/problem-link", { problem_id: panel.querySelector("#dt-problem").value }) .then(function (d) { if (d.ok) openDetail(id, tab); else alert(d.error || "Fehler"); }); }); var commentBtn = panel.querySelector("#dt-comment-btn"); if (commentBtn) commentBtn.addEventListener("click", function () { panel.querySelector("#dt-comment-box").hidden = false; panel.querySelector("#dt-comment").focus(); }); var commentSave = panel.querySelector("#dt-comment-save"); if (commentSave) commentSave.addEventListener("click", function () { var text = panel.querySelector("#dt-comment").value.trim(); if (!text) return; post("/tickets/" + id + "/comment", { text: text }).then(function (d) { if (d.ok) openDetail(id, "akt"); else alert(d.error || "Fehler"); }); }); var close = panel.querySelector("#dt-close"); if (close) close.addEventListener("click", closeDetail); var repoLoad = panel.querySelector("#repo-load"); if (repoLoad) { repoLoad.addEventListener("click", function () { var repo = panel.querySelector("#repo-name").value.trim(); var path = panel.querySelector("#repo-path").value.trim(); var branch = panel.querySelector("#repo-branch").value.trim() || "main"; var status = panel.querySelector("#repo-status"); if (!repo || !path) { status.textContent = "Repo und Dateipfad angeben."; return; } status.textContent = "Laedt..."; fetch("/api/tickets/" + id + "/repo-file?repo=" + encodeURIComponent(repo) + "&path=" + encodeURIComponent(path) + "&branch=" + encodeURIComponent(branch)) .then(function (r) { return r.json(); }) .then(function (d) { if (!d.ok) { status.textContent = "Fehler: " + d.error; return; } status.textContent = "Geladen (sha " + d.sha.slice(0, 10) + ")."; var ta = panel.querySelector("#repo-content"); ta.value = d.content; ta.hidden = false; panel.querySelector("#repo-sha").value = d.sha; panel.querySelector("#repo-msg-row").hidden = false; panel.querySelector("#repo-save").hidden = false; }) .catch(function () { status.textContent = "Fehler beim Laden."; }); }); panel.querySelector("#repo-save").addEventListener("click", function () { var status = panel.querySelector("#repo-status"); var message = panel.querySelector("#repo-msg").value.trim(); if (!message) { status.textContent = "Bitte eine Commit-Nachricht angeben."; return; } status.textContent = "Speichert..."; post("/tickets/" + id + "/repo-edit", { repo: panel.querySelector("#repo-name").value.trim(), path: panel.querySelector("#repo-path").value.trim(), branch: panel.querySelector("#repo-branch").value.trim() || "main", sha: panel.querySelector("#repo-sha").value, content: panel.querySelector("#repo-content").value, message: message }).then(function (d) { if (!d.ok) { status.textContent = "Fehler: " + d.error; return; } status.textContent = "Gespeichert -- Commit " + d.commit.slice(0, 10) + ", im Worklog dokumentiert."; openDetail(id, "akt"); }).catch(function () { status.textContent = "Fehler beim Speichern."; }); }); } } })();