refactor: 数据库授权凭证迁移

This commit is contained in:
meilin.huang
2024-04-12 13:24:20 +08:00
parent 4ef8d27b1e
commit abc015aec0
47 changed files with 1011 additions and 558 deletions

View File

@@ -5,10 +5,10 @@ export const TagResourceTypeEnum = {
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }),
Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }),
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }).tagTypeSuccess(),
Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }).tagTypeWarning(),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }).tagTypeInfo(),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }).tagTypeDanger(),
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
};

View File

@@ -4,6 +4,6 @@ export const AccountUsernamePattern = {
};
export const ResourceCodePattern = {
pattern: /^[a-zA-Z0-9_]{1,32}$/g,
message: '只允许输入1-32位大小写字母、数字、下划线',
pattern: /^[a-zA-Z0-9_.:]{1,32}$/g,
message: '只允许输入1-32位大小写字母、数字、_.:',
};

View File

@@ -1,6 +1,6 @@
<template>
<div class="auth-cert-edit">
<el-dialog title="凭证保存" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog :title="props.title" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
<el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules">
<el-form-item prop="type" label="凭证类型" required>
<el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型">
@@ -118,6 +118,10 @@ import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
title: {
type: String,
default: '凭证保存',
},
authCert: {
type: Object,
},
@@ -162,7 +166,7 @@ const rules = {
],
};
const emit = defineEmits(['confirm']);
const emit = defineEmits(['confirm', 'cancel']);
const dialogVisible = defineModel<boolean>('visible', { default: false });
@@ -238,9 +242,10 @@ const getCiphertext = async () => {
const cancelEdit = () => {
dialogVisible.value = false;
emit('cancel');
setTimeout(() => {
state.form = { ...DefaultForm };
acForm.value?.resetFields();
state.form = { ...DefaultForm };
}, 300);
};

View File

@@ -34,9 +34,17 @@
<EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
</el-table>
<ResourceAuthCertEdit v-model:visible="state.dvisible" :auth-cert="state.form" @confirm="btnOk" :disable-type="[AuthCertTypeEnum.Public.value]" />
<ResourceAuthCertEdit
v-model:visible="state.dvisible"
:auth-cert="state.form"
@confirm="btnOk"
@cancel="cancelEdit"
:disable-type="[AuthCertTypeEnum.Public.value]"
:disable-ciphertext-type="props.disableCiphertextType"
/>
</div>
</template>
@@ -51,6 +59,9 @@ import { ElMessage } from 'element-plus';
const props = defineProps({
resourceType: { type: Number },
resourceCode: { type: String },
disableCiphertextType: {
type: Array,
},
testConnBtnLoading: { type: Boolean },
});
@@ -109,7 +120,19 @@ const cancelEdit = () => {
const btnOk = async (authCert: any) => {
const isEdit = authCert.id;
if (isEdit || state.idx > 0) {
if (!isEdit) {
const res = await resourceAuthCertApi.listByQuery.request({
name: authCert.name,
pageNum: 1,
pageSize: 100,
});
if (res.total) {
ElMessage.error('该授权凭证名称已存在');
return;
}
}
if (isEdit || state.idx >= 0) {
authCerts.value[state.idx] = authCert;
cancelEdit();
return;
@@ -119,15 +142,6 @@ const btnOk = async (authCert: any) => {
ElMessage.error('该名称或用户名已存在于该账号列表中');
return;
}
const res = await resourceAuthCertApi.listByQuery.request({
name: authCert.name,
pageNum: 1,
pageSize: 100,
});
if (res.total) {
ElMessage.error('该授权凭证名称已存在');
return;
}
authCerts.value.push(authCert);
cancelEdit();

View File

@@ -30,13 +30,14 @@
remote
:remote-method="getInstances"
@change="changeInstance"
v-model="form.instanceId"
v-model="state.selectInstalce"
value-key="id"
placeholder="请输入实例名称搜索并选择实例"
filterable
clearable
class="w100"
>
<el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
<el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item">
{{ item.name }}
<el-divider direction="vertical" border-style="dashed" />
@@ -47,6 +48,23 @@
</el-select>
</el-form-item>
<el-form-item prop="authCertName" label="授权凭证" required>
<el-select @focus="getAuthCerts" @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
<el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
{{ item.name }}
<el-divider direction="vertical" border-style="dashed" />
{{ item.username }}
<el-divider direction="vertical" border-style="dashed" />
<EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
<el-divider direction="vertical" border-style="dashed" />
{{ item.remark }}
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="code" label="编号" required>
<el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
</el-form-item>
@@ -100,6 +118,10 @@ import type { CheckboxValueType } from 'element-plus';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
import { DbType } from '@/views/ops/db/dialect';
import { ResourceCodePattern } from '@/common/pattern';
import { resourceAuthCertApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
const props = defineProps({
visible: {
@@ -172,6 +194,8 @@ const state = reactive({
dbNamesSelected: [] as any,
dbNamesFiltered: [] as any,
filterString: '',
selectInstalce: {} as any,
authCerts: [] as any,
form: {
id: null,
tagId: [],
@@ -180,6 +204,7 @@ const state = reactive({
database: '',
remark: '',
instanceId: null as any,
authCertName: '',
flowProcdefKey: '',
},
instances: [] as any,
@@ -205,14 +230,27 @@ watch(props, async (newValue: any) => {
}
});
const changeInstance = () => {
const changeInstance = async () => {
state.dbNamesSelected = [];
getAllDatabase();
state.form.instanceId = state.selectInstalce.id;
};
const getAllDatabase = async () => {
const getAuthCerts = async () => {
const res = await resourceAuthCertApi.listByQuery.request({
resourceCode: state.selectInstalce.code,
resourceType: TagResourceTypeEnum.Db.value,
pageSize: 100,
});
state.authCerts = res.list || [];
};
const changeAuthCert = (val: string) => {
getAllDatabase(val);
};
const getAllDatabase = async (authCertName: string) => {
if (state.form.instanceId > 0) {
let dbs = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId });
let dbs = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId, authCertName });
state.allDatabases = dbs;
// 如果是oracle且没查出数据库列表则取实例sid
@@ -238,8 +276,9 @@ const open = async () => {
if (state.form.instanceId) {
// 根据id获取因为需要回显实例名称
await getInstances('', state.form.instanceId);
state.selectInstalce = state.instances[0];
await getAllDatabase(state.form.authCertName);
}
await getAllDatabase();
};
const btnOk = async () => {

View File

@@ -194,7 +194,7 @@
</el-descriptions>
</el-dialog>
<db-edit @val-change="search" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
<db-edit @val-change="search()" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
</div>
</template>

View File

@@ -1,94 +1,111 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
<template #header>
<DrawerHeader :header="title" :back="cancel" />
</template>
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
<el-tabs v-model="tabActiveName">
<el-tab-pane label="基础信息" name="basic">
<el-form-item prop="name" label="别名" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
<el-divider content-position="left">基本</el-divider>
<el-form-item prop="code" label="编号" required>
<el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型" required>
<el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option
v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
:key="key"
:value="dbTypeAndDialect[0]"
:label="dbTypeAndDialect[1].getInfo().name"
>
<SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" />
{{ dbTypeAndDialect[1].getInfo().name }}
</el-option>
<el-form-item prop="type" label="类型" required>
<el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option
v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
:key="key"
:value="dbTypeAndDialect[0]"
:label="dbTypeAndDialect[1].getInfo().name"
>
<SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" />
{{ dbTypeAndDialect[1].getInfo().name }}
</el-option>
<template #prefix>
<SvgIcon :name="getDbDialect(form.type).getInfo().icon" :size="20" />
<template #prefix>
<SvgIcon :name="getDbDialect(form.type).getInfo().icon" :size="20" />
</template>
</el-select>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required>
<el-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
<el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
</el-form-item>
<el-form-item v-if="form.type === DbType.oracle" label="SID|服务名">
<el-col :span="5">
<el-select
@change="
() => {
state.extra.serviceName = '';
state.extra.sid = '';
}
"
v-model="state.extra.stype"
placeholder="请选择"
>
<el-option label="服务名" :value="1" />
<el-option label="SID" :value="2" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="18">
<el-input v-if="state.extra.stype == 1" v-model="state.extra.serviceName" placeholder="请输入服务名"> </el-input>
<el-input v-else v-model="state.extra.sid" placeholder="请输入SID"> </el-input>
</el-col>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<template v-if="form.type !== DbType.sqlite">
<el-divider content-position="left">账号</el-divider>
<div>
<ResourceAuthCertTableEdit
v-model="form.authCerts"
:resource-code="form.code"
:resource-type="TagResourceTypeEnum.Db.value"
:test-conn-btn-loading="testConnBtnLoading"
@test-conn="testConn"
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
/>
</div>
</template>
<!--
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码 </el-link>
</template>
</el-select>
</el-form-item>
</el-popover>
</template>
</el-input>
</el-form-item> -->
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required>
<el-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
<el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
</el-form-item>
<el-form-item v-if="form.type === DbType.oracle" label="SID|服务名">
<el-col :span="5">
<el-select
@change="
() => {
state.extra.serviceName = '';
state.extra.sid = '';
}
"
v-model="state.extra.stype"
placeholder="请选择"
>
<el-option label="服务名" :value="1" />
<el-option label="SID" :value="2" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="18">
<el-input v-if="state.extra.stype == 1" v-model="state.extra.serviceName" placeholder="请输入服务名"> </el-input>
<el-input v-else v-model="state.extra.sid" placeholder="请输入SID"> </el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5"
>原密码
</el-link>
</template>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="params" label="连接参数">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<!-- <template #suffix>
<el-divider content-position="left">其他</el-divider>
<el-form-item prop="params" label="连接参数">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<!-- <template #suffix>
<el-link
target="_blank"
href="https://github.com/go-sql-driver/mysql#parameters"
@@ -98,24 +115,21 @@
>参数参考</el-link
>
</template> -->
</el-input>
</el-form-item>
</el-input>
</el-form-item>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-tab-pane>
</el-tabs>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
@@ -123,11 +137,14 @@
import { reactive, ref, toRefs, watch } from 'vue';
import { dbApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import { DbType, getDbDialect, getDbDialectMap } from './dialect';
import SvgIcon from '@/components/svgIcon/index.vue';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import ResourceAuthCertTableEdit from '../component/ResourceAuthCertTableEdit.vue';
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
const props = defineProps({
visible: {
@@ -145,6 +162,18 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const rules = {
code: [
{
required: true,
message: '请输入编码',
trigger: ['change', 'blur'],
},
{
pattern: ResourceCodePattern.pattern,
message: ResourceCodePattern.message,
trigger: ['blur'],
},
],
name: [
{
required: true,
@@ -166,13 +195,6 @@ const rules = {
trigger: ['blur'],
},
],
username: [
{
required: true,
message: '请输入用户名',
trigger: ['change', 'blur'],
},
],
sid: [
{
required: true,
@@ -186,29 +208,24 @@ const dbForm: any = ref(null);
const state = reactive({
dialogVisible: false,
tabActiveName: 'basic',
extra: {} as any, // 连接需要的额外参数json
form: {
id: null,
type: '',
code: '',
name: null,
host: '',
port: null,
username: null,
authCerts: [],
extra: '', // 连接需要的额外参数json字符串
password: null,
params: null,
remark: '',
sshTunnelMachineId: null as any,
},
submitForm: {},
// 原密码
pwd: '',
// 原用户名
oldUserName: null,
submitForm: {} as any,
});
const { dialogVisible, tabActiveName, form, submitForm, pwd } = toRefs(state);
const { dialogVisible, form, submitForm } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
@@ -218,14 +235,12 @@ watch(props, (newValue: any) => {
if (!state.dialogVisible) {
return;
}
state.tabActiveName = 'basic';
if (newValue.data) {
state.form = { ...newValue.data };
state.oldUserName = state.form.username;
state.extra = JSON.parse(state.form.extra);
} else {
state.form = { port: null, type: DbType.mysql } as any;
state.oldUserName = null;
state.form.authCerts = [];
}
});
@@ -236,13 +251,8 @@ const changeDbType = (val: string) => {
state.extra = {};
};
const getDbPwd = async () => {
state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
};
const getReqForm = async () => {
const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
if (!state.form.sshTunnelMachineId) {
reqForm.sshTunnelMachineId = -1;
}
@@ -252,7 +262,7 @@ const getReqForm = async () => {
return reqForm;
};
const testConn = async () => {
const testConn = async (authCert: any) => {
dbForm.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('请正确填写信息');
@@ -260,20 +270,13 @@ const testConn = async () => {
}
state.submitForm = await getReqForm();
state.submitForm.authCerts = [authCert];
await testConnExec();
ElMessage.success('连接成功');
});
};
const btnOk = async () => {
if (state.form.type !== DbType.sqlite) {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
} else if (state.form.username != state.oldUserName) {
notBlank(state.form.password, '已修改用户名,请输入密码');
}
}
dbForm.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('请正确填写信息');

View File

@@ -3,6 +3,7 @@
<page-table
ref="pageTableRef"
:page-api="dbApi.instances"
:data-handler-fn="handleData"
:searchItems="searchItems"
v-model:query-form="query"
:show-selection="true"
@@ -16,6 +17,10 @@
>
</template>
<template #authCert="{ data }">
<ResourceAuthCert v-model:select-auth-cert="data.selectAuthCert" :auth-certs="data.authCerts" />
</template>
<template #type="{ data }">
<el-tooltip :content="getDbDialect(data.type).getInfo().name" placement="top">
<SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
@@ -35,7 +40,6 @@
<el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
<el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
<el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
@@ -71,6 +75,7 @@ import { hasPerms } from '@/components/auth/auth';
import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect } from './dialect';
import { SearchItem } from '@/components/SearchForm';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
@@ -79,13 +84,14 @@ const perms = {
delInstance: 'db:instance:del',
};
const searchItems = [SearchItem.input('name', '名称')];
const searchItems = [SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
const columns = ref([
TableColumn.new('code', '编号'),
TableColumn.new('name', '名称'),
TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
TableColumn.new('username', '用户名'),
TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(10),
TableColumn.new('params', '连接参数'),
TableColumn.new('remark', '备注'),
]);
@@ -134,6 +140,15 @@ const search = () => {
pageTableRef.value.search();
};
const handleData = (res: any) => {
const dataList = res.list;
// 赋值授权凭证
for (let x of dataList) {
x.selectAuthCert = x.authCerts[0];
}
return res;
};
const showInfo = (info: any) => {
state.infoDialog.data = info;
state.infoDialog.visible = true;

View File

@@ -43,7 +43,6 @@ export const dbApi = {
getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
testConn: Api.newPost('/instances/test-conn'),
saveInstance: Api.newPost('/instances'),
getInstancePwd: Api.newGet('/instances/{id}/pwd'),
deleteInstance: Api.newDelete('/instances/{id}'),
// 获取数据库备份列表

View File

@@ -287,7 +287,7 @@ const columns = [
TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50),
TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55),
TableColumn.new('fs', '磁盘(挂载点=>可用/总)').isSlot().setAddWidth(25),
TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(20),
TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(10),
TableColumn.new('status', '状态').isSlot().setMinWidth(85),
TableColumn.new('remark', '备注'),
TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(),

View File

@@ -19,9 +19,11 @@
</page-table>
<ResourceAuthCertEdit
:title="editor.title"
v-model:visible="editor.visible"
:auth-cert="editor.authcert"
@confirm="confirmSave"
@cancel="editor.authcert = {}"
:disable-type="state.disableAuthCertType"
:disable-ciphertext-type="state.disableAuthCertCiphertextType"
:resource-edit="false"
@@ -73,7 +75,7 @@ const state = reactive({
paramsFormItem: [] as any,
},
editor: {
title: '授权凭证保存',
title: '添加授权凭证',
visible: false,
authcert: {},
},
@@ -93,6 +95,7 @@ const edit = (data: any) => {
state.disableAuthCertType = [];
state.disableAuthCertCiphertextType = [];
if (data) {
state.editor.title = `编辑授权凭证-[${data.name}]`;
state.editor.authcert = data;
// 如果数据为公共授权凭证,则不允许修改凭证类型
if (data.type == AuthCertTypeEnum.Public.value) {
@@ -103,6 +106,7 @@ const edit = (data: any) => {
state.disableAuthCertType = [AuthCertTypeEnum.Public.value];
}
} else {
state.editor.title = '添加授权凭证';
state.editor.authcert = {
type: AuthCertTypeEnum.Public.value,
ciphertextType: AuthCertCiphertextTypeEnum.Password.value,

View File

@@ -65,9 +65,16 @@
<el-descriptions-item label="类型">
<EnumTag :enums="TagResourceTypeEnum" :value="currentTag.type" />
</el-descriptions-item>
<el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item>
<el-descriptions-item label="code路径">{{ currentTag.codePath }}</el-descriptions-item>
<el-descriptions-item label="路径" :span="2">
<span v-for="item in parseTagPath(currentTag.codePath)" :key="item.code">
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon" class="mr2" />
<span> {{ item.code }}</span>
<SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
</span>
</el-descriptions-item>
<el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
@@ -255,6 +262,38 @@ watch(
}
);
const parseTagPath = (tagPath: string) => {
if (!tagPath) {
return [];
}
const res = [] as any;
const codes = tagPath.split('/');
for (let code of codes) {
const typeAndCode = code.split('|');
if (typeAndCode.length == 1) {
const tagCode = typeAndCode[0];
if (!tagCode) {
continue;
}
res.push({
type: TagResourceTypeEnum.Tag.value,
code: typeAndCode[0],
});
continue;
}
res.push({
type: typeAndCode[0],
code: typeAndCode[1],
});
}
res[res.length - 1].isEnd = true;
return res;
};
const tabChange = () => {
setNowTabData();
};

View File

@@ -28,9 +28,15 @@
</template>
</page-table>
<el-drawer title="团队编辑" v-model="addTeamDialog.visible" :before-close="cancelSaveTeam" :destroy-on-close="true" :close-on-click-modal="false">
<el-drawer
:title="addTeamDialog.form.id ? '编辑团队' : '添加团队'"
v-model="addTeamDialog.visible"
:before-close="cancelSaveTeam"
:destroy-on-close="true"
:close-on-click-modal="false"
>
<template #header>
<DrawerHeader header="团队编辑" :back="cancelSaveTeam" />
<DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
</template>
<el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto">
@@ -58,6 +64,7 @@
value: 'id',
label: 'codePath',
children: 'children',
disabled: 'disabled',
}"
@check="tagTreeNodeCheck"
>
@@ -152,7 +159,6 @@ const state = reactive({
currentEditPermissions: false,
tags: [],
addTeamDialog: {
title: '新增团队',
visible: false,
form: { id: 0, name: '', remark: '', tags: [] },
},
@@ -201,16 +207,22 @@ const search = async () => {
};
const showSaveTeamDialog = async (data: any) => {
if (state.tags.length == 0) {
state.tags = await tagApi.getTagTrees.request(null);
}
state.tags = await tagApi.getTagTrees.request(null);
if (data) {
state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name;
state.addTeamDialog.form.remark = data.remark;
state.addTeamDialog.title = `修改 [${data.codePath}] 信息`;
state.addTeamDialog.form.tags = await tagApi.getTeamTagIds.request({ teamId: data.id });
setTimeout(() => {
const checkedNodes = tagTreeRef.value.getCheckedNodes();
console.log('check nodes: ', checkedNodes);
// 禁用选中节点的所有父节点,不可选中
for (let checkNodeData of checkedNodes) {
disableParentNodes(tagTreeRef.value.getNode(checkNodeData.id).parent);
}
}, 200);
}
state.addTeamDialog.visible = true;
@@ -231,8 +243,10 @@ const saveTeam = async () => {
const cancelSaveTeam = () => {
state.addTeamDialog.visible = false;
state.addTeamDialog.form = {} as any;
teamForm.value.resetFields();
setTimeout(() => {
state.addTeamDialog.form = {} as any;
}, 500);
};
const deleteTeam = () => {
@@ -289,34 +303,47 @@ const cancelAddMember = () => {
state.showMemDialog.addVisible = false;
};
const tagTreeNodeCheck = () => {
// const node = tagTreeRef.value.getNode(data.id);
// console.log(node);
// // state.showTagDialog.tagTreeTeams = [16]
// if (node.checked) {
// if (node.parent) {
// console.log(node.parent);
// // removeCheckedTagId(node.parent.key);
// tagTreeRef.value.setChecked(node.parent, false, false);
// }
// // // parentNode = node.parent
// // for (let parentNode of node.parent) {
// // parentNode.setChecked(false);
// // }
// }
// console.log(data);
// console.log(checkInfo);
const tagTreeNodeCheck = (data: any) => {
const node = tagTreeRef.value.getNode(data.id);
console.log('check node: ', node);
if (node.checked) {
// 如果选中了子节点,则需要将父节点全部取消选中,并禁用父节点
unCheckParentNodes(node.parent);
disableParentNodes(node.parent);
} else {
// 如果取消了选中,则需要根据条件恢复父节点的选中状态
disableParentNodes(node.parent, false);
}
};
// function removeCheckedTagId(id: any) {
// console.log(state.showTagDialog.tagTreeTeams);
// for (let i = 0; i < state.showTagDialog.tagTreeTeams.length; i++) {
// if (state.showTagDialog.tagTreeTeams[i] == id) {
// console.log('has id', id);
// state.showTagDialog.tagTreeTeams.splice(i, 1);
// }
// }
// console.log(state.showTagDialog.tagTreeTeams);
// }
const unCheckParentNodes = (node: any) => {
if (!node) {
return;
}
tagTreeRef.value.setChecked(node, false, false);
unCheckParentNodes(node.parent);
};
/**
* 禁用该节点以及所有父节点
* @param node 节点
* @param disable 是否禁用
*/
const disableParentNodes = (node: any, disable = true) => {
if (!node) {
return;
}
if (!disable) {
// 恢复为非禁用状态时,若同层级存在一个选中状态或者禁用状态,则继续禁用 不恢复非禁用状态。
for (let oneLevelNodes of node.childNodes) {
if (oneLevelNodes.checked || oneLevelNodes.data.disabled) {
return;
}
}
}
node.data.disabled = disable;
disableParentNodes(node.parent, disable);
};
</script>
<style lang="scss" scoped></style>

View File

@@ -2,10 +2,10 @@ import { EnumValue } from '@/common/Enum';
// 授权凭证类型
export const AuthCertTypeEnum = {
Public: EnumValue.of(2, '公共凭证').tagTypeSuccess(),
Private: EnumValue.of(1, '普通账号').tagTypeSuccess(),
Privileged: EnumValue.of(11, '特权账号').tagTypeSuccess(),
PrivateDefault: EnumValue.of(12, '默认账号').tagTypeSuccess(),
Public: EnumValue.of(2, '公共凭证').tagTypeSuccess().tagTypeSuccess(),
Private: EnumValue.of(1, '普通账号'),
Privileged: EnumValue.of(11, '特权账号').tagTypeDanger(),
PrivateDefault: EnumValue.of(12, '默认账号').tagTypeWarning(),
};
// 授权凭证密文类型