624 lines
37 KiB
HTML
624 lines
37 KiB
HTML
{% extends "base.html" %}
|
|
{% load static i18n %}
|
|
|
|
{% block title %}{{ source.name }} - {% trans "Source Details" %}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
|
|
<!-- Breadcrumb -->
|
|
<nav aria-label="breadcrumb" class="mb-6">
|
|
<ol class="flex items-center gap-2 text-sm">
|
|
<li>
|
|
<a href="{% url 'source_list' %}"
|
|
class="text-gray-500 hover:text-temple-red transition-colors flex items-center gap-1">
|
|
<i data-lucide="database" class="w-4 h-4"></i>
|
|
{% trans "Source Settings" %}
|
|
</a>
|
|
</li>
|
|
<li class="text-gray-400">/</li>
|
|
<li class="text-temple-red font-semibold">{% trans "Source Detail" %}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Page Header -->
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
|
<div>
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
|
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="database" class="w-6 h-6 text-temple-red"></i>
|
|
</div>
|
|
{{ source.name }}
|
|
</h1>
|
|
<p class="text-gray-500 text-sm sm:text-base">
|
|
{% trans "View and manage integration source details" %}
|
|
</p>
|
|
</div>
|
|
<div class="flex gap-3">
|
|
<button id="toggle-source-status"
|
|
type="button"
|
|
class="inline-flex items-center gap-2 px-5 py-3 border-2 {% if source.is_active %}border-yellow-500 text-yellow-600 hover:bg-yellow-50{% else %}border-emerald-500 text-emerald-600 hover:bg-emerald-50{% endif %} rounded-xl font-semibold transition"
|
|
hx-post="{% url 'toggle_source_status' source.pk %}"
|
|
hx-target="#toggle-source-status"
|
|
hx-select="#toggle-source-status"
|
|
hx-select-oob="#source-status"
|
|
hx-swap="outerHTML"
|
|
hx-confirm="{% blocktrans %}Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?{% endblocktrans %}">
|
|
<i data-lucide="{% if source.is_active %}pause{% else %}play{% endif %}" class="w-5 h-5"></i>
|
|
{% if source.is_active %}{% trans "Deactivate" %}{% else %}{% trans "Activate" %}{% endif %}
|
|
</button>
|
|
<a href="{% url 'source_update' source.pk %}"
|
|
class="inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300 transition">
|
|
<i data-lucide="edit-3" class="w-5 h-5"></i>
|
|
{% trans "Edit" %}
|
|
</a>
|
|
<a href="{% url 'source_delete' source.pk %}"
|
|
class="inline-flex items-center gap-2 px-5 py-3 bg-red-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl transition">
|
|
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
|
{% trans "Delete" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Main Content Column -->
|
|
<div class="lg:col-span-2 space-y-6">
|
|
|
|
<!-- Source Information Card -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">{% trans "Source Information" %}</h2>
|
|
<p class="text-sm text-gray-500">{% trans "Basic integration details" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="tag" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Name" %}
|
|
</label>
|
|
<div class="text-gray-900 font-medium">{{ source.name }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="server" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Type" %}
|
|
</label>
|
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-temple-red/10 text-temple-red border border-temple-red">
|
|
{{ source.get_source_type_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if source.description %}
|
|
<div class="mt-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="align-left" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Description" %}
|
|
</label>
|
|
<div class="text-gray-700 bg-gray-50 rounded-xl p-4">{{ source.description|linebreaks }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="mail" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Contact Email" %}
|
|
</label>
|
|
<div>
|
|
{% if source.contact_email %}
|
|
<a href="mailto:{{ source.contact_email }}" class="text-temple-red hover:text-[#7a1a29] transition-colors">{{ source.contact_email }}</a>
|
|
{% else %}
|
|
<span class="text-gray-400">{% trans "Not specified" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="phone" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Contact Phone" %}
|
|
</label>
|
|
<div>
|
|
{% if source.contact_phone %}
|
|
<span class="text-gray-700">{{ source.contact_phone }}</span>
|
|
{% else %}
|
|
<span class="text-gray-400">{% trans "Not specified" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="power" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Status" %}
|
|
</label>
|
|
<div id="source-status">
|
|
{% if source.is_active %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-sm">
|
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {% trans "Active" %}
|
|
</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 font-semibold text-sm">
|
|
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Inactive" %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="shield" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Requires Authentication" %}
|
|
</label>
|
|
<div>
|
|
{% if source.requires_auth %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-sm">
|
|
<i data-lucide="lock" class="w-4 h-4"></i> {% trans "Yes" %}
|
|
</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 font-semibold text-sm">
|
|
<i data-lucide="unlock" class="w-4 h-4"></i> {% trans "No" %}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if source.webhook_url %}
|
|
<div class="mt-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="link" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Webhook URL" %}
|
|
</label>
|
|
<div class="bg-gray-50 rounded-xl p-4">
|
|
<code class="text-sm text-temple-red break-all">{{ source.webhook_url }}</code>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if source.api_timeout %}
|
|
<div class="mt-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="clock" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "API Timeout" %}
|
|
</label>
|
|
<div class="text-gray-700">{{ source.api_timeout }} {% trans "seconds" %}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if source.notes %}
|
|
<div class="mt-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="file-text" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Notes" %}
|
|
</label>
|
|
<div class="text-gray-700 bg-gray-50 rounded-xl p-4">{{ source.notes|linebreaks }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6 pt-6 border-t border-gray-100">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="calendar-plus" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Created" %}
|
|
</label>
|
|
<div class="text-gray-700">{{ source.created_at|date:"M d, Y H:i" }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Last Updated" %}
|
|
</label>
|
|
<div class="text-gray-700">{{ source.updated_at|date:"M d, Y H:i" }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Integration Logs Card -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white flex justify-between items-center">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="list" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">{% trans "Recent Integration Logs" %}</h2>
|
|
<p class="text-sm text-gray-500">{% trans "Last 10 logs" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-6">
|
|
{% if integration_logs %}
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Timestamp" %}</th>
|
|
<th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Method" %}</th>
|
|
<th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Status" %}</th>
|
|
<th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Response Time" %}</th>
|
|
<th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Details" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-100">
|
|
{% for log in integration_logs %}
|
|
<tr class="hover:bg-gray-50 transition-colors">
|
|
<td class="px-4 py-4 text-sm text-gray-700">
|
|
<small>{{ log.created_at|date:"M d, Y H:i:s" }}</small>
|
|
</td>
|
|
<td class="px-4 py-4">
|
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-gray-100 text-gray-600">
|
|
{{ log.method }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-4">
|
|
{% if log.status_code >= 200 and log.status_code < 300 %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-xs">
|
|
<i data-lucide="check-circle" class="w-3 h-3"></i> {{ log.status_code }}
|
|
</span>
|
|
{% elif log.status_code >= 400 %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-red-100 text-red-700 font-semibold text-xs">
|
|
<i data-lucide="x-circle" class="w-3 h-3"></i> {{ log.status_code }}
|
|
</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-xs">
|
|
<i data-lucide="alert-circle" class="w-3 h-3"></i> {{ log.status_code }}
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-4 py-4 text-sm text-gray-700">
|
|
{% if log.response_time_ms %}
|
|
<small>{{ log.response_time_ms }}ms</small>
|
|
{% else %}
|
|
<span class="text-gray-400">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-4 py-4">
|
|
{% if log.request_data %}
|
|
<button type="button"
|
|
class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-temple-red/30 text-temple-red hover:bg-temple-red hover:text-white transition"
|
|
onclick="openLogDetailModal({{ log.id }})"
|
|
title="{% trans 'View details' %}">
|
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
|
</button>
|
|
{% else %}
|
|
<span class="text-gray-400 text-sm">{% trans "No data" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-12">
|
|
<i data-lucide="clipboard-list" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i>
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">{% trans "No integration logs found" %}</h3>
|
|
<p class="text-gray-500">{% trans "There are no logs recorded for this source yet." %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar Column -->
|
|
<div class="space-y-6">
|
|
|
|
<!-- API Credentials Card -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="key" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">{% trans "API Credentials" %}</h2>
|
|
<p class="text-sm text-gray-500">{% trans "API access keys" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="api-credentials" class="p-6 space-y-4">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="key" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "API Key" %}
|
|
</label>
|
|
<div class="flex">
|
|
<input type="text" value="{{ source.api_key }}" readonly
|
|
class="flex-1 px-4 py-2.5 border border-gray-200 border-r-0 rounded-l-xl bg-gray-50 text-sm focus:outline-none">
|
|
<button type="button"
|
|
hx-post="{% url 'copy_to_clipboard' %}"
|
|
hx-vals='{"text": "{{ source.api_key }}"}'
|
|
title="{% trans 'Copy to clipboard' %}"
|
|
class="inline-flex items-center justify-center px-3 border border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-xl transition">
|
|
<i data-lucide="copy" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="lock" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "API Secret" %}
|
|
</label>
|
|
<div class="flex">
|
|
<input type="password" value="{{ source.api_secret }}" readonly id="api-secret"
|
|
class="flex-1 px-4 py-2.5 border border-gray-200 rounded-l-xl bg-gray-50 text-sm focus:outline-none">
|
|
<button type="button" onclick="toggleSecretVisibility()"
|
|
class="inline-flex items-center justify-center px-3 border border-l-0 border-r-0 border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 transition">
|
|
<i data-lucide="eye" id="secret-toggle-icon" class="w-4 h-4"></i>
|
|
</button>
|
|
<button type="button"
|
|
hx-post="{% url 'copy_to_clipboard' %}"
|
|
hx-vals='{"text": "{{ source.api_secret }}"}'
|
|
title="{% trans 'Copy to clipboard' %}"
|
|
class="inline-flex items-center justify-center px-3 border border-l-0 border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-xl transition">
|
|
<i data-lucide="copy" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="pt-2">
|
|
<a hx-post="{% url 'generate_api_keys' source.pk %}"
|
|
hx-target="#api-credentials"
|
|
hx-select="#api-credentials"
|
|
hx-swap="outerHTML"
|
|
class="inline-flex items-center justify-center gap-2 w-full px-5 py-3 bg-yellow-500 text-white rounded-xl font-semibold shadow hover:shadow-lg transition">
|
|
<i data-lucide="refresh-cw" class="w-5 h-5"></i>
|
|
{% trans "Generate New Keys" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Card -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="bar-chart-2" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">{% trans "Integration Statistics" %}</h2>
|
|
<p class="text-sm text-gray-500">{% trans "API call metrics" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-6 space-y-4">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Total API Calls" %}</label>
|
|
<div class="text-2xl font-bold text-gray-900">{{ total_logs }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Successful Calls" %}</label>
|
|
<div class="text-2xl font-bold text-emerald-600">{{ successful_logs }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Failed Calls" %}</label>
|
|
<div class="text-2xl font-bold text-red-600">{{ failed_logs }}</div>
|
|
</div>
|
|
{% if total_logs > 0 %}
|
|
<div class="pt-4 border-t border-gray-100">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Success Rate" %}</label>
|
|
<div class="text-2xl font-bold text-gray-900">
|
|
{% widthratio successful_logs total_logs 100 %}%
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Detail Modal -->
|
|
{% for log in integration_logs %}
|
|
{% if log.request_data %}
|
|
<div id="logDetailModal{{ log.id }}" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gradient-to-r from-gray-50 to-white">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-bold text-gray-900">{% trans "Integration Log Details" %}</h3>
|
|
<p class="text-sm text-gray-500">ID: {{ log.id }}</p>
|
|
</div>
|
|
</div>
|
|
<button type="button" onclick="closeLogDetailModal({{ log.id }})"
|
|
class="inline-flex items-center justify-center w-10 h-10 rounded-lg text-gray-400 hover:bg-gray-100 transition">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
<div class="p-6 overflow-y-auto max-h-[calc(90vh-80px)]">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Timestamp" %}
|
|
</label>
|
|
<div class="text-gray-700 bg-gray-50 rounded-xl p-3">{{ log.created_at|date:"M d, Y H:i:s" }}</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="git-commit" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Method" %}
|
|
</label>
|
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-gray-100 text-gray-600">
|
|
{{ log.method }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="badge-check" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Status Code" %}
|
|
</label>
|
|
{% if log.status_code >= 200 and log.status_code < 300 %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-sm">
|
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {{ log.status_code }}
|
|
</span>
|
|
{% elif log.status_code >= 400 %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-red-100 text-red-700 font-semibold text-sm">
|
|
<i data-lucide="x-circle" class="w-4 h-4"></i> {{ log.status_code }}
|
|
</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-sm">
|
|
<i data-lucide="alert-circle" class="w-4 h-4"></i> {{ log.status_code }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="zap" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Response Time" %}
|
|
</label>
|
|
<div class="text-gray-700 bg-gray-50 rounded-xl p-3">
|
|
{% if log.response_time_ms %}
|
|
{{ log.response_time_ms }}ms
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="upload" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Request Data" %}
|
|
</label>
|
|
<pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto text-sm"><code>{{ log.request_data|pprint }}</code></pre>
|
|
</div>
|
|
{% if log.response_data %}
|
|
<div class="mb-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="download" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Response Data" %}
|
|
</label>
|
|
<pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto text-sm"><code>{{ log.response_data|pprint }}</code></pre>
|
|
</div>
|
|
{% endif %}
|
|
{% if log.error_message %}
|
|
<div class="mb-6">
|
|
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Error Message" %}
|
|
</label>
|
|
<div class="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700">{{ log.error_message }}</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="px-6 py-4 border-t border-gray-100 bg-gray-50 flex justify-end">
|
|
<button type="button" onclick="closeLogDetailModal({{ log.id }})"
|
|
class="inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300 transition">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
{% trans "Close" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
// Toggle Secret Visibility
|
|
function toggleSecretVisibility() {
|
|
const secretInput = document.getElementById('api-secret');
|
|
const toggleIcon = document.getElementById('secret-toggle-icon');
|
|
|
|
if (secretInput.type === 'password') {
|
|
secretInput.type = 'text';
|
|
toggleIcon.setAttribute('data-lucide', 'eye-off');
|
|
} else {
|
|
secretInput.type = 'password';
|
|
toggleIcon.setAttribute('data-lucide', 'eye');
|
|
}
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Open Log Detail Modal
|
|
function openLogDetailModal(logId) {
|
|
const modal = document.getElementById('logDetailModal' + logId);
|
|
if (modal) {
|
|
modal.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
}
|
|
|
|
// Close Log Detail Modal
|
|
function closeLogDetailModal(logId) {
|
|
const modal = document.getElementById('logDetailModal' + logId);
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
}
|
|
|
|
// Close modal on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
document.querySelectorAll('[id^="logDetailModal"]').forEach(modal => {
|
|
if (!modal.classList.contains('hidden')) {
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
document.querySelectorAll('[id^="logDetailModal"]').forEach(modal => {
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Handle HTMX copy to clipboard feedback
|
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
|
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="copy_to_clipboard"]')) {
|
|
const button = evt.detail.target;
|
|
const originalHTML = button.innerHTML;
|
|
button.innerHTML = '<i data-lucide="check" class="w-4 h-4"></i>';
|
|
button.classList.remove('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600', 'border-gray-200');
|
|
button.classList.add('bg-emerald-500', 'text-white', 'border-emerald-500');
|
|
lucide.createIcons();
|
|
|
|
setTimeout(() => {
|
|
button.innerHTML = originalHTML;
|
|
button.classList.remove('bg-emerald-500', 'text-white', 'border-emerald-500');
|
|
button.classList.add('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600', 'border-gray-200');
|
|
lucide.createIcons();
|
|
}, 2000);
|
|
}
|
|
});
|
|
|
|
// Auto-refresh after status toggle
|
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
|
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="toggle_source_status"]')) {
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 500);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |