feat: 用户管理列表增加来源字段(auth_source),支持本地/LDAP筛选

This commit is contained in:
hermes-$(hostname) committed 2026-05-29 17:05:56 +00:00
1 parent ac07e6ef38
commit 8384006ae9
5 files changed
+196 -117

No files matched your search

+31 -3
View File
@@ -24,6 +24,7 @@ interface UserItem {
is_active: boolean;
created_at: string;
last_login?: string | null;
auth_source?: string; // 'local' | 'ldap'
}
interface UserActivity {
@@ -70,6 +71,12 @@ const statusOptions = [
{ value: 'disabled', label: '禁用' },
];
const sourceOptions = [
{ value: '', label: '全部来源' },
{ value: 'local', label: '本地' },
{ value: 'ldap', label: 'LDAP' },
];
const BATCH_OPTIONS = [
{ key: 'enable', label: '批量启用', icon: UserCheck },
{ key: 'disable', label: '批量禁用', icon: UserX },
@@ -124,6 +131,7 @@ export default function UsersPage() {
const [search, setSearch] = useState('');
const [roleFilter, setRoleFilter] = useState('');
const [statusFilter, setStatusFilter] = useState('');
const [sourceFilter, setSourceFilter] = useState('');
const [users, setUsers] = useState<UserItem[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
@@ -159,7 +167,10 @@ export default function UsersPage() {
useEffect(() => { fetchUsers(); }, [fetchUsers]);
const filtered = users;
const filtered = users.filter(u => {
if (sourceFilter && u.auth_source !== sourceFilter) return false;
return true;
});
const allSelected = filtered.length > 0 && filtered.every(u => selectedIds.has(u.id));
@@ -298,9 +309,16 @@ export default function UsersPage() {
>
{statusOptions.map(s => <option key={s.value} value={s.value}>{s.label}</option>)}
</select>
{(search || roleFilter || statusFilter) && (
<select
className="px-3 py-2.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
value={sourceFilter}
onChange={e => setSourceFilter(e.target.value)}
>
{sourceOptions.map(s => <option key={s.value} value={s.value}>{s.label}</option>)}
</select>
{(search || roleFilter || statusFilter || sourceFilter) && (
<button
onClick={() => { setSearch(''); setRoleFilter(''); setStatusFilter(''); setPage(1); }}
onClick={() => { setSearch(''); setRoleFilter(''); setStatusFilter(''); setSourceFilter(''); setPage(1); }}
className="flex items-center gap-1 text-xs text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)] transition-colors"
>
<RotateCcw className="w-3 h-3" /> 重置
@@ -382,6 +400,7 @@ export default function UsersPage() {
<th className="text-left px-4 py-3 font-semibold">邮箱</th>
<th className="text-left px-4 py-3 font-semibold">最后登录</th>
<th className="text-left px-4 py-3 font-semibold">状态</th>
<th className="text-left px-4 py-3 font-semibold">来源</th>
<th className="text-left px-4 py-3 font-semibold">操作</th>
</tr>
</thead>
@@ -432,6 +451,15 @@ export default function UsersPage() {
/>
</button>
</td>
<td className="px-4 py-3.5">
<span className={`inline-flex px-2 py-0.5 rounded text-xs font-medium ${
user.auth_source === 'ldap'
? 'bg-blue-500/15 text-blue-400'
: 'bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-muted)]'
}`}>
{user.auth_source === 'ldap' ? 'LDAP' : '本地'}
</span>
</td>
<td className="px-4 py-3.5">
<div className="flex gap-1.5">
<button