Merge pull request 'small fix' (#115) from frontend into main

Reviewed-on: #115
This commit is contained in:
ismail 2025-12-16 11:12:27 +03:00
commit 85170c8dfa
8 changed files with 380 additions and 258 deletions

View File

@ -1588,6 +1588,9 @@ class MessageForm(forms.ModelForm):
# Validate messaging permissions
if self.user and cleaned_data.get("recipient"):
self._validate_messaging_permissions(cleaned_data)
if self.cleaned_data.get('recipient')==self.user:
raise forms.ValidationError(_("You cannot message yourself"))
return cleaned_data
@ -2015,8 +2018,13 @@ class SettingsForm(forms.ModelForm):
class Meta:
model = Settings
fields = ['key', 'value']
fields = ['name','key', 'value']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control mb-3',
'placeholder': 'e.g., Zoom',
'required': True
}),
'key': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter setting key',

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-12-15 14:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0003_interview_interview_result_interview_result_comments'),
]
operations = [
migrations.AddField(
model_name='settings',
name='name',
field=models.CharField(blank=True, help_text="A human-readable name (e.g., 'Zoom')", max_length=100, null=True, verbose_name='Friendly Name'),
),
]

View File

@ -2593,6 +2593,12 @@ class Document(Base):
class Settings(Base):
"""Model to store key-value pair settings"""
name = models.CharField(
max_length=100,
verbose_name=_("Friendly Name"),
help_text=_("A human-readable name (e.g., 'Zoom')"),
null=True, blank=True
)
key = models.CharField(
max_length=100,
@ -2604,6 +2610,7 @@ class Settings(Base):
verbose_name=_("Setting Value"),
help_text=_("Value for the setting"),
)
class Meta:
verbose_name = _("Setting")

View File

@ -51,7 +51,7 @@ def get_setting(key, default=None):
return default
def set_setting(key, value):
def set_setting(key, value,name):
"""
Set a setting value in the database
@ -62,8 +62,9 @@ def set_setting(key, value):
Returns:
Settings: The created or updated setting object
"""
print(key,value)
setting, created = Settings.objects.update_or_create(
key=key, defaults={"value": str(value)}
key=key, value=value,name=name
)
return setting
@ -262,26 +263,35 @@ def initialize_default_settings():
"""
# Zoom settings
zoom_settings = {
"ZOOM_ACCOUNT_ID": getattr(settings, "ZOOM_ACCOUNT_ID", ""),
"ZOOM_CLIENT_ID": getattr(settings, "ZOOM_CLIENT_ID", ""),
"ZOOM_CLIENT_SECRET": getattr(settings, "ZOOM_CLIENT_SECRET", ""),
"ZOOM_WEBHOOK_API_KEY": getattr(settings, "ZOOM_WEBHOOK_API_KEY", ""),
"SECRET_TOKEN": getattr(settings, "SECRET_TOKEN", ""),
"ZOOM_ACCOUNT_ID": "",
"ZOOM_CLIENT_ID": "",
"ZOOM_CLIENT_SECRET": "",
"ZOOM_WEBHOOK_API_KEY": "",
"SECRET_TOKEN": "",
}
# LinkedIn settings
linkedin_settings = {
"LINKEDIN_CLIENT_ID": getattr(settings, "LINKEDIN_CLIENT_ID", ""),
"LINKEDIN_CLIENT_SECRET": getattr(settings, "LINKEDIN_CLIENT_SECRET", ""),
"LINKEDIN_REDIRECT_URI": getattr(settings, "LINKEDIN_REDIRECT_URI", ""),
"LINKEDIN_CLIENT_ID": "",
"LINKEDIN_CLIENT_SECRET": "",
"LINKEDIN_REDIRECT_URI": "",
}
# Create settings if they don't exist
all_settings = {**zoom_settings, **linkedin_settings}
openrouter_settings = {
"OPENROUTER_API_URL":"",
"OPENROUTER_API_KEY":"",
"OPENROUTER_MODEL":""
}
# Create settings if they don't exist
all_settings = {**zoom_settings, **linkedin_settings,**openrouter_settings}
names=['ZOOM','ZOOM','ZOOM','ZOOM','ZOOM','LINKEDIN','LINKEDIN','LINKEDIN','OPENROUTER','OPENROUTER','OPENROUTER']
i=0
for key, value in all_settings.items():
if value: # Only set if value exists
set_setting(key, value)
set_setting(key, value,names[i])
i=i+1
#####################################

View File

@ -4290,12 +4290,10 @@ def update_interview_result(request,slug):
form = InterviewResultForm(request.POST, instance=interview)
if form.is_valid():
interview.save(update_fields=['interview_result', 'result_comments'])
form.save() # Saves form data
messages.success(request, _("Interview cancelled successfully."))
messages.success(request, _(f"Interview result updated successfully to {interview.interview_result}."))
return redirect("interview_detail", slug=schedule.slug)
else:
error_list = [

View File

@ -523,11 +523,34 @@
</button>
{% if schedule.status == 'completed' %}
<button type="button" class="btn btn-outline-success btn-sm"
<button type="button" class="btn btn-outline-success btn-sm w-100"
data-bs-toggle="modal"
data-bs-target="#resultModal">
<i class="fas fa-check-circle me-1"></i> {% trans "Update Result" %}
</button>
<div class="w-100 text-center">
{% if interview.interview_result %}
{% trans 'Interview Result : ' %}
{% if interview.interview_result == 'passed' %}
<span class="badge bg-success text-white p-1">
<i class="fas fa-check-circle me-1"></i> {{ interview.interview_result }}
</span>
{% elif interview.interview_result == 'failed' %}
<span class="badge bg-danger text-white p-1 fs-5">
<i class="fas fa-times-circle me-1"></i> {{ interview.interview_result }}
</span>
{% else %}
<span class="badge bg-info text-dark p-1">
<i class="fas fa-info-circle me-1"></i> {{ interview.interview_result }}
</span>
{% endif %}
{% else %}
<span class="badge rounded-pill bg-secondary text-white">
{% trans "No Result Yet" %}
</span>
{% endif %}
</div>
{% endif %}
</div>
</div>

View File

@ -1,82 +1,153 @@
{% extends "base.html" %}
{% load widget_tweaks %}
{% block title %}Setting Details{% endblock %}
{% load i18n %}
{% block title %}{% trans "Setting Details" %} | {{ setting.key }}{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{% url 'settings_list' %}" class="text-decoration-none text-muted">
<i class="fas fa-sliders-h me-1"></i> {% trans "Integrations" %}
</a>
</li>
<li class="breadcrumb-item active" style="color: #F43B5E; font-weight: 600;">{{ setting.key }}</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1 text-dark fw-bold">{% trans "Setting Details" %}</h1>
<p class="text-muted small mb-0">{{ setting.name|default:setting.key }}</p>
</div>
<div class="d-flex gap-2">
<a href="{% url 'settings_update' setting.pk %}" class="btn btn-main-action shadow-sm">
<i class="fas fa-edit me-1"></i> {% trans "Edit" %}
</a>
<a href="{% url 'settings_list' %}" class="btn btn-outline-secondary shadow-sm">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back" %}
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<!-- Breadcrumb Navigation -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="{% url 'settings_list' %}" class="text-decoration-none text-secondary">
<i class="fas fa-cog me-1"></i> Settings
</a>
</li>
<li class="breadcrumb-item active" aria-current="page"
style="color: #F43B5E; font-weight: 600;">{{ setting.key }}</li>
</ol>
</nav>
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-primary-theme">
<i class="fas fa-cog me-2"></i>
Setting Details
</h1>
<div class="d-flex gap-2">
<a href="{% url 'settings_update' pk=setting.pk %}" class="btn btn-main-action">
<i class="fas fa-edit me-1"></i> Edit Setting
</a>
<a href="{% url 'settings_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> Back to List
</a>
<div class="col-lg-8">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-bottom-0 pt-4 px-4">
<h5 class="card-title text-primary-theme fw-bold mb-0">{% trans "Configuration" %}</h5>
</div>
</div>
<div class="card-body p-4">
<div class="mb-4">
<label class="text-muted small fw-bold text-uppercase tracking-wider mb-2">{% trans "Internal Key" %}</label>
<div class="p-2 bg-light rounded border d-flex justify-content-between align-items-center">
<code class="text-primary-theme fs-5">{{ setting.key }}</code>
<button class="btn btn-link btn-sm text-secondary" onclick="copyToClipboard('{{ setting.key|escapejs }}', this)">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<!-- Setting Details Card -->
<div class="card shadow-sm">
<div class="card-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<div class="mb-0">
<label class="text-muted small fw-bold text-uppercase tracking-wider mb-2">{% trans "Sensitive Value" %}</label>
<div class="p-3 bg-light rounded border">
<div class="d-flex justify-content-between align-items-start">
<div class="font-monospace text-break" id="revealContainer" style="word-break: break-all;">
<span class="reveal-toggle text-muted fs-5"
data-full-value="{{ setting.value }}"
data-masked="••••••••••••••••••••••••"
style="user-select: none;">
••••••••••••••••••••••••
</span>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-light btn-sm border" onclick="toggleSecret(document.querySelector('.reveal-toggle'))" title="{% trans 'Show/Hide' %}">
<i class="fas fa-eye"></i>
</button>
<button type="button" class="btn btn-light btn-sm border" onclick="copyToClipboard('{{ setting.value|escapejs }}', this)" title="{% trans 'Copy' %}">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col-md-6">
<h6 class="text-primary-theme"><strong>Key:</strong></h6>
<p><code class="text-primary-theme">{{ setting.key }}</code></p>
</div>
<div class="col-md-6">
<h6 class="text-primary-theme"><strong>Value:</strong></h6>
<p>{{ setting.value|default:"-" }}</p>
</div>
</div>
<div class="row mt-3">
<div class="col-md-4">
<h6 class="text-primary-theme"><strong>Created:</strong></h6>
<p>{{ setting.created_at|date:"Y-m-d H:i" }}</p>
</div>
<div class="col-md-4">
<h6 class="text-primary-theme"><strong>Last Updated:</strong></h6>
<p>{{ setting.updated_at|date:"Y-m-d H:i" }}</p>
</div>
<div class="col-md-4">
<h6 class="text-primary-theme"><strong>Updated By:</strong></h6>
<p>{{ setting.updated_by.get_full_name|default:"System" }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<h6 class="text-dark fw-bold mb-3">{% trans "Metadata" %}</h6>
<div class="mb-3">
<label class="text-muted small d-block">{% trans "Category" %}</label>
<span class="badge rounded-pill bg-light text-dark border">{{ setting.get_category_display }}</span>
</div>
<hr class="text-light">
<div class="d-flex align-items-center mb-3">
<div class="flex-shrink-0 bg-light p-2 rounded-circle me-3">
<i class="fas fa-calendar-alt text-muted"></i>
</div>
<div>
<label class="text-muted small d-block">{% trans "Created" %}</label>
<span class="text-dark small">{{ setting.created_at|date:"M d, Y H:i" }}</span>
</div>
</div>
<div class="d-flex align-items-center mb-3">
<div class="flex-shrink-0 bg-light p-2 rounded-circle me-3">
<i class="fas fa-history text-muted"></i>
</div>
<div>
<label class="text-muted small d-block">{% trans "Last Updated" %}</label>
<span class="text-dark small">{{ setting.updated_at|date:"M d, Y H:i" }}</span>
</div>
</div>
<div class="d-flex align-items-center">
<div class="flex-shrink-0 bg-light p-2 rounded-circle me-3">
<i class="fas fa-user text-muted"></i>
</div>
<div>
<label class="text-muted small d-block">{% trans "Modified By" %}</label>
<span class="text-dark small">{{ setting.updated_by.get_full_name|default:"System" }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}
<script>
function toggleSecret(element) {
const fullValue = element.getAttribute('data-full-value');
const maskedValue = element.getAttribute('data-masked');
const icon = event.currentTarget.querySelector('i');
if (element.textContent.trim() === maskedValue) {
element.textContent = fullValue;
element.classList.replace('text-muted', 'text-dark');
if(icon) icon.classList.replace('fa-eye', 'fa-eye-slash');
} else {
element.textContent = maskedValue;
element.classList.replace('text-dark', 'text-muted');
if(icon) icon.classList.replace('fa-eye-slash', 'fa-eye');
}
}
function copyToClipboard(text, btn) {
navigator.clipboard.writeText(text).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'fas fa-check text-success';
setTimeout(() => { icon.className = originalClass; }, 2000);
});
}
</script>
{% endblock %}

View File

@ -1,188 +1,175 @@
{% extends "base.html" %}
{% load widget_tweaks %}
{% load i18n %}
{% block title %}Settings{% endblock %}
{% block title %}{% trans "Integration Settings" %}{% endblock %}
{% block content %}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<div class="container-fluid py-4">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
<li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">{% trans "Integration Settings" %}</li>
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-muted">{% trans "Settings" %}</a></li>
<li class="breadcrumb-item active" style="color: #F43B5E; font-weight: 600;">{% trans "Integrations" %}</li>
</ol>
</nav>
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-primary-theme">
<i class="fas fa-cog me-2"></i>
{% trans "Integration Settings" %}
</h1>
<a href="{% url 'settings_create' %}" class="btn btn-main-action">
<i class="fas fa-plus me-2"></i>
Create New Setting
</a>
</div>
<!-- Search and Filters -->
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-8">
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-search"></i>
</span>
<input type="text" class="form-control" name="q"
placeholder="Search settings..." value="{{ search_query }}">
</div>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-outline-primary">
<i class="fas fa-search"></i> Search
</button>
{% if search_query %}
<a href="{% url 'settings_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times"></i> Clear
</a>
{% endif %}
</div>
</form>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1 text-dark fw-bold">
<i class="fas fa-sliders-h me-2 text-primary-theme"></i>
{% trans "Integration Settings" %}
</h1>
<p class="text-muted small mb-0">{% trans "Manage API keys, Webhooks, and external service configurations." %}</p>
</div>
<a href="{% url 'settings_create' %}" class="btn btn-main-action shadow-sm">
<i class="fas fa-plus-circle me-2"></i>{% trans "Add New Setting" %}
</a>
</div>
<!-- Results Summary -->
{% if search_query %}
<div class="alert alert-info">
Found {{ page_obj.paginator.count }} setting{{ page_obj.paginator.count|pluralize }} matching "{{ search_query }}"
</div>
{% endif %}
<!-- Settings Table -->
<div class="card">
<div class="card-body">
{% if page_obj %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th style="width: 45%;">Key</th>
<th style="width: 45%;">Value</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for setting in page_obj %}
<tr>
<td>
<code class="text-primary-theme">{{ setting.key }}</code>
</td>
<td>{{ setting.value|truncatechars:50 }}</td>
<td>
<div class="btn-group" role="group">
<a href="{% url 'settings_detail' pk=setting.pk %}"
class="btn btn-sm btn-outline-primary" title="View Details">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'settings_update' pk=setting.pk %}"
class="btn btn-sm btn-outline-secondary" title="Edit Setting">
<i class="fas fa-edit"></i>
</a>
{% comment %} <form method="post" action="{% url 'settings_delete' pk=setting.pk %}"
onsubmit="return confirm('Are you sure you want to delete this setting?');"
style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete Setting">
<i class="fas fa-trash"></i>
</button>
</form> {% endcomment %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Settings pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-double-left"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-cog fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No settings found</h5>
<p class="text-muted">
{% if search_query %}
No settings match your search criteria "{{ search_query }}".
{% else %}
Get started by creating your first setting.
{% endif %}
</p>
<a href="{% url 'settings_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i> Create Setting
</a>
</div>
{% endif %}
<div class="text-center mt-3">
<small class="text-muted">
{% if page_obj %}
Showing {{ page_obj.start_index }}-{{ page_obj.end_index }} of {{ page_obj.paginator.count }} settings
{% if search_query %}
(filtered by: "{{ search_query }}")
{% endif %}
{% endif %}
</small>
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-3">
<form method="get" class="row g-2">
<div class="col-md-9">
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="fas fa-search text-muted"></i></span>
<input type="text" class="form-control border-start-0 ps-0" name="q"
placeholder="{% trans 'Search by name, key, or category...' %}" value="{{ search_query }}">
</div>
</div>
</div>
</div>
<div class="col-md-3 d-grid gap-2 d-md-flex">
<button type="submit" class="btn btn-main-action px-4">{% trans "Search" %}</button>
{% if search_query %}
<a href="{% url 'settings_list' %}" class="btn btn-light border">{% trans "Clear" %}</a>
{% endif %}
</div>
</form>
</div>
</div>
{% if page_obj %}
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4" style="width: 40%;">{% trans "Setting Name & Key" %}</th>
<th style="width: 40%;">{% trans "Value" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for setting in page_obj %}
<tr>
<td class="ps-4">
<div class="fw-bold text-dark">{{ setting.name|default:setting.key }}</div>
<code class="small text-muted" style="font-size: 0.75rem;">{{ setting.key }}</code>
</td>
<td>
<div class="d-flex align-items-center">
<div class="text-truncate" style="max-width: 300px; min-width: 150px;">
<span class="font-monospace small text-muted reveal-toggle"
data-full-value="{{ setting.value }}"
data-masked="••••••••••••••••"
onclick="toggleSecret(this)"
style="cursor: pointer; user-select: none;">
••••••••••••••••
</span>
</div>
<div class="ms-2 d-flex">
<button type="button" class="btn btn-link btn-sm p-1 text-decoration-none text-secondary"
onclick="toggleSecret(this.parentElement.previousElementSibling.firstElementChild)"
title="{% trans 'Show/Hide' %}">
<i class="fas fa-eye fa-xs"></i>
</button>
<button type="button" class="btn btn-link btn-sm p-1 text-decoration-none text-secondary"
onclick="copyToClipboard('{{ setting.value|escapejs }}', this)"
title="{% trans 'Copy to Clipboard' %}">
<i class="fas fa-copy fa-xs"></i>
</button>
</div>
</div>
</td>
<td class="text-end pe-4">
<div class="btn-group shadow-sm">
<a href="{% url 'settings_detail' setting.pk %}" class="btn btn-white btn-sm border" title="{% trans 'View' %}">
<i class="fas fa-eye text-primary"></i>
</a>
<a href="{% url 'settings_update' setting.pk %}" class="btn btn-white btn-sm border" title="{% trans 'Edit' %}">
<i class="fas fa-edit text-secondary"></i>
</a>
{% comment %} <button type="button" class="btn btn-white btn-sm border"
onclick="deleteDocument(this, {{ setting.pk }})" title="{% trans 'Delete' %}">
<i class="fas fa-trash-alt text-danger"></i>
</button> {% endcomment %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if page_obj.has_other_pages %}
<div class="card-footer bg-white border-top-0 py-3">
{% include "includes/paginator.html" %}
</div>
{% endif %}
</div>
{% else %}
<div class="card border-0 shadow-sm py-5">
<div class="card-body text-center">
<div class="mb-4">
<i class="fas fa-tools fa-4x text-light"></i>
</div>
<h4 class="text-dark">{% trans "No settings found" %}</h4>
<p class="text-muted mx-auto" style="max-width: 400px;">
{% 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>
<a href="{% url 'settings_create' %}" class="btn btn-main-action mt-2">
<i class="fas fa-plus me-2"></i>{% trans "Create First Setting" %}
</a>
</div>
</div>
{% endif %}
</div>
<style>
.reveal-toggle { transition: color 0.2s; }
.reveal-toggle:hover { color: #212529 !important; }
</style>
{% endblock %}
{% block customJS %}
<script>
function toggleSecret(element) {
const fullValue = element.getAttribute('data-full-value');
const maskedValue = element.getAttribute('data-masked');
// Find the icon inside the button next to the div container
const icon = element.parentElement.parentElement.querySelector('.fa-eye, .fa-eye-slash');
if (element.textContent.trim() === maskedValue) {
element.textContent = fullValue;
element.classList.replace('text-muted', 'text-dark');
if(icon) icon.classList.replace('fa-eye', 'fa-eye-slash');
} else {
element.textContent = maskedValue;
element.classList.replace('text-dark', 'text-muted');
if(icon) icon.classList.replace('fa-eye-slash', 'fa-eye');
}
}
function copyToClipboard(text, btn) {
navigator.clipboard.writeText(text).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'fas fa-check text-success fa-xs';
setTimeout(() => { icon.className = originalClass; }, 2000);
});
}
</script>
{% endblock %}