fix(2.7.1): Auskuenfte, die stimmen - Fassung, Health, doctor, Ansicht
Der erste Durchlauf der Testinstanz hat neun Bugs zutage gefoerdert, und fast
alle waren dieselbe Sorte Fehler: eine Auskunft, die nicht stimmte.
Am folgenreichsten die Fassung. config/config.php fuehrte app.version als feste
Zeichenkette und stand seit 2.0.0 darauf, waehrend 2.7.0 lief - unbemerkt, weil
die Datei gitignored ist und in keinem Diff auftaucht. Zwei gemeldete Bugs sind
daran fehldiagnostiziert worden ("der Server ist zu alt"). src/bootstrap.php
leitet die Fassung jetzt aus "current" in public/docs/changelog.json ab.
Im Einzelnen:
- #346 Fassung aus dem Changelog statt aus der Konfiguration
- #60 /api/health ueberlebt eine leere Datenbank: eigener Check "schema",
healthy=false mit Begruendung statt HTTP 500
- #54 dc-testkit doctor prueft Mindestfassung (semantisch) und Rechte, statt
sie nur anzuzeigen - dafuer neu: GET /api/tokens/v1/whoami
- #53 dc-testkit.sh unter /testkit/ erreichbar (Route fehlte, der
dokumentierte Bezugsweg hat nie funktioniert) und mit LF ausgeliefert
(deploy.py/deploy_dev.py entfernen CR bei .sh/.bash beim Upload)
- #288 Filter und Formulare werfen nicht mehr aufs Dashboard zurueck: die
offene Ansicht wandert bei GET als "view" in den Query-String, und die
Filter loesen ueber requestSubmit() aus - form.submit() erzeugt kein
submit-Ereignis, weshalb der zentrale Zuhoerer dort nie lief
- #347 /api/bugtracker/v1/roadmap auch ohne .php erreichbar
- #55 Fassungshinweis in TESTUMGEBUNG.md 4.6 (serverseitig war das mit 2.6.0
bereits behoben)
Nicht geprueft: php -l. Auf dem Arbeitsplatz ist kein PHP installiert; die
Abnahme laeuft ueber die Testinstanz.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
afcbb3c091
commit
3b56839a1e
+85
-35
@@ -4018,7 +4018,7 @@ chmod +x update-agent
|
||||
|
||||
<div class="card" style="padding:0.85rem 1.1rem;">
|
||||
<form method="GET" action="index.php" style="display:flex; flex-wrap:wrap; gap:0.45rem; align-items:center;">
|
||||
<select name="rm_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="rm_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<option value="all">🌐 Alle Projekte</option>
|
||||
<?php foreach ($projects as $p): ?>
|
||||
<option value="<?= e($p['slug']) ?>" <?= $rmProject === $p['slug'] ? 'selected' : '' ?>>
|
||||
@@ -4027,7 +4027,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="rm_lage" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="rm_lage" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
// Voreinstellung ist "aktiv" und nicht "alle": Eine Roadmap,
|
||||
// die mit sechzig erledigten Eintraegen beginnt, beantwortet
|
||||
@@ -4046,7 +4046,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="rm_epic" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="rm_epic" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<option value="all">🗂️ Alle Bereiche</option>
|
||||
<?php foreach ($roadmapEpics as $rmE): ?>
|
||||
<option value="<?= e((string)$rmE['epic']) ?>" <?= $rmEpic === (string)$rmE['epic'] ? 'selected' : '' ?>>
|
||||
@@ -4055,7 +4055,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="rm_build" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="rm_build" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<option value="all">🎯 Alle Zielfassungen</option>
|
||||
<?php foreach ($roadmapBuilds as $rmB): ?>
|
||||
<option value="<?= e((string)$rmB) ?>" <?= $rmBuild === (string)$rmB ? 'selected' : '' ?>><?= e((string)$rmB) ?></option>
|
||||
@@ -4641,7 +4641,7 @@ chmod +x update-agent
|
||||
und ging bei jedem Auto-Refresh verloren.
|
||||
-->
|
||||
<form method="GET" action="index.php" style="display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;">
|
||||
<select name="bt_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<option value="all">🌐 Alle Projekte</option>
|
||||
<?php foreach ($projects as $p): ?>
|
||||
<option value="<?= e($p['slug']) ?>" <?= $btFilters['project_slug'] === $p['slug'] ? 'selected' : '' ?>>
|
||||
@@ -4650,7 +4650,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_env" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_env" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$envOptions = [
|
||||
'all' => '🌍 Alle Umgebungen',
|
||||
@@ -4664,7 +4664,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_type" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_type" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$typeOptions = [
|
||||
'all' => '📂 Alle Typen',
|
||||
@@ -4676,7 +4676,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_severity" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_severity" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$sevOptions = [
|
||||
'all' => '🔥 Alle Schweregrade',
|
||||
@@ -4692,7 +4692,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_status" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_status" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$statusOptions = [
|
||||
'all' => '📌 Alle Status',
|
||||
@@ -4720,7 +4720,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="bt_order" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="bt_order" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$orderOptions = [
|
||||
'newest' => '🕒 Neueste zuerst',
|
||||
@@ -5026,7 +5026,7 @@ chmod +x update-agent
|
||||
</h2>
|
||||
|
||||
<form method="GET" action="index.php" style="display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;">
|
||||
<select name="err_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="err_project" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<option value="all">🌐 Alle Projekte</option>
|
||||
<?php foreach ($projects as $p): ?>
|
||||
<option value="<?= e($p['slug']) ?>" <?= $errFilters['project_slug'] === $p['slug'] ? 'selected' : '' ?>>
|
||||
@@ -5035,7 +5035,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="err_level" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="err_level" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$levelOptions = [
|
||||
'all' => '🎚️ Alle Klassen',
|
||||
@@ -5048,7 +5048,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="err_env" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="err_env" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$errEnvOptions = [
|
||||
'all' => '🌍 Alle Umgebungen',
|
||||
@@ -5062,7 +5062,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="err_show" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="err_show" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$showOptions = [
|
||||
'active' => '🔔 Nur relevante',
|
||||
@@ -5074,7 +5074,7 @@ chmod +x update-agent
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<select name="err_hours" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="this.form.submit()">
|
||||
<select name="err_hours" class="form-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;" onchange="dcSubmit(this.form)">
|
||||
<?php
|
||||
$hourOptions = [
|
||||
'0' => '🕒 Gesamter Zeitraum',
|
||||
@@ -6246,42 +6246,81 @@ SYSTEM
|
||||
}
|
||||
|
||||
/**
|
||||
* Merkt sich beim Absenden, welche Unteransicht offen war.
|
||||
* Ansichtsrettung: Merkt sich beim Absenden, welche Unteransicht offen
|
||||
* war, und stellt sie nach dem Neuladen wieder her.
|
||||
*
|
||||
* Der Server haengt diesen Wert nach dem Speichern wieder an die URL.
|
||||
* Ohne das landete man stets auf dem Dashboard des Moduls - man legte
|
||||
* einen Monitor an und wurde aus dem Formular geworfen.
|
||||
* Ohne das landete man stets auf dem Dashboard - man legte einen
|
||||
* Monitor an und wurde aus dem Formular geworfen, und jede
|
||||
* Filteraenderung im Bugtracker warf einen dorthin zurueck.
|
||||
*
|
||||
* Als zentraler Zuhoerer umgesetzt, damit auch kuenftige Formulare das
|
||||
* Verhalten automatisch erben.
|
||||
* Verhalten automatisch erben. Die drei Funktionen darunter gehoeren
|
||||
* zusammen: dcCurrentView() liest die Ansicht ab, dcRememberView()
|
||||
* haengt sie an das Formular, dcSubmit() sorgt dafuer, dass beim
|
||||
* Absenden aus einem onchange ueberhaupt ein submit-Ereignis entsteht.
|
||||
*/
|
||||
document.addEventListener('submit', (event) => {
|
||||
const form = event.target;
|
||||
if (!(form instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
if ((form.method || '').toLowerCase() !== 'post') {
|
||||
return;
|
||||
}
|
||||
if (form.querySelector('input[name="_return"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function dcCurrentView() {
|
||||
const activeSub = document.querySelector('.subtab-content.active');
|
||||
const activeTab = document.querySelector('.main-content > .tab-content.active');
|
||||
const target = activeSub ? activeSub.id : (activeTab ? activeTab.id : '');
|
||||
return activeSub ? activeSub.id : (activeTab ? activeTab.id : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Haengt die gerade offene Ansicht an ein Formular.
|
||||
*
|
||||
* POST: als _return - dc_redirect() macht daraus den Anker der
|
||||
* Weiterleitung.
|
||||
* GET: als view - ein Anker ueberlebt die Serialisierung eines
|
||||
* GET-Formulars nicht, der Query-String schon. Genau daran
|
||||
* scheiterten die Filter: nach jeder Aenderung stand die Seite
|
||||
* wieder auf dem Dashboard, weil die neue URL keinen Anker
|
||||
* mehr trug.
|
||||
*/
|
||||
function dcRememberView(form) {
|
||||
const isPost = (form.method || 'get').toLowerCase() === 'post';
|
||||
const name = isPost ? '_return' : 'view';
|
||||
|
||||
if (form.querySelector('input[name="' + name + '"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = dcCurrentView();
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = document.createElement('input');
|
||||
field.type = 'hidden';
|
||||
field.name = '_return';
|
||||
field.name = name;
|
||||
field.value = target;
|
||||
form.appendChild(field);
|
||||
}
|
||||
|
||||
document.addEventListener('submit', (event) => {
|
||||
const form = event.target;
|
||||
if (!(form instanceof HTMLFormElement)) {
|
||||
return;
|
||||
}
|
||||
dcRememberView(form);
|
||||
}, true);
|
||||
|
||||
/**
|
||||
* Absenden aus einem onchange heraus.
|
||||
*
|
||||
* form.submit() loest KEIN submit-Ereignis aus - der Zuhoerer oben
|
||||
* lief deshalb bei den Filtern nie, obwohl es ihn gab.
|
||||
* requestSubmit() tut es. Wo es fehlt, wird die Ansicht vorher von
|
||||
* Hand angehaengt.
|
||||
*/
|
||||
function dcSubmit(form) {
|
||||
if (typeof form.requestSubmit === 'function') {
|
||||
form.requestSubmit();
|
||||
return;
|
||||
}
|
||||
dcRememberView(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// Sidebar Toggle Handler
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
@@ -6926,7 +6965,18 @@ SYSTEM
|
||||
* Modulnamen. Der Rueckweg landete deshalb immer auf dem Dashboard.
|
||||
*/
|
||||
function restoreViewFromHash() {
|
||||
const hash = location.hash.replace('#', '');
|
||||
let hash = location.hash.replace('#', '');
|
||||
|
||||
// Ohne Anker: der Wert aus dem Query-String, den dcRememberView()
|
||||
// an GET-Formulare haengt. Nur eigene Ansichtskennungen zulassen -
|
||||
// der Wert steuert, was angezeigt wird.
|
||||
if (!hash) {
|
||||
const fromQuery = new URLSearchParams(location.search).get('view');
|
||||
if (fromQuery && /^(?:tab|sub)-[a-z0-9-]{1,60}$/.test(fromQuery)) {
|
||||
hash = fromQuery;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user