mirror of
https://github.com/lockfale/OSINT-Framework.git
synced 2026-08-22 13:52:23 +02:00
merge: resolve conflict with master — keep zoomToNode + search functions (THE-79)
Both zoomToNode() (pan/zoom, THE-79) and the search/badge additions from master (THE-66, THE-73) are preserved. No logic changes to either side. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
+31
-29
@@ -13,6 +13,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;
|
||||
--color-accent: #4a9eda;
|
||||
--color-border: #444;
|
||||
|
||||
@@ -147,39 +153,51 @@ 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); }
|
||||
/* Search */
|
||||
#search-container {
|
||||
text-align: center;
|
||||
padding: var(--space-2) 0 var(--space-1);
|
||||
padding: 8px 0 4px;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
width: 400px;
|
||||
max-width: 90vw;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-family);
|
||||
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: var(--radius-sm);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#search-input:focus {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px rgba(74, 158, 218, 0.25);
|
||||
border-color: var(--color-node-stroke);
|
||||
}
|
||||
|
||||
#search-results {
|
||||
margin: var(--space-2) auto 0;
|
||||
margin: 8px auto 0;
|
||||
width: 600px;
|
||||
max-width: 90vw;
|
||||
text-align: left;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-link);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-radius: 4px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
@@ -190,7 +208,7 @@ a {
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--color-link);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -211,28 +229,12 @@ a {
|
||||
|
||||
.search-result-path {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
font-size: 11px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.search-no-results {
|
||||
padding: var(--space-3);
|
||||
padding: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* === Mobile responsive === */
|
||||
@media (max-width: 768px) {
|
||||
#header {
|
||||
font-size: var(--font-size-lg);
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
position: relative;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -28,14 +28,20 @@
|
||||
OSINT Framework
|
||||
<hr/>
|
||||
</div>
|
||||
<div id="search-container">
|
||||
<input type="text" id="search-input" placeholder="Search tools..." autocomplete="off" aria-label="Search OSINT tools">
|
||||
<div id="search-results" role="listbox" aria-label="Search results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/arf.js"></script>
|
||||
|
||||
<div class="legend"><p>(T) - Indicates a link to a tool that must be installed and run locally<br>
|
||||
(D) - Google Dork, for more information: <a href="https://en.wikipedia.org/wiki/Google_hacking">Google Hacking</a><br>
|
||||
(R) - Requires registration<br>
|
||||
(M) - Indicates a URL that contains the search term and the URL itself must be edited manually<br></p>
|
||||
<div class="legend"><p>
|
||||
<span class="badge badge-T">T</span> - Indicates a link to a tool that must be installed and run locally<br>
|
||||
<span class="badge badge-D">D</span> - Google Dork, for more information: <a href="https://en.wikipedia.org/wiki/Google_hacking">Google Hacking</a><br>
|
||||
<span class="badge badge-R">R</span> - Requires registration<br>
|
||||
<span class="badge badge-M">M</span> - Indicates a URL that contains the search term and the URL itself must be edited manually<br>
|
||||
</p>
|
||||
<button id="theme-toggle" onclick="goDark()">Switch to light mode</button>
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
+102
-3
@@ -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]);
|
||||
@@ -37,6 +38,22 @@ function getCSSVar(name) {
|
||||
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
||||
}
|
||||
|
||||
// Parse "(T)", "(D)", "(R)", "(M)" suffixes out of a tool name.
|
||||
// Returns { cleanName: string, badges: string[] }.
|
||||
var BADGE_TYPES = ['T', 'D', 'R', 'M'];
|
||||
function parseName(name) {
|
||||
var badges = [];
|
||||
var clean = name;
|
||||
BADGE_TYPES.forEach(function(b) {
|
||||
var suffix = ' (' + b + ')';
|
||||
if (clean.indexOf(suffix) !== -1) {
|
||||
badges.push(b);
|
||||
clean = clean.replace(suffix, '');
|
||||
}
|
||||
});
|
||||
return { cleanName: clean, badges: badges };
|
||||
}
|
||||
|
||||
d3.json("arf.json").then(function(json) {
|
||||
root = d3.hierarchy(json, function(d) {
|
||||
return d && d.children ? d.children.filter(function(c) { return c != null; }) : null;
|
||||
@@ -44,6 +61,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;
|
||||
@@ -54,6 +76,7 @@ d3.json("arf.json").then(function(json) {
|
||||
|
||||
root.children.forEach(collapse);
|
||||
update(root);
|
||||
initSearch();
|
||||
});
|
||||
|
||||
function update(source) {
|
||||
@@ -87,11 +110,22 @@ function update(source) {
|
||||
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
|
||||
.text(function(d) { return d.data.name; })
|
||||
.style("fill", function(d) {
|
||||
return d.data.free ? getCSSVar("--color-text-primary") : getCSSVar("--color-text-secondary");
|
||||
})
|
||||
.style("fill-opacity", 1e-6);
|
||||
.style("fill-opacity", 1e-6)
|
||||
.each(function(d) {
|
||||
var parsed = parseName(d.data.name);
|
||||
var el = d3.select(this);
|
||||
el.append("tspan").text(parsed.cleanName);
|
||||
parsed.badges.forEach(function(b) {
|
||||
el.append("tspan")
|
||||
.attr("dx", "4")
|
||||
.style("font-size", "10px")
|
||||
.style("fill", getCSSVar("--badge-" + b))
|
||||
.text("(" + b + ")");
|
||||
});
|
||||
});
|
||||
|
||||
nodeEnter.append("title")
|
||||
.text(function(d) {
|
||||
@@ -184,6 +218,71 @@ function zoomToNode(d) {
|
||||
.call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(currentK));
|
||||
}
|
||||
|
||||
// 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 = '<div class="search-no-results">No results found for \u201c' + escapeHtml(query) + '\u201d</div>';
|
||||
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 '<div class="search-result-item">' +
|
||||
'<a href="' + escapeAttr(url) + '" target="_blank" rel="noopener noreferrer">' + name + '</a>' +
|
||||
(path ? '<div class="search-result-path">' + path + '</div>' : '') +
|
||||
'</div>';
|
||||
}).join("");
|
||||
|
||||
results.innerHTML = html;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return (str || "").replace(/&/g, "&").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;
|
||||
|
||||
Reference in New Issue
Block a user