{# API tokens, as one swappable section. Every state renders through this fragment - empty, listed, freshly issued, refused - so the settings page and the htmx swaps can never disagree about what the section looks like. A freshly issued token is shown exactly once, in selectable mono. Only its salted hash is stored, so no later render of this fragment can repeat it; the warning above the value says so in as many words. A refused submission renders at 200 with the refusal inline: htmx does not swap an error status, so answering 400 would freeze the screen and report nothing. #} {% import "_macros.html" as m %}
{% if api_token_problem %} {{ m.problem(api_token_problem.fix, api_token_problem.output) }} {% endif %} {% if api_token_created %}

The token {{ api_token_created.name }} is shown once and stored hashed, so copy it now. It authenticates as Authorization: Bearer <token> with the {{ api_token_created.scope }} scope.

{{ api_token_created.token }}

{% endif %} {% if api_tokens %} {% for token in api_tokens %} {% endfor %}
Name Scope Created Expires Last used Actions
{{ token.name }} {{ token.scope }} {{ token.created | since }} {% if token.expires %}{{ token.expires | until }}{% else %}Never{% endif %} {% if token.last_used %}{{ token.last_used | since }}{% else %}Never{% endif %} {% if token.revoked %} {{ m.badge("failed", "revoked") }} {% else %} {% endif %}
{% else %}

No tokens issued. A token lets a script reach the API without the master token, narrowed to a scope and revocable on its own.

{% endif %}
Name it after what will hold it, such as ci-deploy.