Spaces:
Running
Running
File size: 9,793 Bytes
d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 bb59211 d646dd7 849e0ae d646dd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | {% extends "base.html" %}
{% block title %}Users — EgoData{% endblock %}
{% block content %}
<div class="flex-1 overflow-y-auto hide-scrollbar p-6">
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-lg font-semibold text-gray-200"><span data-i18n="users_title">{{ t('users_title') }}</span></h2>
<p class="text-xs text-gray-500 mt-1"><span data-i18n="users_subtitle">{{ t('users_subtitle') }}</span></p>
</div>
<button onclick="openUserModal()" class="bg-blue-600 hover:bg-blue-500 text-white text-xs px-3 py-1.5 rounded font-medium flex items-center gap-1.5">
<iconify-icon icon="ant-design:plus-outlined" class="icon-sm"></iconify-icon>
<span data-i18n="users_new">{{ t('users_new') }}</span>
</button>
</div>
<div id="user-alert" class="hidden mb-4 rounded-lg border px-4 py-3 text-sm"></div>
<div class="grid grid-cols-3 gap-4 mb-6">
<div class="bg-gray-900 rounded-lg p-4 border border-gray-800">
<div class="text-xs text-gray-500"><span data-i18n="users_total">{{ t('users_total') }}</span></div>
<div id="count-total" class="text-2xl font-bold text-white mt-1">—</div>
</div>
<div class="bg-gray-900 rounded-lg p-4 border border-gray-800">
<div class="text-xs text-gray-500"><span data-i18n="users_active">{{ t('users_active') }}</span></div>
<div id="count-active" class="text-2xl font-bold text-green-400 mt-1">—</div>
</div>
<div class="bg-gray-900 rounded-lg p-4 border border-gray-800">
<div class="text-xs text-gray-500"><span data-i18n="users_disabled_expired">{{ t('users_disabled_expired') }}</span></div>
<div id="count-inactive" class="text-2xl font-bold text-yellow-400 mt-1">—</div>
</div>
</div>
<div class="bg-gray-900 rounded-lg border border-gray-800 overflow-hidden">
<div class="flex items-center gap-3 p-4 border-b border-gray-800">
<input id="user-search" oninput="renderUsers()" placeholder="Search username or email..."
class="bg-gray-800 text-sm rounded px-3 py-1.5 border border-gray-700 w-64 focus:border-blue-500 focus:outline-none">
<select id="user-role-filter" onchange="loadUsers()" class="bg-gray-800 text-sm rounded px-3 py-1.5 border border-gray-700 focus:border-blue-500 focus:outline-none">
<option value="" data-i18n="users_all_roles">{{ t('users_all_roles') }}</option>
<option value="admin" data-i18n="role_admin">{{ t('role_admin') }}</option>
<option value="engineer" data-i18n="role_engineer">{{ t('role_engineer') }}</option>
<option value="reviewer" data-i18n="role_reviewer">{{ t('role_reviewer') }}</option>
</select>
<select id="user-status-filter" onchange="loadUsers()" class="bg-gray-800 text-sm rounded px-3 py-1.5 border border-gray-700 focus:border-blue-500 focus:outline-none">
<option value="" data-i18n="users_all_status">{{ t('users_all_status') }}</option>
<option value="active" data-i18n="status_active">{{ t('status_active') }}</option>
<option value="disabled" data-i18n="status_disabled">{{ t('status_disabled') }}</option>
</select>
<span id="user-list-count" class="text-xs text-gray-600 ml-auto"></span>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm" style="min-width: 900px">
<thead class="text-xs text-gray-500 bg-gray-950/40">
<tr>
<th class="text-left font-medium px-4 py-3"><span data-i18n="col_user">{{ t('col_user') }}</span></th>
<th class="text-left font-medium px-4 py-3"><span data-i18n="col_role">{{ t('col_role') }}</span></th>
<th class="text-left font-medium px-4 py-3"><span data-i18n="col_status">{{ t('col_status') }}</span></th>
<th class="text-left font-medium px-4 py-3"><span data-i18n="col_last_login">{{ t('col_last_login') }}</span></th>
<th class="text-left font-medium px-4 py-3"><span data-i18n="col_expires">{{ t('col_expires') }}</span></th>
<th class="text-right font-medium px-4 py-3"><span data-i18n="col_actions">{{ t('col_actions') }}</span></th>
</tr>
</thead>
<tbody id="users-body">
<tr><td colspan="6" class="px-4 py-10 text-center text-gray-600" data-i18n="loading">{{ t('loading') }}</td></tr>
</tbody>
</table>
</div>
</div>
<div class="mt-3 text-xs text-gray-600">
<span class="text-yellow-500" data-i18n="users_disable">{{ t('users_disable') }}</span> <span data-i18n="users_disable_warning">{{ t('users_disable_warning') }}</span>
<span class="ml-3 text-red-400" data-i18n="users_delete">{{ t('users_delete') }}</span> <span data-i18n="users_delete_warning">{{ t('users_delete_warning') }}</span>
</div>
<div class="mt-6 bg-gray-900 rounded-lg border border-gray-800 p-4">
<h3 class="text-sm font-semibold text-gray-300 mb-3"><span data-i18n="users_role_perms">{{ t('users_role_perms') }}</span></h3>
<div class="grid grid-cols-3 gap-4 text-xs">
<div><div class="text-blue-400 font-medium mb-1">Admin</div><div class="text-gray-500"><span data-i18n="users_perm_admin">{{ t('users_perm_admin') }}</span></div></div>
<div><div class="text-purple-400 font-medium mb-1">Engineer</div><div class="text-gray-500"><span data-i18n="users_perm_engineer">{{ t('users_perm_engineer') }}</span></div></div>
<div><div class="text-green-400 font-medium mb-1">Reviewer</div><div class="text-gray-500"><span data-i18n="users_perm_reviewer">{{ t('users_perm_reviewer') }}</span></div></div>
</div>
</div>
</div>
<div id="user-modal" class="hidden fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
<div class="w-full max-w-lg bg-gray-900 border border-gray-700 rounded-lg shadow-2xl">
<div class="flex items-center justify-between px-5 py-4 border-b border-gray-800">
<h3 id="user-modal-title" class="text-sm font-semibold text-gray-200"><span data-i18n="users_new">{{ t('users_new') }}</span></h3>
<button onclick="closeUserModal()" class="text-gray-500 hover:text-gray-200"><iconify-icon icon="ant-design:close-outlined"></iconify-icon></button>
</div>
<form id="user-form" onsubmit="saveUser(event)" class="p-5 space-y-4 text-xs">
<input type="hidden" id="user-id">
<div class="grid grid-cols-2 gap-3">
<label class="block text-gray-500">Username
<input id="user-username" required maxlength="64" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
</label>
<label class="block text-gray-500">Role
<select id="user-role" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
<option value="engineer">Engineer</option>
<option value="reviewer">Reviewer</option>
<option value="admin">Admin</option>
</select>
</label>
</div>
<label class="block text-gray-500">Email
<input id="user-email" type="email" maxlength="128" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
</label>
<label class="block text-gray-500"><span data-i18n="field_password">{{ t('field_password') }}</span> <span id="password-hint" class="text-gray-600">(at least 6 characters)</span>
<input id="user-password" type="password" minlength="6" maxlength="128" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
</label>
<div class="grid grid-cols-2 gap-3">
<label id="status-field" class="block text-gray-500">Status
<select id="user-status" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
<option value="active">Active</option>
<option value="disabled">Disabled</option>
</select>
</label>
<label class="block text-gray-500"><span data-i18n="field_expires_at">{{ t('field_expires_at') }}</span> <span class="text-gray-600" data-i18n="users_optional">{{ t('users_optional') }}</span>
<input id="user-expires" type="datetime-local" class="mt-1 w-full bg-gray-800 text-sm rounded px-3 py-2 border border-gray-700 focus:border-blue-500 focus:outline-none">
</label>
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" onclick="closeUserModal()" class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-4 py-2 rounded"><span data-i18n="cancel">{{ t('cancel') }}</span></button>
<button id="user-save-btn" type="submit" class="bg-blue-600 hover:bg-blue-500 text-white text-xs px-4 py-2 rounded font-medium"><span data-i18n="users_create">{{ t('users_create') }}</span></button>
</div>
</form>
</div>
</div>
<script src="/static/js/users.js?v={{ asset_version }}"></script>
{% endblock %}
|