blancco: CMMC evidence pack, ServiceNow tick list, cloud-upload status
Automates the Blancco parts of the onsite-support CMMC playbook:
- ENT-056/158 evidence pack: /reports/evidence?month=YYYY-MM returns
ENT-056_<Site>_<YYYY-MM>.zip (summary.csv, README, each report's XML
and PDF). pxe-blancco-evidence.timer saves last month's pack to the
reports share's evidence/ folder on the 1st (Persistent).
- ENT-045/159 ServiceNow tick list: /reports/servicenow lists recent
reports as "Wiped" or "Blancco Wipe failed" with a pre-written
destruction note, copy buttons and Done (CSRF-protected).
- Cloud upload: blancco-progress.py posts each report's upload result
("Sending report document id" -> "Report sent to MC") to
/reports/cloud-status (CSRF-exempt like /imaging/status); the Reports
page and the CSVs show it, so a wipe missing from BMP is visible.
- CSV gains failure_reason (exception messages, failed steps, error
counts), business_location and cloud_upload.
- Preferences: business_location "West Jefferson" (config.BLANCCO_SITE)
and pdf+xml reports; the report copier now copies the PDF too, named
after its XML. .part and hidden files are not listed as reports.
State (cloud confirmations, ServiceNow ticks) is a JSON file at
/var/lib/pxe-webapp/blancco-state.json, off the report share.
This commit is contained in:
@@ -29,6 +29,23 @@
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-title">Drive Erasure Certificates</div>
|
||||
{% if reports %}
|
||||
@@ -40,6 +57,7 @@
|
||||
<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>
|
||||
@@ -55,6 +73,11 @@
|
||||
{% 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) }}"
|
||||
|
||||
104
webapp/templates/reports_servicenow.html
Normal file
104
webapp/templates/reports_servicenow.html
Normal file
@@ -0,0 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}ServiceNow Tick List - PXE Server Manager{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-list-check"></i> ServiceNow Tick List</h1>
|
||||
<div class="header-actions">
|
||||
<a href="{{ url_for('blancco_reports') }}" class="pxe-btn pxe-btn-ghost"><i class="bi bi-arrow-left"></i> Erasure reports</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-title">Onsite Bin Management RITM (ENT-045 / ENT-159)</div>
|
||||
<p class="mb-2">
|
||||
For each wiped asset, open its Onsite Bin Management RITM and tick
|
||||
<strong>Wiped</strong>, or <strong>Blancco Wipe failed</strong> with the destruction note below.
|
||||
Then click <strong>Done</strong> here so it drops off the list.
|
||||
</p>
|
||||
<form method="get" class="d-flex flex-wrap align-items-center gap-2 mb-0">
|
||||
<label for="days" class="mb-0">Last</label>
|
||||
<select id="days" name="days" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
|
||||
{% for d in (7, 30, 90, 366) %}<option value="{{ d }}" {% if d == days %}selected{% endif %}>{{ d }} days</option>{% endfor %}
|
||||
</select>
|
||||
<label class="mb-0 ms-2"><input type="checkbox" name="all" value="1" {% if include_done %}checked{% endif %} onchange="this.form.submit()"> show done</label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
{% if items %}
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Serial</th>
|
||||
<th>Model</th>
|
||||
<th>Wiped (UTC)</th>
|
||||
<th>Tick in ServiceNow</th>
|
||||
<th>Destruction note</th>
|
||||
<th>Cloud</th>
|
||||
<th class="text-end">Done</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr title="{{ it.file }}">
|
||||
<td class="text-nowrap">
|
||||
<span class="mono">{{ it.serial or '-' }}</span>
|
||||
{% if it.serial %}<button type="button" class="btn btn-sm btn-outline-secondary btn-row-action copy-btn" data-copy-text="{{ it.serial }}">copy</button>{% endif %}
|
||||
</td>
|
||||
<td>{{ it.model or '-' }}</td>
|
||||
<td class="mono text-nowrap">{{ it.when.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
{% if it.failed %}<span class="badge badge-danger">Blancco Wipe failed</span>
|
||||
{% else %}<span class="badge badge-success">Wiped</span>{% endif %}
|
||||
</td>
|
||||
<td style="max-width: 28rem;">
|
||||
{% if it.note %}
|
||||
<div class="small">{{ it.note }}</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-row-action copy-btn mt-1" data-copy-text="{{ it.note }}">copy note</button>
|
||||
{% else %}<span class="text-muted">-</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if it.cloud == 'sent' %}<span class="badge badge-success">Sent</span>
|
||||
{% elif it.cloud == 'failed' %}<span class="badge badge-danger">Not sent</span>
|
||||
{% else %}<span class="text-muted">-</span>{% endif %}
|
||||
</td>
|
||||
<td class="text-end text-nowrap">
|
||||
<form method="post" action="{{ url_for('blancco_servicenow_mark', document_id=it.document_id, all='1' if include_done else None) }}" style="display:inline;">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
{% if it.done %}
|
||||
<input type="hidden" name="done" value="0">
|
||||
<button type="submit" class="pxe-btn pxe-btn-ghost btn-row-action" title="Ticked {{ it.done }}">Undo</button>
|
||||
{% else %}
|
||||
<input type="hidden" name="done" value="1">
|
||||
<button type="submit" class="pxe-btn pxe-btn-success btn-row-action">Done</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<p class="mb-1">Nothing left to tick for the last {{ days }} days.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
document.querySelectorAll('.copy-btn').forEach(function (b) {
|
||||
b.addEventListener('click', function () {
|
||||
var t = b.getAttribute('data-copy-text') || '';
|
||||
var done = function () { var o = b.textContent; b.textContent = 'copied'; setTimeout(function () { b.textContent = o; }, 1200); };
|
||||
if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(t).then(done); return; }
|
||||
var ta = document.createElement('textarea'); ta.value = t; document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand('copy'); done(); } finally { document.body.removeChild(ta); }
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user