diff --git a/frontend/src/components/ApiKeySetupModal.tsx b/frontend/src/components/ApiKeySetupModal.tsx index 60ab4c105..3d9bd04ed 100644 --- a/frontend/src/components/ApiKeySetupModal.tsx +++ b/frontend/src/components/ApiKeySetupModal.tsx @@ -18,6 +18,26 @@ export default function ApiKeySetupModal({ onSaved }: Props) { const [visible, setVisible] = useState(false) const [error, setError] = useState('') + // NEW: Sanitize pasted API keys + const sanitizeApiKey = (value: string): string => { + return value + .trim() // Remove leading/trailing whitespace + .replace(/\n/g, '') // Remove newlines + .replace(/\r/g, '') // Remove carriage returns + .replace(/\t/g, '') // Remove tabs + .replace(/\u00A0/g, ' ') // Replace non-breaking spaces with regular space + .trim(); // Final trim after replacements + } + + // NEW: Handle paste events + const handlePaste = (event: React.ClipboardEvent) => { + event.preventDefault() + const pastedText = event.clipboardData.getData('text') + const sanitizedText = sanitizeApiKey(pastedText) + setKey(sanitizedText) + setError('') + } + function handleSave() { const trimmed = key.trim() if (!trimmed) { @@ -78,6 +98,7 @@ export default function ApiKeySetupModal({ onSaved }: Props) { type="password" value={key} onChange={(e) => { setKey(e.target.value); setError('') }} + onPaste={handlePaste} // NEW: Added paste handler onKeyDown={(e) => e.key === 'Enter' && handleSave()} placeholder="Paste API key here" aria-label="Backend API Key" diff --git a/frontend/src/pages/Scans.tsx b/frontend/src/pages/Scans.tsx index 143319a70..71da8efd4 100644 --- a/frontend/src/pages/Scans.tsx +++ b/frontend/src/pages/Scans.tsx @@ -65,7 +65,13 @@ export default function Scans() { const [selectedIds, setSelectedIds] = useState([]); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); - const PAGE_LIMIT = 10; + + // Page size with localStorage persistence + const getSavedPageSize = () => { + const saved = localStorage.getItem('pagination_pageSize_scans'); + return saved ? parseInt(saved, 10) : 10; + }; + const [pageLimit, setPageLimit] = useState(getSavedPageSize); // Modal state for confirm dialogs const [modalState, setModalState] = useState<{ @@ -124,7 +130,7 @@ export default function Scans() { } document.removeEventListener("visibilitychange", handleVisibilityChange); }; - }, [filter, page]); + }, [filter, page, pageLimit]); async function loadTasks() { const requestSeq = requestSeqRef.current + 1; @@ -137,7 +143,7 @@ export default function Scans() { const params = new URLSearchParams(); if (filter !== "all") params.set("status", filter); params.set("page", String(page)); - params.set("per_page", String(PAGE_LIMIT)); + params.set("per_page", String(pageLimit)); const res = await fetch(`${API_BASE}/tasks?${params.toString()}`, { signal: controller.signal, @@ -675,14 +681,18 @@ export default function Scans() { )} - {total > PAGE_LIMIT && ( + {total > pageLimit && ( setPage((p) => p - 1)} onNext={() => setPage((p) => p + 1)} + onPageSizeChange={(newSize) => { + setPageLimit(newSize); + setPage(1); + }} /> )}