<template>
<el-dialog :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true"
:model-value="data.show" :title="data.title" :class="['openDesignDialog', 'super-large-dialog']"
:ok-loading="data.loading" @close="close">
<template #footer>
<div class="flex-between">
<div class="flex-row" v-if="data.form.nodes.length > 1">
<div class="label-color mr" v-if="data.form.status === jdbcStatusEnum.unTest">
{{ $t('database.AddJdbc.else1') }}
</div>
<el-tag v-if="data.form.status === jdbcStatusEnum.success" type="success">
{{ $t('database.AddJdbc.5oxhkhiks5k0') }}
</el-tag>
<el-tag v-if="data.form.status === jdbcStatusEnum.fail" type="danger">
{{ $t('database.AddJdbc.5oxhkhimwfg0') }}
</el-tag>
</div>
<div>
<el-button class="mr" @click="close">{{ $t('database.AddJdbc.5oxhkhimwy00') }}</el-button>
<el-button :loading="data.testLoading" class="mr" @click="handleTestHost()" v-if="data.form.nodes.length > 1">
{{ $t('database.AddJdbc.5oxhkhimx5c0') }}
</el-button>
<el-button :loading="data.loading" type="primary" @click="submit">
{{ $t('database.AddJdbc.5oxhkhimxbg0') }}
</el-button>
</div>
</div>
</template>
<el-form :model="data.form" ref="formRef" label-width="auto" :rules="formRules">
<el-row :gutter="16">
<el-col :span="18">
<el-form-item v-if="!data.form.isCustomName" :label="$t('database.AddJdbc.5oxhkhimxho0')"
validate-trigger="blur">
<el-input v-model="clusterName" :placeholder="$t('database.AddJdbc.customNamePlaceholder')" disabled></el-input>
</el-form-item>
<el-form-item v-else prop="name" :label="$t('database.AddJdbc.5oxhkhimxho0')" validate-trigger="blur">
<el-input v-model="data.form.name" :placeholder="$t('database.AddJdbc.5oxhkhimz480')"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="0">
<el-checkbox v-model="data.form.isCustomName" @change="handleCustomChange(data.form.isCustomName)">
{{ $t('database.AddJdbc.isCustomName') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('database.AddJdbc.5oxhkhimxto0')" validate-trigger="change" style="margin-bottom: 14px">
<el-radio-group v-model="data.form.dbType" class="radio-group-w" @change="changeJDBCType">
<el-radio-button v-for="item in data.dbTypes" :label="item.label" :value="item.value" class="radio-item">
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<div v-if="data.form.dbType === JDBCType.Milvus || data.form.dbType === JDBCType.Elasticsearch">
<div class="jdbc-instance-c">
<jdbc-instance :form-data="data.form.nodes[0]" :host-list="data.hostList" :jdbc-type="data.form.dbType"
:ref="(el: any) => setRefMap(el, data.form.nodes[0].id)" />
</div>
</div>
<div v-else>
<el-tabs type="card" :editable="true" :closable="true" class="clean-add-icon-only"
@tab-remove="handleDelete" @add="handleAdd" @edit="handleTabsEdit"
v-model="data.activeTab" :show-add-button="true" :auto-switch="true">
<template #add-icon>
<el-icon class="custom-add-icon"><IconPlus /></el-icon>
</template>
<el-tab-pane v-for="item of data.form.nodes" :key="item.id" :name="item.id"
:closable="data.form.nodes.length > 1" class="bordered-tab-pane">
<template #label>
<span class="tab-label">
<el-tooltip v-if="item.status === jdbcStatusEnum.fail" :content="$t('database.AddJdbc.5oxhkhimwfg0')" placement="top">
<el-icon><WarningFilled /></el-icon>
</el-tooltip>
{{ item.ip.trim() ? item.ip : $t('database.AddJdbc.5oxhkhimyio0') + item.tabName }}
</span>
</template>
<div class="jdbc-instance-c">
<jdbc-instance :form-data="item" :host-list="data.hostList" :jdbc-type="data.form.dbType"
:ref="(el: any) => setRefMap(el, item.id)"></jdbc-instance>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { KeyValue } from '@/types/global'
import { ElForm } from 'element-plus'
import { nextTick, reactive, ref, computed } from 'vue'
import { addJdbc, editJdbc, hostListAll } from '@/api/ops'
import { Message } from '@arco-design/web-vue'
import JdbcInstance from './JdbcInstance.vue'
import { useI18n } from 'vue-i18n'
import { encryptPassword } from "@/utils/jsencrypt";
import showMessage from '@/hooks/showMessage'
import {JDBCType} from "@/types/jdbc";
import type { TabPaneName } from 'element-plus'
const { t } = useI18n()
enum jdbcStatusEnum {
unTest = -1,
success = 1,
fail = 0
}
const data = reactive<KeyValue>({
show: false,
title: '',
testLoading: false,
loading: false,
getHostLoading: false,
form: {
clusterId: '',
name: '',
isCustomName: false,
dbType: JDBCType.MySQL,
nodes: [],
status: jdbcStatusEnum.unTest
},
hostList: [],
activeTab: '',
dbTypes: [
{ label: 'MySQL', value: JDBCType.MySQL },
{ label: 'openGauss', value: JDBCType.openGauss },
{ label: 'PostgreSQL', value: JDBCType.PostgreSQL },
{ label: 'Elasticsearch', value: JDBCType.Elasticsearch },
{ label: 'Milvus', value: JDBCType.Milvus }
]
})
const formRef = ref<null | InstanceType<typeof ElForm>>(null)
const handleCustomChange = (val: boolean) => {
formRef.value?.clearValidate()
if (val) {
data.form.name = clusterName.value
}
}
const clusterName = computed(() => {
let result = ''
if (!data.form.isCustomName) {
result = getNameByNode(data.form)
} else {
result = clusterName.value
}
return result
})
const getNameByNode = (data: KeyValue) => {
let result = ''
if (data.nodes.length) {
data.nodes.forEach((item: KeyValue, index: number) => {
if (index < 2 && item.ip && item.port) {
result += `${item.ip}(${item.port})-`
}
})
if (result) {
result += data.nodes.length
}
}
return result
}
const formRules = computed(() => {
return {
name: [
{ required: true, message: t('database.AddJdbc.5oxhkhimz480'), trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (!value.trim()) {
callback(new Error(t('database.AddJdbc.5oxhkhimzcg0')))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
})
const refObj = ref<KeyValue>({})
const setRefMap = (el: HTMLElement, key: string) => {
if (!refObj.value[key]) {
refObj.value[key] = el
}
}
const refList = computed(() => {
const result = []
const refs = Object.keys(refObj.value)
const validNodeIds = data.form.nodes.map((node: KeyValue) => node.id)
if (refs.length) {
for (let key in refObj.value) {
if (refObj.value[key] && validNodeIds.includes(key)) {
result.push(refObj.value[key])
}
}
}
return result
})
const getHostList = () => {
data.getHostLoading = true
hostListAll().then((res: KeyValue) => {
console.log('show hostLIst', res)
data.hostList = []
if (Number(res.code) === 200) {
if (res.data.length) {
res.data.forEach((item: KeyValue) => {
data.hostList.push({
label: item.publicIp,
value: item.publicIp
})
})
}
}
}).finally(() => {
data.getHostLoading = false
})
}
const emits = defineEmits([`finish`])
const submit = async () => {
if (!formRef.value) return
const methodArr = []
for (let i = 0; i < refList.value.length; i++) {
if (refList.value[i]) {
methodArr.push(refList.value[i].formValidate())
}
}
try {
await formRef.value.validate()
const res = await Promise.all(methodArr)
const validRes = res.filter((item: KeyValue) => {
return item && item.res === false
})
if (validRes.length) {
data.activeTab = validRes[0].id
return
}
data.loading = true
let param: {
clusterName: string,
dbType: string,
deployType: string,
nodes: Array<KeyValue>
} = {
clusterName: data.form.name,
dbType: data.form.dbType,
deployType: data.form.nodes.length > 1 ? 'CLUSTER' : 'SINGLE_NODE',
nodes: []
}
if (!data.form.isCustomName) {
param.clusterName = clusterName.value
}
data.form.nodes.forEach((item: any) => {
const newItem = { ...item }
newItem.extendProps = JSON.stringify(newItem.props)
param.nodes.push(newItem)
})
if(data.form.dbType !== JDBCType.Milvus && data.form.dbType !== JDBCType.Elasticsearch) {
for (const item of param.nodes) {
if ( item.password.length > 0 ) {
const temppassword = await encryptPassword(item.password)
item.password = temppassword
}
}
}
if (data.form.clusterId) {
editJdbc(data.form.clusterId, param).then((res: KeyValue) => {
data.loading = false
if (Number(res.code) === 200) {
Message.success({content: `Modified success`})
emits(`finish`)
}
close()
}).catch((error) => {
data.form.status = jdbcStatusEnum.fail
const errorMsg = error?.message || error?.msg || String(error)
if (errorMsg.includes('jdbc failed to get connection') && data.form.nodes.length === 1) {
data.form.nodes[0].status = jdbcStatusEnum.fail
} else {
data.form.nodes[0].status = jdbcStatusEnum.unTest
}
console.log(error)
}).finally(() => {
data.loading = false
})
} else {
addJdbc(param).then((res: KeyValue) => {
data.loading = false
if (Number(res.code) === 200) {
Message.success({content: `Create success`})
emits(`finish`)
}
close()
}).catch((error) => {
data.form.status = jdbcStatusEnum.fail
const errorMsg = error?.message || error?.msg || String(error)
if (errorMsg.includes('jdbc failed to get connection') && data.form.nodes.length === 1) {
data.form.nodes[0].status = jdbcStatusEnum.fail
} else {
data.form.nodes[0].status = jdbcStatusEnum.unTest
}
console.log(error)
}).finally(() => {
data.loading = false
})
}
} catch (error) {
console.log('Validation failed:', error)
return
}
}
const close = () => {
nextTick(() => {
formRef.value?.clearValidate()
formRef.value?.resetFields()
delRefObj()
})
data.show = false
}
const handleTestHost = () => {
const methodArr = []
for (let i = 0; i < refList.value.length; i++) {
if (refList.value[i]) {
methodArr.push(refList.value[i].formValidate())
}
}
Promise.all(methodArr).then((res) => {
const validRes = res.filter((item: KeyValue) => {
return item.res === false
})
if (validRes.length) {
data.activeTab = validRes[0].id
return
}
data.testLoading = true
const methodTestArr = []
for (let i = 0; i < refList.value.length; i++) {
if (refList.value[i]) {
methodTestArr.push(refList.value[i].handelTest())
}
}
Promise.all(methodTestArr).then((testRes) => {
const noPass = testRes.filter((item: KeyValue) => {
return item.res === false
})
if (noPass.length) {
data.activeTab = noPass[0].id
data.form.status = jdbcStatusEnum.fail
return
}
data.form.status = jdbcStatusEnum.success
}).finally(() => {
data.testLoading = false
})
})
}
const handleAdd = () => {
const id = new Date().getTime() + ''
let port = JDBCType.getDefaultPort(data.form.dbType)
let username = ''
let password = ''
const firstNode = data.form.nodes[0]
if (firstNode) {
port = firstNode.port
username = firstNode.username
password = firstNode.password
}
data.form.nodes.push({
id: id,
tabName: data.form.nodes.length + 1,
url: '',
urlSuffix: '',
ip: '',
port: port,
username: username,
password: password,
props: [{
name: '',
value: ''
}],
status: jdbcStatusEnum.unTest
})
data.activeTab = id
}
const handleDelete = (val: any) => {
if (refObj.value[val]) {
delete refObj.value[val]
}
data.form.nodes = data.form.nodes.filter((item: KeyValue) => {
return item.id !== val
})
if (data.form.nodes.length > 0) {
data.activeTab = data.form.nodes[0].id
}
}
const handleTabsEdit = (
targetName: TabPaneName | undefined,
action: 'remove' | 'add'
) => {
if (action === 'add') {
handleAdd()
} else if (action === 'remove') {
handleDelete(targetName)
}
}
const changeJDBCType = () => {
delRefObj()
data.form.nodes = []
handleAdd()
}
const getProps = (url: string): KeyValue[] => {
const result: KeyValue[] = []
if (!url) {
result.push({
name: '',
value: ''
})
return result
}
const urlSuffix = url.split('?')[1]
if (!urlSuffix) {
result.push({
name: '',
value: ''
})
return result
}
const extendPropsArr = urlSuffix.split('&')
extendPropsArr.forEach((item: string) => {
const itemArr = item.split('=')
const temp = {
name: itemArr[0],
value: itemArr[1]
}
result.push(temp)
})
if (!result.length) {
result.push({
name: '',
value: ''
})
}
return result
}
const open = async (type: string, editData?: KeyValue) => {
data.show = true
data.loading = false
data.testLoading = false
getHostList()
if (type === 'update' && data) {
delRefObj()
data.title = t('database.AddJdbc.5oxhkhimzmw0')
if (editData) {
Object.assign(data.form, {
clusterId: editData.clusterId,
name: editData.name,
dbType: editData.dbType,
nodes: []
})
let flag = true;
for (const item of editData.nodes) {
const temp = {
id: item.clusterNodeId,
url: item.url,
ip: item.ip,
port: Number(item.port),
username: item.username,
password: '',
props: getProps(item.url),
status: jdbcStatusEnum.unTest
}
if (!temp.password) {
flag = false;
}
data.form.nodes.push(temp)
}
if (!flag) {
showMessage('warning', t('database.AddJdbc.checkPassword'))
}
const nameByNode = getNameByNode(data.form)
if (nameByNode === data.form.name) {
data.form.isCustomName = false
}
nextTick(() => {
data.activeTab = data.form.nodes[0].id
})
}
} else {
data.title = t('database.AddJdbc.5oxhkhimzww0')
delRefObj()
Object.assign(data.form, {
clusterId: '',
name: '',
dbType: JDBCType.MySQL,
nodes: [],
status: jdbcStatusEnum.unTest
})
handleAdd()
}
}
const delRefObj = () => {
for (let key in refObj.value) {
delete refObj.value[key]
}
}
defineExpose({
open
})
</script>
<style lang="less" scoped>
.jdbc-instance-c {
padding: 15px;
}
.bordered-tab-pane {
border: 1px solid var(--o-border-color-base);
border-top: none;
margin-top: -1px;
}
.custom-add-btn-tabs :deep(.el-tabs__new-tab) {
background: transparent;
border: 1px dashed var(--el-border-color);
color: var(--el-text-color-secondary);
}
.custom-add-btn-tabs :deep(.el-tabs__new-tab:hover) {
background: transparent;
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
.clean-add-icon-only :deep(.el-tabs__new-tab) {
border: none !important;
background: transparent !important;
box-shadow: none !important;
margin-left: 8px;
}
.add-icon-clean {
color: var(--o-color-info);
transition: color 0.3s ease;
}
.tab-content-wrapper {
padding: 16px;
}
</style>