From ab65f65a5ff952fcf4f640f8e96118908e77c182 Mon Sep 17 00:00:00 2001 From: s0lray Date: Tue, 24 Mar 2026 17:56:24 -0400 Subject: [PATCH 1/2] feat(ui): add client-side search for tools (THE-66) Adds a debounced text search input above the D3 tree that filters all ~1,000+ tool nodes by name and description. Results appear as a flat list with breadcrumb paths; clearing the input hides the panel. - No build step required: vanilla JS, no new dependencies - Sanitizes HTML output (escapeHtml/escapeAttr) and validates URLs (http/https only) - Dark and light mode compatible via existing CSS variables - Results capped at 50 to keep rendering fast Co-Authored-By: Paperclip --- public/css/arf.css | 72 ++++++++++++++++++++++++++++++++++++++++++++ public/index.html | 4 +++ public/js/arf.js | 74 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/public/css/arf.css b/public/css/arf.css index 3238f8b..d3654f5 100644 --- a/public/css/arf.css +++ b/public/css/arf.css @@ -105,3 +105,75 @@ h3 { a { color: var(--color-node-stroke); } + +/* Search */ +#search-container { + text-align: center; + padding: 8px 0 4px; +} + +#search-input { + width: 400px; + max-width: 90vw; + padding: 8px 12px; + font-size: 14px; + font-family: "Helvetica Neue", Helvetica; + background: var(--color-surface); + color: var(--color-text-primary); + border: 1px solid var(--color-link); + border-radius: 4px; + outline: none; +} + +#search-input:focus { + border-color: var(--color-node-stroke); +} + +#search-results { + margin: 8px auto 0; + width: 600px; + max-width: 90vw; + text-align: left; + background: var(--color-surface); + border: 1px solid var(--color-link); + border-radius: 4px; + max-height: 400px; + overflow-y: auto; + display: none; +} + +#search-results.visible { + display: block; +} + +.search-result-item { + padding: 8px 12px; + border-bottom: 1px solid var(--color-link); + font-size: 13px; +} + +.search-result-item:last-child { + border-bottom: none; +} + +.search-result-item a { + font-weight: bold; + color: var(--color-node-stroke); + text-decoration: none; +} + +.search-result-item a:hover { + text-decoration: underline; +} + +.search-result-path { + color: var(--color-text-secondary); + font-size: 11px; + margin-top: 2px; +} + +.search-no-results { + padding: 12px; + color: var(--color-text-secondary); + text-align: center; +} diff --git a/public/index.html b/public/index.html index e349123..f55f4eb 100644 --- a/public/index.html +++ b/public/index.html @@ -28,6 +28,10 @@ OSINT Framework
+
+ +
+
diff --git a/public/js/arf.js b/public/js/arf.js index b3fa05f..10c5dcc 100644 --- a/public/js/arf.js +++ b/public/js/arf.js @@ -3,7 +3,8 @@ var margin = [20, 120, 20, 140], height = 800 - margin[0] - margin[2], i = 0, duration = 1250, - root; + root, + allSearchNodes = []; var tree = d3.tree() .size([height, width]); @@ -29,6 +30,11 @@ d3.json("arf.json").then(function(json) { root.x0 = height / 2; root.y0 = 0; + // Collect all tool nodes (those with URLs) for search + allSearchNodes = root.descendants().filter(function(d) { + return d.data.url; + }); + function collapse(d) { if (d.children) { d._children = d.children; @@ -39,6 +45,7 @@ d3.json("arf.json").then(function(json) { root.children.forEach(collapse); update(root); + initSearch(); }); function update(source) { @@ -160,6 +167,71 @@ function toggle(d) { } } +// Client-side search over all tool nodes. +var searchDebounceTimer = null; + +function initSearch() { + var input = document.getElementById("search-input"); + if (!input) return; + input.addEventListener("input", function() { + clearTimeout(searchDebounceTimer); + var query = input.value.trim(); + searchDebounceTimer = setTimeout(function() { doSearch(query); }, 200); + }); +} + +function doSearch(query) { + var results = document.getElementById("search-results"); + if (!results) return; + + if (!query) { + results.innerHTML = ""; + results.classList.remove("visible"); + return; + } + + var lower = query.toLowerCase(); + var matches = allSearchNodes.filter(function(d) { + var name = (d.data.name || "").toLowerCase(); + var desc = (d.data.description || "").toLowerCase(); + return name.indexOf(lower) !== -1 || desc.indexOf(lower) !== -1; + }).slice(0, 50); + + results.classList.add("visible"); + + if (matches.length === 0) { + results.innerHTML = '
No results found for \u201c' + escapeHtml(query) + '\u201d
'; + return; + } + + var html = matches.map(function(d) { + var path = d.ancestors().reverse().slice(1, -1).map(function(a) { + return escapeHtml(a.data.name); + }).join(" \u203a "); + var name = escapeHtml(d.data.name); + var url = safeUrl(d.data.url); + return '
' + + '' + name + '' + + (path ? '
' + path + '
' : '') + + '
'; + }).join(""); + + results.innerHTML = html; +} + +function escapeHtml(str) { + return (str || "").replace(/&/g, "&").replace(//g, ">"); +} + +function escapeAttr(str) { + return (str || "").replace(/"/g, """).replace(/'/g, "'"); +} + +function safeUrl(url) { + if (!url) return "#"; + return /^https?:\/\//i.test(url) ? url : "#"; +} + // Toggle light/dark mode and persist preference. function goDark() { var body = document.body; From b684b3cd88ff3488ace474b8841cd6f2cc0f729f Mon Sep 17 00:00:00 2001 From: s0lray Date: Tue, 24 Mar 2026 18:51:15 -0400 Subject: [PATCH 2/2] feat(ui): replace text suffixes with color-coded SVG badges (THE-73) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse (T)/(D)/(R)/(M) suffixes from tool names and render them as colored tspan elements in the SVG tree. Add matching badge pill styles to the HTML legend so it visually matches the tree indicators. - T (tool) → blue #4a9eda - D (dork) → orange #e8944a - R (registration) → amber #c8a030 - M (manual URL) → purple #9a7eda Badge colors are theme-invariant; they work on both dark and light backgrounds without separate light-mode overrides. Co-Authored-By: Paperclip --- public/css/arf.css | 21 +++++++++++++++++++++ public/index.html | 10 ++++++---- public/js/arf.js | 31 +++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/public/css/arf.css b/public/css/arf.css index 3238f8b..d590b95 100644 --- a/public/css/arf.css +++ b/public/css/arf.css @@ -12,6 +12,12 @@ --color-noscript-border: #7a4a00; --color-legend: #e0e0e0; --color-header: #e0e0e0; + + /* Status badge colors — saturated enough for both light and dark backgrounds */ + --badge-T: #4a9eda; + --badge-D: #e8944a; + --badge-R: #c8a030; + --badge-M: #9a7eda; } body.light-mode { @@ -105,3 +111,18 @@ h3 { a { color: var(--color-node-stroke); } + +/* Status badge pills (used in legend) */ +.badge { + display: inline-block; + font-size: 10px; + font-weight: bold; + padding: 1px 4px; + border-radius: 3px; + color: #fff; + vertical-align: middle; +} +.badge-T { background-color: var(--badge-T); } +.badge-D { background-color: var(--badge-D); } +.badge-R { background-color: var(--badge-R); } +.badge-M { background-color: var(--badge-M); } diff --git a/public/index.html b/public/index.html index e349123..991a897 100644 --- a/public/index.html +++ b/public/index.html @@ -32,10 +32,12 @@ -

(T) - Indicates a link to a tool that must be installed and run locally
- (D) - Google Dork, for more information: Google Hacking
- (R) - Requires registration
- (M) - Indicates a URL that contains the search term and the URL itself must be edited manually

+

+ T - Indicates a link to a tool that must be installed and run locally
+ D - Google Dork, for more information: Google Hacking
+ R - Requires registration
+ M - Indicates a URL that contains the search term and the URL itself must be edited manually
+