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.
105 lines
4.6 KiB
HTML
105 lines
4.6 KiB
HTML
{% 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 %}
|