This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 8d057cf Improve the public signing key tables
8d057cf is described below
commit 8d057cf79a7da015b8980cb3484998b195fc82ac
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jun 18 16:20:31 2025 +0100
Improve the public signing key tables
---
atr/routes/keys.py | 2 +
atr/templates/keys-details.html | 172 ++++++++++++++++++++--------------------
atr/templates/keys-review.html | 120 +++++++++-------------------
3 files changed, 122 insertions(+), 172 deletions(-)
diff --git a/atr/routes/keys.py b/atr/routes/keys.py
index 28518b0..0bdbffb 100644
--- a/atr/routes/keys.py
+++ b/atr/routes/keys.py
@@ -410,6 +410,8 @@ async def keys(session: routes.CommitterSession) -> str:
user_keys = await data.public_signing_key(apache_uid=session.uid,
_committees=True).all()
user_ssh_keys = await data.ssh_key(asf_uid=session.uid).all()
user_committees_with_keys = await
data.committee(name_in=committees_to_query, _public_signing_keys=True).all()
+ for key in user_keys:
+ key.committees.sort(key=lambda c: c.name)
status_message = quart.request.args.get("status_message")
status_type = quart.request.args.get("status_type")
diff --git a/atr/templates/keys-details.html b/atr/templates/keys-details.html
index 4feead3..08bce30 100644
--- a/atr/templates/keys-details.html
+++ b/atr/templates/keys-details.html
@@ -15,98 +15,94 @@
<h1>OpenPGP key details</h1>
- <div class="card p-3 border mb-4">
- <table class="mb-0">
- <tbody>
- <tr>
- <th class="p-2 text-dark">Fingerprint</th>
- <td class="text-break">{{ key.fingerprint.upper() }}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Type</th>
- <td class="text-break">{{ algorithms[key.algorithm] }} ({{
key.length }} bits)</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Created</th>
- <td class="text-break">{{ key.created.strftime("%Y-%m-%d %H:%M:%S")
}}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Latest self signature</th>
- <td class="text-break">
- {{ key.latest_self_signature.strftime("%Y-%m-%d %H:%M:%S") if
key.latest_self_signature else 'Never' }}
- </td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Expires</th>
- <td class="text-break">
- {% if key.expires %}
- {% set days_until_expiry = (key.expires - now).days %}
- {% if days_until_expiry < 0 %}
- <span class="text-danger fw-bold">
- {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- <span class="badge bg-danger text-white ms-2">Expired</span>
- </span>
- {% elif days_until_expiry <= 30 %}
- <span class="text-warning fw-bold">
- {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- <span class="badge bg-warning text-dark ms-2">Expires in {{
days_until_expiry }} days</span>
- </span>
- {% else %}
+ <table class="mb-0 table border border-2 table-striped table-sm">
+ <tbody>
+ <tr>
+ <th class="p-2 text-dark">Fingerprint</th>
+ <td class="text-break align-middle">{{ key.fingerprint.upper() }}</td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Type</th>
+ <td class="text-break align-middle">{{ algorithms[key.algorithm] }}
({{ key.length }} bits)</td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Created</th>
+ <td class="text-break align-middle">{{ key.created.strftime("%Y-%m-%d
%H:%M:%S") }}</td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Latest self signature</th>
+ <td class="text-break align-middle">
+ {{ key.latest_self_signature.strftime("%Y-%m-%d %H:%M:%S") if
key.latest_self_signature else 'Never' }}
+ </td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Expires</th>
+ <td class="text-break align-middle">
+ {% if key.expires %}
+ {% set days_until_expiry = (key.expires - now).days %}
+ {% if days_until_expiry < 0 %}
+ <span class="text-danger fw-bold">
{{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- {% endif %}
+ <span class="badge bg-danger text-white ms-2">Expired</span>
+ </span>
+ {% elif days_until_expiry <= 30 %}
+ <span class="text-warning fw-bold">
+ {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
+ <span class="badge bg-warning text-dark ms-2">Expires in {{
days_until_expiry }} days</span>
+ </span>
{% else %}
- Never
+ {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
{% endif %}
- </td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Primary UID</th>
- <td class="text-break">{{ key.primary_declared_uid or 'Not
specified' }}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Secondary UIDs</th>
- <td class="text-break">
- {{ key.secondary_declared_uids | join(", ") if
key.secondary_declared_uids else 'Not specified' }}
- </td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Apache UID</th>
- <td class="text-break">{{ key.apache_uid }}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark align-top">Associated PMCs</th>
- <td class="text-break pt-2">
- {% if form %}
- <form method="post" novalidate>
- {{ form.hidden_tag() }}
- <div class="row">
- {% for subfield in form.selected_committees %}
- <div class="col-sm-12 col-md-6 col-lg-4">
- <div class="form-check mb-2">
- {{ forms.widget(subfield, classes="form-check-input")
}}
- {{ forms.label(subfield, classes="form-check-label") }}
- </div>
+ {% else %}
+ Never
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Primary UID</th>
+ <td class="text-break align-middle">{{ key.primary_declared_uid or '-'
}}</td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Secondary UIDs</th>
+ <td class="text-break align-middle">
+ {{ key.secondary_declared_uids | join(", ") if
key.secondary_declared_uids else '-' }}
+ </td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark">Apache UID</th>
+ <td class="text-break align-middle">{{ key.apache_uid }}</td>
+ </tr>
+ <tr>
+ <th class="p-2 text-dark align-top">Associated PMCs</th>
+ <td class="text-break pt-2">
+ {% if form %}
+ <form method="post" novalidate>
+ {{ form.hidden_tag() }}
+ <div class="row">
+ {% for subfield in form.selected_committees %}
+ <div class="col-sm-12 col-md-6 col-lg-4">
+ <div class="form-check mb-2">
+ {{ forms.widget(subfield, classes="form-check-input") }}
+ {{ forms.label(subfield, classes="form-check-label") }}
</div>
- {% endfor %}
- </div>
- {{ forms.errors(form.selected_committees,
classes="invalid-feedback d-block") }}
- <div class="mt-3">{{ form.submit(class_='btn btn-primary
btn-sm') }}</div>
- </form>
+ </div>
+ {% endfor %}
+ </div>
+ {{ forms.errors(form.selected_committees,
classes="invalid-feedback d-block") }}
+ <div class="mt-3">{{ form.submit(class_='btn btn-primary
btn-sm') }}</div>
+ </form>
+ {% else %}
+ {% if key.committees %}
+ {{ key.committees|map(attribute='name') |join(', ') }}
{% else %}
- {% if key.committees %}
- {{ key.committees|map(attribute='name') |join(', ') }}
- {% else %}
- No PMCs associated
- {% endif %}
+ No PMCs associated
{% endif %}
- </td>
- </tr>
- </tbody>
- </table>
-
- <details class="mt-3 p-3 bg-light rounded">
- <summary class="fw-bold">View ASCII armored key</summary>
- <pre class="mt-3">{{ key.ascii_armored_key }}</pre>
- </details>
- </div>
+ {% endif %}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <h2>ASCII armored key</h2>
+ <pre class="mt-3 border border-2 p-3">{{ key.ascii_armored_key }}</pre>
+ {# TODO: Add download button for the ASCII armored key #}
{% endblock content %}
diff --git a/atr/templates/keys-review.html b/atr/templates/keys-review.html
index c11d865..f3698c9 100644
--- a/atr/templates/keys-review.html
+++ b/atr/templates/keys-review.html
@@ -19,12 +19,6 @@
<h2 id="your-public-keys">Your public keys</h2>
<p>Review your public keys used for signing release artifacts.</p>
- <div>
- <p>
- Welcome, <strong>{{ asf_id }}</strong>! You are authenticated as an ASF
committer.
- </p>
- </div>
-
<div class="d-flex gap-3 mb-4">
<a href="{{ as_url(routes.keys.add) }}" class="btn
btn-outline-primary">Add your OpenPGP key</a>
<a href="{{ as_url(routes.keys.ssh_add) }}"
@@ -34,84 +28,42 @@
<h3>OpenPGP keys</h3>
{% if user_keys %}
- <div class="mb-5 p-4 bg-light rounded">
- <div class="d-grid gap-4">
- {% for key in user_keys %}
- <div class="card p-3 border">
- <table class="mb-0">
- <tbody>
- <tr>
- <th class="p-2 text-dark">Fingerprint</th>
- <td class="text-break">
- <a href="{{ as_url(routes.keys.details,
fingerprint=key.fingerprint) }}">{{ key.fingerprint.upper() }}</a>
- </td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Type</th>
- <td class="text-break">{{ algorithms[key.algorithm] }} ({{
key.length }} bits)</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Created</th>
- <td class="text-break">{{ key.created.strftime("%Y-%m-%d
%H:%M:%S") }}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Expires</th>
- <td class="text-break">
- {% if key.expires %}
- {% set days_until_expiry = (key.expires - now).days %}
- {% if days_until_expiry < 0 %}
- <span class="text-danger fw-bold">
- {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- <span class="badge bg-danger text-white
ms-2">Expired</span>
- </span>
- {% elif days_until_expiry <= 30 %}
- <span class="text-warning fw-bold">
- {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- <span class="badge bg-warning text-dark
ms-2">Expires in {{ days_until_expiry }} days</span>
- </span>
- {% else %}
- {{ key.expires.strftime("%Y-%m-%d %H:%M:%S") }}
- {% endif %}
- {% else %}
- Never
- {% endif %}
- </td>
- </tr>
- <tr>
- <th class="p-2 text-dark">User ID</th>
- <td class="text-break">{{ key.apache_uid or 'Not specified'
}}</td>
- </tr>
- <tr>
- <th class="p-2 text-dark">Associated PMCs</th>
- <td class="text-break">
- {% if key.committees %}
- {{ key.committees|map(attribute='name') |join(', ') }}
- {% else %}
- No PMCs associated
- {% endif %}
- </td>
- </tr>
- </tbody>
- </table>
-
- <!-- TODO: We could link to a downloadable version of the key
instead -->
- <details class="mt-3 p-3 bg-light rounded">
- <summary class="fw-bold">View whole key</summary>
- <pre class="mt-3">{{ key.ascii_armored_key }}</pre>
- </details>
-
- <form method="post"
- action="{{ as_url(routes.keys.delete) }}"
- class="mt-3"
- onsubmit="return confirm('Are you sure you want to delete
this OpenPGP key?');">
- {{ delete_form.hidden_tag() }}
-
- <input type="hidden" name="fingerprint" value="{{
key.fingerprint }}" />
- {{ delete_form.submit(class_='btn btn-danger', value='Delete
key') }}
- </form>
- </div>
- {% endfor %}
- </div>
+ <div class="table-responsive mb-5">
+ <table class="table border table-striped table-hover table-sm">
+ <thead>
+ <tr>
+ <th class="px-2" scope="col">Key ID</th>
+ <th class="px-2" scope="col">Committees</th>
+ <th class="px-2" scope="col">Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for key in user_keys %}
+ <tr>
+ <td class="text-break px-2 align-middle">
+ <a href="{{ as_url(routes.keys.details,
fingerprint=key.fingerprint) }}">{{ key.fingerprint[-16:]|upper }}</a>
+ </td>
+ <td class="text-break px-2 align-middle">
+ {% if key.committees %}
+ {{ key.committees|map(attribute='name') |join(', ') }}
+ {% else %}
+ No PMCs associated
+ {% endif %}
+ </td>
+ <td class="px-2">
+ <form method="post"
+ action="{{ as_url(routes.keys.delete) }}"
+ class="m-0"
+ onsubmit="return confirm('Are you sure you want to
delete this OpenPGP key?');">
+ {{ delete_form.hidden_tag() }}
+ <input type="hidden" name="fingerprint" value="{{
key.fingerprint }}" />
+ {{ delete_form.submit(class_='btn btn-sm btn-danger',
value='Delete key') }}
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
</div>
{% else %}
<p>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]