stouchinglhh
Claude
sync processor from GitLab c697b6e: 版本 1.19.4 / 前端 1.12.4 —— 服务端 i18n(首屏不再闪英文)、推理模块并入模型页、前端抽出 shared/、缓存键改用前端号
bb59211
Raw History Blame Contribute Delete
9.79 kB
{% 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 %}