330 lines
15 KiB
HTML
330 lines
15 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Integration Settings" %}{% endblock %}
|
|
|
|
{% block customCSS %}
|
|
<style>
|
|
/* Card Hover Effects */
|
|
.settings-card {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.settings-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
/* Table Row Hover */
|
|
.table-row {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.table-row:hover {
|
|
background-color: rgba(157, 34, 53, 0.02);
|
|
transform: scale(1.01);
|
|
}
|
|
|
|
/* Button Hover Effects */
|
|
.btn-action {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-action:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.btn-primary {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
|
|
}
|
|
|
|
.btn-secondary {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: rgba(157, 34, 53, 0.05);
|
|
border-color: rgba(157, 34, 53, 0.3);
|
|
}
|
|
|
|
/* Input Focus Animation */
|
|
.search-input {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.search-input:focus {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Secret Value Toggle */
|
|
.secret-value {
|
|
transition: all 0.2s ease;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.secret-value:hover {
|
|
color: #9d2235 !important;
|
|
}
|
|
|
|
/* Empty State Animation */
|
|
.empty-icon {
|
|
animation: float 3s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% {
|
|
transform: translateY(0px);
|
|
}
|
|
50% {
|
|
transform: translateY(-10px);
|
|
}
|
|
}
|
|
</style>
|
|
{% 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 'settings' %}"
|
|
class="text-gray-500 hover:text-temple-red transition-colors flex items-center gap-1">
|
|
<i data-lucide="settings" class="w-4 h-4"></i>
|
|
{% trans "Settings" %}
|
|
</a>
|
|
</li>
|
|
<li class="text-gray-400">/</li>
|
|
<li class="text-temple-red font-semibold flex items-center gap-1">
|
|
<i data-lucide="link-2" class="w-4 h-4"></i>
|
|
{% trans "Integrations" %}
|
|
</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="sliders-horizontal" class="w-6 h-6 text-temple-red"></i>
|
|
</div>
|
|
{% trans "Integration Settings" %}
|
|
</h1>
|
|
<p class="text-gray-500 text-sm sm:text-base">
|
|
{% trans "Manage API keys, Webhooks, and external service configurations." %}
|
|
</p>
|
|
</div>
|
|
<a href="{% url 'settings_create' %}"
|
|
class="btn-primary inline-flex items-center gap-2 px-6 py-3 bg-temple-red text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
|
<i data-lucide="plus-circle" class="w-5 h-5"></i>
|
|
{% trans "Add New Setting" %}
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Search Card -->
|
|
<div class="settings-card bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
|
|
<div class="p-4 sm:p-6">
|
|
<form method="get" class="flex flex-col sm:flex-row gap-3">
|
|
<div class="flex-1 relative">
|
|
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400">
|
|
<i data-lucide="search" class="w-5 h-5"></i>
|
|
</span>
|
|
<input type="text"
|
|
class="search-input w-full pl-12 pr-4 py-3 rounded-xl border-2 border-gray-200 bg-white focus:outline-none focus:border-temple-red focus:ring-4 focus:ring-temple-red/10 text-gray-900 placeholder-gray-400"
|
|
name="q"
|
|
placeholder="{% trans 'Search by name, key, or category...' %}"
|
|
value="{{ search_query }}">
|
|
</div>
|
|
<div class="flex gap-3">
|
|
<button type="submit"
|
|
class="btn-action inline-flex items-center gap-2 px-6 py-3 bg-temple-red text-white rounded-xl font-semibold shadow-md hover:shadow-lg">
|
|
<i data-lucide="search" class="w-5 h-5"></i>
|
|
{% trans "Search" %}
|
|
</button>
|
|
{% if search_query %}
|
|
<a href="{% url 'settings_list' %}"
|
|
class="btn-secondary inline-flex items-center gap-2 px-6 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
|
<i data-lucide="x-circle" class="w-5 h-5"></i>
|
|
{% trans "Clear" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if page_obj %}
|
|
<!-- Settings Table Card -->
|
|
<div class="settings-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<thead class="bg-gradient-to-r from-gray-50 to-white">
|
|
<tr>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-gray-700 border-b border-gray-200">
|
|
<div class="flex items-center gap-2">
|
|
<i data-lucide="database" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Setting Name & Key" %}
|
|
</div>
|
|
</th>
|
|
<th class="text-left px-6 py-4 text-sm font-semibold text-gray-700 border-b border-gray-200">
|
|
<div class="flex items-center gap-2">
|
|
<i data-lucide="key" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Value" %}
|
|
</div>
|
|
</th>
|
|
<th class="text-right px-6 py-4 text-sm font-semibold text-gray-700 border-b border-gray-200">
|
|
<div class="flex items-center gap-2 justify-end">
|
|
{% trans "Actions" %}
|
|
<i data-lucide="more-horizontal" class="w-4 h-4 text-gray-400"></i>
|
|
</div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for setting in page_obj %}
|
|
<tr class="table-row border-b border-gray-100 last:border-b-0">
|
|
<td class="px-6 py-4">
|
|
<div class="flex flex-col gap-1">
|
|
<div class="font-semibold text-gray-900">{{ setting.name|default:setting.key }}</div>
|
|
<code class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-lg font-mono">
|
|
{{ setting.key }}
|
|
</code>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-6 py-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex-1 min-w-0">
|
|
<span class="secret-value font-mono text-sm text-gray-600 bg-gray-50 px-3 py-2 rounded-lg inline-block"
|
|
data-full-value="{{ setting.value }}"
|
|
data-masked="••••••••••••••"
|
|
onclick="toggleSecret(this)">
|
|
•••••••••••••
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-1">
|
|
<button type="button"
|
|
class="btn-action w-9 h-9 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-gray-500 hover:text-temple-red transition-colors"
|
|
onclick="toggleSecret(this.parentElement.previousElementSibling.firstElementChild)"
|
|
title="{% trans 'Show/Hide' %}">
|
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
|
</button>
|
|
<button type="button"
|
|
class="btn-action w-9 h-9 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-gray-500 hover:text-temple-red transition-colors"
|
|
onclick="copyToClipboard('{{ setting.value|escapejs }}', this)"
|
|
title="{% trans 'Copy to Clipboard' %}">
|
|
<i data-lucide="copy" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-6 py-4 text-right">
|
|
<div class="flex items-center justify-end gap-2">
|
|
<a href="{% url 'settings_detail' setting.pk %}"
|
|
class="btn-action w-9 h-9 rounded-lg bg-temple-red/10 hover:bg-temple-red/20 flex items-center justify-center text-temple-red transition-colors"
|
|
title="{% trans 'View' %}">
|
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
|
</a>
|
|
<a href="{% url 'settings_update' setting.pk %}"
|
|
class="btn-action w-9 h-9 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center text-gray-500 hover:text-temple-red transition-colors"
|
|
title="{% trans 'Edit' %}">
|
|
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50/50">
|
|
{% include "includes/paginator.html" %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<!-- Empty State -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 py-12 sm:py-16">
|
|
<div class="flex flex-col items-center gap-6 text-center px-4">
|
|
<div class="w-24 h-24 rounded-full bg-temple-red/5 flex items-center justify-center empty-icon">
|
|
<i data-lucide="database" class="w-12 h-12 text-temple-red/50"></i>
|
|
</div>
|
|
<div class="max-w-md">
|
|
<h3 class="text-xl font-bold text-gray-900 mb-2">{% trans "No settings found" %}</h3>
|
|
<p class="text-gray-500">
|
|
{% if search_query %}
|
|
{% blocktrans %}We couldn't find any settings matching "{{ search_query }}". Please try a different term.{% endblocktrans %}
|
|
{% else %}
|
|
{% trans "You haven't added any integration settings yet. Get started by adding your first API key or configuration." %}
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
<a href="{% url 'settings_create' %}"
|
|
class="btn-primary inline-flex items-center gap-2 px-8 py-4 bg-temple-red text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
|
<i data-lucide="plus-circle" class="w-5 h-5"></i>
|
|
{% trans "Create First Setting" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
function toggleSecret(element) {
|
|
const fullValue = element.getAttribute('data-full-value');
|
|
const maskedValue = element.getAttribute('data-masked');
|
|
const icon = element.parentElement.parentElement.querySelector('[data-lucide="eye"], [data-lucide="eye-off"]');
|
|
|
|
if (element.textContent.trim() === maskedValue) {
|
|
element.textContent = fullValue;
|
|
element.classList.remove('text-gray-600', 'bg-gray-50');
|
|
element.classList.add('text-gray-900', 'bg-temple-red/5');
|
|
if (icon) icon.setAttribute('data-lucide', 'eye-off');
|
|
} else {
|
|
element.textContent = maskedValue;
|
|
element.classList.remove('text-gray-900', 'bg-temple-red/5');
|
|
element.classList.add('text-gray-600', 'bg-gray-50');
|
|
if (icon) icon.setAttribute('data-lucide', 'eye');
|
|
}
|
|
lucide.createIcons();
|
|
}
|
|
|
|
function copyToClipboard(text, btn) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
const icon = btn.querySelector('i');
|
|
const originalLucide = icon.getAttribute('data-lucide');
|
|
icon.setAttribute('data-lucide', 'check-circle-2');
|
|
icon.classList.remove('text-gray-500', 'hover:text-temple-red');
|
|
icon.classList.add('text-green-500');
|
|
|
|
lucide.createIcons();
|
|
|
|
setTimeout(() => {
|
|
icon.setAttribute('data-lucide', originalLucide);
|
|
icon.classList.remove('text-green-500');
|
|
icon.classList.add('text-gray-500', 'hover:text-temple-red');
|
|
lucide.createIcons();
|
|
}, 2000);
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |