Improve chat experience and begin search implementation

This commit is contained in:
2026-02-15 17:32:26 +00:00
parent 6612274ab9
commit a94bbff655
21 changed files with 3081 additions and 179 deletions

View File

@@ -0,0 +1,225 @@
{% 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 %}>
{% if osint_show_search %}
<form
method="get"
action="{{ osint_search_url }}"
class="osint-table-toolbar"
hx-get="{{ osint_search_url }}"
hx-target="#{{ osint_table_id }}"
hx-swap="outerHTML">
<div class="field has-addons is-flex-wrap-wrap">
<div class="control is-expanded" style="min-width: 14rem;">
<input
class="input"
type="text"
name="q"
value="{{ osint_search_query }}"
placeholder="Search {{ osint_title|lower }}...">
</div>
<div class="control">
<div class="select">
<select name="field">
{% for field in osint_search_fields %}
<option
value="{{ field.value }}"
{% if field.value == osint_search_field %}selected{% endif %}>
{{ field.label }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<button class="button is-link is-light" type="submit">
Search
</button>
</div>
<div class="control">
<a class="button is-light" href="{{ osint_search_url }}">
Reset
</a>
</div>
</div>
</form>
{% endif %}
<div class="table-container osint-results-table-wrap">
<table class="table is-fullwidth is-hoverable osint-results-table">
<thead>
<tr>
{% for column in osint_columns %}
<th>
{% if column.sortable %}
<a
class="osint-sort-link"
href="{{ column.sort_url }}"
hx-get="{{ column.sort_url }}"
hx-target="#{{ osint_table_id }}"
hx-swap="outerHTML">
<span>{{ column.label }}</span>
<span class="icon is-small">
{% if column.is_sorted and column.is_desc %}
<i class="fa-solid fa-sort-down"></i>
{% elif column.is_sorted %}
<i class="fa-solid fa-sort-up"></i>
{% else %}
<i class="fa-solid fa-sort"></i>
{% endif %}
</span>
</a>
{% else %}
{{ column.label }}
{% endif %}
</th>
{% endfor %}
{% if osint_show_actions %}<th>Actions</th>{% endif %}
</tr>
</thead>
<tbody>
{% for row in osint_rows %}
<tr>
{% for cell in row.cells %}
<td>
{% if cell.kind == "id_copy" %}
<a
class="button is-small has-text-grey"
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 == "bool" %}
{% if cell.value %}
<span class="icon has-text-success">
<i class="fa-solid fa-check"></i>
</span>
{% else %}
<span class="icon has-text-grey">
<i class="fa-solid fa-xmark"></i>
</span>
{% endif %}
{% elif cell.kind == "datetime" %}
{% if cell.value %}
{{ cell.value|date:"M j, Y P" }}
{% else %}
<span class="has-text-grey">-</span>
{% endif %}
{% else %}
{% if cell.value or cell.value == 0 %}
{{ cell.value }}
{% else %}
<span class="has-text-grey">-</span>
{% endif %}
{% endif %}
</td>
{% endfor %}
{% if osint_show_actions %}
<td>
<div class="buttons are-small">
{% for action in row.actions %}
{% if action.mode == "hx-get" %}
<button
class="button"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{{ action.url }}"
hx-target="{{ action.target }}"
hx-swap="innerHTML"
title="{{ action.title }}">
<span class="icon"><i class="{{ action.icon }}"></i></span>
</button>
{% elif action.mode == "hx-delete" %}
<button
class="button"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{{ action.url }}"
hx-target="{{ action.target }}"
hx-swap="innerHTML"
{% if action.confirm %}hx-confirm="{{ action.confirm }}"{% endif %}
title="{{ action.title }}">
<span class="icon"><i class="{{ action.icon }}"></i></span>
</button>
{% elif action.mode == "link" %}
<a class="button" href="{{ action.url }}" title="{{ action.title }}">
<span class="icon"><i class="{{ action.icon }}"></i></span>
</a>
{% endif %}
{% endfor %}
</div>
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="{% if osint_show_actions %}{{ osint_columns|length|add:'1' }}{% else %}{{ osint_columns|length }}{% endif %}">
<p class="has-text-grey">No results found.</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if osint_pagination.enabled %}
<nav class="pagination is-small" role="navigation" aria-label="pagination">
{% if osint_pagination.has_previous %}
<a
class="pagination-previous"
href="{{ osint_pagination.previous_url }}"
hx-get="{{ osint_pagination.previous_url }}"
hx-target="#{{ osint_table_id }}"
hx-swap="outerHTML">
Previous
</a>
{% else %}
<a class="pagination-previous is-disabled">Previous</a>
{% endif %}
{% if osint_pagination.has_next %}
<a
class="pagination-next"
href="{{ osint_pagination.next_url }}"
hx-get="{{ osint_pagination.next_url }}"
hx-target="#{{ osint_table_id }}"
hx-swap="outerHTML">
Next
</a>
{% else %}
<a class="pagination-next is-disabled">Next</a>
{% endif %}
<ul class="pagination-list">
{% for page in osint_pagination.pages %}
{% if page.ellipsis %}
<li><span class="pagination-ellipsis">&hellip;</span></li>
{% elif page.current %}
<li><a class="pagination-link is-current">{{ page.number }}</a></li>
{% else %}
<li>
<a
class="pagination-link"
href="{{ page.url }}"
hx-get="{{ page.url }}"
hx-target="#{{ osint_table_id }}"
hx-swap="outerHTML">
{{ page.number }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
{% endif %}
<p class="help has-text-grey">
{{ osint_result_count }} result{% if osint_result_count != 1 %}s{% endif %}
</p>
</div>

View File

@@ -0,0 +1,78 @@
<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
</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">&nbsp;</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>
</div>
</div>
</form>
<div id="osint-search-results">
{% include "partials/results_table.html" %}
</div>
</div>