Viel neues
This commit is contained in:
253
qa-tool/htdocs/js/main.js
Normal file
253
qa-tool/htdocs/js/main.js
Normal file
@@ -0,0 +1,253 @@
|
||||
// ===================== 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 =====================
|
||||
Reference in New Issue
Block a user