Update UI for Sitemap to show last 20 and scroll to button.
Build Docker Image / docker (push) Successful in 6s

This commit is contained in:
2026-04-09 11:33:18 -07:00
parent b166c03e9e
commit 8600ee9c27
+46 -9
View File
@@ -2,15 +2,18 @@ from __future__ import annotations
import contextlib import contextlib
import csv import csv
import html
import importlib.util import importlib.util
import io import io
import os import os
import re import re
import sys import sys
import time import time
from collections import deque
from pathlib import Path from pathlib import Path
import streamlit as st import streamlit as st
import streamlit.components.v1 as components
ROOT_DIR = Path(__file__).resolve().parent ROOT_DIR = Path(__file__).resolve().parent
@@ -58,17 +61,26 @@ def read_csv_preview(csv_bytes: bytes, limit: int = 200) -> list[dict[str, str]]
class StreamlitOutputBuffer(io.TextIOBase): class StreamlitOutputBuffer(io.TextIOBase):
def __init__(self, placeholder, *, height: int = 220, throttle_seconds: float = 0.2) -> None: def __init__(self, placeholder, *, height: int = 220, throttle_seconds: float = 0.2, max_lines: int = 20) -> None:
self.placeholder = placeholder self.placeholder = placeholder
self.height = height self.height = height
self.throttle_seconds = throttle_seconds self.throttle_seconds = throttle_seconds
self.parts: list[str] = [] self.max_lines = max_lines
self.lines: deque[str] = deque(maxlen=max_lines)
self.current_line = ""
self.last_render = 0.0 self.last_render = 0.0
def write(self, text: str) -> int: def write(self, text: str) -> int:
if not text: if not text:
return 0 return 0
self.parts.append(text) normalized = text.replace("\r\n", "\n").replace("\r", "\n")
for chunk in normalized.splitlines(keepends=True):
if chunk.endswith("\n"):
self.current_line += chunk[:-1]
self.lines.append(self.current_line)
self.current_line = ""
else:
self.current_line += chunk
now = time.monotonic() now = time.monotonic()
if "\n" in text or (now - self.last_render) >= self.throttle_seconds: if "\n" in text or (now - self.last_render) >= self.throttle_seconds:
self.render() self.render()
@@ -79,15 +91,40 @@ class StreamlitOutputBuffer(io.TextIOBase):
def render(self) -> None: def render(self) -> None:
self.last_render = time.monotonic() self.last_render = time.monotonic()
self.placeholder.text_area( visible_lines = list(self.lines)
"Scan Details", if self.current_line:
value="".join(self.parts), visible_lines.append(self.current_line)
height=self.height, visible_lines = visible_lines[-self.max_lines :]
disabled=True, content = html.escape("\n".join(visible_lines))
self.placeholder.caption("Scan Details")
components.html(
f"""
<div id="scan-details" style="
height: {self.height}px;
overflow-y: auto;
white-space: pre-wrap;
font-family: monospace;
font-size: 0.9rem;
padding: 0.75rem;
border: 1px solid rgba(49, 51, 63, 0.2);
border-radius: 0.5rem;
background: white;
">{content}</div>
<script>
const el = document.getElementById("scan-details");
if (el) {{
el.scrollTop = el.scrollHeight;
}}
</script>
""",
height=self.height + 16,
) )
def getvalue(self) -> str: def getvalue(self) -> str:
return "".join(self.parts) visible_lines = list(self.lines)
if self.current_line:
visible_lines.append(self.current_line)
return "\n".join(visible_lines[-self.max_lines :])
def render_sitemap_tab() -> None: def render_sitemap_tab() -> None: