Promote develop → main (2026-07-25 22:22 UTC) #16

Zusammengeführt
IntelSight_Admin hat 41 Commits von develop nach main 2026-07-26 00:22:10 +02:00 zusammengeführt
4 geänderte Dateien mit 109 neuen und 9 gelöschten Zeilen
Nur Änderungen aus Commit c35a30a823 werden angezeigt - Alle Commits anzeigen

Datei anzeigen

@@ -958,6 +958,65 @@ input[type="date"].filter-select { padding: 6px 10px; }
.select-col { display: none; } .select-col { display: none; }
table.show-select .select-col { display: table-cell; } table.show-select .select-col { display: table-cell; }
/* === Quellenliste: leichteres Seitwaerts-Scrollen === */
/* Name links und Aktionen rechts bleiben beim Scrollen angepinnt.
Zeilen sind transparent, angepinnte Zellen brauchen deshalb solide
Hintergruende, sonst scheint der Inhalt darunter durch. */
#unifiedTable th.sticky-name,
#unifiedTable td.sticky-name {
position: sticky;
left: 0;
z-index: 2;
background: var(--bg-secondary);
max-width: 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.45);
}
#unifiedTable th.sticky-actions,
#unifiedTable td.sticky-actions {
position: sticky;
right: 0;
z-index: 2;
background: var(--bg-secondary);
white-space: nowrap;
box-shadow: -6px 0 8px -6px rgba(0, 0, 0, 0.45);
}
#unifiedTable.show-select th.select-col,
#unifiedTable.show-select td.select-col {
position: sticky;
left: 0;
z-index: 2;
width: 40px;
background: var(--bg-secondary);
}
#unifiedTable.show-select th.sticky-name,
#unifiedTable.show-select td.sticky-name { left: 40px; }
#unifiedTable tr:hover td.sticky-name,
#unifiedTable tr:hover td.sticky-actions,
#unifiedTable.show-select tr:hover td.select-col {
background: var(--bg-tertiary);
}
/* Kategorie-Zeile und Ausklapp-Inhalte bleiben im sichtbaren Bereich */
.cat-header-sticky {
position: sticky;
left: 12px;
display: inline-flex;
align-items: center;
}
#unifiedTable .src-notes-cell > div {
position: sticky;
left: 16px;
max-width: 1080px;
}
/* Ziehen-zum-Scrollen */
.drag-scroll { cursor: grab; }
.drag-scroll.drag-scrolling {
cursor: grabbing;
user-select: none;
}
/* Health-Details in der Ausklapp-Zeile */ /* Health-Details in der Ausklapp-Zeile */
.src-notes-text { margin-bottom: 8px; } .src-notes-text { margin-bottom: 8px; }
.health-detail-head { .health-detail-head {

Datei anzeigen

@@ -6,7 +6,7 @@
<title>AegisSight Monitor-Verwaltung</title> <title>AegisSight Monitor-Verwaltung</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="apple-touch-icon" href="/static/favicon.svg"> <link rel="apple-touch-icon" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/css/style.css?v=20260725m"> <link rel="stylesheet" href="/static/css/style.css?v=20260725n">
<script>(function(){var t=localStorage.getItem('portal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light');try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script> <script>(function(){var t=localStorage.getItem('portal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light');try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script>
<style> <style>
@@ -305,12 +305,12 @@
</div> </div>
<div class="filter-bar" id="unifiedFilterBar"></div> <div class="filter-bar" id="unifiedFilterBar"></div>
<div class="card"> <div class="card">
<div class="table-wrap"> <div class="table-wrap drag-scroll" id="unifiedTableWrap">
<table id="unifiedTable"> <table id="unifiedTable">
<thead> <thead>
<tr> <tr>
<th class="select-col"><input type="checkbox" id="unifiedSelectAll" onchange="toggleTenantSelectAll(this.checked)"></th> <th class="select-col"><input type="checkbox" id="unifiedSelectAll" onchange="toggleTenantSelectAll(this.checked)"></th>
<th class="sortable" data-sort="name" onclick="sortUnifiedSources('name')">Name <span class="sort-icon"></span></th> <th class="sortable sticky-name" data-sort="name" onclick="sortUnifiedSources('name')">Name <span class="sort-icon"></span></th>
<th>URL</th> <th>URL</th>
<th class="sortable" data-sort="domain" onclick="sortUnifiedSources('domain')">Domain <span class="sort-icon"></span></th> <th class="sortable" data-sort="domain" onclick="sortUnifiedSources('domain')">Domain <span class="sort-icon"></span></th>
<th class="sortable" data-sort="source_type" onclick="sortUnifiedSources('source_type')">Typ <span class="sort-icon"></span></th> <th class="sortable" data-sort="source_type" onclick="sortUnifiedSources('source_type')">Typ <span class="sort-icon"></span></th>
@@ -322,7 +322,7 @@
<th class="sortable" data-sort="health_status" onclick="sortUnifiedSources('health_status')">Health <span class="sort-icon"></span></th> <th class="sortable" data-sort="health_status" onclick="sortUnifiedSources('health_status')">Health <span class="sort-icon"></span></th>
<th class="sortable" data-sort="status" onclick="sortUnifiedSources('status')">Status <span class="sort-icon"></span></th> <th class="sortable" data-sort="status" onclick="sortUnifiedSources('status')">Status <span class="sort-icon"></span></th>
<th class="sortable" data-sort="org_name" onclick="sortUnifiedSources('org_name')">Organisation <span class="sort-icon"></span></th> <th class="sortable" data-sort="org_name" onclick="sortUnifiedSources('org_name')">Organisation <span class="sort-icon"></span></th>
<th>Aktionen</th> <th class="sticky-actions">Aktionen</th>
</tr> </tr>
</thead> </thead>
<tbody id="globalSourceTable"></tbody> <tbody id="globalSourceTable"></tbody>
@@ -987,8 +987,8 @@
</div> </div>
<script src="/static/js/a11y.js?v=20260725a"></script> <script src="/static/js/a11y.js?v=20260725a"></script>
<script src="/static/js/app.js?v=20260725v"></script> <script src="/static/js/app.js?v=20260725w"></script>
<script src="/static/js/sources.js?v=20260725f"></script> <script src="/static/js/sources.js?v=20260725g"></script>
<script src="/static/js/aufgaben.js?v=20260725d"></script> <script src="/static/js/aufgaben.js?v=20260725d"></script>
<script src="/static/js/x-scraper.js?v=20260725c"></script> <script src="/static/js/x-scraper.js?v=20260725c"></script>
<script src="/static/js/audit.js?v=20260725a"></script> <script src="/static/js/audit.js?v=20260725a"></script>

Datei anzeigen

@@ -1458,6 +1458,44 @@ function closeAllFilterDropdowns() {
// Klick irgendwo anders schließt offene Filter-Dropdowns // Klick irgendwo anders schließt offene Filter-Dropdowns
document.addEventListener("click", () => closeAllFilterDropdowns()); document.addEventListener("click", () => closeAllFilterDropdowns());
// Ziehen-zum-Scrollen für breite Tabellen: Fläche greifen und seitwärts
// ziehen. Interaktive Elemente (Knöpfe, Checkboxen, Links) bleiben normal
// bedienbar, nach einem echten Zieh-Vorgang wird der Klick geschluckt.
function enableDragScroll(el) {
if (!el || el.dataset.dragScroll === "1") return;
el.dataset.dragScroll = "1";
let startX = 0, startLeft = 0, dragging = false, moved = false;
el.addEventListener("mousedown", (e) => {
if (e.button !== 0) return;
if (e.target.closest("button, a, input, select, textarea, label, .src-info-toggle, .health-badge-btn")) return;
dragging = true;
moved = false;
startX = e.pageX;
startLeft = el.scrollLeft;
});
document.addEventListener("mousemove", (e) => {
if (!dragging) return;
const dx = e.pageX - startX;
if (Math.abs(dx) > 3) {
moved = true;
el.classList.add("drag-scrolling");
}
el.scrollLeft = startLeft - dx;
});
document.addEventListener("mouseup", () => {
if (!dragging) return;
dragging = false;
el.classList.remove("drag-scrolling");
});
el.addEventListener("click", (e) => {
if (moved) {
e.stopPropagation();
e.preventDefault();
moved = false;
}
}, true);
}
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// Beim Page-Load Meta einmalig laden (asynchron, blockiert nicht) // Beim Page-Load Meta einmalig laden (asynchron, blockiert nicht)
if (window.API && (localStorage.getItem("token") || window.location.pathname === "/dashboard")) { if (window.API && (localStorage.getItem("token") || window.location.pathname === "/dashboard")) {

Datei anzeigen

@@ -57,6 +57,9 @@ function fillSourceModalSelects() {
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
setupSourceSubTabs(); setupSourceSubTabs();
setupSourceForms(); setupSourceForms();
if (typeof enableDragScroll === "function") {
enableDragScroll(document.getElementById("unifiedTableWrap"));
}
// Beim Tab-Wechsel auf "Quellen" laden (+ Aufgaben-Badge aktualisieren) // Beim Tab-Wechsel auf "Quellen" laden (+ Aufgaben-Badge aktualisieren)
document.querySelectorAll('.nav-tab[data-section="sources"]').forEach((tab) => { document.querySelectorAll('.nav-tab[data-section="sources"]').forEach((tab) => {
@@ -302,7 +305,7 @@ function renderUnifiedSources(sources) {
order.forEach((cat) => { order.forEach((cat) => {
const label = CATEGORY_LABELS[cat] || cat; const label = CATEGORY_LABELS[cat] || cat;
const count = grouped[cat].length; const count = grouped[cat].length;
html += `<tr class="cat-header-row"><td colspan="${cols}"><span class="cat-header-label">${esc(label)}</span><span class="cat-header-count">${count}</span></td></tr>`; html += `<tr class="cat-header-row"><td colspan="${cols}"><span class="cat-header-sticky"><span class="cat-header-label">${esc(label)}</span><span class="cat-header-count">${count}</span></span></td></tr>`;
grouped[cat].forEach((s) => { grouped[cat].forEach((s) => {
const isTenant = s.tenant_id != null; const isTenant = s.tenant_id != null;
const hasNotes = s.notes && s.notes.trim(); const hasNotes = s.notes && s.notes.trim();
@@ -334,7 +337,7 @@ function renderUnifiedSources(sources) {
<button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>`; <button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>`;
html += `<tr> html += `<tr>
${selectCell} ${selectCell}
<td>${infoBtn} ${esc(s.name)}</td> <td class="sticky-name" title="${esc(s.name)}">${infoBtn} ${esc(s.name)}</td>
<td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td> <td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td>
<td>${esc(s.domain || "-")}</td> <td>${esc(s.domain || "-")}</td>
<td>${typeLabel(s.source_type)}</td> <td>${typeLabel(s.source_type)}</td>
@@ -346,7 +349,7 @@ function renderUnifiedSources(sources) {
<td>${healthCell}</td> <td>${healthCell}</td>
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td> <td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
<td class="text-secondary">${isTenant ? esc(s.org_name || ("Org " + s.tenant_id)) : '<span class="text-muted">—</span>'}</td> <td class="text-secondary">${isTenant ? esc(s.org_name || ("Org " + s.tenant_id)) : '<span class="text-muted">—</span>'}</td>
<td>${actions}</td> <td class="sticky-actions">${actions}</td>
</tr>${detailRow}`; </tr>${detailRow}`;
}); });
}); });