Webapp redesign phase 5: Blancco reports, report and to-do
Reports (/reports):
- Grouped by month of the erasure date inside each report (not the file
date), newest first; older months fold. Each month header shows the
evidence pack state (builds on the 1st, to upload with Download, or
uploaded).
- One line per report: erasure date, system serial, model, drives,
result ('Successful, fallback' when a failed attempt fell back to
another standard), cloud and ServiceNow state. The row body lists every
drive and erasure attempt with the failure reason.
- Search by system serial, model, drive serial or file; filters for
failed, not in cloud and to tick. 'To tick' uses the to-do's 30-day
window; older unticked reports read 'Not ticked'.
- Export and the evidence pack download moved into header popovers.
Report (/reports/view/<file>):
- Header with serial, model and the per-drive result; a notice names
the drive to destroy until its COD is recorded; Copy ServiceNow note.
- One card per drive with every attempt, a step track and the failure
reason; steps and error counts fold. System, customer and provenance
(integrity signature folded, copyable) on the side. Sizes in decimal
units, as on the list.
- blancco_evidence.failure_note() is shared with the ServiceNow list.
To-do (/reports/todo):
- The ServiceNow tick list is now its first section (period and show
ticked filters); /reports/servicenow redirects there.
- Five counters; one line per item with details in the row body.
- COD recorded, Mark uploaded and Resolved confirm first (the attribute
quoting is safe for JSON) and can be undone for 24 hours from the
page. The header total includes hardware gaps, matching the sidebar.
This commit is contained in:
@@ -1,142 +1,180 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Blancco Reports - PXE Server Manager{% endblock %}
|
||||
{% import '_ui.html' as ui %}
|
||||
{% block title %}Blancco reports - PXE Manager{% endblock %}
|
||||
|
||||
{# Blancco reports: one line per report, grouped by month (the erasure date
|
||||
inside each report). Each row expands to its drives and every erasure
|
||||
attempt; a failed Purge followed by a successful Clear on the same drive
|
||||
counts as wiped and shows as "Successful, fallback". #}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.rgrid { display: grid; grid-template-columns: 116px 110px minmax(0, 1fr) 64px 160px 92px 96px 14px; align-items: center;
|
||||
gap: 0 14px; padding: 11px 120px 11px 24px; font-size: 14px; min-height: 24px; }
|
||||
.rhead { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); padding-top: 6px; padding-bottom: 6px; }
|
||||
.rwhen { font-size: 13px; color: var(--muted); white-space: nowrap; }
|
||||
.month { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 14px 24px 10px; border-top: 1px solid var(--line-2); background: var(--surface-2); }
|
||||
.month > summary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; width: 100%; }
|
||||
.month h3 { margin: 0; font-size: 15px; font-weight: 600; }
|
||||
.month .sp { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.month .sp form { margin: 0; }
|
||||
details.mgroup > summary { list-style: none; }
|
||||
.rbody { padding: 2px 24px 16px 150px; font-size: 13px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.drive { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 16px; align-items: start; padding: 10px 12px; border: 1px solid var(--line-2); border-radius: 8px; }
|
||||
.drive > div:first-child { min-width: 0; overflow-wrap: anywhere; }
|
||||
.drive .attempts { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 4px; }
|
||||
.att { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
|
||||
.att .why { color: var(--fail-text); }
|
||||
.rmeta { display: flex; flex-wrap: wrap; gap: 4px 18px; color: var(--muted); font-size: 12px; }
|
||||
.rmeta .mono { overflow-wrap: anywhere; }
|
||||
.rl-empty { padding: 28px 24px; text-align: center; color: var(--muted); border-top: 1px solid var(--line); }
|
||||
@container (max-width: 1000px) {
|
||||
.rgrid { grid-template-columns: 100px 104px minmax(0, 1fr) 160px 14px; }
|
||||
.rgrid .c-drv, .rgrid .c-cloud, .rgrid .c-sn { display: none; }
|
||||
.rbody { padding-left: 24px; }
|
||||
}
|
||||
@container (max-width: 620px) {
|
||||
.rgrid { grid-template-columns: minmax(0, 1fr) auto 14px; padding: 11px 16px; gap: 2px 10px; }
|
||||
.rgrid .rwhen { grid-column: 1 / -1; }
|
||||
.rgrid .c-model { grid-column: 1 / -1; grid-row: 3; }
|
||||
.rhead { display: none; }
|
||||
.ui-row > .act { position: static; display: flex; justify-content: flex-end; padding: 0 16px 10px; }
|
||||
.rbody { padding: 2px 16px 14px; }
|
||||
.month { padding: 12px 16px 8px; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-shield-check"></i> Blancco Erasure Reports</h1>
|
||||
<div class="header-actions">
|
||||
<span class="badge badge-secondary badge-lg">{{ reports|length }} report{{ 's' if reports|length != 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-title">Export Reports</div>
|
||||
<form method="get" action="{{ url_for('blancco_export_reports') }}" class="d-flex flex-wrap align-items-center gap-2">
|
||||
<label for="exportRange" class="mb-0">Reports from the last</label>
|
||||
<select id="exportRange" name="range" class="form-select form-select-sm w-auto">
|
||||
<option value="1m">1 month</option>
|
||||
<option value="3m">3 months</option>
|
||||
<option value="6m">6 months</option>
|
||||
<option value="12m">12 months</option>
|
||||
</select>
|
||||
<button type="submit" name="format" value="csv" class="pxe-btn pxe-btn-secondary" title="One row per erased drive">
|
||||
<i class="bi bi-filetype-csv"></i> Summary CSV
|
||||
</button>
|
||||
<button type="submit" name="format" value="zip" class="pxe-btn pxe-btn-secondary" title="Original XML/PDF reports plus summary.csv">
|
||||
<i class="bi bi-file-zip"></i> Reports ZIP
|
||||
</button>
|
||||
</form>
|
||||
<p class="small text-muted mt-2 mb-0">Dated by the timestamp inside each report, not the file date.</p>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-title">CMMC Evidence - {{ site }}</div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||
<form method="get" action="{{ url_for('blancco_evidence_pack') }}" class="d-flex flex-wrap align-items-center gap-2">
|
||||
<label for="evidenceMonth" class="mb-0">Evidence pack (ENT-056/158) for</label>
|
||||
<input id="evidenceMonth" type="month" name="month" value="{{ last_month }}" class="form-control form-control-sm w-auto" required>
|
||||
<button type="submit" class="pxe-btn pxe-btn-secondary" title="summary.csv plus every report's XML and PDF for that month">
|
||||
<i class="bi bi-file-zip"></i> Download
|
||||
</button>
|
||||
{% call ui.head('Blancco reports', 'Operate', total ~ ' report' ~ ('s' if total != 1) ~ ' on the share. Dates are the erasure date inside each report.') %}
|
||||
<details class="more">
|
||||
<summary class="ui-btn">Export</summary>
|
||||
<form class="menu" method="get" action="{{ url_for('blancco_export_reports') }}" style="width: min(320px, calc(100vw - 32px)); padding: 14px; gap: 10px">
|
||||
<label for="exportRange" class="sub">Reports from the last</label>
|
||||
<select id="exportRange" name="range" class="field">
|
||||
<option value="1m">1 month</option><option value="3m">3 months</option><option value="6m">6 months</option><option value="12m">12 months</option>
|
||||
</select>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<button type="submit" name="format" value="csv" class="ui-btn" title="One row per erased drive">Summary CSV</button>
|
||||
<button type="submit" name="format" value="zip" class="ui-btn" title="Original XML/PDF reports plus summary.csv">Reports ZIP</button>
|
||||
</div>
|
||||
<div class="sub" style="white-space: normal">Dated by the timestamp inside each report.</div>
|
||||
</form>
|
||||
<a href="{{ url_for('blancco_servicenow') }}" class="pxe-btn pxe-btn-secondary" title="What to tick on each Onsite Bin Management RITM">
|
||||
<i class="bi bi-list-check"></i> ServiceNow tick list (ENT-045/159)
|
||||
</a>
|
||||
</div>
|
||||
<p class="small text-muted mt-2 mb-0">The evidence pack for last month is also saved automatically to <span class="mono">evidence\</span> on the reports share on the 1st. Certificates of record are in the Blancco Management Portal.</p>
|
||||
</div>
|
||||
</details>
|
||||
<details class="more">
|
||||
<summary class="ui-btn">Evidence pack</summary>
|
||||
<form class="menu" method="get" action="{{ url_for('blancco_evidence_pack') }}" style="width: min(320px, calc(100vw - 32px)); padding: 14px; gap: 10px">
|
||||
<label for="evidenceMonth" class="sub">ENT-056/158 evidence pack for</label>
|
||||
<input id="evidenceMonth" type="month" name="month" value="{{ last_month }}" class="field" required>
|
||||
<button type="submit" class="ui-btn pri">Download</button>
|
||||
<div class="sub" style="white-space: normal">Built automatically into <span class="mono">evidence\</span> on the reports share on the 1st.</div>
|
||||
</form>
|
||||
</details>
|
||||
<a class="ui-btn pri" href="{{ url_for('blancco_todo') }}#sn">ServiceNow tick list <span class="chip c-neutral" style="font-size: 11px; padding: 1px 6px">{{ todo.servicenow }}</span></a>
|
||||
{% endcall %}
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-title">Drive Erasure Certificates</div>
|
||||
{% if reports %}
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Serial</th>
|
||||
<th>Model</th>
|
||||
<th>Date</th>
|
||||
<th>Result</th>
|
||||
<th title="Whether Blancco reported uploading this report to the Blancco cloud">Cloud</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in reports %}
|
||||
<tr title="{{ r.filename }}">
|
||||
<td>{% if r.serial %}<span class="mono">{{ r.serial }}</span>{% else %}<span class="text-muted">-</span>{% endif %}</td>
|
||||
<td>{{ r.model or '-' }}</td>
|
||||
<td>{{ r.modified | timestamp_fmt }}</td>
|
||||
<td>
|
||||
{% if r.state == 'Successful' %}<span class="badge badge-success">Successful</span>
|
||||
{% elif r.state == 'Failed' %}<span class="badge badge-danger">Failed</span>
|
||||
{% elif r.state %}<span class="badge badge-secondary">{{ r.state }}</span>
|
||||
{% else %}<span class="text-muted">-</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.cloud == 'sent' %}<span class="badge badge-success">Sent</span>
|
||||
{% elif r.cloud == 'failed' %}<span class="badge badge-danger" title="Resend from Blancco, or upload the XML to the Blancco portal">Not sent</span>
|
||||
{% else %}<span class="text-muted" title="No upload status recorded (older report, or saved manually)">-</span>{% endif %}
|
||||
</td>
|
||||
<td class="actions text-end text-nowrap">
|
||||
{% if r.filename.lower().endswith('.xml') %}
|
||||
<a href="{{ url_for('blancco_view_report', filename=r.filename) }}"
|
||||
class="pxe-btn pxe-btn-success btn-row-action" title="View formatted report">
|
||||
<i class="bi bi-eye"></i> View
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('blancco_download_report', filename=r.filename) }}"
|
||||
class="pxe-btn pxe-btn-secondary btn-row-action" title="Download">
|
||||
<i class="bi bi-download"></i> Download
|
||||
</a>
|
||||
<button type="button" class="pxe-btn pxe-btn-danger btn-row-action"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-filename="{{ r.filename }}" title="Delete">
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<section class="panel ui-list" aria-labelledby="rl-h">
|
||||
<h2 id="rl-h" class="vh">Reports by month</h2>
|
||||
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 16px 24px">
|
||||
<div role="search" style="flex: 1 1 220px; min-width: 160px; display: flex">
|
||||
<label for="rq" class="vh">Search by system serial, model, drive serial or file name</label>
|
||||
<input id="rq" type="search" class="field" style="flex: 1; min-width: 0" placeholder="System serial, model, drive serial or file">
|
||||
</div>
|
||||
<div role="group" aria-label="Show only" id="rfilters" style="display: flex; gap: 6px; flex-wrap: wrap">
|
||||
<button type="button" class="fbtn" data-f="" aria-pressed="true">All</button>
|
||||
<button type="button" class="fbtn" data-f="failed" aria-pressed="false">Failed {{ counts.failed }}</button>
|
||||
<button type="button" class="fbtn" data-f="cloud" aria-pressed="false">Not in cloud {{ counts.cloud }}</button>
|
||||
<button type="button" class="fbtn" data-f="tick" aria-pressed="false">To tick {{ counts.tick }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rgrid rhead" aria-hidden="true"><span>Erased</span><span>System serial</span><span>Model</span><span class="c-drv">Drives</span><span>Result</span><span class="c-cloud">Cloud</span><span class="c-sn">ServiceNow</span><span></span></div>
|
||||
|
||||
{% for g in groups %}
|
||||
{% set open_month = loop.index <= 2 %}
|
||||
<details class="mgroup" {% if open_month %}open{% endif %} data-month="{{ g.month }}">
|
||||
<summary class="month">
|
||||
<h3>{{ g.label }}</h3>
|
||||
<span class="sub">{{ g.rows | length }} report{{ 's' if g.rows | length != 1 }}{% if g.failed %} | {{ g.failed }} failed{% endif %}</span>
|
||||
<span class="sp">
|
||||
{% if g.month == this_month %}<span class="sub">Evidence pack builds on the 1st</span>
|
||||
{% elif g.uploaded %}{{ ui.chip('Evidence pack uploaded', 'ok') }}
|
||||
{% elif g.pack %}{{ ui.chip('Evidence pack to upload', 'act') }}
|
||||
<a class="ui-btn sm" href="{{ url_for('blancco_evidence_file', filename=g.pack) }}">Download pack</a>
|
||||
{% endif %}
|
||||
<span class="chev" aria-hidden="true">▾</span>
|
||||
</span>
|
||||
</summary>
|
||||
{% for r in g.rows %}
|
||||
{% set failed = r.state == 'Failed' %}
|
||||
<div class="ui-row rrow" data-f="{{ 'failed ' if failed }}{{ 'cloud ' if r.cloud == 'failed' }}{{ 'tick' if r.to_tick }}"
|
||||
data-q="{{ (r.serial ~ ' ' ~ r.model ~ ' ' ~ r.filename ~ ' ' ~ (r.drives | map(attribute='serial') | join(' ')) ~ ' ' ~ (r.drives | map(attribute='model') | join(' '))) | lower }}">
|
||||
<details>
|
||||
<summary class="rgrid">
|
||||
<span class="rwhen mono">{{ r.when.strftime('%d %b %-I:%M %p') }}</span>
|
||||
<span class="mono">{{ r.serial or '-' }}</span>
|
||||
<span class="tip c-model" data-tip="{{ r.model }}"><span class="trunc">{{ r.model or r.filename }}</span></span>
|
||||
<span class="c-drv sub">{% if r.drives %}{{ r.drives | length }} drive{{ 's' if r.drives | length != 1 }}{% else %}{{ r.type }}{% endif %}</span>
|
||||
<span>{% if r.state == 'Successful' %}{{ ui.chip('Successful, fallback' if r.fallback else 'Successful', 'ok') }}
|
||||
{% elif failed %}{{ ui.chip('Failed', 'fail') }}
|
||||
{% elif r.parse_error %}{{ ui.chip('Unreadable', 'err') }}
|
||||
{% else %}<span class="sub">no erasure</span>{% endif %}</span>
|
||||
<span class="c-cloud">{% if r.cloud == 'sent' %}{{ ui.chip('Sent', 'ok') }}{% elif r.cloud == 'failed' %}{{ ui.chip('Not sent', 'err') }}{% else %}<span class="sub">no status</span>{% endif %}</span>
|
||||
<span class="c-sn">{% if r.ticked %}{{ ui.chip('Ticked', 'neutral') }}{% elif r.to_tick %}{{ ui.chip('To tick', 'act') }}{% elif r.key %}{{ ui.chip('Not ticked', 'off') }}{% endif %}</span>
|
||||
<span class="chev" aria-hidden="true">▾</span>
|
||||
</summary>
|
||||
<div class="rbody">
|
||||
{% for d in r.drives %}
|
||||
<div class="drive">
|
||||
<div><strong>{{ d.model or 'Drive' }}</strong> <span class="mono">{{ d.serial }}</span></div>
|
||||
<div class="sub">{% if d.capacity %}{{ ((d.capacity | int) / 1e9) | round(0) | int }} GB{% endif %}{% if d.interface %} | {{ d.interface }}{% endif %}</div>
|
||||
<div class="attempts">
|
||||
{% for a in d.attempts %}
|
||||
<div class="att">{{ ui.chip('Erased' if a.ok else 'Failed', 'ok' if a.ok else 'fail') }}<span>{{ a.standard }}</span><span class="sub">{{ a.elapsed }}</span>{% if a.why %}<span class="why">{{ a.why }}</span>{% endif %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if r.fallback %}<p class="sub" style="margin: 0">Counts as successful: every drive has a successful erasure; a failed attempt fell back to another standard.</p>{% endif %}
|
||||
{% if failed %}<div class="notice"><span aria-hidden="true" style="font-weight: 700">!</span><span>A drive must be destroyed. This report is on the <a href="{{ url_for('blancco_todo') }}">to-do</a> for a Certificate of Destruction{% if r.cloud == 'failed' %} and for the Blancco cloud{% endif %}.</span></div>{% endif %}
|
||||
{% if r.parse_error %}<div class="errbox">Could not read this report: {{ r.parse_error }}</div>{% endif %}
|
||||
<div class="rmeta"><span>File <span class="mono">{{ r.filename }}</span></span>{% if r.document_id %}<span>Document <span class="mono">{{ r.document_id }}</span></span>{% endif %}<span>{{ (r.size or 0) | filesizeformat }}</span></div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="act">
|
||||
{% if r.filename.lower().endswith('.xml') %}<a class="ui-btn" href="{{ url_for('blancco_view_report', filename=r.filename) }}">View</a>{% endif %}
|
||||
{% call ui.more(r.serial or r.filename) %}
|
||||
<a href="{{ url_for('blancco_download_report', filename=r.filename) }}">Download {{ r.type }}</a>
|
||||
<button type="button" class="copy-btn" data-copy-text="{{ r.filename }}">Copy file name</button>
|
||||
<div class="sep"></div>
|
||||
<button type="button" class="del" data-bs-toggle="modal" data-bs-target="#deleteModal" data-filename="{{ r.filename }}">Delete report...</button>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<p class="mt-2"><i class="bi bi-inbox" style="font-size:1.75rem;"></i></p>
|
||||
<p class="mb-1">No erasure reports yet.</p>
|
||||
<p class="small">Reports will appear here after Blancco Drive Eraser completes a wipe.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="rl-empty">No erasure reports yet. They appear here after Blancco Drive Eraser completes a wipe and saves its report to <span class="mono">\\172.16.9.1\blancco-reports</span>.</div>
|
||||
{% endfor %}
|
||||
<div class="hidden-bar" id="rnomatch" hidden><span>No report matches.</span></div>
|
||||
</section>
|
||||
|
||||
<div class="pxe-card mt-3">
|
||||
<h3 class="pxe-card-title"><i class="bi bi-hdd-network"></i> Report Storage</h3>
|
||||
<p class="mb-1">
|
||||
Blancco Drive Eraser saves erasure certificates to the network share
|
||||
<span class="mono">\\172.16.9.1\blancco-reports</span>.
|
||||
</p>
|
||||
<p class="mb-0 text-muted">
|
||||
Reports are generated automatically after each drive wipe and contain proof of erasure for compliance and audit purposes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||||
<!-- Delete confirmation -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteTitle">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="deleteForm" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Confirm Delete</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<h3 class="modal-title" id="deleteTitle">Delete report</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete <strong id="deleteFilename"></strong>?</p>
|
||||
<p class="text-muted mb-0">Erasure reports may be needed for compliance audits. This action cannot be undone.</p>
|
||||
<p>Delete <strong id="deleteFilename" class="mono"></strong> from the share?</p>
|
||||
<p class="muted mb-0">Erasure reports are compliance evidence. This cannot be undone.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="pxe-btn pxe-btn-ghost" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="pxe-btn pxe-btn-danger">Delete</button>
|
||||
<button type="button" class="ui-btn pri" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="ui-btn danger">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -146,11 +184,38 @@
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
document.getElementById('deleteModal').addEventListener('show.bs.modal', function (event) {
|
||||
var btn = event.relatedTarget;
|
||||
var filename = btn.getAttribute('data-filename');
|
||||
document.getElementById('deleteFilename').textContent = filename;
|
||||
document.getElementById('deleteForm').action = "{{ url_for('blancco_delete_report', filename='__F__') }}".replace('__F__', encodeURIComponent(filename));
|
||||
});
|
||||
(function () {
|
||||
var modal = document.getElementById('deleteModal');
|
||||
modal.addEventListener('show.bs.modal', function (e) {
|
||||
var filename = e.relatedTarget ? e.relatedTarget.getAttribute('data-filename') : '';
|
||||
document.getElementById('deleteFilename').textContent = filename;
|
||||
document.getElementById('deleteForm').action = "{{ url_for('blancco_delete_report', filename='__F__') }}".replace('__F__', encodeURIComponent(filename));
|
||||
});
|
||||
// Search and filters: rows hide; a month with no visible row hides; a
|
||||
// search or filter opens the months so matches are seen.
|
||||
var q = document.getElementById('rq'), f = '';
|
||||
function apply() {
|
||||
var needle = q.value.trim().toLowerCase(), any = false;
|
||||
document.querySelectorAll('details.mgroup').forEach(function (g) {
|
||||
var shown = 0;
|
||||
g.querySelectorAll('.rrow').forEach(function (r) {
|
||||
var ok = (!needle || r.getAttribute('data-q').indexOf(needle) >= 0) && (!f || r.getAttribute('data-f').indexOf(f) >= 0);
|
||||
r.hidden = !ok; if (ok) shown++;
|
||||
});
|
||||
g.hidden = shown === 0;
|
||||
if ((needle || f) && shown) g.open = true;
|
||||
if (shown) any = true;
|
||||
});
|
||||
document.getElementById('rnomatch').hidden = any;
|
||||
}
|
||||
q.addEventListener('input', apply);
|
||||
document.querySelectorAll('#rfilters .fbtn').forEach(function (b) {
|
||||
b.addEventListener('click', function () {
|
||||
f = b.getAttribute('data-f') || '';
|
||||
document.querySelectorAll('#rfilters .fbtn').forEach(function (x) { x.setAttribute('aria-pressed', x === b ? 'true' : 'false'); });
|
||||
apply();
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user