fix(webui): Bugtracker-Tabellen nutzen die Bildschirmbreite und stehen still

Vier Ursachen fuer ein Bild, das gestaucht und verschoben aussah:

1. .main-content war bei 1600px gedeckelt. Auf einem 2000px-Schirm blieb
   rechts ein leerer Streifen, waehrend acht Spalten sich links draengten.
   Jetzt 2400px - ganz ohne Grenze wandert das Auge bei 3400px quer ueber
   den Tisch.

2. Ohne table-layout:fixed verteilte der Browser die Breite nach Inhalt. Eine
   lange Exception-Meldung oder ein Pfad wie
   "public/api/bugtracker/v1/manage/index.php:407" zog die Titelspalte auf
   und quetschte den Rest - je Zeile anders weit, weshalb die Spalten von
   oben nach unten wanderten. Das war das "Verschobene".

   Die schmalen Spalten haben jetzt feste Breiten, "Projekt & Titel" bekommt
   bewusst keine und damit den Rest: Sie waechst mit dem Bildschirm, gemessen
   1079px bei 2000px Viewport.

3. Die Aktionsknoepfe brachen um, weil die Zelle enger wurde als ihr Inhalt -
   Stift oben, Lupe und Haken darunter. Feste Spaltenbreite plus nowrap.

4. min-width:0 auf .main-content. Der unscheinbare, aber wichtigere Teil:
   body ist ein Flex-Container, .main-content also ein Flex-Element mit
   min-width:auto - es weigerte sich, unter seine Inhaltsbreite zu
   schrumpfen. Eine breite Tabelle schob deshalb die ganze SEITE quer, statt
   im eigenen Behaelter zu scrollen, und beim Scrollen wanderte die
   Seitenleiste aus dem Bild.

Der Titel wird per CSS nach zwei Zeilen gekuerzt statt per mb_strimwidth nach
90 Zeichen - eine feste Zeichenzahl ist auf einem breiten Schirm zu wenig und
auf einem schmalen zu viel. Der vollstaendige Text haengt im title=.

Nachgemessen im Browser gegen das echte Stylesheet, bei 1250px und 2000px:
Seite laeuft nicht mehr quer, die Tabelle bekommt bei Bedarf ihre eigene
Leiste, Knoepfe bleiben einreihig, 94% Breitenausnutzung.

Gilt fuer die Item-Liste und den Fehlerstrom - dieselbe Tabelle, dieselben
Ursachen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Deploymentcenter Bot
2026-08-30 14:24:38 +02:00
co-authored by Claude Opus 5
parent 57aa0bc5be
commit 7fff13ee20
+112 -27
View File
@@ -2249,7 +2249,30 @@ $csrfField = Csrf::field();
.btn-autorefresh { background: rgba(255,255,255,0.04); border: 1px solid var(--border-glass); color: var(--text-main); font-size: 0.75rem; font-weight: 700; padding: 0.5rem 0.85rem; border-radius: 8px; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s; font-family: 'Roboto Mono', monospace; } .btn-autorefresh { background: rgba(255,255,255,0.04); border: 1px solid var(--border-glass); color: var(--text-main); font-size: 0.75rem; font-weight: 700; padding: 0.5rem 0.85rem; border-radius: 8px; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s; font-family: 'Roboto Mono', monospace; }
.btn-autorefresh.active { background: rgba(18,212,138,0.15); border-color: var(--success); color: var(--success); } .btn-autorefresh.active { background: rgba(18,212,138,0.15); border-color: var(--success); color: var(--success); }
.main-content { padding: 1.75rem 2rem; max-width: 1600px; } /*
Die Obergrenze lag bei 1600px. Auf einem breiten Schirm blieb rechts
ein leerer Streifen, waehrend die Tabellen links zusammengedrueckt
standen - besonders im Bugtracker, wo eine Zeile acht Spalten hat.
Ganz ohne Grenze wird es allerdings unlesbar: Bei 3400px wandert das
Auge von der Item-Nummer bis zur Statusspalte quer ueber den Tisch.
2400px ist der Kompromiss - fuellt jeden ueblichen Schirm und
bremst erst darueber.
*/
/*
min-width:0 ist der unscheinbare Teil und der wichtigere.
body ist ein Flex-Container (Seitenleiste + Inhalt), .main-content
also ein Flex-Element - und die haben von Haus aus min-width:auto,
weigern sich also, unter ihre Inhaltsbreite zu schrumpfen. Folge:
Eine breite Tabelle schob den ganzen Inhaltsbereich auf, statt in
ihrem eigenen Behaelter zu scrollen, und die SEITE lief quer. Damit
wandert beim Scrollen auch die Seitenleiste aus dem Bild.
Mit min-width:0 gibt .main-content nach, und .tabelle-scroll tut,
wofuer es da ist.
*/
.main-content { padding: 1.75rem 2rem; max-width: 2400px; min-width: 0; }
/* Stats & KPI Grid */ /* Stats & KPI Grid */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.25rem; margin-bottom: 1.75rem; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.25rem; margin-bottom: 1.75rem; }
@@ -2350,6 +2373,49 @@ $csrfField = Csrf::field();
*/ */
.hilfe { cursor: help; border-bottom: 1px dotted rgba(255,255,255,0.28); } .hilfe { cursor: help; border-bottom: 1px dotted rgba(255,255,255,0.28); }
/* ---------------------------------------------------------------
Breite Datentabellen (Bugtracker, Fehlerstrom)
Ohne table-layout:fixed verteilt der Browser die Breite nach
Inhalt. Eine einzige lange Exception-Meldung oder ein Dateipfad
wie "public/api/bugtracker/v1/manage/index.php:407" zog damit die
Titelspalte auf und quetschte alle anderen zusammen - je Zeile
anders, weshalb die Spalten von oben nach unten wanderten.
Mit fester Verteilung stehen die schmalen Spalten still, und die
Titelspalte bekommt, was uebrig ist. Sie muss dafuer kuerzen
koennen - siehe .zelle-titel.
--------------------------------------------------------------- */
.tabelle-scroll { overflow-x: auto; }
table.tabelle-fest { table-layout: fixed; min-width: 1180px; }
table.tabelle-fest td { overflow-wrap: anywhere; }
/*
Der Titel wird nach zwei Zeilen abgeschnitten, nicht nach einer:
Eine Exception-Meldung ist in der ersten Zeile oft nur Namensraum
und sagt noch nichts. Der vollstaendige Text haengt im title=.
*/
.zelle-titel .titel {
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; color: #fff; font-weight: 700;
}
.zelle-titel .meta {
font-size: 0.75rem; color: var(--text-muted); margin-top: 0.25rem;
display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center;
overflow: hidden;
}
/* Ein langer Dateipfad darf die Zeile nicht in die Hoehe treiben. */
.zelle-titel .meta .badge { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/*
Die Knoepfe brachen um, weil die Zelle enger wurde als ihr Inhalt -
daher das Verschobene im Screenshot: Stift oben, Lupe und Haken in
der Zeile darunter. Sie bleiben jetzt in einer Reihe, und die Spalte
ist breit genug dafuer.
*/
.zelle-aktionen { white-space: nowrap; }
.zelle-aktionen > div { display: flex; gap: 0.3rem; flex-wrap: nowrap; }
.tab-content, .subtab-content { display: none; } .tab-content, .subtab-content { display: none; }
.tab-content.active, .subtab-content.active { display: block; } .tab-content.active, .subtab-content.active { display: block; }
</style> </style>
@@ -4667,17 +4733,26 @@ chmod +x update-agent
</form> </form>
</div> </div>
<table> <!--
Die Breiten stehen hier und nicht im Stylesheet, weil
sie zu dieser Tabelle gehoeren und zu keiner anderen.
"Projekt & Titel" bekommt bewusst keine: Bei
table-layout:fixed nimmt die Spalte ohne Angabe den
Rest - sie waechst also mit dem Bildschirm, waehrend
die uebrigen stehen bleiben.
-->
<div class="tabelle-scroll">
<table class="tabelle-fest">
<thead> <thead>
<tr> <tr>
<th>ID / Typ</th> <th style="width:92px;">ID / Typ</th>
<th>Umgebung</th> <th style="width:96px;">Umgebung</th>
<th>Projekt &amp; Titel</th> <th>Projekt &amp; Titel</th>
<th>Build</th> <th style="width:104px;">Build</th>
<th>Schweregrad</th> <th style="width:124px;">Schweregrad</th>
<th>Anzahl</th> <th style="width:82px;">Anzahl</th>
<th>Status</th> <th style="width:160px;">Status</th>
<th>Aktionen</th> <th style="width:150px;">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody id="btTableBody"> <tbody id="btTableBody">
@@ -4735,9 +4810,9 @@ chmod +x update-agent
<tr class="bt-row"> <tr class="bt-row">
<td>#<?= (int)$item['id'] ?><br><?= $typeBadge ?></td> <td>#<?= (int)$item['id'] ?><br><?= $typeBadge ?></td>
<td><?= $envBadge ?></td> <td><?= $envBadge ?></td>
<td> <td class="zelle-titel">
<strong style="color:#fff;"><?= e((string)$item['title']) ?></strong> <div class="titel" title="<?= e((string)$item['title']) ?>"><?= e((string)$item['title']) ?></div>
<div style="font-size:0.75rem; color:var(--text-muted); margin-top:0.2rem; display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;"> <div class="meta">
<span>Projekt: <code><?= e((string)$item['project_slug']) ?></code></span> <span>Projekt: <code><?= e((string)$item['project_slug']) ?></code></span>
<span>Von: <?= e((string)$item['created_by']) ?></span> <span>Von: <?= e((string)$item['created_by']) ?></span>
<span title="Zuletzt gesehen"><?= e(dc_ago($item['last_seen_at'] ?? null)) ?></span> <span title="Zuletzt gesehen"><?= e(dc_ago($item['last_seen_at'] ?? null)) ?></span>
@@ -4770,9 +4845,9 @@ chmod +x update-agent
</span> </span>
</td> </td>
<td><?= $statusBadge ?></td> <td><?= $statusBadge ?></td>
<td> <td class="zelle-aktionen">
<div style="display:flex; gap:0.3rem; flex-wrap:wrap;"> <div>
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditBugtrackerModal(<?= $jsonItem ?>)">✏️</button> <button type="button" class="btn btn-sm btn-secondary" title="Bearbeiten" onclick="openEditBugtrackerModal(<?= $jsonItem ?>)">✏️</button>
<button type="button" class="btn btn-sm btn-secondary" onclick="openBugtrackerModal(<?= (int)$item['id'] ?>)">🔍</button> <button type="button" class="btn btn-sm btn-secondary" onclick="openBugtrackerModal(<?= (int)$item['id'] ?>)">🔍</button>
<?php if (!in_array((string)$item['status'], ['resolved', 'closed'], true)): ?> <?php if (!in_array((string)$item['status'], ['resolved', 'closed'], true)): ?>
<button type="button" class="btn btn-sm" onclick="openResolveModal(<?= (int)$item['id'] ?>, <?= $jsTitle ?>)">✔</button> <button type="button" class="btn btn-sm" onclick="openResolveModal(<?= (int)$item['id'] ?>, <?= $jsTitle ?>)">✔</button>
@@ -4792,6 +4867,7 @@ chmod +x update-agent
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div>
<?php if ($bugtrackerPages > 1): ?> <?php if ($bugtrackerPages > 1): ?>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:1rem; padding-top:1rem; border-top:1px solid var(--border-soft);"> <div style="display:flex; justify-content:space-between; align-items:center; margin-top:1rem; padding-top:1rem; border-top:1px solid var(--border-soft);">
@@ -5003,16 +5079,20 @@ chmod +x update-agent
</form> </form>
</div> </div>
<table> <!-- Dieselbe feste Verteilung wie im Bugtracker: Ohne sie zieht
eine lange Exception-Meldung die Fehlerspalte auf und quetscht
den Rest zusammen, je Zeile unterschiedlich weit. -->
<div class="tabelle-scroll">
<table class="tabelle-fest">
<thead> <thead>
<tr> <tr>
<th>Klasse</th> <th style="width:110px;">Klasse</th>
<th>Fehler</th> <th>Fehler</th>
<th>Projekt / Umgebung</th> <th style="width:180px;">Projekt / Umgebung</th>
<th>Build</th> <th style="width:104px;">Build</th>
<th>Anzahl</th> <th style="width:82px;">Anzahl</th>
<th>Zuletzt</th> <th style="width:120px;">Zuletzt</th>
<th>Aktionen</th> <th style="width:150px;">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -5045,9 +5125,13 @@ chmod +x update-agent
?> ?>
<tr style="<?= $isIgnored ? 'opacity:0.55;' : '' ?>"> <tr style="<?= $isIgnored ? 'opacity:0.55;' : '' ?>">
<td><?= $levelBadge ?></td> <td><?= $levelBadge ?></td>
<td> <td class="zelle-titel">
<strong style="color:#fff;"><?= e(mb_strimwidth((string)$item['title'], 0, 90, '…')) ?></strong> <?php // Kein mb_strimwidth mehr - die Kuerzung macht jetzt CSS,
<div style="font-size:0.72rem; color:var(--text-muted); margin-top:0.2rem; display:flex; flex-wrap:wrap; gap:0.4rem; align-items:center;"> // und zwar passend zur tatsaechlichen Spaltenbreite. 90 Zeichen
// waren auf einem breiten Schirm zu wenig und auf einem
// schmalen zu viel. ?>
<div class="titel" title="<?= e((string)$item['title']) ?>"><?= e((string)$item['title']) ?></div>
<div class="meta">
<?php if ($isIgnored): ?> <?php if ($isIgnored): ?>
<span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.68rem;"> <span class="badge" style="background:rgba(255,255,255,0.06); font-size:0.68rem;">
🔇 stummgeschaltet<?= !empty($item['ignore_rule_id']) ? ' (Regel #' . (int)$item['ignore_rule_id'] . ')' : '' ?> 🔇 stummgeschaltet<?= !empty($item['ignore_rule_id']) ? ' (Regel #' . (int)$item['ignore_rule_id'] . ')' : '' ?>
@@ -5074,8 +5158,8 @@ chmod +x update-agent
</span> </span>
</td> </td>
<td style="font-size:0.75rem; color:var(--text-muted);"><?= e(dc_ago($item['last_seen_at'] ?? null)) ?></td> <td style="font-size:0.75rem; color:var(--text-muted);"><?= e(dc_ago($item['last_seen_at'] ?? null)) ?></td>
<td> <td class="zelle-aktionen">
<div style="display:flex; gap:0.3rem; flex-wrap:wrap;"> <div>
<button type="button" class="btn btn-sm btn-secondary" <button type="button" class="btn btn-sm btn-secondary"
onclick="openBugtrackerModal(<?= (int)$item['id'] ?>)" title="Details">🔍</button> onclick="openBugtrackerModal(<?= (int)$item['id'] ?>)" title="Details">🔍</button>
@@ -5095,6 +5179,7 @@ chmod +x update-agent
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div>
<?php if ($errorPages > 1): ?> <?php if ($errorPages > 1): ?>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:1rem; padding-top:1rem; border-top:1px solid var(--border-soft);"> <div style="display:flex; justify-content:space-between; align-items:center; margin-top:1rem; padding-top:1rem; border-top:1px solid var(--border-soft);">