fix: 修复CSP白屏并优化LDAP配置
This commit is contained in:
1 parent
a14d9e62e6
commit
c7f59f2cbf
12 files changed
+930
-70
No files matched your search
@@ -73,7 +73,7 @@
|
||||
"passwordLabel": "Password",
|
||||
"passwordPlaceholder": "Enter password",
|
||||
"loginButton": "Sign In",
|
||||
"defaultAccount": "Default account: <strong>admin / admin123</strong> — please change password after first login",
|
||||
"defaultAccount": "Default account: {account} — please change password after first login",
|
||||
"errorEmpty": "Please enter username and password",
|
||||
"loginSuccess": "Login successful",
|
||||
"changePasswordFirst": "Please change your password first",
|
||||
@@ -279,14 +279,22 @@
|
||||
"roleOperator": "Operator",
|
||||
"roleViewer": "Viewer",
|
||||
"noUsers": "No users yet",
|
||||
"ldapTitle": "LDAP Identity Source",
|
||||
"ldapTitle": "LDAP / Active Directory",
|
||||
"ldapHint": "LDAP users can be auto-synced on first login, or pre-imported using the \"LDAP Import\" on the left.",
|
||||
"ldapUrl": "LDAP URL",
|
||||
"bindDn": "Bind DN",
|
||||
"bindPassword": "Bind Password",
|
||||
"ldapConfigHint": "Configure an LDAP server for centralized identity authentication",
|
||||
"ldapEnabled": "Enable LDAP",
|
||||
"ldapServerAddress": "LDAP Server Address",
|
||||
"ldapServerAddressPlaceholder": "e.g. 8.217.205.92 or ldap.example.com",
|
||||
"ldapPort": "Port",
|
||||
"ldapProtocol": "Protocol",
|
||||
"ldapProtocolLDAPS": "LDAPS (Recommended)",
|
||||
"ldapProtocolLDAP": "LDAP",
|
||||
"bindDn": "Administrator Bind DN",
|
||||
"bindPassword": "Administrator Password",
|
||||
"keepOriginal": "Leave empty to keep original",
|
||||
"baseDn": "Base DN",
|
||||
"userFilter": "User Filter",
|
||||
"baseDn": "Base DN (User Search Base)",
|
||||
"userFilter": "Search Filter (optional; %s is the username)",
|
||||
"enableLdaps": "Enable LDAPS (SSL encryption)",
|
||||
"usernameAttr": "Username Attribute",
|
||||
"testConnection": "Test Connection",
|
||||
"editUser": "Edit User",
|
||||
@@ -320,6 +328,7 @@
|
||||
"ldapLoadFailed": "LDAP load failed",
|
||||
"ldapConfigSaved": "LDAP configuration saved",
|
||||
"ldapConnected": "LDAP connection successful",
|
||||
"ldapTestRequired": "Test the LDAP connection successfully before saving",
|
||||
"ldapConnectFailed": "Connection failed",
|
||||
"emptyUsername": "(empty)",
|
||||
"errorDetails": "Error details:"
|
||||
@@ -502,8 +511,9 @@
|
||||
"plugins": {
|
||||
"title": "Custom Protocol Plugins",
|
||||
"registerPlugin": "Register Plugin",
|
||||
"pluginHint": "Plugins implement the unified protocol contract via <b>command hooks</b>: the platform pipes tunnel config JSON to hook commands via stdin.",
|
||||
"pluginContract": "Contract: <code>Create / Start / Stop / Status / Config</code>; Status hook outputs <code>running</code> or <code>stopped</code> on stdout; Config hook outputs client config text.",
|
||||
"commandHooks": "command hooks",
|
||||
"pluginHint": "Plugins implement the unified protocol contract via {hooks}: the platform pipes tunnel config JSON to hook commands via stdin.",
|
||||
"pluginContract": "Contract: {contract}; Status hook outputs {running} or {stopped} on stdout; Config hook outputs client config text.",
|
||||
"protocolId": "Protocol ID",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
@@ -1024,4 +1034,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@
|
||||
"passwordLabel": "密码",
|
||||
"passwordPlaceholder": "请输入密码",
|
||||
"loginButton": "登 录",
|
||||
"defaultAccount": "默认账号 <strong>admin / admin123</strong>,登录后请立即修改",
|
||||
"defaultAccount": "默认账号 {account},登录后请立即修改",
|
||||
"errorEmpty": "请输入用户名和密码",
|
||||
"loginSuccess": "登录成功",
|
||||
"changePasswordFirst": "首次登录请先修改密码",
|
||||
@@ -279,14 +279,22 @@
|
||||
"roleOperator": "运维",
|
||||
"roleViewer": "只读",
|
||||
"noUsers": "还没有用户",
|
||||
"ldapTitle": "LDAP 身份源",
|
||||
"ldapTitle": "LDAP / Active Directory",
|
||||
"ldapHint": "LDAP 用户可首次登录后自动同步,也可使用左侧「LDAP 导入」预先添加。",
|
||||
"ldapUrl": "LDAP URL",
|
||||
"bindDn": "Bind DN",
|
||||
"bindPassword": "Bind 密码",
|
||||
"ldapConfigHint": "配置 LDAP 服务器,实现统一身份认证",
|
||||
"ldapEnabled": "启用 LDAP",
|
||||
"ldapServerAddress": "LDAP 服务器地址",
|
||||
"ldapServerAddressPlaceholder": "例如 8.217.205.92 或 ldap.example.com",
|
||||
"ldapPort": "端口",
|
||||
"ldapProtocol": "连接协议",
|
||||
"ldapProtocolLDAPS": "LDAPS(推荐)",
|
||||
"ldapProtocolLDAP": "LDAP",
|
||||
"bindDn": "管理员 Bind DN",
|
||||
"bindPassword": "管理员密码",
|
||||
"keepOriginal": "留空保持原值",
|
||||
"baseDn": "Base DN",
|
||||
"userFilter": "用户过滤",
|
||||
"baseDn": "Base DN(用户搜索基础)",
|
||||
"userFilter": "搜索过滤器(可选,%s 表示用户名)",
|
||||
"enableLdaps": "启用 LDAPS(SSL 加密)",
|
||||
"usernameAttr": "用户名属性",
|
||||
"testConnection": "测试连接",
|
||||
"editUser": "编辑用户",
|
||||
@@ -320,6 +328,7 @@
|
||||
"ldapLoadFailed": "LDAP 加载失败",
|
||||
"ldapConfigSaved": "LDAP 配置已保存",
|
||||
"ldapConnected": "LDAP 连接成功",
|
||||
"ldapTestRequired": "请先测试 LDAP 连接,通过后才能保存",
|
||||
"ldapConnectFailed": "连接失败",
|
||||
"emptyUsername": "(空)",
|
||||
"errorDetails": "失败详情:"
|
||||
@@ -502,8 +511,9 @@
|
||||
"plugins": {
|
||||
"title": "自定义协议插件",
|
||||
"registerPlugin": "注册插件",
|
||||
"pluginHint": "插件通过<b>命令钩子</b>实现统一协议契约:平台将专线配置 JSON 通过 stdin 传给钩子命令。",
|
||||
"pluginContract": "契约:<code>Create / Start / Stop / Status / Config</code>;Status 钩子 stdout 输出 <code>running</code> 或 <code>stopped</code>;Config 钩子 stdout 输出客户端配置文本。",
|
||||
"commandHooks": "命令钩子",
|
||||
"pluginHint": "插件通过 {hooks} 实现统一协议契约:平台将专线配置 JSON 通过 stdin 传给钩子命令。",
|
||||
"pluginContract": "契约:{contract};Status 钩子 stdout 输出 {running} 或 {stopped};Config 钩子 stdout 输出客户端配置文本。",
|
||||
"protocolId": "协议标识",
|
||||
"enabled": "启用",
|
||||
"disabled": "停用",
|
||||
@@ -1024,4 +1034,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,9 @@
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p v-html="$t('login.defaultAccount')"></p>
|
||||
<i18n-t keypath="login.defaultAccount" tag="p">
|
||||
<template #account><strong>admin / admin123</strong></template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:14px">
|
||||
<p style="margin:0" v-html="$t('plugins.pluginHint')"></p>
|
||||
<p style="margin:4px 0 0;font-size:12px" v-html="$t('plugins.pluginContract')"></p>
|
||||
<i18n-t keypath="plugins.pluginHint" tag="p" style="margin:0">
|
||||
<template #hooks><b>{{ $t('plugins.commandHooks') }}</b></template>
|
||||
</i18n-t>
|
||||
<i18n-t keypath="plugins.pluginContract" tag="p" style="margin:4px 0 0;font-size:12px">
|
||||
<template #contract><code>Create / Start / Stop / Status / Config</code></template>
|
||||
<template #running><code>running</code></template>
|
||||
<template #stopped><code>stopped</code></template>
|
||||
</i18n-t>
|
||||
</el-alert>
|
||||
<el-table :data="plugins" size="default" v-loading="loading">
|
||||
<el-table-column prop="display_name" :label="$t('common.name')" width="160" />
|
||||
|
||||
@@ -46,20 +46,28 @@
|
||||
|
||||
<el-col :xs="24" :lg="9">
|
||||
<el-card class="page-card" shadow="never">
|
||||
<template #header><b>{{ $t('users.ldapTitle') }}</b></template>
|
||||
<el-alert type="info" :closable="false"
|
||||
:title="$t('users.ldapHint')" style="margin-bottom:12px" />
|
||||
<el-form label-width="90px" size="small">
|
||||
<el-form-item :label="$t('common.enabled')"><el-switch v-model="ldap.enabled" /></el-form-item>
|
||||
<el-form-item :label="$t('users.ldapUrl')"><el-input v-model="ldap.url" placeholder="ldaps://ldap.example.com:636" /></el-form-item>
|
||||
<el-form-item :label="$t('users.bindDn')"><el-input v-model="ldap.bind_dn" /></el-form-item>
|
||||
<template #header>
|
||||
<div>
|
||||
<b>{{ $t('users.ldapTitle') }}</b>
|
||||
<div style="margin-top:4px;font-size:12px;color:var(--text-secondary)">{{ $t('users.ldapConfigHint') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="112px" size="small">
|
||||
<el-form-item :label="$t('users.ldapEnabled')"><el-switch v-model="ldap.enabled" /></el-form-item>
|
||||
<el-form-item :label="$t('users.ldapServerAddress')">
|
||||
<el-input v-model.trim="ldap.url" :placeholder="$t('users.ldapServerAddressPlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('users.ldapPort')">
|
||||
<el-input-number v-model="ldapPort" :min="1" :max="65535" :controls="false" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('users.baseDn')"><el-input v-model.trim="ldap.base_dn" placeholder="dc=opsx,dc=vip" /></el-form-item>
|
||||
<el-form-item :label="$t('users.bindDn')"><el-input v-model.trim="ldap.bind_dn" placeholder="cn=admin,dc=opsx,dc=vip" /></el-form-item>
|
||||
<el-form-item :label="$t('users.bindPassword')"><el-input v-model="ldap.bind_password" type="password" show-password :placeholder="$t('users.keepOriginal')" /></el-form-item>
|
||||
<el-form-item :label="$t('users.baseDn')"><el-input v-model="ldap.base_dn" placeholder="dc=example,dc=com" /></el-form-item>
|
||||
<el-form-item :label="$t('users.userFilter')"><el-input v-model="ldap.user_filter" placeholder="(objectClass=inetOrgPerson)" /></el-form-item>
|
||||
<el-form-item :label="$t('users.usernameAttr')"><el-input v-model="ldap.username_attribute" placeholder="uid" /></el-form-item>
|
||||
<el-form-item :label="$t('users.userFilter')"><el-input v-model.trim="ldap.user_filter" placeholder="(uid=%s)" /></el-form-item>
|
||||
<el-form-item :label="$t('users.enableLdaps')"><el-switch v-model="ldapUseLDAPS" /></el-form-item>
|
||||
<div style="text-align:right">
|
||||
<el-button @click="testLDAP">{{ $t('users.testConnection') }}</el-button>
|
||||
<el-button type="primary" @click="saveLDAP">{{ $t('common.save') }}</el-button>
|
||||
<el-button :loading="ldapTesting" @click="testLDAP">{{ $t('users.testConnection') }}</el-button>
|
||||
<el-button type="primary" :disabled="ldap.enabled && !ldapTested" :title="ldap.enabled && !ldapTested ? $t('users.ldapTestRequired') : ''" @click="saveLDAP">{{ $t('common.save') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -147,7 +155,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import api, { auth } from '../api'
|
||||
@@ -163,6 +171,10 @@ const ldap = ref({
|
||||
base_dn: '', user_filter: '(objectClass=inetOrgPerson)', username_attribute: 'uid',
|
||||
start_tls: false,
|
||||
})
|
||||
const ldapUseLDAPS = ref(false)
|
||||
const ldapPort = ref(389)
|
||||
const ldapTested = ref(false)
|
||||
const ldapTesting = ref(false)
|
||||
|
||||
// 批量导入
|
||||
const batchVisible = ref(false)
|
||||
@@ -183,6 +195,13 @@ async function load() {
|
||||
const [u, l] = await Promise.all([api.get('/users'), api.get('/ldap')])
|
||||
users.value = u.data || []
|
||||
if (l.data) {
|
||||
const savedAddress = String(l.data.url || '').trim()
|
||||
const protocolMatch = savedAddress.match(/^(ldap|ldaps):\/\/(\[[^\]]+\]|[^/:]+)(?::(\d+))?$/i)
|
||||
if (protocolMatch) {
|
||||
ldapUseLDAPS.value = protocolMatch[1].toLowerCase() === 'ldaps'
|
||||
l.data.url = protocolMatch[2].replace(/^\[|\]$/g, '')
|
||||
ldapPort.value = Number(protocolMatch[3]) || (ldapUseLDAPS.value ? 636 : 389)
|
||||
}
|
||||
ldap.value = { ...ldap.value, ...l.data, bind_password: '' }
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -190,6 +209,20 @@ async function load() {
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function ldapPayload() {
|
||||
const address = ldap.value.url.trim()
|
||||
const host = address.includes(':') && !address.startsWith('[') ? `[${address}]` : address
|
||||
const port = Number(ldapPort.value)
|
||||
const protocol = ldapUseLDAPS.value ? 'ldaps' : 'ldap'
|
||||
return { ...ldap.value, url: address && port >= 1 && port <= 65535 ? `${protocol}://${host}:${port}` : '' }
|
||||
}
|
||||
|
||||
watch(ldapUseLDAPS, (useLDAPS, previousUseLDAPS) => {
|
||||
const previousPort = previousUseLDAPS ? 636 : 389
|
||||
if (ldapPort.value === previousPort) ldapPort.value = useLDAPS ? 636 : 389
|
||||
})
|
||||
watch([ldap, ldapUseLDAPS, ldapPort], () => { ldapTested.value = false }, { deep: true })
|
||||
|
||||
// ---- 单用户 CRUD ----
|
||||
function openCreate() {
|
||||
editing.value = null
|
||||
@@ -290,16 +323,22 @@ async function doLDAPImport() {
|
||||
// ---- LDAP 配置 ----
|
||||
async function saveLDAP() {
|
||||
try {
|
||||
await api.put('/ldap', ldap.value)
|
||||
if (ldap.value.enabled && !ldapTested.value) return ElMessage.warning(t('users.ldapTestRequired'))
|
||||
await api.put('/ldap', ldapPayload())
|
||||
ldap.value.bind_password = ''
|
||||
ElMessage.success(t('users.ldapConfigSaved'))
|
||||
} catch (e) { ElMessage.error(e.response?.data?.error || t('common.saveFailed')) }
|
||||
}
|
||||
async function testLDAP() {
|
||||
ldapTesting.value = true
|
||||
try {
|
||||
await api.post('/ldap/test')
|
||||
await api.post('/ldap/test', ldapPayload())
|
||||
ldapTested.value = true
|
||||
ElMessage.success(t('users.ldapConnected'))
|
||||
} catch (e) { ElMessage.error(e.response?.data?.error || t('users.ldapConnectFailed')) }
|
||||
} catch (e) {
|
||||
ldapTested.value = false
|
||||
ElMessage.error(e.response?.data?.error || t('users.ldapConnectFailed'))
|
||||
} finally { ldapTesting.value = false }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
|
||||
Reference in new issue
Block a user