| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- {% extends 'layout.twig' %}
- {% block title %}Tokens — IRDB{% endblock %}
- {% block content %}
- {% import 'partials/sort.twig' as sort %}
- <div class="mx-auto max-w-5xl">
- <div class="flex items-center justify-between">
- <h1 class="text-2xl font-semibold tracking-tight">API tokens</h1>
- <span class="text-sm text-slate-500 dark:text-slate-400">{{ list.total|default(0) }} total</span>
- </div>
- {% if can_write %}
- <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900"
- x-data="kindSwitcher" data-initial-kind="admin">
- <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Issue token</h2>
- <form method="post" action="/app/tokens" class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-3 text-sm">
- <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
- <div>
- <label for="t-kind" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Kind</label>
- <select id="t-kind" name="kind" x-model="kind"
- class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
- <option value="admin">admin</option>
- <option value="reporter">reporter</option>
- <option value="consumer">consumer</option>
- </select>
- </div>
- <div x-show="isKind('admin')">
- <label for="t-role" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Role</label>
- <select id="t-role" name="role"
- class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
- <option value="viewer">viewer</option>
- <option value="operator">operator</option>
- <option value="admin">admin</option>
- </select>
- </div>
- <div x-show="isKind('reporter')">
- <label for="t-rep" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Reporter</label>
- <select id="t-rep" name="reporter_id"
- class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
- <option value="">— pick one —</option>
- {% for r in reporters %}
- <option value="{{ r.id }}">{{ r.name }}</option>
- {% endfor %}
- </select>
- </div>
- <div x-show="isKind('consumer')">
- <label for="t-con" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Consumer</label>
- <select id="t-con" name="consumer_id"
- class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
- <option value="">— pick one —</option>
- {% for c in consumers %}
- <option value="{{ c.id }}">{{ c.name }}</option>
- {% endfor %}
- </select>
- </div>
- <div class="md:col-span-3 flex justify-end">
- <button type="submit" class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-indigo-500">Issue</button>
- </div>
- </form>
- </section>
- {% endif %}
- <section class="mt-6 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
- <table class="w-full text-sm" data-sortable-table="tokens-index">
- <thead class="border-b border-slate-200 bg-slate-50 text-left text-xs uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">
- <tr>
- {{ sort.th('Kind', 'kind') }}
- {{ sort.th('Prefix', 'prefix') }}
- {{ sort.th('Role / target', 'role_target') }}
- {{ sort.th('Issuer', 'issuer') }}
- {{ sort.th('Last used', 'last_used', 'date') }}
- {{ sort.th('Status', 'status') }}
- {% if can_write %}<th class="px-4 py-2 text-right font-medium">Actions</th>{% endif %}
- </tr>
- </thead>
- <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
- {% for t in list.data|default([]) %}
- {% set role_target_value = (t.kind == 'admin') ? ('role:' ~ (t.role|default(''))) : ((t.kind == 'reporter') ? ('reporter:' ~ t.reporter_id) : ((t.kind == 'consumer') ? ('consumer:' ~ t.consumer_id) : '')) %}
- <tr>
- <td class="px-4 py-2 font-mono text-xs uppercase" data-sort-value="{{ t.kind }}">{{ t.kind }}</td>
- <td class="px-4 py-2 font-mono" data-sort-value="{{ t.token_prefix }}">{{ t.token_prefix }}</td>
- <td class="px-4 py-2 text-slate-600 dark:text-slate-300" data-sort-value="{{ role_target_value }}">
- {%- if t.kind == 'admin' -%}role: <span class="font-mono">{{ t.role|default('—') }}</span>
- {%- elseif t.kind == 'reporter' -%}reporter #{{ t.reporter_id }}
- {%- elseif t.kind == 'consumer' -%}consumer #{{ t.consumer_id }}
- {%- else -%}—{%- endif -%}
- </td>
- <td class="px-4 py-2 text-slate-600 dark:text-slate-300" data-sort-value="{{ t.user_label|default('') }}">
- {%- if t.user_label -%}
- {{ t.user_label }}
- {%- elseif t.user_id -%}
- <span class="text-slate-400" title="Issuer was deleted">user #{{ t.user_id }}</span>
- {%- else -%}
- <span class="text-slate-400" title="Token is not bound to a user (legacy or console-issued)">—</span>
- {%- endif -%}
- </td>
- <td class="px-4 py-2 text-slate-500 dark:text-slate-400" data-sort-value="{{ t.last_used_at|default('') }}">{% if t.last_used_at %}<time class="irdb-dt" datetime="{{ t.last_used_at }}">{{ t.last_used_at }}</time>{% else %}never{% endif %}</td>
- <td class="px-4 py-2" data-sort-value="{{ t.revoked_at ? 'revoked' : 'active' }}">
- {% if t.revoked_at %}
- <span class="rounded bg-slate-100 px-1.5 py-0.5 text-xs uppercase text-slate-500 dark:bg-slate-800 dark:text-slate-400">revoked</span>
- {% else %}
- <span class="rounded bg-emerald-100 px-1.5 py-0.5 text-xs uppercase text-emerald-800 dark:bg-emerald-900 dark:text-emerald-100">active</span>
- {% endif %}
- </td>
- {% if can_write %}
- <td class="px-4 py-2 text-right">
- {% if not t.revoked_at %}
- {% include 'partials/confirm_form.twig' with {
- action: '/app/tokens/' ~ t.id ~ '/delete',
- label: 'Revoke',
- description: 'Revoke this token immediately. Clients using it will start getting 401.',
- } only %}
- {% else %}
- {% include 'partials/confirm_form.twig' with {
- action: '/app/tokens/' ~ t.id ~ '/purge',
- label: 'Remove',
- description: 'Permanently delete this revoked token row. The audit log entry referring to its prefix will remain.',
- } only %}
- {% endif %}
- </td>
- {% endif %}
- </tr>
- {% else %}
- <tr><td colspan="7" class="px-4 py-6 text-center text-slate-400">No tokens.</td></tr>
- {% endfor %}
- </tbody>
- </table>
- </section>
- </div>
- {% if just_created %}
- <div x-data="rawTokenCopy" x-show="open"
- class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/70 px-4">
- <div class="w-full max-w-lg rounded-2xl border border-amber-300 bg-white p-6 shadow-2xl dark:border-amber-700 dark:bg-slate-900">
- <h2 class="text-lg font-semibold text-amber-700 dark:text-amber-300">Copy this token now</h2>
- <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
- This is the only time you'll see the raw token. Refreshing this page or closing the dialog discards it permanently.
- If you lose it, revoke it and issue a new one.
- </p>
- <div class="mt-4">
- <label class="block text-xs font-medium text-slate-500 dark:text-slate-400">Kind: <span class="font-mono">{{ just_created.kind }}</span> · prefix: <span class="font-mono">{{ just_created.token_prefix }}</span></label>
- <div class="mt-1 flex items-center gap-2">
- <input id="raw-token" type="text" readonly value="{{ just_created.raw_token }}"
- class="w-full rounded-md border border-slate-300 bg-slate-50 px-3 py-2 font-mono text-xs dark:border-slate-700 dark:bg-slate-950">
- <button type="button"
- x-on:click="copy()"
- class="rounded-md border border-slate-300 px-3 py-2 text-xs hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800">Copy</button>
- </div>
- </div>
- <div class="mt-6 flex justify-end">
- <button type="button" x-on:click="hide()"
- class="rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-500">I have stored it safely</button>
- </div>
- </div>
- </div>
- {% endif %}
- {% endblock %}
|