ux(quellen-health): Sub-Tabs Vorschläge / Health-Status / Verlauf, Lucide-Icons statt Emojis
Splittet die Quellen-Health-Section in drei eigene Sub-Tabs auf, damit
der User je nach Aufgabe nur den relevanten Bereich sieht und nicht
durch die ganze Seite scrollen muss.
dashboard.html:
- Innerhalb von <div id=sub-source-health>: neue nav-tabs healthSubTabs
mit drei Buttons (Vorschläge / Health-Status / Verlauf).
- Drei Pane-Container ht-suggestions / ht-checks / ht-verlauf,
jeweils per inline-style display kontrolliert.
source-health.js:
- setupHealthSubTabs(): Click-Handler fuer den Tab-Wechsel
(toggle .active auf den Buttons + display none/block auf den Panes).
- renderHealthDashboard splittet jetzt in drei innerHTML-Calls,
einen pro Pane:
paneSuggestions <- Vorschlaege offen
paneChecks <- Counter + Filter + Tabelle + Mehr-laden
paneVerlauf <- erledigte Vorschlaege + Run-Verlauf
- Tab-Label "Vorschlaege" wird mit Counter angereichert (z.B.
"Vorschlaege (24 offen)"), wenn welche offen sind.
- LUCIDE_ICONS-Konstante mit Inline-SVG fuer check, x, search,
refresh. Emojis und HTML-Entities (✓ × ) ersetzt.
Inline-SVG statt CDN-Library, damit keine externe Abhaengigkeit.
Cache-Buster fuer source-health.js auf 20260509i gebumpt.
Dieser Commit ist enthalten in:
@@ -393,9 +393,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quellen-Health (Sub-Tab; Inhalt wird von source-health.js dynamisch in #healthContent gerendert) -->
|
||||
<!-- Quellen-Health (Sub-Tab) - drei Bereiche als Sub-Sub-Tabs;
|
||||
source-health.js rendert pro Bereich in den jeweiligen Container. -->
|
||||
<div class="section" id="sub-source-health">
|
||||
<div id="healthContent"></div>
|
||||
<div class="nav-tabs" id="healthSubTabs" style="margin-top:0;">
|
||||
<button class="nav-tab active" data-healthtab="suggestions">Vorschläge</button>
|
||||
<button class="nav-tab" data-healthtab="checks">Health-Status</button>
|
||||
<button class="nav-tab" data-healthtab="verlauf">Verlauf</button>
|
||||
</div>
|
||||
<div id="ht-suggestions" class="health-pane active"></div>
|
||||
<div id="ht-checks" class="health-pane" style="display:none;"></div>
|
||||
<div id="ht-verlauf" class="health-pane" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /sec-sources -->
|
||||
@@ -715,7 +723,7 @@
|
||||
|
||||
<script src="/static/js/app.js?v=20260509d"></script>
|
||||
<script src="/static/js/sources.js?v=20260509d"></script>
|
||||
<script src="/static/js/source-health.js?v=20260509h"></script>
|
||||
<script src="/static/js/source-health.js?v=20260509i"></script>
|
||||
<script src="/static/js/audit.js?v=20260509d"></script>
|
||||
<div id="toastContainer" class="toast-container" aria-live="polite" aria-atomic="true"></div>
|
||||
</body>
|
||||
|
||||
@@ -39,6 +39,15 @@ const PRIORITY_LABELS = {
|
||||
low: "Niedrig",
|
||||
};
|
||||
|
||||
// Lucide-Icons als Inline-SVG-Konstanten (statt CDN-Abhängigkeit oder Emojis).
|
||||
// 14x14, currentColor erbt vom Button-Style.
|
||||
const LUCIDE_ICONS = {
|
||||
check: '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><polyline points="20 6 9 17 4 12"/></svg>',
|
||||
x: '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
||||
search:'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>',
|
||||
refresh:'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>',
|
||||
};
|
||||
|
||||
// --- Init ---
|
||||
function setupHealthTab() {
|
||||
const tab = document.querySelector('#sourceSubTabs .nav-tab[data-subtab="source-health"]');
|
||||
@@ -47,7 +56,25 @@ function setupHealthTab() {
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", setupHealthTab);
|
||||
// Sub-Sub-Tabs innerhalb von Quellen-Health: Vorschläge / Health-Status / Verlauf.
|
||||
function setupHealthSubTabs() {
|
||||
document.querySelectorAll("#healthSubTabs .nav-tab").forEach((tab) => {
|
||||
tab.addEventListener("click", () => {
|
||||
const which = tab.dataset.healthtab;
|
||||
document.querySelectorAll("#healthSubTabs .nav-tab").forEach(t => t.classList.remove("active"));
|
||||
tab.classList.add("active");
|
||||
["suggestions", "checks", "verlauf"].forEach(name => {
|
||||
const pane = document.getElementById("ht-" + name);
|
||||
if (pane) pane.style.display = name === which ? "block" : "none";
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
setupHealthTab();
|
||||
setupHealthSubTabs();
|
||||
});
|
||||
|
||||
// --- Health-Daten laden ---
|
||||
async function loadHealthData(force = false) {
|
||||
@@ -110,8 +137,11 @@ function setHealthFilter(field, value) {
|
||||
}
|
||||
|
||||
function renderHealthDashboard() {
|
||||
const container = document.getElementById("healthContent");
|
||||
if (!container) return;
|
||||
// Drei Sub-Panes (statt einer monolithischen Health-Section).
|
||||
const paneSuggestions = document.getElementById("ht-suggestions");
|
||||
const paneChecks = document.getElementById("ht-checks");
|
||||
const paneVerlauf = document.getElementById("ht-verlauf");
|
||||
if (!paneSuggestions || !paneChecks || !paneVerlauf) return;
|
||||
|
||||
// Vorschläge rendern
|
||||
const pendingSuggestions = suggestionsCache.filter((s) => s.status === "pending");
|
||||
@@ -147,9 +177,9 @@ function renderHealthDashboard() {
|
||||
<td><span class="badge badge-priority-${s.priority}">${PRIORITY_LABELS[s.priority] || s.priority}</span></td>
|
||||
<td class="text-secondary">${formatDateTime(s.created_at)}</td>
|
||||
<td style="white-space:nowrap;">
|
||||
${s.suggestion_type === "deactivate_source" && s.source_id ? `<button class="btn btn-secondary btn-small" data-source-id="${s.source_id}" data-source-name="${esc(s.title.split(':')[0] || s.title)}" onclick="searchFix(this)" title="Lösung suchen">🔍</button> ` : ""}
|
||||
<button class="btn btn-success btn-small" onclick="handleSuggestion(${s.id}, true)" title="Annehmen">✓</button>
|
||||
<button class="btn btn-danger btn-small" onclick="handleSuggestion(${s.id}, false)" title="Ablehnen">×</button>
|
||||
${s.suggestion_type === "deactivate_source" && s.source_id ? `<button class="btn btn-secondary btn-small" data-source-id="${s.source_id}" data-source-name="${esc(s.title.split(':')[0] || s.title)}" onclick="searchFix(this)" title="Lösung suchen">${LUCIDE_ICONS.search}</button> ` : ""}
|
||||
<button class="btn btn-success btn-small" onclick="handleSuggestion(${s.id}, true)" title="Annehmen">${LUCIDE_ICONS.check}</button>
|
||||
<button class="btn btn-danger btn-small" onclick="handleSuggestion(${s.id}, false)" title="Ablehnen">${LUCIDE_ICONS.x}</button>
|
||||
</td>
|
||||
</tr>`,
|
||||
)
|
||||
@@ -289,7 +319,7 @@ function renderHealthDashboard() {
|
||||
<td class="text-secondary">${c.tenant_id == null ? '<span style="color:#94a3b8;">global</span>' : esc(c.org_name || ("Org " + c.tenant_id))}</td>
|
||||
<td><span class="badge badge-health-${c.status}">${c.status === "error" ? "Fehler" : (c.status === "warning" ? "Warnung" : "OK")}</span></td>
|
||||
<td class="text-secondary" style="max-width:300px;" title="${esc(c.message || "")}">${esc(c.message || "")}</td>
|
||||
<td>${c.status === "error" && c.check_type === "reachability" ? `<button class="btn btn-secondary btn-small" data-source-id="${c.source_id}" data-source-name="${esc(c.name)}" onclick="searchFix(this)" title="Lösung suchen">🔍</button>` : ""}</td>
|
||||
<td>${c.status === "error" && c.check_type === "reachability" ? `<button class="btn btn-secondary btn-small" data-source-id="${c.source_id}" data-source-name="${esc(c.name)}" onclick="searchFix(this)" title="Lösung suchen">${LUCIDE_ICONS.search}</button>` : ""}</td>
|
||||
</tr>`;
|
||||
}
|
||||
)
|
||||
@@ -356,7 +386,17 @@ function renderHealthDashboard() {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
container.innerHTML = suggestionsHtml + historyHtml + healthHtml + runsHtml;
|
||||
// Statt einer monolithischen Render: drei Sub-Panes, einer pro Sub-Tab.
|
||||
paneSuggestions.innerHTML = suggestionsHtml;
|
||||
paneChecks.innerHTML = healthHtml;
|
||||
paneVerlauf.innerHTML = historyHtml + runsHtml;
|
||||
|
||||
// Tab-Label "Vorschläge" mit Counter der offenen Vorschläge anreichern.
|
||||
const tabBtnSugg = document.querySelector('#healthSubTabs .nav-tab[data-healthtab="suggestions"]');
|
||||
if (tabBtnSugg) {
|
||||
const open = pendingSuggestions.length;
|
||||
tabBtnSugg.textContent = open > 0 ? `Vorschläge (${open} offen)` : "Vorschläge";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Vorschlag annehmen/ablehnen ---
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren