mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-11 12:08:30 +08:00
- 新增 Management Token(访问令牌)功能,支持创建、更新、删除和管理 - 前端添加访问令牌管理页面,支持普通用户和管理员 - 后端实现完整的令牌生命周期管理 API - 添加数据库迁移脚本创建 management_tokens 表 - Nginx 配置添加 gzip 压缩,优化响应传输 - Dialog 组件添加 persistent 属性,防止意外关闭 - 为管理后台 API 添加详细的中文文档注释 - 简化多处类型注解,统一代码风格
427 lines
13 KiB
Vue
427 lines
13 KiB
Vue
<template>
|
||
<PageContainer>
|
||
<PageHeader
|
||
title="LDAP 配置"
|
||
description="配置 LDAP 认证服务"
|
||
/>
|
||
|
||
<div class="mt-6 space-y-6">
|
||
<CardSection
|
||
title="LDAP 服务器配置"
|
||
description="配置 LDAP 服务器连接参数"
|
||
>
|
||
<template #actions>
|
||
<div class="flex gap-2">
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
:disabled="testLoading"
|
||
@click="handleTestConnection"
|
||
>
|
||
{{ testLoading ? '测试中...' : '测试连接' }}
|
||
</Button>
|
||
<Button
|
||
size="sm"
|
||
:disabled="saveLoading"
|
||
@click="handleSave"
|
||
>
|
||
{{ saveLoading ? '保存中...' : '保存' }}
|
||
</Button>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<Label
|
||
for="server-url"
|
||
class="block text-sm font-medium"
|
||
>
|
||
服务器地址
|
||
</Label>
|
||
<Input
|
||
id="server-url"
|
||
v-model="ldapConfig.server_url"
|
||
type="text"
|
||
placeholder="ldap://ldap.example.com:389"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
格式: ldap://host:389 或 ldaps://host:636
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="bind-dn"
|
||
class="block text-sm font-medium"
|
||
>
|
||
绑定 DN
|
||
</Label>
|
||
<Input
|
||
id="bind-dn"
|
||
v-model="ldapConfig.bind_dn"
|
||
type="text"
|
||
placeholder="cn=admin,dc=example,dc=com"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
用于连接 LDAP 服务器的管理员 DN
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="bind-password"
|
||
class="block text-sm font-medium"
|
||
>
|
||
绑定密码
|
||
</Label>
|
||
<div class="relative mt-1">
|
||
<Input
|
||
id="bind-password"
|
||
v-model="ldapConfig.bind_password"
|
||
type="password"
|
||
:placeholder="hasPassword ? '已设置(留空保持不变)' : '请输入密码'"
|
||
:class="(hasPassword || ldapConfig.bind_password) ? 'pr-10' : ''"
|
||
autocomplete="new-password"
|
||
/>
|
||
<button
|
||
v-if="hasPassword || ldapConfig.bind_password"
|
||
type="button"
|
||
class="absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-full text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted/50 transition-colors"
|
||
title="清除密码"
|
||
@click="handleClearPassword"
|
||
>
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
><line
|
||
x1="18"
|
||
y1="6"
|
||
x2="6"
|
||
y2="18"
|
||
/><line
|
||
x1="6"
|
||
y1="6"
|
||
x2="18"
|
||
y2="18"
|
||
/></svg>
|
||
</button>
|
||
</div>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
绑定账号的密码
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="base-dn"
|
||
class="block text-sm font-medium"
|
||
>
|
||
基础 DN
|
||
</Label>
|
||
<Input
|
||
id="base-dn"
|
||
v-model="ldapConfig.base_dn"
|
||
type="text"
|
||
placeholder="ou=users,dc=example,dc=com"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
用户搜索的基础 DN
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="user-search-filter"
|
||
class="block text-sm font-medium"
|
||
>
|
||
用户搜索过滤器
|
||
</Label>
|
||
<Input
|
||
id="user-search-filter"
|
||
v-model="ldapConfig.user_search_filter"
|
||
type="text"
|
||
placeholder="(uid={username})"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
{username} 会被替换为登录用户名
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="username-attr"
|
||
class="block text-sm font-medium"
|
||
>
|
||
用户名属性
|
||
</Label>
|
||
<Input
|
||
id="username-attr"
|
||
v-model="ldapConfig.username_attr"
|
||
type="text"
|
||
placeholder="uid"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
常用: uid (OpenLDAP), sAMAccountName (AD)
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="email-attr"
|
||
class="block text-sm font-medium"
|
||
>
|
||
邮箱属性
|
||
</Label>
|
||
<Input
|
||
id="email-attr"
|
||
v-model="ldapConfig.email_attr"
|
||
type="text"
|
||
placeholder="mail"
|
||
class="mt-1"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="display-name-attr"
|
||
class="block text-sm font-medium"
|
||
>
|
||
显示名称属性
|
||
</Label>
|
||
<Input
|
||
id="display-name-attr"
|
||
v-model="ldapConfig.display_name_attr"
|
||
type="text"
|
||
placeholder="cn"
|
||
class="mt-1"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<Label
|
||
for="connect-timeout"
|
||
class="block text-sm font-medium"
|
||
>
|
||
连接超时 (秒)
|
||
</Label>
|
||
<Input
|
||
id="connect-timeout"
|
||
v-model.number="ldapConfig.connect_timeout"
|
||
type="number"
|
||
min="1"
|
||
max="60"
|
||
placeholder="10"
|
||
class="mt-1"
|
||
/>
|
||
<p class="mt-1 text-xs text-muted-foreground">
|
||
单次 LDAP 操作超时时间 (1-60秒),跨国网络建议 15-30 秒
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 space-y-4">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<Label class="text-sm font-medium">使用 STARTTLS</Label>
|
||
<p class="text-xs text-muted-foreground">
|
||
在非 SSL 连接上启用 TLS 加密
|
||
</p>
|
||
</div>
|
||
<Switch v-model="ldapConfig.use_starttls" />
|
||
</div>
|
||
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<Label class="text-sm font-medium">启用 LDAP 认证</Label>
|
||
<p class="text-xs text-muted-foreground">
|
||
允许用户使用 LDAP 账号登录
|
||
</p>
|
||
</div>
|
||
<Switch v-model="ldapConfig.is_enabled" />
|
||
</div>
|
||
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<Label class="text-sm font-medium">仅允许 LDAP 登录</Label>
|
||
<p class="text-xs text-muted-foreground">
|
||
禁用本地账号登录,仅允许 LDAP 认证
|
||
</p>
|
||
</div>
|
||
<Switch v-model="ldapConfig.is_exclusive" />
|
||
</div>
|
||
</div>
|
||
</CardSection>
|
||
</div>
|
||
</PageContainer>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { PageContainer, PageHeader, CardSection } from '@/components/layout'
|
||
import { Button, Input, Label, Switch } from '@/components/ui'
|
||
import { useToast } from '@/composables/useToast'
|
||
import { adminApi, type LdapConfigUpdateRequest } from '@/api/admin'
|
||
|
||
const { success, error } = useToast()
|
||
|
||
const loading = ref(false)
|
||
const saveLoading = ref(false)
|
||
const testLoading = ref(false)
|
||
const hasPassword = ref(false)
|
||
const clearPassword = ref(false) // 标记是否要清除密码
|
||
|
||
const ldapConfig = ref({
|
||
server_url: '',
|
||
bind_dn: '',
|
||
bind_password: '',
|
||
base_dn: '',
|
||
user_search_filter: '(uid={username})',
|
||
username_attr: 'uid',
|
||
email_attr: 'mail',
|
||
display_name_attr: 'cn',
|
||
is_enabled: false,
|
||
is_exclusive: false,
|
||
use_starttls: false,
|
||
connect_timeout: 10,
|
||
})
|
||
|
||
onMounted(async () => {
|
||
await loadConfig()
|
||
})
|
||
|
||
async function loadConfig() {
|
||
loading.value = true
|
||
try {
|
||
const response = await adminApi.getLdapConfig()
|
||
ldapConfig.value = {
|
||
server_url: response.server_url || '',
|
||
bind_dn: response.bind_dn || '',
|
||
bind_password: '',
|
||
base_dn: response.base_dn || '',
|
||
user_search_filter: response.user_search_filter || '(uid={username})',
|
||
username_attr: response.username_attr || 'uid',
|
||
email_attr: response.email_attr || 'mail',
|
||
display_name_attr: response.display_name_attr || 'cn',
|
||
is_enabled: response.is_enabled || false,
|
||
is_exclusive: response.is_exclusive || false,
|
||
use_starttls: response.use_starttls || false,
|
||
connect_timeout: response.connect_timeout || 10,
|
||
}
|
||
hasPassword.value = !!response.has_bind_password
|
||
clearPassword.value = false
|
||
} catch (err) {
|
||
error('加载 LDAP 配置失败')
|
||
console.error('加载 LDAP 配置失败:', err)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSave() {
|
||
saveLoading.value = true
|
||
try {
|
||
const payload: LdapConfigUpdateRequest = {
|
||
server_url: ldapConfig.value.server_url,
|
||
bind_dn: ldapConfig.value.bind_dn,
|
||
base_dn: ldapConfig.value.base_dn,
|
||
user_search_filter: ldapConfig.value.user_search_filter,
|
||
username_attr: ldapConfig.value.username_attr,
|
||
email_attr: ldapConfig.value.email_attr,
|
||
display_name_attr: ldapConfig.value.display_name_attr,
|
||
is_enabled: ldapConfig.value.is_enabled,
|
||
is_exclusive: ldapConfig.value.is_exclusive,
|
||
use_starttls: ldapConfig.value.use_starttls,
|
||
connect_timeout: ldapConfig.value.connect_timeout,
|
||
}
|
||
|
||
// 优先使用输入的新密码;否则如果标记清除则发送空字符串
|
||
let passwordAction: 'unchanged' | 'updated' | 'cleared' = 'unchanged'
|
||
if (ldapConfig.value.bind_password) {
|
||
payload.bind_password = ldapConfig.value.bind_password
|
||
passwordAction = 'updated'
|
||
} else if (clearPassword.value) {
|
||
payload.bind_password = ''
|
||
passwordAction = 'cleared'
|
||
}
|
||
|
||
await adminApi.updateLdapConfig(payload)
|
||
success('LDAP 配置保存成功')
|
||
|
||
if (passwordAction === 'cleared') {
|
||
hasPassword.value = false
|
||
clearPassword.value = false
|
||
} else if (passwordAction === 'updated') {
|
||
hasPassword.value = true
|
||
clearPassword.value = false
|
||
}
|
||
ldapConfig.value.bind_password = ''
|
||
} catch (err) {
|
||
error('保存 LDAP 配置失败')
|
||
console.error('保存 LDAP 配置失败:', err)
|
||
} finally {
|
||
saveLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleTestConnection() {
|
||
if (clearPassword.value && !ldapConfig.value.bind_password) {
|
||
error('已标记清除绑定密码,请先保存或输入新的绑定密码再测试')
|
||
return
|
||
}
|
||
|
||
testLoading.value = true
|
||
try {
|
||
const payload: LdapConfigUpdateRequest = {
|
||
server_url: ldapConfig.value.server_url,
|
||
bind_dn: ldapConfig.value.bind_dn,
|
||
base_dn: ldapConfig.value.base_dn,
|
||
user_search_filter: ldapConfig.value.user_search_filter,
|
||
username_attr: ldapConfig.value.username_attr,
|
||
email_attr: ldapConfig.value.email_attr,
|
||
display_name_attr: ldapConfig.value.display_name_attr,
|
||
is_enabled: ldapConfig.value.is_enabled,
|
||
is_exclusive: ldapConfig.value.is_exclusive,
|
||
use_starttls: ldapConfig.value.use_starttls,
|
||
connect_timeout: ldapConfig.value.connect_timeout,
|
||
...(ldapConfig.value.bind_password && { bind_password: ldapConfig.value.bind_password }),
|
||
}
|
||
const response = await adminApi.testLdapConnection(payload)
|
||
if (response.success) {
|
||
success('LDAP 连接测试成功')
|
||
} else {
|
||
error(`LDAP 连接测试失败: ${response.message}`)
|
||
}
|
||
} catch (err) {
|
||
error('LDAP 连接测试失败')
|
||
console.error('LDAP 连接测试失败:', err)
|
||
} finally {
|
||
testLoading.value = false
|
||
}
|
||
}
|
||
|
||
function handleClearPassword() {
|
||
// 如果有输入内容,先清空输入框
|
||
if (ldapConfig.value.bind_password) {
|
||
ldapConfig.value.bind_password = ''
|
||
return
|
||
}
|
||
// 标记要清除服务端密码(保存时生效)
|
||
if (hasPassword.value) {
|
||
clearPassword.value = true
|
||
hasPassword.value = false
|
||
}
|
||
}
|
||
</script>
|