fix: merge main branch and renumber migration 027→037
Resolve 3 merge conflicts and renumber the automation_hooks migration to follow main's migration chain (036_add_document_translation_fields). Conflicts resolved: - app/api/__init__.py: add automation_router alongside main's new routers - app/utils/settings_service.py: add automation_hooks_enabled alongside compliance_enabled - tests/conftest.py: add AutomationHook alongside AuditLog/ComplianceTemplate imports Migration renumbered: - 027_add_automation_hooks → 037_add_automation_hooks - down_revision: 026_add_scheduled_jobs → 036_add_document_translation_fields Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -1,32 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}API Tokens – DocuElevate{% endblock %}
|
||||
{% block title %}{{ _("api_tokens.page_title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="apiTokens()" x-init="loadTokens()" class="container mx-auto px-4 py-8 max-w-4xl">
|
||||
<header class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<i class="fas fa-key text-yellow-500" aria-hidden="true"></i>
|
||||
API Tokens
|
||||
{{ _("api_tokens.heading") }}
|
||||
</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400 text-sm leading-relaxed max-w-2xl">
|
||||
Create personal API tokens to interact with the DocuElevate API programmatically.
|
||||
Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload
|
||||
or retrieve documents.
|
||||
{{ _("api_tokens.intro") }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Create token section -->
|
||||
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6" aria-labelledby="create-token-heading">
|
||||
<h2 id="create-token-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Create New Token</h2>
|
||||
<h2 id="create-token-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _("api_tokens.create_heading") }}</h2>
|
||||
<form @submit.prevent="createToken()" class="flex flex-col sm:flex-row gap-3">
|
||||
<div class="flex-1">
|
||||
<label for="token-name" class="sr-only">Token name</label>
|
||||
<label for="token-name" class="sr-only">{{ _("api_tokens.token_name_label") }}</label>
|
||||
<input
|
||||
id="token-name"
|
||||
type="text"
|
||||
x-model="newTokenName"
|
||||
placeholder="e.g. CI Pipeline, Webhook Upload, My Script"
|
||||
placeholder="{{ _('api_tokens.token_name_placeholder') }}"
|
||||
required
|
||||
minlength="1"
|
||||
maxlength="255"
|
||||
@@ -44,7 +42,7 @@
|
||||
style="min-height:40px; min-width:44px;"
|
||||
>
|
||||
<i class="fas fa-plus mr-2" aria-hidden="true"></i>
|
||||
<span x-text="creating ? 'Creating…' : 'Create Token'"></span>
|
||||
<span x-text="creating ? '{{ _('api_tokens.creating') }}' : '{{ _('api_tokens.create_token') }}'"></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -54,9 +52,9 @@
|
||||
<div class="flex items-start gap-3">
|
||||
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mt-0.5 text-lg" aria-hidden="true"></i>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">Token created successfully!</p>
|
||||
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">{{ _("api_tokens.token_created") }}</p>
|
||||
<p class="text-green-700 dark:text-green-300 text-xs mt-1">
|
||||
Copy this token now — it will <strong>not be shown again</strong>.
|
||||
{{ _("api_tokens.copy_warning_1") }} <strong>{{ _("api_tokens.copy_warning_2") }}</strong>.
|
||||
</p>
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<code
|
||||
@@ -72,10 +70,10 @@
|
||||
hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500
|
||||
transition-colors"
|
||||
style="min-height:40px; min-width:44px;"
|
||||
:aria-label="copied ? 'Copied!' : 'Copy token to clipboard'"
|
||||
:aria-label="copied ? '{{ _('common.copied') }}' : '{{ _('api_tokens.copy_to_clipboard') }}'"
|
||||
>
|
||||
<i :class="copied ? 'fas fa-check text-green-600' : 'fas fa-copy'" aria-hidden="true"></i>
|
||||
<span class="ml-1 hidden sm:inline" x-text="copied ? 'Copied!' : 'Copy'"></span>
|
||||
<span class="ml-1 hidden sm:inline" x-text="copied ? '{{ _('common.copied') }}' : '{{ _('common.copy') }}'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,11 +86,11 @@
|
||||
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6" aria-labelledby="usage-heading">
|
||||
<h2 id="usage-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-3">
|
||||
<i class="fas fa-code text-blue-500 mr-2" aria-hidden="true"></i>
|
||||
Usage Example
|
||||
{{ _("api_tokens.usage_heading") }}
|
||||
</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm mb-3">
|
||||
Use your API token in the <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded text-xs">Authorization</code>
|
||||
header with any API request:
|
||||
{{ _("api_tokens.usage_intro_pre") }} <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded text-xs">Authorization</code>
|
||||
{{ _("api_tokens.usage_intro_post") }}
|
||||
</p>
|
||||
<div class="relative">
|
||||
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-sm overflow-x-auto font-mono leading-relaxed"><code>curl -X POST "<span x-text="baseUrl"></span>/api/files/ui-upload" \
|
||||
@@ -104,7 +102,7 @@
|
||||
class="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-gray-300 rounded text-xs hover:bg-gray-600
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors"
|
||||
style="min-height:30px; min-width:30px;"
|
||||
aria-label="Copy upload example to clipboard"
|
||||
aria-label="{{ _('api_tokens.copy_upload_example') }}"
|
||||
>
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</button>
|
||||
@@ -114,14 +112,14 @@
|
||||
<!-- Tokens list -->
|
||||
<section class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden" aria-labelledby="tokens-heading">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 id="tokens-heading" class="text-lg font-semibold text-gray-900 dark:text-white">Your Tokens</h2>
|
||||
<h2 id="tokens-heading" class="text-lg font-semibold text-gray-900 dark:text-white">{{ _("api_tokens.your_tokens") }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<template x-if="loading">
|
||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-spinner fa-spin text-2xl mb-2" aria-hidden="true"></i>
|
||||
<p class="text-sm">Loading tokens…</p>
|
||||
<p class="text-sm">{{ _("api_tokens.loading_tokens") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -129,24 +127,24 @@
|
||||
<template x-if="!loading && tokens.length === 0">
|
||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-key text-4xl mb-3 text-gray-300 dark:text-gray-600" aria-hidden="true"></i>
|
||||
<p class="font-medium">No API tokens yet</p>
|
||||
<p class="text-sm mt-1">Create your first token above to get started.</p>
|
||||
<p class="font-medium">{{ _("api_tokens.no_tokens_heading") }}</p>
|
||||
<p class="text-sm mt-1">{{ _("api_tokens.no_tokens_help") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tokens table -->
|
||||
<template x-if="!loading && tokens.length > 0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm" aria-label="API Tokens">
|
||||
<table class="w-full text-sm" aria-label="{{ _('api_tokens.table_aria') }}">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-750 text-left">
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Name</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Token Prefix</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Created</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Last Used</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Last IP</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Status</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider sr-only">Actions</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_name") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_prefix") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_created") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_last_used") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_last_ip") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("common.status") }}</th>
|
||||
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider sr-only">{{ _("common.actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -159,7 +157,7 @@
|
||||
<code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded text-xs font-mono" x-text="token.token_prefix + '…'"></code>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="formatDate(token.created_at)"></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="token.last_used_at ? formatDate(token.last_used_at) : 'Never'"></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="token.last_used_at ? formatDate(token.last_used_at) : '{{ _('api_tokens.never') }}'"></td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400">
|
||||
<code x-show="token.last_used_ip" class="bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded text-xs font-mono" x-text="token.last_used_ip"></code>
|
||||
<span x-show="!token.last_used_ip" class="text-gray-400">—</span>
|
||||
@@ -168,7 +166,7 @@
|
||||
<span
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="token.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'"
|
||||
x-text="token.is_active ? 'Active' : 'Revoked'"
|
||||
x-text="token.is_active ? '{{ _('api_tokens.status_active') }}' : '{{ _('api_tokens.status_revoked') }}'"
|
||||
></span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right">
|
||||
@@ -181,10 +179,10 @@
|
||||
dark:text-red-400 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md
|
||||
focus:outline-none focus:ring-2 focus:ring-red-500 disabled:opacity-50 transition-colors"
|
||||
style="min-height:36px; min-width:44px;"
|
||||
:aria-label="'Revoke token ' + token.name"
|
||||
:aria-label="'{{ _('api_tokens.revoke_prefix') }} ' + token.name"
|
||||
>
|
||||
<i :class="revoking === token.id ? 'fas fa-spinner fa-spin' : 'fas fa-trash-alt'" class="mr-1" aria-hidden="true"></i>
|
||||
Revoke
|
||||
{{ _("api_tokens.revoke") }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user