atr/templates/keys-upload.html (234 lines of code) (raw):
{% extends "layouts/base.html" %}
{% block title %}
Upload a KEYS file ~ ATR
{% endblock title %}
{% block description %}
Upload a KEYS file containing multiple GPG public keys.
{% endblock description %}
{% block stylesheets %}
{{ super() }}
<style>
.page-rotated-header {
height: 180px;
position: relative;
vertical-align: bottom;
padding-bottom: 5px;
width: 40px;
}
.page-rotated-header>div {
transform-origin: bottom left;
transform: translateX(25px) rotate(-90deg);
position: absolute;
bottom: 12px;
left: 6px;
white-space: nowrap;
text-align: left;
}
.table th,
.table td {
text-align: center;
vertical-align: middle;
}
.table td.page-key-details {
text-align: left;
font-family: ui-monospace, "SFMono-Regular", "Menlo", "Monaco", "Consolas", monospace;
font-size: 0.9em;
word-break: break-all;
}
.page-status-cell-new {
background-color: #197a4e !important;
}
.page-status-cell-existing {
background-color: #868686 !important;
}
.page-status-cell-unknown {
background-color: #ffecb5 !important;
}
.page-status-cell-error {
background-color: #dc3545 !important;
}
.page-status-square {
display: inline-block;
width: 36px;
height: 36px;
vertical-align: middle;
}
.page-table-bordered th,
.page-table-bordered td {
border: 1px solid #dee2e6;
}
tbody tr {
height: 40px;
}
</style>
{% endblock stylesheets %}
{% block content %}
<p>
<a href="{{ as_url(routes.keys.keys) }}" class="atr-back-link">← Back to Manage keys</a>
</p>
<h1>Upload a KEYS file</h1>
<p>Upload a KEYS file containing multiple GPG public keys.</p>
{% if form.errors %}
<h2 class="text-danger">Form errors</h2>
<div class="mt-3 mb-3">
{% for field, errors in form.errors.items() %}
{% for error in errors %}<p class="text-danger mb-1">{{ field }}: {{ error }}</p>{% endfor %}
{% endfor %}
</div>
{% endif %}
{% if results and submitted_committees %}
<h2>KEYS processing results</h2>
<p>
The following keys were found in your KEYS file and processed against the selected committees. Green squares indicate that a key was added, grey squares indicate that a key already existed, and red squares indicate an error.
</p>
<div class="table-responsive">
<table class="table table-striped page-table-bordered table-sm mt-3">
<thead>
<tr>
<th scope="col">Fingerprint</th>
<th scope="col">User ID</th>
{% for committee_name in submitted_committees %}
<th scope="col" class="page-rotated-header">
<div>{{ committee_map.get(committee_name, committee_name) }}</div>
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for key_info in results %}
<tr>
<td class="page-key-details px-2">
<code>{{ key_info.fingerprint[:16]|upper }}</code>
</td>
<td class="page-key-details px-2">{{ key_info.email }}</td>
{% for committee_name in submitted_committees %}
{% set status = key_info.committee_statuses.get(committee_name) %}
{% set cell_class = 'page-status-cell-error' if key_info.status == 'error'
else 'page-status-cell-new' if status == 'newly_linked'
else 'page-status-cell-existing' if status == 'already_linked'
else 'page-status-cell-unknown' %}
{% set title_text = 'Error processing key' if key_info.status == 'error'
else 'Newly linked' if status == 'newly_linked'
else 'Already linked' if status == 'already_linked'
else 'Unknown status' %}
<td class="text-center align-middle page-status-cell-container">
<span class="page-status-square {{ cell_class }}" title="{{ title_text }}"></span>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% set processing_errors = results | selectattr('status', 'equalto', 'error') | list %}
{% if processing_errors %}
<h3 class="text-danger mt-4">Processing errors</h3>
{% for error_info in processing_errors %}
<div class="alert alert-danger p-2 mb-3">
<strong>{{ error_info.key_id }} / {{ error_info.fingerprint }}</strong>: {{ error_info.message }}
</div>
{% endfor %}
{% endif %}
{% endif %}
<form method="post"
class="atr-canary py-4 px-5"
enctype="multipart/form-data">
{# {{ form.csrf_token }} #}
{{ form.hidden_tag() if form.hidden_tag }}
<div class="mb-4">
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-2 text-md-end fw-medium pt-2">{{ form.key.label(class="form-label") }}</div>
<div class="col-md-9">
{{ form.key(class="form-control", aria_describedby="keys-help") }}
<small id="keys-help" class="form-text text-muted mt-2">
Upload a KEYS file containing multiple PGP public keys. The file should contain keys in ASCII-armored format, starting with "-----BEGIN PGP PUBLIC KEY BLOCK-----".
</small>
{% if form.key.errors %}
<div class="invalid-feedback d-block">
{% for error in form.key.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
{% if user_committees %}
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-2 text-md-end fw-medium pt-2">Associate keys with committees</div>
<div class="col-md-9">
<div class="row">
{% for value, label in form.selected_committees.choices %}
<div class="col-sm-12 col-md-6 col-lg-4">
<div class="form-check mb-2">
<input type="checkbox"
name="selected_committees"
value="{{ value }}"
id="committee_{{ loop.index }}"
class="form-check-input" />
<label for="committee_{{ loop.index }}" class="form-check-label">{{ label }}</label>
</div>
</div>
{% else %}
<p class="text-muted fst-italic">No committees available for association.</p>
{% endfor %}
</div>
<div class="mt-2 mb-2">
<button type="button"
id="toggleCommitteesBtn"
class="btn btn-sm btn-outline-secondary">Select all</button>
</div>
<small class="form-text text-muted mt-2">
Select the committees with which to associate these keys. You must be a member of the selected committees.
</small>
{% if form.selected_committees.errors %}
<div class="invalid-feedback d-block">
{% for error in form.selected_committees.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
{% else %}
<div class="row mb-3 pb-3 border-bottom">
<div class="col-md-9 offset-md-2">
<p class="text-danger">You must be a member of at least one committee to add signing keys.</p>
</div>
</div>
{% endif %}
</div>
<div class="mt-4 col-md-9 offset-md-2">
{{ form.submit(class="btn btn-primary") }}
<a href="{{ as_url(routes.keys.keys) }}"
class="btn btn-link text-secondary">Cancel</a>
</div>
</form>
{% endblock content %}
{% block javascripts %}
{{ super() }}
<script>
document.addEventListener("DOMContentLoaded", () => {
const btn = document.getElementById("toggleCommitteesBtn");
const checkboxes = document.querySelectorAll("input[name='selected_committees']");
if (!btn || checkboxes.length === 0) return;
function updateButtonText() {
let allChecked = true;
checkboxes.forEach(cb => {
if (!cb.checked) allChecked = false;
});
btn.textContent = allChecked ? "Select none" : "Select all";
}
btn.addEventListener("click", () => {
let allChecked = true;
checkboxes.forEach(cb => {
if (!cb.checked) allChecked = false;
});
const shouldCheck = !allChecked;
checkboxes.forEach(cb => {
cb.checked = shouldCheck;
});
updateButtonText();
});
checkboxes.forEach(cb => {
cb.addEventListener("change", updateButtonText);
});
updateButtonText();
});
</script>
{% endblock javascripts %}