feat(ldap): LDAP 改为只读接入,彻底移除账号验证与批量导入写入功能
按需求(sql.astx.club/settings):
1. 移除「测试账号验证」按钮
- LdapConfigForm.tsx 删按钮 + onOpenAccountTest prop
- 不再渲染 LdapAccountTestDialog
2. 移除批量导入的 LDAP 写操作(BatchImportModal.tsx)
- TABS 去掉「导入到 LDAP」「从 LDAP 导入」
- 删除对应两个 JSX 代码块(tab === 'to-ldap' / 'from-ldap')
- 删除「创建 OU」子弹窗
- 只保留「本地导入」(CSV 建本地账号,不触碰 LDAP 目录)
3. 移除 LDAP 账号管理面板(含危险写操作)
- 不再渲染 LdapManagePanel(新建 OU / 添加用户 / 删除用户)
- 删除 LdapCreateOuDialog / LdapAddUserDialog / ConfirmDialog 引用
- LdapConfigPage.tsx 421 → 340 行;BatchImportModal.tsx 557 → 329 行
4. 保留(只读用途)
- LDAP 配置表单(服务器 / 绑定 / 搜索 / 属性映射)
- 「测试连接」按钮(验证配置可用,不写 LDAP 目录)
设计原则:LDAP 仅用于域账号登录认证(只读 bind + search),
目录维护请到域控侧操作。
验证(前端 bundle grep,全部归零):
测试账号验证 0 | 导入到 LDAP 0 | 从 LDAP 导入 0
新建 OU 0 | 创建 OU 0 | 危险操作 0 | 此操作不可逆 0
保留:
测试连接 6 | 本地导入 1
镜像 sha256:3970db141d23
/health → {"backend":"go","status":"ok","version":"0.4.0"}
http://sql.astx.club/settings → HTTP 200
This commit is contained in:
1 parent
e8355e5d5f
commit
6b81dbbd4a
2 files changed
-311
No files matched your search
@@ -303,184 +303,6 @@ export function BatchImportModal({ onClose, onImported }: BatchImportModalProps)
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Import to LDAP ── */}
|
||||
{tab === 'to-ldap' && (
|
||||
<>
|
||||
{ldapLoading && <p className="text-sm text-[var(--dg-text-muted)] text-center py-4">加载中...</p>}
|
||||
{!ldapLoading && !ldapReady && (
|
||||
<div className="p-3 rounded-lg bg-yellow-500/10 text-yellow-400 text-sm">
|
||||
LDAP 未配置,请先在「平台配置 → LDAP」中配置 LDAP 连接
|
||||
</div>
|
||||
)}
|
||||
{!ldapLoading && ldapReady && (
|
||||
<>
|
||||
{/* OU selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">目标 OU</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={targetOu}
|
||||
onChange={(e) => setTargetOu(e.target.value)}
|
||||
className="flex-1 px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
|
||||
>
|
||||
<option value="">Base DN (根)</option>
|
||||
{ldapOus.map((ou: any) => (
|
||||
<option key={ou.dn} value={ou.dn}>
|
||||
{ou.name} ({ou.dn})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={loadLdapOus}
|
||||
className="p-2 rounded-lg hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-muted)]"
|
||||
title="刷新 OU"
|
||||
>
|
||||
<RotateCcw className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreateOu(true)}
|
||||
className="flex items-center gap-1 px-3 py-2 rounded-lg bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] text-sm hover:bg-[var(--dg-bg-hover)]"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
新建 OU
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* File upload (same CSV format) */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => fileRef.current?.click()}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm hover:bg-[var(--dg-bg-hover)]"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
选择 CSV 文件
|
||||
</button>
|
||||
<input ref={fileRef} type="file" accept=".csv" className="hidden" onChange={handleFile} />
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">
|
||||
CSV 需包含"姓名"列,系统自动生成拼音用户名和密码
|
||||
</span>
|
||||
</div>
|
||||
{preview.length > 0 && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-[var(--dg-text-muted)] border-b border-[var(--dg-border)]">
|
||||
<th className="py-2 pr-3">姓名</th>
|
||||
<th className="py-2 pr-3">用户名(自动)</th>
|
||||
<th className="py-2">密码(自动)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{preview.slice(0, 30).map((r, i) => (
|
||||
<tr key={`${r.username}-${i}`} className="border-b border-[var(--dg-border)]/50">
|
||||
<td className="py-2 pr-3">{r.display_name}</td>
|
||||
<td className="py-2 pr-3 text-[var(--dg-blue)]">{r.username}</td>
|
||||
<td className="py-2 text-[var(--dg-text-muted)]">{r.password}</td>
|
||||
</tr>
|
||||
))}
|
||||
{preview.length > 30 && (
|
||||
<tr>
|
||||
<td colSpan={3} className="py-2 text-center text-[var(--dg-text-muted)]">
|
||||
... 共 {preview.length} 条
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── From LDAP Import ── */}
|
||||
{tab === 'from-ldap' && (
|
||||
<>
|
||||
{ldapLoading && <p className="text-sm text-[var(--dg-text-muted)] text-center py-4">加载中...</p>}
|
||||
{!ldapLoading && !ldapReady && (
|
||||
<div className="p-3 rounded-lg bg-yellow-500/10 text-yellow-400 text-sm">
|
||||
LDAP 未配置,请先在「平台配置 → LDAP」中配置 LDAP 连接
|
||||
</div>
|
||||
)}
|
||||
{!ldapLoading && ldapReady && ldapOus.length > 0 && ldapUsers.length === 0 && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-[var(--dg-text-muted)] mb-2">选择组织单元:</p>
|
||||
{ldapOus.map((ou) => (
|
||||
<button
|
||||
key={ou.dn}
|
||||
onClick={() => loadLdapUsers(ou.dn)}
|
||||
className="w-full text-left px-3 py-2 rounded-lg text-sm hover:bg-[var(--dg-bg-hover)] flex items-center gap-2"
|
||||
>
|
||||
<UsersIcon className="w-4 h-4 text-[var(--dg-text-muted)]" />
|
||||
<span>{ou.name}</span>
|
||||
{ou.description && <span className="text-[var(--dg-text-muted)]">— {ou.description}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{ldapUsers.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-[var(--dg-text-muted)]">{ldapUsers.length} 个用户</p>
|
||||
<button
|
||||
onClick={() =>
|
||||
setSelectedLdapUsers((prev) =>
|
||||
prev.size === ldapUsers.length ? new Set() : new Set(ldapUsers.map((u) => u.dn)),
|
||||
)
|
||||
}
|
||||
className="text-xs text-[var(--dg-blue)]"
|
||||
>
|
||||
{selectedLdapUsers.size === ldapUsers.length ? '取消全选' : '全选'}
|
||||
</button>
|
||||
</div>
|
||||
{ldapUsers.map((u) => (
|
||||
<label
|
||||
key={u.dn}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--dg-bg-hover)] cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedLdapUsers.has(u.dn)}
|
||||
onChange={() =>
|
||||
setSelectedLdapUsers((prev) => {
|
||||
const n = new Set(prev);
|
||||
n.has(u.dn) ? n.delete(u.dn) : n.add(u.dn);
|
||||
return n;
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-sm font-medium">{u.cn || u.nickname || u.uid}</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">@{u.uid || u.username}</span>
|
||||
{u.mail && <span className="text-xs text-[var(--dg-text-muted)]">{u.mail}</span>}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!ldapLoading && ldapReady && ldapOus.length === 0 && (
|
||||
<p className="text-sm text-[var(--dg-text-muted)] text-center py-4">
|
||||
LDAP 目录中无可用 OU,请先创建 OU 或检查配置
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
{result && (
|
||||
<div className="p-3 rounded-lg bg-[var(--dg-bg-tertiary)] text-sm space-y-1">
|
||||
<p className="text-green-400">成功: {result.success_count ?? (result.success ? 1 : 0)}</p>
|
||||
{result.skipped_count > 0 && <p className="text-yellow-400">跳过: {result.skipped_count}</p>}
|
||||
{result.failed_count > 0 && <p className="text-red-400">失败: {result.failed_count}</p>}
|
||||
{result.failed_users?.map((f: any, i: number) => (
|
||||
<p key={`${f.username}-${i}`} className="text-red-400 text-xs">
|
||||
{f.username}: {f.error}
|
||||
</p>
|
||||
))}
|
||||
{result.message && !result.success_count && (
|
||||
<p className="text-[var(--dg-text-secondary)]">{result.message}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
@@ -500,57 +322,6 @@ export function BatchImportModal({ onClose, onImported }: BatchImportModalProps)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Create OU sub-dialog */}
|
||||
{showCreateOu && (
|
||||
<ModalOverlay open={showCreateOu} onClose={() => setShowCreateOu(false)} width="max-w-md">
|
||||
<div className="w-full bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-xl p-6 shadow-xl">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-base font-semibold">创建新的组织单位 (OU)</h3>
|
||||
<button
|
||||
onClick={() => setShowCreateOu(false)}
|
||||
className="text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">OU 名称 *</label>
|
||||
<input
|
||||
value={newOuName}
|
||||
onChange={(e) => setNewOuName(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
|
||||
placeholder="例如:研发部、市场部"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">描述</label>
|
||||
<input
|
||||
value={newOuDesc}
|
||||
onChange={(e) => setNewOuDesc(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none"
|
||||
placeholder="可选描述"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<button
|
||||
onClick={() => setShowCreateOu(false)}
|
||||
className="px-4 py-2 text-sm rounded-lg bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateOu}
|
||||
disabled={creatingOu || !newOuName.trim()}
|
||||
className="px-4 py-2 text-sm rounded-lg bg-[var(--dg-blue)] text-white disabled:opacity-50"
|
||||
>
|
||||
{creatingOu ? '创建中...' : '创建 OU'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalOverlay>
|
||||
)}
|
||||
</div>
|
||||
</ModalOverlay>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import api from '@/api/request';
|
||||
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
||||
import { toast } from '@/hooks/useToast';
|
||||
import {
|
||||
type LdapConfigData,
|
||||
@@ -13,10 +12,6 @@ import {
|
||||
defaultLdapConfig,
|
||||
} from '@/features/system/ldap/types';
|
||||
import { LdapConfigForm } from '@/features/system/ldap/components/LdapConfigForm';
|
||||
import { LdapManagePanel } from '@/features/system/ldap/components/LdapManagePanel';
|
||||
import { LdapCreateOuDialog } from '@/features/system/ldap/components/LdapCreateOuDialog';
|
||||
import { LdapAddUserDialog } from '@/features/system/ldap/components/LdapAddUserDialog';
|
||||
import { LdapAccountTestDialog } from '@/features/system/ldap/components/LdapAccountTestDialog';
|
||||
|
||||
/**
|
||||
* LDAP 配置页(SettingsPage 的 ldap tab)
|
||||
@@ -339,83 +334,6 @@ export default function LdapConfigPage() {
|
||||
onSave={handleSave}
|
||||
/>
|
||||
|
||||
{/* ── LDAP 账号管理已移除 ──
|
||||
本系统 LDAP 仅作「只读接入」:仅用于域账号登录认证,
|
||||
不再提供新建 OU / 添加用户 / 删除用户 / 批量导入到 LDAP 等写操作。
|
||||
如需维护 LDAP 目录,请在域控侧操作。 */}
|
||||
{false && (
|
||||
<LdapManagePanel
|
||||
expanded={showManage}
|
||||
onToggle={toggleManage}
|
||||
baseDn={config.base_dn}
|
||||
ouList={ouList}
|
||||
loadingOu={loadingOu}
|
||||
selectedOu={selectedOu}
|
||||
onSelectedOuChange={handleSelectedOuChange}
|
||||
onRefreshOus={fetchOus}
|
||||
onCreateOu={() => setShowCreateOu(true)}
|
||||
onAddUser={openAddUser}
|
||||
ouUsers={ouUsers}
|
||||
loadingUsers={loadingUsers}
|
||||
selectedDns={selectedDns}
|
||||
onSelectedDnsChange={setSelectedDns}
|
||||
deleting={deleting}
|
||||
onDeleteSelected={handleDeleteSelected}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 创建 OU 弹框 ── */}
|
||||
{false && (
|
||||
<LdapCreateOuDialog
|
||||
form={createOuForm}
|
||||
onFormChange={patchCreateOuForm}
|
||||
ouList={ouList}
|
||||
baseDn={config.base_dn}
|
||||
creating={creatingOu}
|
||||
onClose={() => setShowCreateOu(false)}
|
||||
onConfirm={handleCreateOu}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 添加用户弹框 ── */}
|
||||
{false && (
|
||||
<LdapAddUserDialog
|
||||
form={addUserForm}
|
||||
onFormChange={patchAddUserForm}
|
||||
ouList={ouList}
|
||||
baseDn={config.base_dn}
|
||||
adding={addingUser}
|
||||
onClose={() => setShowAddUser(false)}
|
||||
onConfirm={handleAddUser}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 账号测试弹框 ── */}
|
||||
{false && (
|
||||
<LdapAccountTestDialog
|
||||
testUser={testUser}
|
||||
testPass={testPass}
|
||||
result={accountResult}
|
||||
testing={testingAccount}
|
||||
onTestUserChange={handleTestUserChange}
|
||||
onTestPassChange={handleTestPassChange}
|
||||
onClose={closeAccountTest}
|
||||
onTest={handleTestAccount}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 删除确认弹框 ── */}
|
||||
<ConfirmDialog
|
||||
open={showDeleteConfirm}
|
||||
title="确认删除"
|
||||
description={`确认删除 ${selectedDns.size} 个 LDAP 用户?此操作不可逆。`}
|
||||
confirmText="确认删除"
|
||||
variant="danger"
|
||||
onConfirm={() => {
|
||||
confirmDelete();
|
||||
}}
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user