a11y: Web Interface Guidelines — 19 правок доступности и семантики
- base.html: lang динамический, meta theme-color, skip-link, убран дубль Dashboard - Фильтры: labels на search/select, type=search, placeholder с …, autocomplete=off - llm-retry: <span> → <button> во всех шаблонах (_llm_report, scan, package) - sortable th: tabindex=0, role=button через JS + keyboard handler - sort-icon: aria-hidden=true - style.css: color-scheme:dark, prefers-reduced-motion, tabular-nums, touch-action - app.js: clipboard fallback для не-HTTPS
This commit is contained in:
@@ -1,14 +1,45 @@
|
|||||||
// GuardDog Nexus — shared UI utilities
|
// GuardDog Nexus — shared UI utilities
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.querySelectorAll('th.sortable').forEach(function (th) {
|
||||||
|
th.setAttribute('tabindex', '0');
|
||||||
|
th.setAttribute('role', 'button');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if ((e.key === 'Enter' || e.key === ' ') && e.target.matches('th.sortable')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function copyCode(btn, codeId) {
|
function copyCode(btn, codeId) {
|
||||||
var el = document.getElementById(codeId);
|
var el = document.getElementById(codeId);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(el.textContent).then(function () {
|
navigator.clipboard.writeText(el.textContent).then(function () {
|
||||||
|
showCopied(btn);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for non-HTTPS
|
||||||
|
var ta = document.createElement('textarea');
|
||||||
|
ta.value = el.textContent;
|
||||||
|
ta.style.position = 'fixed';
|
||||||
|
ta.style.left = '-9999px';
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
showCopied(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCopied(btn) {
|
||||||
btn.textContent = 'Copied!';
|
btn.textContent = 'Copied!';
|
||||||
btn.classList.add('copied');
|
btn.classList.add('copied');
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
btn.textContent = 'Copy';
|
btn.textContent = 'Copy';
|
||||||
btn.classList.remove('copied');
|
btn.classList.remove('copied');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/* GuardDog Nexus — Web UI styles */
|
/* GuardDog Nexus — Web UI styles */
|
||||||
|
|
||||||
|
html { color-scheme: dark; }
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Status / severity colours */
|
/* Status / severity colours */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -35,10 +37,16 @@
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Tables */
|
/* Tables */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
table { font-size: 0.9rem; }
|
table { font-size: 0.9rem; touch-action: manipulation; }
|
||||||
table.compact { font-size: 0.82rem; }
|
table.compact { font-size: 0.82rem; }
|
||||||
table.compact th,
|
table.compact th,
|
||||||
table.compact td { padding: 0.35rem 0.5rem; }
|
table.compact td { padding: 0.35rem 0.5rem; }
|
||||||
|
table td:first-child { font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.spinner { animation: none; }
|
||||||
|
.status-scanning { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
table { display: block; overflow-x: auto; white-space: nowrap; }
|
table { display: block; overflow-x: auto; white-space: nowrap; }
|
||||||
@@ -182,9 +190,14 @@ table.compact td { padding: 0.35rem 0.5rem; }
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
border-bottom: 1px dashed;
|
border-bottom: 1px dashed;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
.llm-retry:hover { opacity: 0.8; }
|
.llm-retry:hover { opacity: 0.8; }
|
||||||
|
.llm-retry:focus-visible { outline: 2px solid #2196f3; outline-offset: 2px; }
|
||||||
|
|
||||||
.llm-disclaimer {
|
.llm-disclaimer {
|
||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
<span class="llm-severity">{{ report.severity_rating }}</span>
|
<span class="llm-severity">{{ report.severity_rating }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
||||||
<span class="llm-retry"
|
<button class="llm-retry"
|
||||||
hx-post="/api/v1/findings/{{ finding_id }}/analyze?retry=1"
|
hx-post="/api/v1/findings/{{ finding_id }}/analyze?retry=1"
|
||||||
hx-target="closest .llm-report"
|
hx-target="closest .llm-report"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</span>
|
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-summary">{{ report.summary }}</p>
|
<p class="llm-summary">{{ report.summary }}</p>
|
||||||
|
|||||||
@@ -2,19 +2,19 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sortable {% if sort_by == 'name' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=name&sort_dir={% if sort_by == 'name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'name' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=name&sort_dir={% if sort_by == 'name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_name', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'name' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_name', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'name' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th>{{ t('col_version', request.state.lang) }}</th>
|
<th>{{ t('col_version', request.state.lang) }}</th>
|
||||||
<th>{{ t('col_ecosystem', request.state.lang) }}</th>
|
<th>{{ t('col_ecosystem', request.state.lang) }}</th>
|
||||||
<th>{{ t('col_repo', request.state.lang) }}</th>
|
<th>{{ t('col_repo', request.state.lang) }}</th>
|
||||||
<th class="sortable {% if sort_by == 'flagged' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=flagged&sort_dir={% if sort_by == 'flagged' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'flagged' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=flagged&sort_dir={% if sort_by == 'flagged' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_flagged', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'flagged' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_flagged', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'flagged' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="sortable {% if sort_by == 'total_findings' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=total_findings&sort_dir={% if sort_by == 'total_findings' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'total_findings' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=total_findings&sort_dir={% if sort_by == 'total_findings' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_findings', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'total_findings' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_findings', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'total_findings' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="sortable {% if sort_by == 'last_scanned_at' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=last_scanned_at&sort_dir={% if sort_by == 'last_scanned_at' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'last_scanned_at' %}active{% endif %}" hx-get="/packages?page=1&flagged={{ flagged_filter }}&search={{ search }}&sort_by=last_scanned_at&sort_dir={% if sort_by == 'last_scanned_at' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#packages-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_last_scan', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'last_scanned_at' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_last_scan', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'last_scanned_at' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -2,21 +2,21 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sortable {% if sort_by == 'id' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=id&sort_dir={% if sort_by == 'id' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'id' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=id&sort_dir={% if sort_by == 'id' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_id', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'id' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_id', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'id' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="sortable {% if sort_by == 'package_name' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=package_name&sort_dir={% if sort_by == 'package_name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'package_name' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=package_name&sort_dir={% if sort_by == 'package_name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_package', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'package_name' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_package', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'package_name' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th>{{ t('col_version', request.state.lang) }}</th>
|
<th>{{ t('col_version', request.state.lang) }}</th>
|
||||||
<th>{{ t('col_repo', request.state.lang) }}</th>
|
<th>{{ t('col_repo', request.state.lang) }}</th>
|
||||||
<th class="sortable {% if sort_by == 'status' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=status&sort_dir={% if sort_by == 'status' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'status' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=status&sort_dir={% if sort_by == 'status' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_status', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'status' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_status', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'status' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="sortable {% if sort_by == 'total_findings' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=total_findings&sort_dir={% if sort_by == 'total_findings' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'total_findings' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=total_findings&sort_dir={% if sort_by == 'total_findings' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_findings', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'total_findings' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_findings', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'total_findings' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="sortable {% if sort_by == 'started_at' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=started_at&sort_dir={% if sort_by == 'started_at' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
<th class="sortable {% if sort_by == 'started_at' %}active{% endif %}" hx-get="/scans?page=1&flagged={{ flagged_filter }}&search={{ search }}&status={{ status_filter }}&sort_by=started_at&sort_dir={% if sort_by == 'started_at' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" hx-target="#scans-table-container" hx-swap="innerHTML">
|
||||||
{{ t('col_time', request.state.lang) }} <span class="sort-icon">{% if sort_by == 'started_at' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
{{ t('col_time', request.state.lang) }} <span class="sort-icon" aria-hidden="true">{% if sort_by == 'started_at' %}{{ '▲' if sort_dir == 'asc' else '▼' }}{% else %}↕{% endif %}</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="{{ request.state.lang }}" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#13171f">
|
||||||
<title>{% block title %}GuardDog Nexus{% endblock %}</title>
|
<title>{% block title %}GuardDog Nexus{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="/static/pico.min.css">
|
<link rel="stylesheet" href="/static/pico.min.css">
|
||||||
<script src="/static/htmx.min.js"></script>
|
<script src="/static/htmx.min.js"></script>
|
||||||
@@ -10,11 +11,11 @@
|
|||||||
<script src="/static/app.js"></script>
|
<script src="/static/app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="container">
|
<a href="#main-content" style="position:absolute;left:-9999px;top:0;z-index:999;padding:0.5rem 1rem;background:#2a3140;color:#fff;">Skip to content</a>
|
||||||
|
<main class="container" id="main-content">
|
||||||
<nav class="sticky">
|
<nav class="sticky">
|
||||||
<ul><li><strong><a href="/">{{ t('nav_dashboard', request.state.lang) }}</a></strong></li></ul>
|
<ul><li><strong><a href="/">{{ t('nav_dashboard', request.state.lang) }}</a></strong></li></ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">{{ t('nav_dashboard', request.state.lang) }}</a></li>
|
|
||||||
<li><a href="/scans">{{ t('nav_scans', request.state.lang) }}</a></li>
|
<li><a href="/scans">{{ t('nav_scans', request.state.lang) }}</a></li>
|
||||||
<li><a href="/packages">{{ t('nav_packages', request.state.lang) }}</a></li>
|
<li><a href="/packages">{{ t('nav_packages', request.state.lang) }}</a></li>
|
||||||
<li style="margin-left: 1rem;">
|
<li style="margin-left: 1rem;">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% if total_findings %}
|
{% 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; flex-wrap:wrap;">
|
<div style="display:flex; gap:1.5rem; padding:0.3rem 0; margin-bottom:1rem; border-bottom:1px solid #2a3140; 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('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('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('col_findings', request.state.lang) }}: <strong>{{ total_findings }}</strong></span>
|
||||||
|
|||||||
@@ -64,11 +64,11 @@
|
|||||||
<span class="llm-severity">{{ f.report.severity_rating }}</span>
|
<span class="llm-severity">{{ f.report.severity_rating }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
||||||
<span class="llm-retry"
|
<button class="llm-retry"
|
||||||
hx-post="/api/v1/findings/{{ f.id }}/analyze?retry=1"
|
hx-post="/api/v1/findings/{{ f.id }}/analyze?retry=1"
|
||||||
hx-target="closest .llm-report"
|
hx-target="closest .llm-report"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</span>
|
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-summary">{{ f.report.summary }}</p>
|
<p class="llm-summary">{{ f.report.summary }}</p>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<input type="hidden" name="flagged" value="{{ flagged_filter }}">
|
<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]">
|
<label for="search-pkgs" style="display:none;">{{ t('filter_search', request.state.lang) }}</label>
|
||||||
|
<input type="search" id="search-pkgs" 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]" autocomplete="off">
|
||||||
<a href="?flagged={% if flagged_filter == '1' %}0{% else %}1{% endif %}" role="button" class="outline">
|
<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 %}
|
{% if flagged_filter == '1' %}{{ t('btn_show_all', request.state.lang) }}{% else %}{{ t('btn_flagged_only', request.state.lang) }}{% endif %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -60,11 +60,11 @@
|
|||||||
<span class="llm-severity">{{ f.report.severity_rating }}</span>
|
<span class="llm-severity">{{ f.report.severity_rating }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
{% if config.llm_enabled and not config.llm_auto_analyze %}
|
||||||
<span class="llm-retry"
|
<button class="llm-retry"
|
||||||
hx-post="/api/v1/findings/{{ f.id }}/analyze?retry=1"
|
hx-post="/api/v1/findings/{{ f.id }}/analyze?retry=1"
|
||||||
hx-target="closest .llm-report"
|
hx-target="closest .llm-report"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</span>
|
hx-indicator="closest .llm-report">{{ t('llm_retry', request.state.lang) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-summary">{{ f.report.summary }}</p>
|
<p class="llm-summary">{{ f.report.summary }}</p>
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
|
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<input type="hidden" name="flagged" value="{{ flagged_filter }}">
|
<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]">
|
<label for="search-scans" style="display:none;">{{ t('filter_search', request.state.lang) }}</label>
|
||||||
|
<input type="search" id="search-scans" 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]" autocomplete="off">
|
||||||
|
<label for="status-filter" style="display:none;">Status</label>
|
||||||
<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]">
|
<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="">{{ 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="pending" {% if status_filter == 'pending' %}selected{% endif %}>{{ t('filter_pending', request.state.lang) }}</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user