Files
proxmox-selfservice/qa-tool/htdocs/js/main.js
Sven Steinert fce31ebcd7 Viel neues
2026-04-30 12:06:00 +02:00

253 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===================== QA App komplette app.js =====================
// === DocBee: Token HIER eintragen ===
const DOCBEE_TOKEN = window.DOCBEE_TOKEN || ""; // DocBee Token eintragen"
const ENABLE_FALLBACK_NOTE = false; // auf true setzen, falls bei Fehler automatisch Notiz angelegt werden soll
window.addEventListener('error', e => {
try {
console.error(e.error || e.message || e);
} catch (_) {}
alert('JS-Fehler: ' + (e.message || e));
});
let template = null; // {name,module,module_version,pbx_version,steps:[...]}
// === DOM-Refs ===
const els = {
yamlFile: document.getElementById('yamlFile'),
stepsTableBody: document.querySelector('#stepsTable tbody'),
tplName: document.getElementById('tplName'),
statusTag: document.getElementById('statusTag'),
module: document.getElementById('module'),
moduleVersion: document.getElementById('moduleVersion'),
pbxVersion: document.getElementById('pbxVersion'),
tester: document.getElementById('tester'),
olmNummer: document.getElementById('olmNummer'),
docbeeUrl: document.getElementById('docbeeUrl'),
btnAddStep: document.getElementById('btnAddStep'),
btnSaveJSON: document.getElementById('btnSaveJSON'),
loadJSON: document.getElementById('loadJSON'),
btnAddGroup: document.getElementById('btnAddGroup'),
btnExportTemplateYAML: document.getElementById('btnExportTemplateYAML'),
btnExportAll: document.getElementById('btnExportAll'),
gitlabTplSelect: document.getElementById('gitlabTplSelect'),
gitlabTplStatus: document.getElementById('gitlabTplStatus'),
docbeeTokenStatus: document.getElementById('docbeeTokenStatus'),
btnGitlabReload: document.getElementById('btnGitlabReload'),
};
// === Drag Guard + Observer: Nur Drag über expliziten Griff zulassen ===
function enforceDragHandleOnly() {
if (!els.stepsTableBody) return;
els.stepsTableBody.querySelectorAll('tr[draggable="true"]').forEach(tr => tr.setAttribute('draggable', 'false'));
els.stepsTableBody.querySelectorAll('.drag-handle').forEach(h => h.setAttribute('draggable', 'true'));
}
document.addEventListener('DOMContentLoaded', () => {
if (els.stepsTableBody) {
const mo = new MutationObserver(() => enforceDragHandleOnly());
mo.observe(els.stepsTableBody, {
childList: true,
subtree: true
});
enforceDragHandleOnly();
}
});
document.addEventListener('dragstart', (e) => {
if (!e.target.closest('.drag-handle')) {
e.preventDefault();
}
}, {
capture: true
});
// === GitLab Events ===
if (els.gitlabTplSelect) els.gitlabTplSelect.addEventListener('change', async (e) => {
const p = e.target.value;
if (!p) return;
try {
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = "GitLab: lade Datei…");
const y = await fetchGitlabFileRaw(p);
loadYAMLText(y);
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = `GitLab: geladen ${p.split('/').pop()}`);
} catch (err) {
els.gitlabTplStatus && (els.gitlabTplStatus.textContent = "GitLab: Fehler");
console.error("[GitLab] raw failed:", err);
alert("Vorlage aus GitLab konnte nicht geladen werden: " + err.message);
}
});
if (els.btnGitlabReload) els.btnGitlabReload.addEventListener('click', (e) => {
e.preventDefault();
populateGitlabDropdown();
});
// === Drag & Drop (Rows verschieben, inkl. Gruppen) ===
let dragIndex = -1;
// ===== Funktion: rowIndexOf =====
// Zweck: siehe Inline-Kommentare im Funktionskörper.
function rowIndexOf(target) {
const tr = target.closest('tr');
if (!tr) return -1;
return [...els.stepsTableBody.children].indexOf(tr);
}
els.stepsTableBody && els.stepsTableBody.addEventListener('dragstart', (e) => {
if (!e.target.closest('.drag-handle')) {
e.preventDefault();
return;
}
const i = rowIndexOf(e.target);
if (i < 0) return;
dragIndex = i;
e.dataTransfer?.setData('text/plain', String(i));
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
const row = e.target.closest('tr') || e.target;
e.dataTransfer?.setDragImage?.(row, 16, 16);
row.classList.add('dragging');
});
els.stepsTableBody && els.stepsTableBody.addEventListener('dragend', (e) => {
const tr = e.target.closest('tr');
if (tr) tr.classList.remove('dragging');
[...els.stepsTableBody.children].forEach(r => {
r.classList.remove('drag-over');
r.removeAttribute('data-pos');
});
dragIndex = -1;
});
els.stepsTableBody && els.stepsTableBody.addEventListener('dragover', (e) => {
e.preventDefault();
const tr = e.target.closest('tr');
if (!tr) return;
[...els.stepsTableBody.children].forEach(r => {
r.classList.remove('drag-over');
r.removeAttribute('data-pos');
});
tr.classList.add('drag-over');
const rect = tr.getBoundingClientRect();
const pos = (e.clientY - rect.top) < rect.height / 2 ? 'before' : 'after';
tr.setAttribute('data-pos', pos);
});
els.stepsTableBody && els.stepsTableBody.addEventListener('drop', (e) => {
e.preventDefault();
captureEditsIntoTemplate();
const from = dragIndex >= 0 ? dragIndex : parseInt(e.dataTransfer?.getData('text/plain') || '-1', 10);
const tr = e.target.closest('tr');
if (from < 0 || !tr) return;
const toBase = rowIndexOf(tr);
const pos = tr.getAttribute('data-pos') || 'after';
let to = toBase + (pos === 'after' ? 1 : 0);
if (to === from || to - 1 === from) {
renderSteps(template.steps);
recomputeGroupStyles();
return;
}
// Element verschieben
const item = template.steps.splice(from, 1)[0];
if (to > from) to--; // nach dem Entfernen verschiebt sich Index
template.steps.splice(to, 0, item);
ensureRenumberAndRender();
});
document.getElementById('stepsTable')?.addEventListener('change', (e) => {
if (e.target && e.target.matches('select.status')) {
updateStatusClass(e.target);
recomputeGroupStyles();
}
});
els.btnExportMD && els.btnExportMD.addEventListener('click', exportMD);
els.btnExportCSV && els.btnExportCSV.addEventListener('click', exportCSV);
els.btnPrintPDF && els.btnPrintPDF.addEventListener('click', printPDF);
els.btnExportTemplateYAML && els.btnExportTemplateYAML.addEventListener('click', exportTemplateYAML);
els.btnSaveJSON && els.btnSaveJSON.addEventListener('click', () => {
const run = collectRun();
if (!run) return;
if (!checkRequired(run)) return;
const base = `qa-run-${safeName(run.module)}-${safeName(run.module_version)}-${safeName(run.pbx_version)}`;
download(JSON.stringify(run, null, 2), `${base}.json`, 'application/json');
});
els.loadJSON && els.loadJSON.addEventListener('change', async (e) => {
const f = e.target.files?.[0];
if (!f) return;
const txt = await f.text();
try {
const run = JSON.parse(txt);
template = {
name: run.name || '',
steps: (run.steps || []).map(s => ({
id: s.id,
title: s.title,
expected: s.expected,
required: !!s.required,
status: s.status || '',
comment: s.comment || '',
evidence: s.evidence || ''
}))
};
if (els.tplName) els.tplName.textContent = run.name || '—';
els.module.value = run.module || '';
els.moduleVersion.value = run.module_version || '';
els.pbxVersion.value = run.pbx_version || '';
els.tester.value = run.tester || '';
els.docbeeUrl.value = run.docbee_url || '';
renderSteps(template.steps);
recomputeGroupStyles();
if (els.statusTag) els.statusTag.textContent = 'Lauf geladen';
updateAutosave();
} catch (err) {
alert('Ungültiges JSON: ' + err.message);
}
});
// === Bindings absichern + Busy-Fix + Mini-CSS ===
(function ensureBindings() {
// ===== Funktion: bind =====
// Zweck: siehe Inline-Kommentare im Funktionskörper.
function bind() {
const b = document.getElementById('btnPushDocBee');
if (b) {
b.removeAttribute('disabled');
b.classList.remove('is-busy');
b.style.pointerEvents = 'auto';
b.addEventListener('click', postToDocBee, {
once: false
});
}
const p = document.getElementById('btnPrintPDF');
const btn = document.getElementById('btnExportAll');
if (btn) {
btn.addEventListener('click', e => {
e.preventDefault();
exportAll().catch(err => alert('Export error: ' + err.message));
});
console.log('Export-Button gebunden');
} else {
alert('Export-Button nicht gefunden (id=btnExportAll).');
}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind);
else bind();
window.addEventListener('pageshow', () => {
const b = document.getElementById('btnPushDocBee');
if (b) {
b.removeAttribute('disabled');
b.classList.remove('is-busy');
b.style.pointerEvents = 'auto';
}
});
})();
(function injectAuxCss() {
const s = document.createElement('style');
s.textContent = `
.is-busy{opacity:.6;pointer-events:none}
.docbee-hint{margin-top:8px;font-size:14px}
.docbee-hint a{text-decoration:underline}
`;
document.head.appendChild(s);
})();
// ===================== Ende app.js =====================