Improve search
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
<div
|
||||
id="{{ osint_table_id }}"
|
||||
class="osint-table-shell"
|
||||
hx-get="{{ osint_refresh_url }}"
|
||||
hx-target="#{{ osint_table_id }}"
|
||||
hx-swap="outerHTML"
|
||||
{% if osint_event_name %}hx-trigger="{{ osint_event_name }} from:body"{% endif %}>
|
||||
class="osint-table-shell {% if osint_shell_borderless %}osint-table-shell--borderless{% endif %}"
|
||||
{% if osint_event_name %}
|
||||
hx-get="{{ osint_refresh_url }}"
|
||||
hx-target="#{{ osint_table_id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ osint_event_name }} from:body"
|
||||
{% endif %}>
|
||||
|
||||
{% if osint_show_search %}
|
||||
<form
|
||||
@@ -53,9 +55,15 @@
|
||||
|
||||
<div class="osint-results-meta">
|
||||
<div class="osint-results-meta-left">
|
||||
<div class="dropdown is-hoverable" id="{{ osint_table_id }}-columns-dropdown">
|
||||
<div class="dropdown" id="{{ osint_table_id }}-columns-dropdown" hx-disable>
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small is-light" aria-haspopup="true" aria-controls="{{ osint_table_id }}-columns-menu">
|
||||
<button
|
||||
class="button is-small is-light"
|
||||
type="button"
|
||||
data-role="columns-toggle-trigger"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ osint_table_id }}-columns-menu">
|
||||
<span>Show/Hide Fields</span>
|
||||
<span class="icon is-small"><i class="fa-solid fa-angle-down"></i></span>
|
||||
</button>
|
||||
@@ -63,15 +71,15 @@
|
||||
<div class="dropdown-menu" id="{{ osint_table_id }}-columns-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
{% for column in osint_columns %}
|
||||
<a
|
||||
<button
|
||||
type="button"
|
||||
class="dropdown-item osint-col-toggle"
|
||||
data-col-index="{{ forloop.counter0 }}"
|
||||
href="#"
|
||||
onclick="return false;">
|
||||
aria-pressed="false">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>{{ column.label }}</span>
|
||||
<span class="is-size-7 has-text-grey ml-2">({{ column.field_name }})</span>
|
||||
</a>
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,11 +134,33 @@
|
||||
{% if cell.kind == "id_copy" %}
|
||||
<a
|
||||
class="button is-small has-text-grey"
|
||||
title="Copy value"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ cell.value }}');">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</span>
|
||||
<span>{{ cell.value }}</span>
|
||||
</a>
|
||||
{% elif cell.kind == "chat_ref" %}
|
||||
{% if cell.value.copy %}
|
||||
<a
|
||||
class="button is-small has-text-grey"
|
||||
title="Copy chat id"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ cell.value.copy }}');">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="has-text-grey">-</span>
|
||||
{% endif %}
|
||||
{% elif cell.value.copy %}
|
||||
<a
|
||||
class="button is-small has-text-grey"
|
||||
title="Copy value"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ cell.value.copy }}');">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</span>
|
||||
</a>
|
||||
{% elif cell.kind == "bool" %}
|
||||
{% if cell.value %}
|
||||
@@ -281,6 +311,22 @@
|
||||
#{{ osint_table_id }} .osint-col-toggle.is-hidden-col .icon {
|
||||
color: #b5b5b5;
|
||||
}
|
||||
#{{ osint_table_id }} .osint-col-toggle {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
#{{ osint_table_id }}.osint-table-shell--borderless {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -292,6 +338,13 @@
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
const dropdown = document.getElementById(tableId + "-columns-dropdown");
|
||||
const trigger = dropdown
|
||||
? dropdown.querySelector("[data-role='columns-toggle-trigger']")
|
||||
: null;
|
||||
const dropdownMenu = dropdown
|
||||
? dropdown.querySelector(".dropdown-menu")
|
||||
: null;
|
||||
const storageKey = [
|
||||
"gia_osint_hidden_cols_v2",
|
||||
tableId,
|
||||
@@ -319,6 +372,7 @@
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
const isHidden = hiddenSet.has(idx);
|
||||
toggle.classList.toggle("is-hidden-col", isHidden);
|
||||
toggle.setAttribute("aria-pressed", isHidden ? "true" : "false");
|
||||
const icon = toggle.querySelector("i");
|
||||
if (icon) {
|
||||
icon.className = isHidden ? "fa-solid fa-xmark" : "fa-solid fa-check";
|
||||
@@ -335,7 +389,9 @@
|
||||
};
|
||||
|
||||
shell.querySelectorAll(".osint-col-toggle").forEach(function (toggle) {
|
||||
toggle.addEventListener("click", function () {
|
||||
toggle.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
if (!idx) {
|
||||
return;
|
||||
@@ -347,9 +403,51 @@
|
||||
}
|
||||
persist();
|
||||
applyVisibility();
|
||||
if (dropdown) {
|
||||
dropdown.classList.remove("is-active");
|
||||
}
|
||||
if (trigger) {
|
||||
trigger.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (trigger && dropdown) {
|
||||
trigger.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const nextActive = !dropdown.classList.contains("is-active");
|
||||
dropdown.classList.toggle("is-active", nextActive);
|
||||
trigger.setAttribute("aria-expanded", nextActive ? "true" : "false");
|
||||
});
|
||||
}
|
||||
if (dropdownMenu) {
|
||||
dropdownMenu.addEventListener("click", function (event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
}
|
||||
document.addEventListener("click", function (event) {
|
||||
if (!dropdown) {
|
||||
return;
|
||||
}
|
||||
if (dropdown.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
dropdown.classList.remove("is-active");
|
||||
if (trigger) {
|
||||
trigger.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key !== "Escape" || !dropdown) {
|
||||
return;
|
||||
}
|
||||
dropdown.classList.remove("is-active");
|
||||
if (trigger) {
|
||||
trigger.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
});
|
||||
|
||||
applyVisibility();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -1,78 +1,176 @@
|
||||
<div class="box">
|
||||
<form
|
||||
class="osint-search-form"
|
||||
method="get"
|
||||
action="{{ osint_search_url }}"
|
||||
hx-get="{{ osint_search_url }}"
|
||||
hx-target="#osint-search-results"
|
||||
hx-swap="innerHTML">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-4">
|
||||
<label class="label">Scope</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="scope">
|
||||
{% for option in scope_options %}
|
||||
<option
|
||||
value="{{ option.value }}"
|
||||
{% if option.value == selected_scope %}selected{% endif %}>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<label class="label">Field</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="field">
|
||||
<option value="__all__" {% if selected_field == "__all__" %}selected{% endif %}>
|
||||
All Fields
|
||||
<form
|
||||
class="osint-search-form box"
|
||||
method="get"
|
||||
action="{{ osint_search_url }}"
|
||||
hx-get="{{ osint_search_url }}"
|
||||
hx-trigger="change delay:120ms"
|
||||
hx-target="#osint-search-results"
|
||||
hx-swap="innerHTML">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-4">
|
||||
<label class="label">Scope</label>
|
||||
<div class="select">
|
||||
<select name="scope">
|
||||
{% for option in scope_options %}
|
||||
<option
|
||||
value="{{ option.value }}"
|
||||
{% if option.value == selected_scope %}selected{% endif %}>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
{% for option in field_options %}
|
||||
<option
|
||||
value="{{ option.value }}"
|
||||
{% if option.value == selected_field %}selected{% endif %}>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<label class="label">Rows Per Page</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select name="per_page">
|
||||
<option value="10" {% if selected_per_page == 10 %}selected{% endif %}>10</option>
|
||||
<option value="20" {% if selected_per_page == 20 %}selected{% endif %}>20</option>
|
||||
<option value="50" {% if selected_per_page == 50 %}selected{% endif %}>50</option>
|
||||
<option value="100" {% if selected_per_page == 100 %}selected{% endif %}>100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-9">
|
||||
<label class="label">Search Query</label>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
name="q"
|
||||
value="{{ search_query }}"
|
||||
placeholder="Search text, values, or relation names...">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label"> </label>
|
||||
<div class="buttons">
|
||||
<button class="button is-link is-light is-fullwidth" type="submit">
|
||||
Search
|
||||
</button>
|
||||
<a class="button is-light is-fullwidth" href="{{ osint_search_url }}">
|
||||
Reset
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<label class="label">Field</label>
|
||||
<div class="select">
|
||||
<select name="field">
|
||||
<option value="__all__" {% if selected_field == "__all__" %}selected{% endif %}>
|
||||
All Fields
|
||||
</option>
|
||||
{% for option in field_options %}
|
||||
<option
|
||||
value="{{ option.value }}"
|
||||
{% if option.value == selected_field %}selected{% endif %}>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<label class="label">Rows Per Page</label>
|
||||
<div class="select">
|
||||
<select name="per_page">
|
||||
<option value="10" {% if selected_per_page == 10 %}selected{% endif %}>10</option>
|
||||
<option value="20" {% if selected_per_page == 20 %}selected{% endif %}>20</option>
|
||||
<option value="50" {% if selected_per_page == 50 %}selected{% endif %}>50</option>
|
||||
<option value="100" {% if selected_per_page == 100 %}selected{% endif %}>100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-9">
|
||||
<label class="label">Search Query</label>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
name="q"
|
||||
hx-get="{{ osint_search_url }}"
|
||||
hx-trigger="keyup changed delay:220ms"
|
||||
hx-include="closest form"
|
||||
hx-target="#osint-search-results"
|
||||
hx-swap="innerHTML"
|
||||
value="{{ search_query }}"
|
||||
placeholder="Search contacts, identifiers, and messages...">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label"> </label>
|
||||
<div class="buttons">
|
||||
<button class="button is-link is-light is-fullwidth" type="submit">
|
||||
Search
|
||||
</button>
|
||||
<a class="button is-light is-fullwidth" href="{{ osint_search_url }}" style="margin-bottom: 0.55rem;">
|
||||
Reset
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="osint-search-results">
|
||||
{% include "partials/results_table.html" %}
|
||||
</div>
|
||||
<details style="margin-top: 0.45rem;">
|
||||
<summary class="has-text-weight-semibold">Advanced Filters (SIQTSRSS/ADR)</summary>
|
||||
<p class="help" style="margin: 0.1rem 0 0.2rem 0;">Tip: use inline tags like <code>tag:messages</code> or <code>tag:people</code> in the query to narrow All-scope results quickly.</p>
|
||||
<p class="help" style="margin: 0 0 0.35rem 0;"><strong>Source Service</strong>, <strong>From Date</strong>, <strong>To Date</strong>, and <strong>Sort Mode</strong> shape which results you see and in what order.</p>
|
||||
<div style="margin-top: 0.2rem;">
|
||||
<div class="columns is-multiline" style="margin-top: 0.3rem;">
|
||||
<div class="column is-12-mobile is-6-tablet is-4-desktop">
|
||||
<label class="label">Source Service</label>
|
||||
<div class="select">
|
||||
<select name="source">
|
||||
<option value="all" {% if selected_source == "all" %}selected{% endif %}>All</option>
|
||||
<option value="web" {% if selected_source == "web" %}selected{% endif %}>Web</option>
|
||||
<option value="xmpp" {% if selected_source == "xmpp" %}selected{% endif %}>XMPP</option>
|
||||
<option value="signal" {% if selected_source == "signal" %}selected{% endif %}>Signal</option>
|
||||
<option value="whatsapp" {% if selected_source == "whatsapp" %}selected{% endif %}>WhatsApp</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-12"></div>
|
||||
|
||||
<div class="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<label class="label">From Date</label>
|
||||
<input
|
||||
class="input{% if selected_date_from %} date-has-value{% endif %}"
|
||||
type="date"
|
||||
name="date_from"
|
||||
value="{{ selected_date_from }}"
|
||||
aria-label="From date">
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<label class="label">To Date</label>
|
||||
<input
|
||||
class="input{% if selected_date_to %} date-has-value{% endif %}"
|
||||
type="date"
|
||||
name="date_to"
|
||||
value="{{ selected_date_to }}"
|
||||
aria-label="To date">
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-tablet is-3-desktop">
|
||||
<label class="label">Sort Mode</label>
|
||||
<div class="select">
|
||||
<select name="sort_mode">
|
||||
<option value="relevance" {% if selected_sort_mode == "relevance" %}selected{% endif %}>Relevance</option>
|
||||
<option value="recent" {% if selected_sort_mode == "recent" %}selected{% endif %}>Recent First</option>
|
||||
<option value="oldest" {% if selected_sort_mode == "oldest" %}selected{% endif %}>Oldest First</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-12"></div>
|
||||
|
||||
<div class="column is-12-mobile is-6-tablet is-2-desktop">
|
||||
<label class="label">Min Sent.</label>
|
||||
<input class="input" type="number" step="0.01" min="-1" max="1" name="sentiment_min" value="{{ selected_sentiment_min }}">
|
||||
</div>
|
||||
<div class="column is-12-mobile is-6-tablet is-2-desktop">
|
||||
<label class="label">Max Sent.</label>
|
||||
<input class="input" type="number" step="0.01" min="-1" max="1" name="sentiment_max" value="{{ selected_sentiment_max }}">
|
||||
</div>
|
||||
|
||||
<div class="column is-12"></div>
|
||||
|
||||
<div class="column is-12">
|
||||
<label class="checkbox" style="margin-right: 0.9rem;">
|
||||
<input type="checkbox" name="annotate" value="1" {% if selected_annotate %}checked{% endif %}>
|
||||
Annotate snippets
|
||||
</label>
|
||||
<label class="checkbox" style="margin-right: 0.9rem;">
|
||||
<input type="checkbox" name="dedup" value="1" {% if selected_dedup %}checked{% endif %}>
|
||||
Deduplicate
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="reverse" value="1" {% if selected_reverse %}checked{% endif %}>
|
||||
Reverse output
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content is-size-7" style="margin-top: 0.2rem;">
|
||||
<ul>
|
||||
<li><strong>Min/Max Sent.</strong>: sentiment bounds for people/contact results (-1 to 1).</li>
|
||||
<li><strong>Annotate snippets</strong>: shows contextual snippets around query hits.</li>
|
||||
<li><strong>Deduplicate</strong>: removes near-identical repeated rows.</li>
|
||||
<li><strong>Reverse output</strong>: reverses final result order after sorting.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</form>
|
||||
|
||||
<div id="osint-search-results">
|
||||
{% include "partials/results_table.html" %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.date-has-value::-webkit-calendar-picker-indicator {
|
||||
filter: brightness(0) saturate(100%) invert(38%) sepia(85%) saturate(1820%) hue-rotate(201deg) brightness(96%) contrast(92%);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user