feat: LDAP HA

This commit is contained in:
wangruidong
2024-09-04 15:46:56 +08:00
committed by Bryan
parent 0b9f47dd84
commit 41f841532f
7 changed files with 432 additions and 155 deletions

View File

@@ -0,0 +1,103 @@
<template>
<Dialog
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('SyncSetting')"
top="10%"
v-bind="$attrs"
width="50%"
v-on="$listeners"
>
<GenericCreateUpdateForm
:has-detail-in-msg="false"
v-bind="settings"
@submitSuccess="onSuccess"
/>
</Dialog>
</template>
<script>
import { GenericCreateUpdateForm } from '@/layout/components'
import { CronTab, Dialog } from '@/components'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import { Required } from '@/components/Form/DataForm/rules'
export default {
name: 'SyncSettingDialog',
components: {
GenericCreateUpdateForm,
Dialog
},
data() {
return {
settings: {
visible: false,
url: '/api/v1/settings/setting/?category=ldap_ha',
fields: [
'AUTH_LDAP_HA_SYNC_ORG_IDS', 'AUTH_LDAP_HA_SYNC_IS_PERIODIC', 'AUTH_LDAP_HA_SYNC_CRONTAB',
'AUTH_LDAP_HA_SYNC_INTERVAL', 'AUTH_LDAP_HA_SYNC_RECEIVERS'
],
fieldsMeta: {
AUTH_LDAP_HA_SYNC_ORG_IDS: {
component: Select2,
rules: [Required],
el: {
popperClass: 'sync-setting-org',
multiple: true,
ajax: {
url: '/api/v1/orgs/orgs/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: (formValue) => {
return !this.$hasLicense()
}
},
AUTH_LDAP_HA_SYNC_IS_PERIODIC: {
type: 'switch'
},
AUTH_LDAP_HA_SYNC_CRONTAB: {
component: CronTab,
helpTip: this.$t('CrontabOfCreateUpdatePage')
},
AUTH_LDAP_HA_SYNC_INTERVAL: {
helpText: this.$t('IntervalOfCreateUpdatePage')
},
AUTH_LDAP_HA_SYNC_RECEIVERS: {
component: Select2,
el: {
value: [],
multiple: true,
ajax: {
url: '/api/v1/users/users/?fields_size=mini&oid=ROOT',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
submitMethod: () => 'patch',
cleanFormValue(value) {
if (value['AUTH_LDAP_HA_SYNC_INTERVAL'] === '') {
value['AUTH_LDAP_HA_SYNC_INTERVAL'] = null
}
return value
}
}
}
},
methods: {
onSuccess() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -58,6 +58,12 @@ export default {
Dialog,
Select2
},
props: {
category: {
type: String,
required: true
}
},
data() {
return {
dialogLdapUserImportLoginStatus: false,
@@ -76,7 +82,7 @@ export default {
}
},
tableConfig: {
url: '/api/v1/settings/ldap/users/',
url: `/api/v1/settings/ldap/users/?category=${this.category}`,
columns: ['status', 'username', 'name', 'email', 'groups', 'existing'],
columnsMeta: {
...getStatusColumnMeta.bind(this)('status'),
@@ -191,7 +197,7 @@ export default {
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ldap/'
const url = `/ws/ldap/?category=${this.category}`
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
},

View File

@@ -0,0 +1,151 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
<ImportDialog v-if="dialogLdapUserImport" :category="category" :visible.sync="dialogLdapUserImport" />
<TestLoginDialog :visible.sync="dialogTest" :category="category" />
<SyncSettingDialog :visible.sync="dialogSyncSetting" />
</IBox>
</template>
<script>
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
import ImportDialog from './ImportDialog.vue'
import TestLoginDialog from './TestLoginDialog.vue'
import SyncSettingDialog from './SyncSettingDialog.vue'
import { IBox } from '@/components'
import rules, { JsonRequired } from '@/components/Form/DataForm/rules'
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
export default {
name: 'Ldap',
components: {
GenericCreateUpdateForm,
IBox,
ImportDialog,
TestLoginDialog,
SyncSettingDialog
},
data() {
const category = 'ldap'
return {
category: category,
url: `/api/v1/settings/setting/?category=${category}`,
dialogTest: false,
dialogLdapUserImport: false,
dialogSyncSetting: false,
encryptedFields: ['AUTH_LDAP_BIND_PASSWORD'],
fields: [
[
this.$t('Basic'),
[
'AUTH_LDAP', 'AUTH_LDAP_SERVER_URI',
'AUTH_LDAP_BIND_DN', 'AUTH_LDAP_BIND_PASSWORD'
]
],
[
this.$t('Search'),
[
'AUTH_LDAP_SEARCH_OU', 'AUTH_LDAP_SEARCH_FILTER',
'AUTH_LDAP_USER_ATTR_MAP'
]
],
[
this.$t('Other'),
[
'AUTH_LDAP_CONNECT_TIMEOUT', 'AUTH_LDAP_SEARCH_PAGED_SIZE',
'AUTH_LDAP_CACHE_TIMEOUT'
]
]
],
fieldsMeta: {
AUTH_LDAP_BIND_DN: {
rules: [
rules.Required
]
},
AUTH_LDAP_BIND_PASSWORD: {
component: UpdateToken
},
AUTH_LDAP_SEARCH_OU: {
rules: [
rules.Required
]
},
AUTH_LDAP_USER_ATTR_MAP: {
component: JsonEditor,
rules: [JsonRequired]
}
},
hasDetailInMsg: false,
moreButtons: [
{
title: this.$t('LdapConnectTest'),
loading: false,
callback: function(value, form, btn) {
if (value['AUTH_LDAP_BIND_PASSWORD'] === undefined) {
value['AUTH_LDAP_BIND_PASSWORD'] = ''
}
btn.loading = true
this.enableWS()
this.ws.onopen = (e) => {
this.ws.send(JSON.stringify({ msg_type: 'testing_config', ...value }))
}
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data)
if (data.ok) {
this.$message.success(data.msg)
} else {
this.$message.error(data.msg)
}
btn.loading = false
}
}.bind(this)
},
{
title: this.$t('LdapLoginTest'),
callback: function(value, form) {
this.dialogTest = true
}.bind(this)
},
{
title: this.$t('LdapBulkImport'),
callback: function(value, form) {
this.dialogLdapUserImport = true
}.bind(this)
},
{
title: this.$t('SyncSetting'),
callback: function(value, form) {
this.dialogSyncSetting = true
}.bind(this)
}
],
submitMethod: () => 'patch',
afterGetFormValue(obj) {
return obj
},
cleanFormValue(data) {
if (data['AUTH_LDAP_BIND_PASSWORD'] === '') {
delete data['AUTH_LDAP_BIND_PASSWORD']
}
return data
}
}
},
methods: {
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ldap/'
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
}
}
}
</script>
<style scoped>
.listTable ::v-deep .table-action-right-side {
padding-top: 0 !important;
}
</style>

View File

@@ -0,0 +1,151 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
<ImportDialog v-if="dialogLdapUserImport" :category="category" :visible.sync="dialogLdapUserImport" />
<TestLoginDialog :visible.sync="dialogTest" :category="category" />
<SyncSettingDialog :visible.sync="dialogSyncSetting" />
</IBox>
</template>
<script>
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
import ImportDialog from './ImportDialog.vue'
import TestLoginDialog from './TestLoginDialog.vue'
import SyncSettingDialog from './HaSyncSettingDialog.vue'
import { IBox } from '@/components'
import rules, { JsonRequired } from '@/components/Form/DataForm/rules'
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
export default {
name: 'LdapHA',
components: {
GenericCreateUpdateForm,
IBox,
ImportDialog,
TestLoginDialog,
SyncSettingDialog
},
data() {
const category = 'ldap_ha'
return {
category: category,
url: `/api/v1/settings/setting/?category=${category}`,
dialogTest: false,
dialogLdapUserImport: false,
dialogSyncSetting: false,
encryptedFields: ['AUTH_LDAP_HA_BIND_PASSWORD'],
fields: [
[
this.$t('Basic'),
[
'AUTH_LDAP_HA', 'AUTH_LDAP_HA_SERVER_URI',
'AUTH_LDAP_HA_BIND_DN', 'AUTH_LDAP_HA_BIND_PASSWORD'
]
],
[
this.$t('Search'),
[
'AUTH_LDAP_HA_SEARCH_OU', 'AUTH_LDAP_HA_SEARCH_FILTER',
'AUTH_LDAP_HA_USER_ATTR_MAP'
]
],
[
this.$t('Other'),
[
'AUTH_LDAP_HA_CONNECT_TIMEOUT', 'AUTH_LDAP_HA_SEARCH_PAGED_SIZE',
'AUTH_LDAP_HA_CACHE_TIMEOUT'
]
]
],
fieldsMeta: {
AUTH_LDAP_HA_BIND_DN: {
rules: [
rules.Required
]
},
AUTH_LDAP_HA_BIND_PASSWORD: {
component: UpdateToken
},
AUTH_LDAP_HA_SEARCH_OU: {
rules: [
rules.Required
]
},
AUTH_LDAP_HA_USER_ATTR_MAP: {
component: JsonEditor,
rules: [JsonRequired]
}
},
hasDetailInMsg: false,
moreButtons: [
{
title: this.$t('LdapConnectTest'),
loading: false,
callback: function(value, form, btn) {
if (value['AUTH_LDAP_HA_BIND_PASSWORD'] === undefined) {
value['AUTH_LDAP_HA_BIND_PASSWORD'] = ''
}
btn.loading = true
this.enableWS()
this.ws.onopen = (e) => {
this.ws.send(JSON.stringify({ msg_type: 'testing_config', ...value }))
}
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data)
if (data.ok) {
this.$message.success(data.msg)
} else {
this.$message.error(data.msg)
}
btn.loading = false
}
}.bind(this)
},
{
title: this.$t('LdapLoginTest'),
callback: function(value, form) {
this.dialogTest = true
}.bind(this)
},
{
title: this.$t('LdapBulkImport'),
callback: function(value, form) {
this.dialogLdapUserImport = true
}.bind(this)
},
{
title: this.$t('SyncSetting'),
callback: function(value, form) {
this.dialogSyncSetting = true
}.bind(this)
}
],
submitMethod: () => 'patch',
afterGetFormValue(obj) {
return obj
},
cleanFormValue(data) {
if (data['AUTH_LDAP_HA_BIND_PASSWORD'] === '') {
delete data['AUTH_LDAP_HA_BIND_PASSWORD']
}
return data
}
}
},
methods: {
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = `/ws/ldap/?category=${this.category}`
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
}
}
}
</script>
<style scoped>
.listTable ::v-deep .table-action-right-side {
padding-top: 0 !important;
}
</style>

View File

@@ -40,6 +40,12 @@ export default {
components: {
Dialog
},
props: {
category: {
type: String,
required: true
}
},
data() {
return {
testLdapLoginStatus: false,
@@ -69,7 +75,7 @@ export default {
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ldap/'
const url = `/ws/ldap/?category=${this.category}`
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
}

View File

@@ -1,152 +1 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
<ImportDialog v-if="dialogLdapUserImport" :visible.sync="dialogLdapUserImport" />
<TestLoginDialog :visible.sync="dialogTest" />
<SyncSettingDialog :visible.sync="dialogSyncSetting" />
</IBox>
</template>
<script>
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
import ImportDialog from './ImportDialog.vue'
import TestLoginDialog from './TestLoginDialog.vue'
import SyncSettingDialog from './SyncSettingDialog.vue'
import { IBox } from '@/components'
import rules, { JsonRequired } from '@/components/Form/DataForm/rules'
import { JsonEditor, UpdateToken } from '@/components/Form/FormFields'
export default {
name: 'Ldap',
components: {
GenericCreateUpdateForm,
IBox,
ImportDialog,
TestLoginDialog,
SyncSettingDialog
},
data() {
return {
dialogTest: false,
dialogLdapUserImport: false,
dialogSyncSetting: false,
encryptedFields: ['AUTH_LDAP_BIND_PASSWORD'],
fields: [
[
this.$t('Basic'),
[
'AUTH_LDAP', 'AUTH_LDAP_SERVER_URI',
'AUTH_LDAP_BIND_DN', 'AUTH_LDAP_BIND_PASSWORD'
]
],
[
this.$t('Search'),
[
'AUTH_LDAP_SEARCH_OU', 'AUTH_LDAP_SEARCH_FILTER',
'AUTH_LDAP_USER_ATTR_MAP'
]
],
[
this.$t('Other'),
[
'AUTH_LDAP_CONNECT_TIMEOUT', 'AUTH_LDAP_SEARCH_PAGED_SIZE',
'AUTH_LDAP_CACHE_TIMEOUT'
]
]
],
fieldsMeta: {
AUTH_LDAP_BIND_DN: {
rules: [
rules.Required
]
},
AUTH_LDAP_BIND_PASSWORD: {
component: UpdateToken
},
AUTH_LDAP_SEARCH_OU: {
rules: [
rules.Required
]
},
AUTH_LDAP_USER_ATTR_MAP: {
component: JsonEditor,
rules: [JsonRequired]
}
},
url: '/api/v1/settings/setting/?category=ldap',
hasDetailInMsg: false,
moreButtons: [
{
title: this.$t('LdapConnectTest'),
loading: false,
callback: function(value, form, btn) {
if (value['AUTH_LDAP_BIND_PASSWORD'] === undefined) {
value['AUTH_LDAP_BIND_PASSWORD'] = ''
}
btn.loading = true
this.enableWS()
this.ws.onopen = (e) => {
this.ws.send(JSON.stringify({ msg_type: 'testing_config', ...value }))
}
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data)
if (data.ok) {
this.$message.success(data.msg)
} else {
this.$message.error(data.msg)
}
btn.loading = false
}
}.bind(this)
},
{
title: this.$t('LdapLoginTest'),
callback: function(value, form) {
this.dialogTest = true
}.bind(this)
},
{
title: this.$t('LdapBulkImport'),
callback: function(value, form) {
this.dialogLdapUserImport = true
}.bind(this)
},
{
title: this.$t('SyncSetting'),
callback: function(value, form) {
this.dialogSyncSetting = true
}.bind(this)
}
],
submitMethod: () => 'patch',
afterGetFormValue(obj) {
return obj
},
cleanFormValue(data) {
if (data['AUTH_LDAP_BIND_PASSWORD'] === '') {
delete data['AUTH_LDAP_BIND_PASSWORD']
}
return data
}
}
},
mounted() {
// this.loading = false
},
methods: {
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ldap/'
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
}
}
}
</script>
<style scoped>
.listTable ::v-deep .table-action-right-side {
padding-top: 0 !important;
}
</style>

View File

@@ -8,7 +8,8 @@
<script>
import TabPage from '@/layout/components/TabPage'
import LDAP from './Ldap'
import LdapHA from './Ldap/LdapHA.vue'
import LDAP from './Ldap/Ldap.vue'
import Base from './Base'
import Basic from './Basic'
import CAS from './CAS'
@@ -28,6 +29,7 @@ export default {
components: {
TabPage,
LDAP,
LdapHA,
Base,
Basic,
CAS,
@@ -45,6 +47,7 @@ export default {
},
data() {
let extraBackends = []
let ldapHABackends = []
if (this.$store.getters.hasValidLicense) {
extraBackends = [
{
@@ -93,6 +96,13 @@ export default {
key: 'AUTH_RADIUS'
}
]
ldapHABackends = [
{
title: this.$t('LDAP HA'),
name: 'LdapHA',
key: 'AUTH_LDAP_HA'
}
]
}
return {
loading: true,
@@ -107,6 +117,7 @@ export default {
name: 'LDAP',
key: 'AUTH_LDAP'
},
...ldapHABackends,
{
title: this.$t('CAS'),
name: 'CAS',