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 %}