Add multilingual support with translations for German, Russian, French, and Chinese
Added time limits and backup functions for servers
This commit is contained in:
+227
-21
@@ -13,33 +13,61 @@
|
||||
</dl>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow p-6">
|
||||
<h3 class="font-bold mb-4">Create Client</h3>
|
||||
<form method="POST" action="/servers/{{ server.id }}/clients/create" class="flex gap-2" id="createClientForm">
|
||||
<input name="name" placeholder="Client name" required class="flex-1 px-3 py-2 border rounded" id="clientName">
|
||||
<button type="submit" class="gradient-bg text-white px-4 py-2 rounded" id="createClientBtn">
|
||||
<span id="createClientText">Create</span>
|
||||
<h3 class="font-bold mb-4">{{ t('clients.create') }}</h3>
|
||||
<form method="POST" action="/servers/{{ server.id }}/clients/create" class="space-y-3" id="createClientForm">
|
||||
<input name="name" placeholder="{{ t('clients.name') }}" required class="w-full px-3 py-2 border rounded" id="clientName">
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1">{{ t('clients.expiration') }}</label>
|
||||
<select name="expires_in_days" class="w-full px-3 py-2 border rounded">
|
||||
<option value="" selected>{{ t('clients.never_expires') }}</option>
|
||||
<option value="7">7 {{ t('common.days') }}</option>
|
||||
<option value="30">30 {{ t('common.days') }}</option>
|
||||
<option value="60">60 {{ t('common.days') }}</option>
|
||||
<option value="90">90 {{ t('common.days') }}</option>
|
||||
<option value="180">180 {{ t('common.days') }}</option>
|
||||
<option value="365">365 {{ t('common.days') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="gradient-bg text-white px-4 py-2 rounded w-full" id="createClientBtn">
|
||||
<span id="createClientText">{{ t('form.create') }}</span>
|
||||
<i class="fas fa-spinner fa-spin" id="createClientSpinner" style="display:none;"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup Section -->
|
||||
<div class="bg-white rounded shadow mb-8">
|
||||
<div class="px-6 py-4 border-b flex justify-between items-center">
|
||||
<h3 class="font-bold">{{ t('backups.title') }}</h3>
|
||||
<button onclick="createBackup({{ server.id }})" class="gradient-bg text-white px-4 py-2 rounded text-sm">
|
||||
<i class="fas fa-save"></i> {{ t('backups.create') }}
|
||||
</button>
|
||||
</div>
|
||||
<div id="backupsList" class="p-4">
|
||||
<div class="text-center text-gray-500 py-4">
|
||||
<i class="fas fa-spinner fa-spin"></i> {{ t('form.loading') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded shadow">
|
||||
<div class="px-6 py-4 border-b flex justify-between items-center">
|
||||
<h3 class="font-bold">Clients ({{ clients|length }})</h3>
|
||||
<h3 class="font-bold">{{ t('clients.title') }} ({{ clients|length }})</h3>
|
||||
<button onclick="syncAllStats({{ server.id }})" class="text-purple-600 hover:text-purple-800 text-sm">
|
||||
<i class="fas fa-sync-alt"></i> Sync Stats
|
||||
<i class="fas fa-sync-alt"></i> {{ t('clients.sync_stats') }}
|
||||
</button>
|
||||
</div>
|
||||
{% if clients|length > 0 %}
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Traffic</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Last Seen</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.name') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.ip') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.status') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.expiration') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.traffic') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.last_handshake') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('clients.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -49,9 +77,30 @@
|
||||
<td class="px-6 py-4">{{ client.client_ip }}</td>
|
||||
<td class="px-6 py-4">
|
||||
{% if client.status == 'active' %}
|
||||
<span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">Active</span>
|
||||
<span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">{{ t('status.active') }}</span>
|
||||
{% else %}
|
||||
<span class="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">Disabled</span>
|
||||
<span class="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">{{ t('status.disabled') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
{% if client.expires_at %}
|
||||
{% set expires_ts = client.expires_at|date('U') %}
|
||||
{% set now_ts = "now"|date('U') %}
|
||||
{% set diff_days = ((expires_ts - now_ts) / 86400)|round %}
|
||||
|
||||
{% if diff_days < 0 %}
|
||||
<span class="px-2 py-1 bg-red-100 text-red-800 rounded text-xs">
|
||||
<i class="fas fa-exclamation-circle"></i> {{ t('clients.expired') }}
|
||||
</span>
|
||||
{% elseif diff_days <= 7 %}
|
||||
<span class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded text-xs">
|
||||
<i class="fas fa-clock"></i> {{ diff_days }} {{ t('common.days') }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600">{{ client.expires_at|date('Y-m-d') }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-gray-400">{{ t('clients.never_expires') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
@@ -66,22 +115,22 @@
|
||||
{% if client.last_handshake %}
|
||||
<span class="text-gray-600">{{ client.last_handshake }}</span>
|
||||
{% else %}
|
||||
<span class="text-gray-400">Never</span>
|
||||
<span class="text-gray-400">{{ t('clients.never') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<a href="/clients/{{ client.id }}" class="text-purple-600 hover:text-purple-800 mr-2">View</a>
|
||||
<a href="/clients/{{ client.id }}" class="text-purple-600 hover:text-purple-800 mr-2">{{ t('servers.view') }}</a>
|
||||
{% if client.status == 'active' %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/revoke" style="display:inline;">
|
||||
<button type="submit" class="text-orange-600 hover:text-orange-800 mr-2" onclick="return confirm('Revoke access for this client?')">Revoke</button>
|
||||
<button type="submit" class="text-orange-600 hover:text-orange-800 mr-2" onclick="return confirm('{{ t('clients.revoke_confirm') }}')">{{ t('clients.revoke') }}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/restore" style="display:inline;">
|
||||
<button type="submit" class="text-green-600 hover:text-green-800 mr-2">Restore</button>
|
||||
<button type="submit" class="text-green-600 hover:text-green-800 mr-2">{{ t('clients.restore') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="POST" action="/clients/{{ client.id }}/delete" style="display:inline;">
|
||||
<button type="submit" class="text-red-600 hover:text-red-800" onclick="return confirm('Delete this client permanently?')">Delete</button>
|
||||
<button type="submit" class="text-red-600 hover:text-red-800" onclick="return confirm('{{ t('clients.delete_confirm') }}')">{{ t('clients.delete') }}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -89,7 +138,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-12 text-center text-gray-500">No clients yet</div>
|
||||
<div class="p-12 text-center text-gray-500">{{ t('clients.no_clients') }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,5 +179,162 @@ async function syncAllStats(serverId) {
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Load backups on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadBackups({{ server.id }});
|
||||
});
|
||||
|
||||
async function loadBackups(serverId) {
|
||||
try {
|
||||
const response = await fetch(`/api/servers/${serverId}/backups`, {
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
document.getElementById('backupsList').innerHTML = '<div class="text-center text-red-500 py-4">{{ t("message.error") }}: {{ t("backups.login_required") }}</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.backups.length > 0) {
|
||||
let html = '<div class="space-y-2">';
|
||||
data.backups.forEach(backup => {
|
||||
const size = (backup.backup_size / 1024 / 1024).toFixed(2);
|
||||
const statusClass = backup.status === 'completed' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800';
|
||||
|
||||
html += `
|
||||
<div class="flex items-center justify-between p-3 border rounded">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">${backup.backup_name}</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
${backup.clients_count} {{ t('clients.title') }} • ${size} MB • ${backup.created_at}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<span class="px-2 py-1 ${statusClass} rounded text-xs">${backup.status}</span>
|
||||
${backup.status === 'completed' ? `
|
||||
<button onclick="restoreBackup(${serverId}, ${backup.id})" class="text-blue-600 hover:text-blue-800 px-3 py-1 border rounded text-sm">
|
||||
<i class="fas fa-undo"></i> {{ t('backups.restore') }}
|
||||
</button>
|
||||
` : ''}
|
||||
<button onclick="deleteBackup(${backup.id})" class="text-red-600 hover:text-red-800 px-3 py-1 border rounded text-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
document.getElementById('backupsList').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('backupsList').innerHTML = '<div class="text-center text-gray-500 py-4">{{ t("backups.no_backups") }}</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('backupsList').innerHTML = `<div class="text-center text-red-500 py-4">Error: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function createBackup(serverId) {
|
||||
if (!confirm('{{ t("backups.create_confirm") }}')) return;
|
||||
|
||||
// Show loading state
|
||||
document.getElementById('backupsList').innerHTML = '<div class="text-center text-gray-500 py-4"><i class="fas fa-spinner fa-spin"></i> {{ t("form.processing") }}</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/servers/${serverId}/backup`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('{{ t("backups.created_success") }}');
|
||||
loadBackups(serverId);
|
||||
} else {
|
||||
alert('Error: ' + (data.error || 'Unknown error'));
|
||||
loadBackups(serverId);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error: ' + error.message);
|
||||
loadBackups(serverId);
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreBackup(serverId, backupId) {
|
||||
if (!confirm('{{ t("backups.restore_confirm") }}')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/servers/${serverId}/restore`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ backup_id: backupId })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Restore response:', data);
|
||||
|
||||
if (data.success !== false) {
|
||||
let message = `{{ t("backups.restored_success") }}: ${data.restored} {{ t("clients.title") }}`;
|
||||
|
||||
if (data.failed > 0) {
|
||||
message += `\n\nПропущено: ${data.failed}`;
|
||||
if (data.errors && data.errors.length > 0) {
|
||||
message += '\n\nПричины:\n' + data.errors.slice(0, 5).join('\n');
|
||||
if (data.errors.length > 5) {
|
||||
message += `\n... и ещё ${data.errors.length - 5}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert(message);
|
||||
if (data.restored > 0) {
|
||||
location.reload();
|
||||
} else {
|
||||
loadBackups(serverId);
|
||||
}
|
||||
} else {
|
||||
let errorMsg = 'Error: ';
|
||||
if (data.error) {
|
||||
errorMsg += data.error;
|
||||
} else if (data.errors && data.errors.length > 0) {
|
||||
errorMsg += data.errors.join(', ');
|
||||
} else {
|
||||
errorMsg += 'Unknown error';
|
||||
}
|
||||
alert(errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Restore error:', error);
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteBackup(backupId) {
|
||||
if (!confirm('{{ t("backups.delete_confirm") }}')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/backups/${backupId}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('{{ t("backups.deleted_success") }}');
|
||||
loadBackups({{ server.id }});
|
||||
} else {
|
||||
alert('Error: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
+35
-29
@@ -32,7 +32,7 @@
|
||||
<nav class="-mb-px flex space-x-8">
|
||||
<a href="#" onclick="showTab('profile'); return false;" id="tab-profile"
|
||||
class="tab-link border-purple-500 text-purple-600 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
<i class="fas fa-user mr-2"></i>Profile
|
||||
<i class="fas fa-user mr-2"></i>{{ t('settings.profile') }}
|
||||
</a>
|
||||
<a href="#" onclick="showTab('api'); return false;" id="tab-api"
|
||||
class="tab-link border-transparent text-gray-500 hover:text-gray-700 py-4 px-1 border-b-2 font-medium text-sm">
|
||||
@@ -40,12 +40,12 @@
|
||||
</a>
|
||||
<a href="#" onclick="showTab('translations'); return false;" id="tab-translations"
|
||||
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>Translations
|
||||
<i class="fas fa-language mr-2"></i>{{ t('settings.translations') }}
|
||||
</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>Users
|
||||
<i class="fas fa-users mr-2"></i>{{ t('settings.users') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
@@ -56,30 +56,30 @@
|
||||
<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">
|
||||
<i class="fas fa-lock mr-2 text-purple-600"></i>Change Password
|
||||
<i class="fas fa-lock mr-2 text-purple-600"></i>{{ t('settings.change_password') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-6 py-5">
|
||||
<form method="POST" action="/settings/change-password">
|
||||
<div class="space-y-4 max-w-md">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Current Password</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('settings.current_password') }}</label>
|
||||
<input type="password" name="current_password" required
|
||||
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">New Password</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('settings.new_password') }}</label>
|
||||
<input type="password" name="new_password" required minlength="6"
|
||||
class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
<p class="mt-1 text-xs text-gray-500">Minimum 6 characters</p>
|
||||
<p class="mt-1 text-xs text-gray-500">{{ t('settings.min_6_chars') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('settings.confirm_password') }}</label>
|
||||
<input type="password" name="confirm_password" required minlength="6"
|
||||
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>Change Password
|
||||
<i class="fas fa-save mr-2"></i>{{ t('settings.change_password') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<i class="fas fa-check-circle text-green-600 mr-2"></i>
|
||||
<span class="text-sm font-medium text-green-900">API Key Configured</span>
|
||||
<span class="text-sm font-medium text-green-900">{{ t('settings.api_key_configured') }}</span>
|
||||
</div>
|
||||
<code class="text-xs text-green-700 bg-green-100 px-2 py-1 rounded">
|
||||
{{ openrouter_key[:15] }}...{{ openrouter_key[-4:] }}
|
||||
@@ -111,7 +111,7 @@
|
||||
{% else %}
|
||||
<div class="mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 mr-2"></i>
|
||||
<span class="text-sm text-yellow-800">No API key configured. Auto-translation will not work.</span>
|
||||
<span class="text-sm text-yellow-800">{{ t('settings.no_api_key') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -126,6 +126,12 @@
|
||||
<i class="fas fa-info-circle"></i> {{ t('settings.get_key_at') }}
|
||||
<a href="https://openrouter.ai/keys" target="_blank" class="text-purple-600">openrouter.ai/keys</a>
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="skip_test" value="1" class="rounded border-gray-300 text-purple-600 focus:ring-purple-500">
|
||||
<span class="ml-2 text-sm text-gray-600">{{ t('settings.skip_validation') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" name="service" value="openrouter">
|
||||
<button type="submit" class="mt-4 px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
||||
<i class="fas fa-save mr-2"></i>{{ openrouter_key ? t('form.update') : t('form.save') }}
|
||||
@@ -194,34 +200,34 @@
|
||||
<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-plus mr-2 text-purple-600"></i>Add User
|
||||
<i class="fas fa-user-plus mr-2 text-purple-600"></i>{{ t('users.add_user') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-6 py-5">
|
||||
<form method="POST" action="/settings/add-user">
|
||||
<div class="grid grid-cols-2 gap-4 max-w-2xl">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('auth.name') }}</label>
|
||||
<input type="text" name="name" required class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('auth.email') }}</label>
|
||||
<input type="email" name="email" required class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('auth.password') }}</label>
|
||||
<input type="password" name="password" required minlength="6" class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Role</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{{ t('users.role') }}</label>
|
||||
<select name="role" class="block w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-purple-500 focus:border-purple-500">
|
||||
<option value="user">User</option>
|
||||
<option value="admin">Administrator</option>
|
||||
<option value="user">{{ t('users.role_user') }}</option>
|
||||
<option value="admin">{{ t('users.role_admin') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="mt-4 px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700">
|
||||
<i class="fas fa-user-plus mr-2"></i>Add User
|
||||
<i class="fas fa-user-plus mr-2"></i>{{ t('users.add_user') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -230,17 +236,17 @@
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-5 border-b border-gray-200">
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
<i class="fas fa-users mr-2 text-purple-600"></i>All Users
|
||||
<i class="fas fa-users mr-2 text-purple-600"></i>{{ t('users.all_users') }}
|
||||
</h2>
|
||||
</div>
|
||||
<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">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Role</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('auth.name') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('auth.email') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('users.role') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('users.created') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ t('settings.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@@ -255,17 +261,17 @@
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ u.email }}</td>
|
||||
<td class="px-6 py-4">
|
||||
{% if u.role == 'admin' %}
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">Admin</span>
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">{{ t('users.role_admin') }}</span>
|
||||
{% else %}
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800">User</span>
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800">{{ t('users.role_user') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ u.created_at|date('Y-m-d') }}</td>
|
||||
<td class="px-6 py-4 text-sm">
|
||||
{% if u.id != user.id %}
|
||||
<form method="POST" action="/settings/delete-user/{{ u.id }}" class="inline" onsubmit="return confirm('Delete {{ u.name }}?')">
|
||||
<form method="POST" action="/settings/delete-user/{{ u.id }}" class="inline" onsubmit="return confirm('{{ t('users.delete_confirm', [u.name]) }}')">
|
||||
<button type="submit" class="text-red-600 hover:text-red-900">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
<i class="fas fa-trash"></i> {{ t('clients.delete') }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user