fix: аудит — 19 фиксов безопасности, надёжности, UI и 16 новых тестов
- S4: bump jinja2>=3.1.4, python-multipart>=0.0.18, httpx>=0.28.0
- S5: _detect_ecosystem — DEFAULT_ECOSYSTEM для неизвестных форматов
- S6: harvester — log.exception() вместо log.error()
- S8: _scan_component — urlencode параметров
- P1: scanner — proc.kill() при таймауте
- P3: api_packages — selectinload(Scan.findings), убран N+1
- P4+P5: утечка _url_locks и _llm_locks при early return
- P6: DB reaper — сброс {'status':'analyzing'} при старте
- UI: htmx-пагинация, фильтры не теряют flagged, 404 с layout
- UI: мобильные таблицы overflow-x, полная стата на дашборде
- UI: i18n статусов в _status_badge, urlencode package_name
- 16 новых тестов: analyze endpoint (6), scanner errors (4),
webhook signature (2), llm client (4)
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tables */
|
||||
/* ------------------------------------------------------------------ */
|
||||
table { font-size: 0.9rem; }
|
||||
table { font-size: 0.9rem; display: block; overflow-x: auto; }
|
||||
table.compact { font-size: 0.82rem; }
|
||||
table.compact th,
|
||||
table.compact td { padding: 0.35rem 0.5rem; }
|
||||
|
||||
6
guarddog_nexus/web/templates/404.html
Normal file
6
guarddog_nexus/web/templates/404.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ t('not_found', request.state.lang) }}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ t('not_found', request.state.lang) }}</h1>
|
||||
<p><a href="/">{{ t('nav_dashboard', request.state.lang) }}</a></p>
|
||||
{% endblock %}
|
||||
@@ -2,9 +2,15 @@
|
||||
{% if total_pages > 1 %}
|
||||
<nav>
|
||||
<ul>
|
||||
<li>{% if page > 1 %}<a href="?page={{ page - 1 }}{% if flagged_filter %}&flagged={{ flagged_filter }}{% endif %}{% if search %}&search={{ search }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}&sort_by={{ sort_by }}&sort_dir={{ sort_dir }}">{{ t('btn_prev', request.state.lang) }}</a>{% else %}<span>{{ t('btn_prev', request.state.lang) }}</span>{% endif %}</li>
|
||||
<li>{% if page > 1 %}<a
|
||||
hx-get="{{ url_prefix or '' }}?page={{ page - 1 }}{% if flagged_filter %}&flagged={{ flagged_filter }}{% endif %}{% if search %}&search={{ search }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}&sort_by={{ sort_by }}&sort_dir={{ sort_dir }}"
|
||||
hx-target="{{ hx_target or '#scans-table-container' }}"
|
||||
hx-swap="innerHTML">{{ t('btn_prev', request.state.lang) }}</a>{% else %}<span>{{ t('btn_prev', request.state.lang) }}</span>{% endif %}</li>
|
||||
<li><small>{{ t('page_label', request.state.lang) }} {{ page }} {{ t('page_of', request.state.lang) }} {{ total_pages }}</small></li>
|
||||
<li>{% if page < total_pages %}<a href="?page={{ page + 1 }}{% if flagged_filter %}&flagged={{ flagged_filter }}{% endif %}{% if search %}&search={{ search }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}&sort_by={{ sort_by }}&sort_dir={{ sort_dir }}">{{ t('btn_next', request.state.lang) }}</a>{% else %}<span>{{ t('btn_next', request.state.lang) }}</span>{% endif %}</li>
|
||||
<li>{% if page < total_pages %}<a
|
||||
hx-get="{{ url_prefix or '' }}?page={{ page + 1 }}{% if flagged_filter %}&flagged={{ flagged_filter }}{% endif %}{% if search %}&search={{ search }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}&sort_by={{ sort_by }}&sort_dir={{ sort_dir }}"
|
||||
hx-target="{{ hx_target or '#scans-table-container' }}"
|
||||
hx-swap="innerHTML">{{ t('btn_next', request.state.lang) }}</a>{% else %}<span>{{ t('btn_next', request.state.lang) }}</span>{% endif %}</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% for s in scans %}
|
||||
<tr>
|
||||
<td><a href="/scans/{{ s.id }}">#{{ s.id }}</a></td>
|
||||
<td>{{ s.package_name }}</td>
|
||||
<td><a href="/packages/{{ s.package_name | urlencode }}/{{ s.package_version | urlencode }}">{{ s.package_name }}</a></td>
|
||||
<td>{{ s.package_version }}</td>
|
||||
<td>{{ s.repository }}</td>
|
||||
<td>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
{% if status == 'scanning' %}<span class="status-scanning"><span class="spinner"></span>scanning</span>{% else %}<span class="status-{{ status }}">{{ status }}</span>{% endif %}
|
||||
{% set label = t('status_' + status, request.state.lang) %}
|
||||
{% if status == 'scanning' %}<span class="status-scanning"><span class="spinner"></span>{{ label }}</span>{% else %}<span class="status-{{ status }}">{{ label }}</span>{% endif %}
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
{% if total_findings %}
|
||||
<div style="display:flex; gap:1.5rem; padding:0.3rem 0; margin-bottom:1rem; border-bottom:1px solid var(--pico-color-gray-500); font-size:0.82rem; opacity:0.8;">
|
||||
<div style="display:flex; gap:1.5rem; padding:0.3rem 0; margin-bottom:1rem; border-bottom:1px solid var(--pico-color-gray-500); font-size:0.82rem; opacity:0.8; flex-wrap:wrap;">
|
||||
<span>{{ t('total_scans_label', request.state.lang) }}: <strong>{{ total_scans }}</strong></span>
|
||||
<span>{{ t('flagged_scans_label', request.state.lang) }}: <strong>{{ flagged_scans }}</strong></span>
|
||||
<span>{{ t('col_findings', request.state.lang) }}: <strong>{{ total_findings }}</strong></span>
|
||||
<span>{{ t('llm_analyzed', request.state.lang) }}: <strong>{{ llm_analyzed }}</strong></span>
|
||||
<span>{{ t('llm_pending', request.state.lang) }}: <strong>{{ llm_pending }}</strong></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if top_rules %}
|
||||
<article class="dash-block" style="margin-bottom:1rem;">
|
||||
<h3>{{ t('heading_top_rules', request.state.lang) }}</h3>
|
||||
<table class="compact">
|
||||
<tbody>
|
||||
{% for r in top_rules %}
|
||||
<tr><td><strong>{{ r.rule }}</strong></td><td>{{ r.count }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% if latest_flagged %}
|
||||
<article class="dash-block dash-block-warn">
|
||||
<h3>{{ t('heading_latest_flagged', request.state.lang) }}</h3>
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<h1>{{ t('heading_packages', request.state.lang) }}</h1>
|
||||
|
||||
<div class="filter-bar">
|
||||
<input type="text" name="search" placeholder="{{ t('filter_search', request.state.lang) }}" value="{{ search }}" hx-get="/packages" hx-trigger="input changed, keyup[entered] delay:300ms" hx-target="#packages-table-container" hx-swap="innerHTML">
|
||||
<input type="hidden" name="flagged" value="{{ flagged_filter }}">
|
||||
<input type="text" name="search" placeholder="{{ t('filter_search', request.state.lang) }}" value="{{ search }}" hx-get="/packages" hx-trigger="input changed, keyup[entered] delay:300ms" hx-target="#packages-table-container" hx-swap="innerHTML" hx-include="[name=flagged]">
|
||||
<a href="?flagged={% if flagged_filter == '1' %}0{% else %}1{% endif %}" role="button" class="outline">
|
||||
{% if flagged_filter == '1' %}{{ t('btn_show_all', request.state.lang) }}{% else %}{{ t('btn_flagged_only', request.state.lang) }}{% endif %}
|
||||
</a>
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
<h1>{{ t('heading_scans', request.state.lang) }}</h1>
|
||||
|
||||
<div class="filter-bar">
|
||||
<input type="text" name="search" placeholder="{{ t('filter_search', request.state.lang) }}" value="{{ search }}" hx-get="/scans" hx-trigger="input changed, keyup[entered] delay:300ms" hx-target="#scans-table-container" hx-swap="innerHTML" hx-include="#status-filter">
|
||||
<select name="status" id="status-filter" hx-get="/scans" hx-trigger="change" hx-target="#scans-table-container" hx-swap="innerHTML" hx-include="[name=search]">
|
||||
<input type="hidden" name="flagged" value="{{ flagged_filter }}">
|
||||
<input type="text" name="search" placeholder="{{ t('filter_search', request.state.lang) }}" value="{{ search }}" hx-get="/scans" hx-trigger="input changed, keyup[entered] delay:300ms" hx-target="#scans-table-container" hx-swap="innerHTML" hx-include="#status-filter,[name=flagged]">
|
||||
<select name="status" id="status-filter" hx-get="/scans" hx-trigger="change" hx-target="#scans-table-container" hx-swap="innerHTML" hx-include="[name=search],[name=flagged]">
|
||||
<option value="">{{ t('filter_all_statuses', request.state.lang) }}</option>
|
||||
<option value="pending" {% if status_filter == 'pending' %}selected{% endif %}>{{ t('filter_pending', request.state.lang) }}</option>
|
||||
<option value="scanning" {% if status_filter == 'scanning' %}selected{% endif %}>{{ t('filter_scanning', request.state.lang) }}</option>
|
||||
|
||||
Reference in New Issue
Block a user