Update UI for Sitemap to show last 20 and scroll to button.
Build Docker Image / docker (push) Successful in 6s
Build Docker Image / docker (push) Successful in 6s
This commit is contained in:
@@ -2,15 +2,18 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import csv
|
||||
import html
|
||||
import importlib.util
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
|
||||
import streamlit as st
|
||||
import streamlit.components.v1 as components
|
||||
|
||||
|
||||
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):
|
||||
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.height = height
|
||||
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
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
if not text:
|
||||
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()
|
||||
if "\n" in text or (now - self.last_render) >= self.throttle_seconds:
|
||||
self.render()
|
||||
@@ -79,15 +91,40 @@ class StreamlitOutputBuffer(io.TextIOBase):
|
||||
|
||||
def render(self) -> None:
|
||||
self.last_render = time.monotonic()
|
||||
self.placeholder.text_area(
|
||||
"Scan Details",
|
||||
value="".join(self.parts),
|
||||
height=self.height,
|
||||
disabled=True,
|
||||
visible_lines = list(self.lines)
|
||||
if self.current_line:
|
||||
visible_lines.append(self.current_line)
|
||||
visible_lines = visible_lines[-self.max_lines :]
|
||||
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:
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user