feat: ssh auth, protocol management, and cleanup
This commit is contained in:
+222
-2
@@ -42,21 +42,54 @@
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-language mr-2"></i>{{ t('settings.translations') }}
|
||||
</a>
|
||||
<a href="/tools/qr-decode"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-qrcode mr-2"></i>QR Декодер
|
||||
</a>
|
||||
{% if user.role == 'admin' %}
|
||||
<a href="#" onclick="showTab('users'); return false;" id="tab-users"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-users mr-2"></i>{{ t('settings.users') }}
|
||||
</a>
|
||||
<a href="/settings/ldap"
|
||||
<a href="#" onclick="showTab('ldap'); return false;" id="tab-ldap"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-network-wired mr-2"></i>LDAP
|
||||
</a>
|
||||
<a href="#" onclick="showTab('protocols'); return false;" id="tab-protocols"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-cubes mr-2"></i>{{ t('settings.protocol_management') }}
|
||||
</a>
|
||||
<a href="/admin/logs"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-file-alt mr-2"></i>{{ 'Логи' | trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Profile Tab -->
|
||||
<div id="content-profile" class="tab-content">
|
||||
<div class="bg-white shadow rounded-lg mb-6">
|
||||
<div class="px-6 py-5 border-b border-gray-200">
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
<i class="fas fa-user mr-2 text-purple-600"></i>Профиль
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-6 py-5">
|
||||
<form method="POST" action="/settings/profile">
|
||||
<div class="space-y-4 max-w-md">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Отображаемое имя</label>
|
||||
<input type="text" name="display_name" value="{{ user.display_name|default(user.name) }}"
|
||||
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
||||
<i class="fas fa-save mr-2"></i>{{ t('form.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white shadow rounded-lg">
|
||||
<div class="px-6 py-5 border-b border-gray-200">
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
@@ -278,6 +311,11 @@
|
||||
<i class="fas fa-trash"></i> {{ t('clients.delete') }}
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<!-- For the current user show a Change Password action in Actions column -->
|
||||
<a href="#" id="self-change-password" onclick="showTab('profile'); return false;" class="text-purple-600 hover:text-purple-900">
|
||||
<i class="fas fa-key mr-1"></i> {{ t('settings.change_password') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -286,6 +324,163 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LDAP Tab -->
|
||||
<div id="content-ldap" class="tab-content hidden">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 max-w-5xl">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900">{{ t('ldap.settings') }}</h2>
|
||||
<button id="testConnection" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
||||
{{ t('ldap.test_connection') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="ldapForm" method="POST" action="/settings/ldap/save">
|
||||
<div class="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="checkbox" name="enabled" value="1"
|
||||
{% if config.enabled %}checked{% endif %}
|
||||
class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
|
||||
<span class="ml-3 text-lg font-medium text-gray-900">{{ t('ldap.enable_ldap_auth') }}</span>
|
||||
</label>
|
||||
<p class="mt-2 ml-8 text-sm text-gray-600">{{ t('ldap.enable_description') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.host') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="host" value="{{ config.host }}" required
|
||||
placeholder="ldap.example.com"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.port') }}
|
||||
</label>
|
||||
<input type="number" name="port" value="{{ config.port }}"
|
||||
placeholder="389"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input type="checkbox" name="use_tls" value="1"
|
||||
{% if config.use_tls %}checked{% endif %}
|
||||
class="w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">{{ t('ldap.use_tls') }} (LDAPS)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.base_dn') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="base_dn" value="{{ config.base_dn }}" required
|
||||
placeholder="dc=example,dc=com"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-xs text-gray-500">{{ t('ldap.base_dn_description') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.bind_dn') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" name="bind_dn" value="{{ config.bind_dn }}" required
|
||||
placeholder="cn=admin,dc=example,dc=com"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-xs text-gray-500">{{ t('ldap.bind_dn_description') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.bind_password') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="password" name="bind_password" value="{{ config.bind_password }}" required
|
||||
placeholder="••••••••"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.user_search_filter') }}
|
||||
</label>
|
||||
<input type="text" name="user_search_filter" value="{{ config.user_search_filter }}"
|
||||
placeholder="(uid=%s)"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-xs text-gray-500">{{ t('ldap.user_search_filter_description') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.group_search_filter') }}
|
||||
</label>
|
||||
<input type="text" name="group_search_filter" value="{{ config.group_search_filter }}"
|
||||
placeholder="(memberUid=%s)"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ t('ldap.sync_interval') }}
|
||||
</label>
|
||||
<input type="number" name="sync_interval" value="{{ config.sync_interval }}"
|
||||
placeholder="30"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-xs text-gray-500">{{ t('ldap.sync_interval_description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">
|
||||
{{ t('common.save') }}
|
||||
</button>
|
||||
<a href="/settings" class="px-6 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">
|
||||
{{ t('common.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 max-w-5xl">
|
||||
<h3 class="text-xl font-bold text-gray-800 mb-4">{{ t('ldap.group_mappings') }}</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('ldap.group') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('ldap.role') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('ldap.description') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for mapping in mappings %}
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ mapping.ldap_group }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full
|
||||
{% if mapping.role_name == 'admin' %}bg-red-100 text-red-800
|
||||
{% elseif mapping.role_name == 'manager' %}bg-blue-100 text-blue-800
|
||||
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||
{{ mapping.role_name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ mapping.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Protocols Tab -->
|
||||
<div id="content-protocols" class="tab-content hidden">
|
||||
{% include 'settings/protocols_management.twig' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -341,8 +536,33 @@ function translateLanguage(lang) {
|
||||
.catch(err => {
|
||||
alert('{{ t('message.error') }}: ' + err.message);
|
||||
button.disabled = false;
|
||||
button.innerHTML = originalText;
|
||||
button.innerHTML = originalText;
|
||||
});
|
||||
}
|
||||
|
||||
// LDAP test button inside settings page
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target && e.target.id === 'testConnection') {
|
||||
const btn = e.target;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '{{ t('ldap.testing') }}...';
|
||||
fetch('/settings/ldap/test', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
alert('✓ ' + result.message);
|
||||
} else {
|
||||
alert('✗ ' + result.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
alert('{{ t('ldap.connection_test_failed') }}: ' + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '{{ t('ldap.test_connection') }}';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user