mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-02-10 23:05:37 +08:00
refactor: 数据库实例与凭证关联至标签&其他问题修复重构等
This commit is contained in:
@@ -1,14 +1,24 @@
|
||||
import EnumValue from './Enum';
|
||||
|
||||
// 资源类型
|
||||
export const ResourceTypeEnum = {
|
||||
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor', iconColor: 'var(--el-color-primary)' }).tagTypeSuccess(),
|
||||
Db: EnumValue.of(2, '数据库实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(),
|
||||
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
|
||||
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
|
||||
};
|
||||
|
||||
// 标签关联的资源类型
|
||||
export const TagResourceTypeEnum = {
|
||||
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
|
||||
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
|
||||
|
||||
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(),
|
||||
Machine: ResourceTypeEnum.Machine,
|
||||
Db: ResourceTypeEnum.Db,
|
||||
Redis: ResourceTypeEnum.Redis,
|
||||
Mongo: ResourceTypeEnum.Mongo,
|
||||
|
||||
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
|
||||
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbAuthCert: EnumValue.of(21, '数据库-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbName: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.8.0',
|
||||
version: 'v1.8.1',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const AccountUsernamePattern = {
|
||||
pattern: /^[a-zA-Z0-9_]{5,20}$/g,
|
||||
message: '只允许输入5-20位大小写字母、数字、下划线',
|
||||
message: '只允许输入5-20位大小写字母、数字、_-.:',
|
||||
};
|
||||
|
||||
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位大小写字母、数字、_-.:',
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@ export const usePageTable = (
|
||||
}
|
||||
|
||||
let res = await api.request(sp);
|
||||
res.list = res.list || [];
|
||||
dataCallBack && (res = await dataCallBack(res));
|
||||
|
||||
if (pageable) {
|
||||
|
||||
@@ -159,6 +159,6 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
|
||||
return null;
|
||||
}
|
||||
|
||||
console.error(`未匹配到[${component}]组件名对应的组件文件`);
|
||||
console.warn(`未匹配到[${component}]组件名对应的组件文件`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="props.authCerts">
|
||||
<el-select default-first-option value-key="name" style="width: 100%" v-model="selectAuthCert" size="small">
|
||||
<el-select value-key="name" v-model="selectAuthCert" size="small">
|
||||
<el-option v-for="item in props.authCerts" :key="item.name" :label="item.username" :value="item">
|
||||
{{ item.username }}
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, toRefs, onMounted, watch, computed } from 'vue';
|
||||
import { reactive, ref, toRefs, computed, watch } from 'vue';
|
||||
import { AuthCertTypeEnum, AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
import { resourceAuthCertApi } from '../tag/api';
|
||||
@@ -166,6 +166,13 @@ const rules = {
|
||||
trigger: ['blur'],
|
||||
},
|
||||
],
|
||||
resourceCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入资源编号',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel']);
|
||||
@@ -184,10 +191,6 @@ const showResourceEdit = computed(() => {
|
||||
return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
setForm(props.authCert);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.authCert,
|
||||
(val: any) => {
|
||||
@@ -217,7 +220,6 @@ const changeType = (val: any) => {
|
||||
|
||||
const changeCiphertextType = (val: any) => {
|
||||
if (val == AuthCertCiphertextTypeEnum.Public.value) {
|
||||
state.form.type = AuthCertTypeEnum.Private.value;
|
||||
getPublicAuthCerts();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div v-if="props.tags">
|
||||
<el-row v-for="(tag, idx) in props.tags?.slice(0, 1)" :key="idx">
|
||||
<TagInfo :tag-path="tag.tagPath" />
|
||||
<span class="ml3">{{ tag.tagPath }}</span>
|
||||
<el-row v-for="(tagPath, idx) in tagPaths?.slice(0, 1)" :key="idx">
|
||||
<TagInfo :tag-path="tagPath" />
|
||||
<span class="ml3">{{ tagPath }}</span>
|
||||
|
||||
<!-- 展示剩余的标签信息 -->
|
||||
<el-popover :show-after="300" v-if="props.tags.length > 1 && idx == 0" placement="top-start" width="230" trigger="hover">
|
||||
<el-popover :show-after="300" v-if="tagPaths?.length > 1 && idx == 0" placement="top-start" width="230" trigger="hover">
|
||||
<template #reference>
|
||||
<SvgIcon class="mt5 ml5" color="var(--el-color-primary)" name="MoreFilled" />
|
||||
</template>
|
||||
|
||||
<el-row v-for="i in props.tags.slice(1)" :key="i">
|
||||
<TagInfo :tag-path="i.tagPath" />
|
||||
<span class="ml3">{{ i.tagPath }}</span>
|
||||
<el-row v-for="i in tagPaths.slice(1)" :key="i">
|
||||
<TagInfo :tag-path="i" />
|
||||
<span class="ml3">{{ i }}</span>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
</el-row>
|
||||
@@ -22,12 +22,18 @@
|
||||
<script lang="ts" setup>
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
import { computed } from 'vue';
|
||||
import { getTagPath } from './tag';
|
||||
const props = defineProps({
|
||||
tags: {
|
||||
type: [Array<any>],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const tagPaths = computed(() => {
|
||||
return props.tags?.map((item) => getTagPath(item.codePath)) as any;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
v-bind="$attrs"
|
||||
v-model="state.selectTags"
|
||||
@change="changeTag"
|
||||
style="width: 100%"
|
||||
:data="tags"
|
||||
placeholder="请选择关联标签"
|
||||
:render-after-expand="true"
|
||||
:default-expanded-keys="[state.selectTags]"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
node-key="codePath"
|
||||
:props="{
|
||||
value: 'id',
|
||||
value: 'codePath',
|
||||
label: 'codePath',
|
||||
children: 'children',
|
||||
}"
|
||||
@@ -35,6 +34,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
import { getTagPath } from './tag';
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
|
||||
@@ -47,7 +47,7 @@ const props = defineProps({
|
||||
|
||||
const state = reactive({
|
||||
tags: [],
|
||||
// 单选则为id,多选为id数组
|
||||
// 单选则为codePath,多选为codePath数组
|
||||
selectTags: [] as any,
|
||||
});
|
||||
|
||||
@@ -55,7 +55,7 @@ const { tags } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.selectTags) {
|
||||
state.selectTags = props.selectTags;
|
||||
state.selectTags = props.selectTags.map((item: any) => getTagPath(item));
|
||||
}
|
||||
|
||||
state.tags = await tagApi.getTagTrees.request({ type: -1 });
|
||||
|
||||
@@ -171,3 +171,29 @@ export function getTagPathSearchItem(resourceType: number) {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据codepath获取对应的tagPath
|
||||
* @param codePath codePath tag1/tag2/1|testmachien1/
|
||||
* @returns tagPath tag1/tag2/
|
||||
*/
|
||||
export function getTagPath(codePath: string) {
|
||||
// 以资源分隔符 "|" 对字符串进行分割
|
||||
let parts = codePath.split('|');
|
||||
if (parts.length < 2) {
|
||||
return codePath;
|
||||
}
|
||||
|
||||
// 从分割后的第一个子串中提取所需部分
|
||||
let substringBeforeNumber = parts[0];
|
||||
|
||||
// 找到最后一个 "/" 的位置
|
||||
let lastSlashIndex = substringBeforeNumber.lastIndexOf('/');
|
||||
|
||||
// 如果找到最后一个 "/" 符号,则截取子串
|
||||
if (lastSlashIndex !== -1) {
|
||||
return substringBeforeNumber.slice(0, lastSlashIndex + 1);
|
||||
}
|
||||
|
||||
return codePath;
|
||||
}
|
||||
|
||||
@@ -10,46 +10,20 @@
|
||||
width="38%"
|
||||
>
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签" required>
|
||||
<tag-tree-select
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<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="instanceId" label="数据库实例" required>
|
||||
<el-select
|
||||
:disabled="form.id !== undefined"
|
||||
remote
|
||||
:remote-method="getInstances"
|
||||
@change="changeInstance"
|
||||
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">
|
||||
{{ item.name }}
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
{{ item.type }} / {{ item.host }}:{{ item.port }}
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
{{ item.username }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
<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="authCertName" label="授权凭证" required>
|
||||
<el-select @focus="getAuthCerts" @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
|
||||
<el-select @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 }}
|
||||
|
||||
@@ -65,13 +39,6 @@
|
||||
</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>
|
||||
<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="database" label="数据库名">
|
||||
<el-select
|
||||
v-model="dbNamesSelected"
|
||||
@@ -102,7 +69,7 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
<el-button type="primary" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -110,23 +77,27 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { toRefs, reactive, watch, ref, watchEffect } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
// import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
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';
|
||||
import { resourceAuthCertApi } from '../tag/api';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
instance: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
db: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
@@ -136,7 +107,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'confirm']);
|
||||
|
||||
const rules = {
|
||||
tagId: [
|
||||
@@ -186,7 +157,7 @@ const checkAllDbNames = ref(false);
|
||||
const indeterminateDbNames = ref(false);
|
||||
|
||||
const dbForm: any = ref(null);
|
||||
const tagSelectRef: any = ref(null);
|
||||
// const tagSelectRef: any = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
@@ -198,7 +169,7 @@ const state = reactive({
|
||||
authCerts: [] as any,
|
||||
form: {
|
||||
id: null,
|
||||
tagId: [],
|
||||
// tagId: [],
|
||||
name: null,
|
||||
code: '',
|
||||
database: '',
|
||||
@@ -212,71 +183,53 @@ const state = reactive({
|
||||
|
||||
const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveDbExec } = dbApi.saveDb.useApi(form);
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
if (newValue.db) {
|
||||
state.form = { ...newValue.db };
|
||||
state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
|
||||
const db: any = props.db;
|
||||
if (db.code) {
|
||||
state.form = { ...db };
|
||||
// state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
|
||||
// 将数据库名使用空格切割,获取所有数据库列表
|
||||
state.dbNamesSelected = newValue.db.database.split(' ');
|
||||
state.dbNamesSelected = db.database.split(' ');
|
||||
} else {
|
||||
state.form = {} as any;
|
||||
state.dbNamesSelected = [];
|
||||
}
|
||||
});
|
||||
|
||||
const changeInstance = async () => {
|
||||
state.dbNamesSelected = [];
|
||||
state.form.instanceId = state.selectInstalce.id;
|
||||
const changeAuthCert = (val: string) => {
|
||||
getAllDatabase(val);
|
||||
};
|
||||
|
||||
const getAuthCerts = async () => {
|
||||
const inst: any = props.instance;
|
||||
const res = await resourceAuthCertApi.listByQuery.request({
|
||||
resourceCode: state.selectInstalce.code,
|
||||
resourceCode: inst.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, authCertName });
|
||||
state.allDatabases = dbs;
|
||||
const req = { ...(props.instance as any) };
|
||||
req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
|
||||
let dbs = await dbApi.getAllDatabase.request(req);
|
||||
state.allDatabases = dbs;
|
||||
|
||||
// 如果是oracle,且没查出数据库列表,则取实例sid
|
||||
let instance = state.instances.find((item: any) => item.id === state.form.instanceId);
|
||||
if (instance && instance.type === DbType.oracle && dbs.length === 0) {
|
||||
state.allDatabases = [instance.sid];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getInstances = async (instanceName: string = '', id = 0) => {
|
||||
if (!id && !instanceName) {
|
||||
state.instances = [];
|
||||
return;
|
||||
}
|
||||
const data = await dbApi.instances.request({ id, name: instanceName });
|
||||
if (data) {
|
||||
state.instances = data.list;
|
||||
// 如果是oracle,且没查出数据库列表,则取实例sid
|
||||
let instance = state.instances.find((item: any) => item.id === state.form.instanceId);
|
||||
if (instance && instance.type === DbType.oracle && dbs.length === 0) {
|
||||
state.allDatabases = [instance.sid];
|
||||
}
|
||||
};
|
||||
|
||||
const open = async () => {
|
||||
if (state.form.instanceId) {
|
||||
// 根据id获取,因为需要回显实例名称
|
||||
await getInstances('', state.form.instanceId);
|
||||
state.selectInstalce = state.instances[0];
|
||||
await getAuthCerts();
|
||||
if (state.form.authCertName) {
|
||||
await getAllDatabase(state.form.authCertName);
|
||||
}
|
||||
};
|
||||
@@ -288,10 +241,11 @@ const btnOk = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
await saveDbExec();
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
cancel();
|
||||
emit('confirm', state.form);
|
||||
// await saveDbExec();
|
||||
// ElMessage.success('保存成功');
|
||||
// emit('val-change', state.form);
|
||||
// cancel();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
:before-query-fn="checkRouteTagPath"
|
||||
:search-items="searchItems"
|
||||
v-model:query-form="query"
|
||||
:show-selection="true"
|
||||
v-model:selection-data="state.selectionData"
|
||||
:columns="columns"
|
||||
lazy
|
||||
>
|
||||
@@ -24,11 +22,6 @@
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="perms.saveDb" type="primary" icon="plus" @click="editDb(false)">添加</el-button>
|
||||
<el-button v-auth="perms.delDb" :disabled="selectionData.length < 1" @click="deleteDb()" type="danger" icon="delete">删除</el-button>
|
||||
</template>
|
||||
|
||||
<template #type="{ data }">
|
||||
<el-tooltip :content="data.type" placement="top">
|
||||
<SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
|
||||
@@ -39,16 +32,26 @@
|
||||
{{ `${data.host}:${data.port}` }}
|
||||
</template>
|
||||
|
||||
<template #database="{ data }">
|
||||
<el-popover placement="bottom" :width="200" trigger="click">
|
||||
<template #reference>
|
||||
<el-button @click="state.currentDbs = data.database" type="primary" link>查看库</el-button>
|
||||
</template>
|
||||
<el-table :data="filterDbs" size="small">
|
||||
<el-table-column prop="dbName" label="数据库">
|
||||
<template #header>
|
||||
<el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<ResourceTags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<span v-if="actionBtns[perms.saveDb]">
|
||||
<el-button type="primary" @click="editDb(data)" link>编辑</el-button>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
</span>
|
||||
|
||||
<el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
@@ -176,7 +179,7 @@
|
||||
<el-descriptions-item :span="2" label="主机">{{ infoDialog.instance?.host }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.instance?.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="授权凭证">{{ infoDialog.instance.authCertName }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">
|
||||
<SvgIcon :name="getDbDialect(infoDialog.instance?.type).getInfo().icon" :size="20" />{{ infoDialog.instance?.type }}
|
||||
</el-descriptions-item>
|
||||
@@ -199,8 +202,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
@@ -224,23 +226,8 @@ import { sleep } from '@/common/utils/loading';
|
||||
|
||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||
|
||||
const props = defineProps({
|
||||
lazy: {
|
||||
type: [Boolean],
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const perms = {
|
||||
base: 'db',
|
||||
saveDb: 'db:save',
|
||||
delDb: 'db:del',
|
||||
backupDb: 'db:backup',
|
||||
restoreDb: 'db:restore',
|
||||
};
|
||||
|
||||
const searchItems = [
|
||||
getTagPathSearchItem(TagResourceTypeEnum.Db.value),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.DbName.value),
|
||||
SearchItem.slot('instanceId', '实例', 'instanceSelect'),
|
||||
SearchItem.input('code', '编号'),
|
||||
];
|
||||
@@ -252,15 +239,21 @@ const columns = ref([
|
||||
TableColumn.new('instanceName', '实例名'),
|
||||
TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
|
||||
TableColumn.new('authCertName', '授权凭证'),
|
||||
TableColumn.new('database', '库').isSlot().setMinWidth(80),
|
||||
TableColumn.new('flowProcdefKey', '关联流程'),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('code', '编号'),
|
||||
]);
|
||||
|
||||
const perms = {
|
||||
backupDb: 'db:backup',
|
||||
restoreDb: 'db:restore',
|
||||
};
|
||||
|
||||
// 该用户拥有的的操作列按钮权限
|
||||
// const actionBtns = hasPerms([perms.base, perms.saveDb]);
|
||||
const actionBtns = hasPerms(Object.values(perms));
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter();
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter();
|
||||
|
||||
const route = useRoute();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
@@ -268,6 +261,8 @@ const state = reactive({
|
||||
row: {} as any,
|
||||
dbId: 0,
|
||||
db: '',
|
||||
currentDbs: '',
|
||||
dbNameSearch: '',
|
||||
instances: [] as any,
|
||||
/**
|
||||
* 选中的数据
|
||||
@@ -343,16 +338,26 @@ const state = reactive({
|
||||
},
|
||||
});
|
||||
|
||||
const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } =
|
||||
toRefs(state);
|
||||
const { db, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
if (Object.keys(actionBtns).length > 0) {
|
||||
columns.value.push(actionColumn);
|
||||
}
|
||||
if (!props.lazy) {
|
||||
search();
|
||||
search();
|
||||
});
|
||||
|
||||
const filterDbs = computed(() => {
|
||||
const dbsStr = state.currentDbs;
|
||||
if (!dbsStr) {
|
||||
return [];
|
||||
}
|
||||
const dbs = dbsStr.split(' ').map((db: any) => {
|
||||
return { dbName: db };
|
||||
});
|
||||
return dbs.filter((db: any) => {
|
||||
return db.dbName.includes(state.dbNameSearch);
|
||||
});
|
||||
});
|
||||
|
||||
const checkRouteTagPath = (query: any) => {
|
||||
@@ -402,10 +407,6 @@ const handleMoreActionCommand = (commond: any) => {
|
||||
showInfo(data);
|
||||
return;
|
||||
}
|
||||
case 'edit': {
|
||||
editDb(data);
|
||||
return;
|
||||
}
|
||||
case 'dumpDb': {
|
||||
onDumpDbs(data);
|
||||
return;
|
||||
@@ -425,32 +426,6 @@ const handleMoreActionCommand = (commond: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const editDb = async (data: any) => {
|
||||
if (!data) {
|
||||
state.dbEditDialog.data = null;
|
||||
state.dbEditDialog.title = '新增数据库资源';
|
||||
} else {
|
||||
state.dbEditDialog.data = data;
|
||||
state.dbEditDialog.title = '修改数据库资源';
|
||||
}
|
||||
state.dbEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const deleteDb = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await dbApi.deleteDb.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
|
||||
ElMessage.success('删除成功');
|
||||
search();
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
const onShowSqlExec = async (row: any) => {
|
||||
state.sqlExecLogDialog.title = `${row.name}`;
|
||||
state.sqlExecLogDialog.dbId = row.id;
|
||||
|
||||
171
mayfly_go_web/src/views/ops/db/InstanceDbConf.vue
Normal file
171
mayfly_go_web/src/views/ops/db/InstanceDbConf.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
<el-table :data="state.dbs" stripe>
|
||||
<el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100"> </el-table-column>
|
||||
<el-table-column prop="authCertName" label="授权凭证" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="database" label="库" min-width="80">
|
||||
<template #default="scope">
|
||||
<el-popover placement="bottom" :width="200" trigger="click">
|
||||
<template #reference>
|
||||
<el-button @click="state.currentDbs = scope.row.database" type="primary" link>查看库</el-button>
|
||||
</template>
|
||||
<el-table :data="filterDbs" size="small">
|
||||
<el-table-column prop="dbName" label="数据库">
|
||||
<template #header>
|
||||
<el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="remark" label="备注" show-overflow-tooltip min-width="120"> </el-table-column>
|
||||
<el-table-column prop="flowProcdefKey" label="关联流程" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="code" label="编号" show-overflow-tooltip min-width="120"> </el-table-column>
|
||||
<el-table-column min-wdith="120px">
|
||||
<template #header>
|
||||
操作
|
||||
<el-button v-auth="perms.saveDb" type="primary" circle size="small" icon="Plus" @click="editDb(null)"> </el-button>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button v-auth="perms.saveDb" @click="editDb(scope.row)" type="primary" icon="edit" link></el-button>
|
||||
<el-button class="ml1" v-auth="perms.delDb" type="danger" @click="deleteDb(scope.row)" icon="delete" link></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<db-edit
|
||||
@confirm="confirmEditDb"
|
||||
@cancel="cancelEditDb"
|
||||
:title="dbEditDialog.title"
|
||||
v-model:visible="dbEditDialog.visible"
|
||||
:instance="props.instance"
|
||||
v-model:db="dbEditDialog.data"
|
||||
></db-edit>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, toRefs, watchEffect } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import DbEdit from './DbEdit.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
instance: {
|
||||
type: [Object],
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const perms = {
|
||||
base: 'db',
|
||||
saveDb: 'db:save',
|
||||
delDb: 'db:del',
|
||||
};
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
dbs: [] as any,
|
||||
currentDbs: '', // 当前数据库名,空格分割库名
|
||||
dbNameSearch: '',
|
||||
dbEditDialog: {
|
||||
visible: false,
|
||||
data: null as any,
|
||||
title: '新增数据库',
|
||||
},
|
||||
});
|
||||
|
||||
const { dialogVisible, dbEditDialog } = toRefs(state);
|
||||
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
getDbs();
|
||||
});
|
||||
|
||||
const filterDbs = computed(() => {
|
||||
const dbsStr = state.currentDbs;
|
||||
if (!dbsStr) {
|
||||
return [];
|
||||
}
|
||||
const dbs = dbsStr.split(' ').map((db: any) => {
|
||||
return { dbName: db };
|
||||
});
|
||||
return dbs.filter((db: any) => {
|
||||
return db.dbName.includes(state.dbNameSearch);
|
||||
});
|
||||
});
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const getDbs = () => {
|
||||
dbApi.dbs.request({ pageSize: 200, instanceId: props.instance.id }).then((res: any) => {
|
||||
state.dbs = res.list || [];
|
||||
});
|
||||
};
|
||||
|
||||
const editDb = (data: any) => {
|
||||
if (data) {
|
||||
state.dbEditDialog.data = { ...data };
|
||||
} else {
|
||||
state.dbEditDialog.data = {
|
||||
instanceId: props.instance.id,
|
||||
};
|
||||
}
|
||||
state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
|
||||
state.dbEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const deleteDb = async (db: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除【${db.name}】库?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await dbApi.deleteDb.request({ id: db.id });
|
||||
ElMessage.success('删除成功');
|
||||
getDbs();
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
const confirmEditDb = async (db: any) => {
|
||||
db.instanceId = props.instance.id;
|
||||
await dbApi.saveDb.request(db);
|
||||
ElMessage.success('保存成功');
|
||||
getDbs();
|
||||
cancelEditDb();
|
||||
};
|
||||
|
||||
const cancelEditDb = () => {
|
||||
state.dbEditDialog.visible = false;
|
||||
state.dbEditDialog.data = {};
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
@@ -7,9 +7,30 @@
|
||||
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
|
||||
<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 ref="tagSelectRef" prop="tagCodePaths" label="标签">
|
||||
<tag-tree-select
|
||||
multiple
|
||||
@change-tag="
|
||||
(paths: any) => {
|
||||
form.tagCodePaths = paths;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
:select-tags="form.tagCodePaths"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<el-form-item prop="name" label="名称" required>
|
||||
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
@@ -34,7 +55,7 @@
|
||||
|
||||
<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-input 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">
|
||||
@@ -87,18 +108,7 @@
|
||||
|
||||
<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"
|
||||
:underline="false"
|
||||
type="primary"
|
||||
class="mr5"
|
||||
>参数参考</el-link
|
||||
>
|
||||
</template> -->
|
||||
</el-input>
|
||||
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2"> </el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">
|
||||
@@ -107,17 +117,15 @@
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, toRefs, watch } from 'vue';
|
||||
import { reactive, ref, toRefs, watchEffect } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
@@ -128,6 +136,8 @@ import { ResourceCodePattern } from '@/common/pattern';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import ResourceAuthCertTableEdit from '../component/ResourceAuthCertTableEdit.vue';
|
||||
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import { getTagPath } from '../component/tag';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -145,6 +155,13 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
|
||||
const rules = {
|
||||
tagCodePaths: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择标签',
|
||||
trigger: ['change'],
|
||||
},
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
@@ -188,61 +205,63 @@ const rules = {
|
||||
};
|
||||
|
||||
const dbForm: any = ref(null);
|
||||
const tagSelectRef: any = ref(null);
|
||||
|
||||
const DefaultForm = {
|
||||
id: null,
|
||||
type: DbType.mysql,
|
||||
code: '',
|
||||
name: null,
|
||||
host: '',
|
||||
port: null,
|
||||
extra: '', // 连接需要的额外参数(json字符串)
|
||||
params: null,
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
authCerts: [],
|
||||
tagCodePaths: [],
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
extra: {} as any, // 连接需要的额外参数(json)
|
||||
form: {
|
||||
id: null,
|
||||
type: '',
|
||||
code: '',
|
||||
name: null,
|
||||
host: '',
|
||||
port: null,
|
||||
authCerts: [],
|
||||
extra: '', // 连接需要的额外参数(json字符串)
|
||||
params: null,
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
},
|
||||
form: DefaultForm,
|
||||
submitForm: {} as any,
|
||||
});
|
||||
|
||||
const { dialogVisible, form, submitForm } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveInstanceExec, data: saveInstanceRes } = dbApi.saveInstance.useApi(submitForm);
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
if (newValue.data) {
|
||||
state.form = { ...newValue.data };
|
||||
const dbInst: any = props.data;
|
||||
if (dbInst) {
|
||||
state.form = { ...dbInst };
|
||||
state.form.tagCodePaths = dbInst.tags.map((t: any) => t.codePath) || [];
|
||||
try {
|
||||
state.extra = JSON.parse(state.form.extra);
|
||||
} catch (e) {
|
||||
state.extra = {};
|
||||
}
|
||||
} else {
|
||||
state.form = { port: null, type: DbType.mysql } as any;
|
||||
state.form = { ...DefaultForm };
|
||||
state.form.authCerts = [];
|
||||
}
|
||||
});
|
||||
|
||||
const changeDbType = (val: string) => {
|
||||
if (!state.form.id) {
|
||||
state.form.port = getDbDialect(val).getInfo().defaultPort as any;
|
||||
}
|
||||
state.extra = {};
|
||||
};
|
||||
|
||||
const getReqForm = async () => {
|
||||
const reqForm = { ...state.form };
|
||||
const reqForm: any = { ...state.form };
|
||||
reqForm.selectAuthCert = null;
|
||||
reqForm.tags = null;
|
||||
if (!state.form.sshTunnelMachineId) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
reqForm.tagCodePaths = state.form.tagCodePaths.map((t: any) => getTagPath(t)) as any;
|
||||
if (Object.keys(state.extra).length > 0) {
|
||||
reqForm.extra = JSON.stringify(state.extra);
|
||||
}
|
||||
@@ -273,6 +292,7 @@ const btnOk = async () => {
|
||||
state.submitForm = await getReqForm();
|
||||
await saveInstanceExec();
|
||||
ElMessage.success('保存成功');
|
||||
state.form.id = saveInstanceRes as any;
|
||||
emit('val-change', state.form);
|
||||
cancel();
|
||||
});
|
||||
@@ -283,5 +303,12 @@ const cancel = () => {
|
||||
emit('cancel');
|
||||
state.extra = {};
|
||||
};
|
||||
|
||||
const changeDbType = (val: string) => {
|
||||
if (!state.form.id) {
|
||||
state.form.port = getDbDialect(val).getInfo().defaultPort as any;
|
||||
}
|
||||
state.extra = {};
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:show-selection="true"
|
||||
v-model:selection-data="state.selectionData"
|
||||
:columns="columns"
|
||||
lazy
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">添加</el-button>
|
||||
@@ -17,6 +18,10 @@
|
||||
>
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<ResourceTags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #authCert="{ data }">
|
||||
<ResourceAuthCert v-model:select-auth-cert="data.selectAuthCert" :auth-certs="data.authCerts" />
|
||||
</template>
|
||||
@@ -30,6 +35,7 @@
|
||||
<template #action="{ data }">
|
||||
<el-button @click="showInfo(data)" link>详情</el-button>
|
||||
<el-button v-if="actionBtns[perms.saveInstance]" @click="editInstance(data)" type="primary" link>编辑</el-button>
|
||||
<el-button v-if="actionBtns[perms.saveDb]" @click="editDb(data)" type="primary" link>库配置</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
@@ -56,11 +62,13 @@
|
||||
</el-dialog>
|
||||
|
||||
<instance-edit
|
||||
@val-change="search"
|
||||
@val-change="search()"
|
||||
:title="instanceEditDialog.title"
|
||||
v-model:visible="instanceEditDialog.visible"
|
||||
v-model:data="instanceEditDialog.data"
|
||||
></instance-edit>
|
||||
|
||||
<instance-db-conf :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" :instance="dbEditDialog.instance" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -76,17 +84,30 @@ import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { getDbDialect } from './dialect';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
|
||||
const InstanceDbConf = defineAsyncComponent(() => import('./InstanceDbConf.vue'));
|
||||
|
||||
const props = defineProps({
|
||||
lazy: {
|
||||
type: [Boolean],
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const perms = {
|
||||
saveInstance: 'db:instance:save',
|
||||
delInstance: 'db:instance:del',
|
||||
saveDb: 'db:save',
|
||||
};
|
||||
|
||||
const searchItems = [SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
|
||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
|
||||
TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
|
||||
@@ -98,7 +119,7 @@ const columns = ref([
|
||||
|
||||
// 该用户拥有的的操作列按钮权限
|
||||
const actionBtns = hasPerms(Object.values(perms));
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter();
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
@@ -114,6 +135,7 @@ const state = reactive({
|
||||
*/
|
||||
query: {
|
||||
name: null,
|
||||
tagPath: '',
|
||||
pageNum: 1,
|
||||
pageSize: 0,
|
||||
},
|
||||
@@ -126,17 +148,28 @@ const state = reactive({
|
||||
data: null as any,
|
||||
title: '新增数据库实例',
|
||||
},
|
||||
dbEditDialog: {
|
||||
visible: false,
|
||||
instance: null as any,
|
||||
title: '新增数据库实例',
|
||||
},
|
||||
});
|
||||
|
||||
const { selectionData, query, infoDialog, instanceEditDialog } = toRefs(state);
|
||||
const { selectionData, query, infoDialog, instanceEditDialog, dbEditDialog } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
if (Object.keys(actionBtns).length > 0) {
|
||||
columns.value.push(actionColumn);
|
||||
}
|
||||
if (!props.lazy) {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
const search = () => {
|
||||
const search = (tagPath: string = '') => {
|
||||
if (tagPath) {
|
||||
state.query.tagPath = tagPath;
|
||||
}
|
||||
pageTableRef.value.search();
|
||||
};
|
||||
|
||||
@@ -179,5 +212,13 @@ const deleteInstance = async () => {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
const editDb = (data: any) => {
|
||||
state.dbEditDialog.instance = data;
|
||||
state.dbEditDialog.title = `配置 "${data.name}" 数据库`;
|
||||
state.dbEditDialog.visible = true;
|
||||
};
|
||||
|
||||
defineExpose({ search });
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="db-sql-exec">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.DbName.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
||||
<el-popover
|
||||
|
||||
@@ -39,7 +39,7 @@ export const dbApi = {
|
||||
|
||||
instances: Api.newGet('/instances'),
|
||||
getInstance: Api.newGet('/instances/{instanceId}'),
|
||||
getAllDatabase: Api.newGet('/instances/{instanceId}/databases'),
|
||||
getAllDatabase: Api.newPost('/instances/databases'),
|
||||
getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
|
||||
testConn: Api.newPost('/instances/test-conn'),
|
||||
saveInstance: Api.newPost('/instances'),
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
:loading="dt.loading"
|
||||
:abort-fn="dt.abortFn"
|
||||
:height="tableDataHeight"
|
||||
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
||||
:empty-text="state.tableDataEmptyText"
|
||||
@change-updated-field="changeUpdatedField($event, dt)"
|
||||
@data-delete="onDeleteData($event, dt)"
|
||||
></db-table-data>
|
||||
@@ -221,6 +221,7 @@ const state = reactive({
|
||||
activeTab: 1,
|
||||
editorHeight: '500',
|
||||
tableDataHeight: '250px',
|
||||
tableDataEmptyText: 'tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改',
|
||||
});
|
||||
|
||||
const { tableDataHeight } = toRefs(state);
|
||||
@@ -368,9 +369,8 @@ const onRunSql = async (newTab = false) => {
|
||||
|
||||
await execute();
|
||||
const colAndData: any = data.value;
|
||||
if (!colAndData.res || colAndData.res.length === 0) {
|
||||
ElMessage.warning('未查询到结果集');
|
||||
return;
|
||||
if (colAndData.res.length == 0) {
|
||||
state.tableDataEmptyText = '查无数据';
|
||||
}
|
||||
|
||||
// 要实时响应,故需要用索引改变数据才生效
|
||||
@@ -467,7 +467,7 @@ const formatSql = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const formatDialect = getNowDbInst().getDialect().getInfo().formatSqlDialect;
|
||||
const formatDialect: any = getNowDbInst().getDialect().getInfo().formatSqlDialect;
|
||||
|
||||
let sql = monacoEditor.getModel()?.getValueInRange(selection);
|
||||
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<template #empty>
|
||||
<div style="text-align: center">
|
||||
<el-empty class="h100" :description="state.emptyText" :image-size="100" />
|
||||
<el-empty class="h100" :description="props.emptyText" :image-size="100" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-v2>
|
||||
@@ -342,8 +342,6 @@ const state = reactive({
|
||||
columns: [] as any,
|
||||
loading: false,
|
||||
tableHeight: '600px',
|
||||
emptyText: '',
|
||||
|
||||
execTime: 0,
|
||||
contextmenu: {
|
||||
dropdown: {
|
||||
@@ -434,7 +432,6 @@ onMounted(async () => {
|
||||
console.log('in DbTable mounted');
|
||||
state.tableHeight = props.height;
|
||||
state.loading = props.loading;
|
||||
state.emptyText = props.emptyText;
|
||||
|
||||
state.dbId = props.dbId;
|
||||
state.dbType = getNowDbInst().type;
|
||||
|
||||
@@ -308,7 +308,7 @@ const showCreateDdl = async (row: any) => {
|
||||
tableName: row.tableName,
|
||||
});
|
||||
|
||||
state.ddlDialog.ddl = sqlFormatter(res, { language: getDbDialect(props.dbType).getInfo().formatSqlDialect });
|
||||
state.ddlDialog.ddl = sqlFormatter(res, { language: getDbDialect(props.dbType).getInfo().formatSqlDialect as any });
|
||||
state.ddlDialog.visible = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,22 +7,26 @@
|
||||
|
||||
<el-form :model="form" ref="machineForm" :rules="rules" label-width="auto">
|
||||
<el-divider content-position="left">基本</el-divider>
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签">
|
||||
<el-form-item ref="tagSelectRef" prop="tagCodePaths" label="标签">
|
||||
<tag-tree-select
|
||||
multiple
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
(paths) => {
|
||||
form.tagCodePaths = paths;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
:tag-path="form.tagPath"
|
||||
:select-tags="form.tagId"
|
||||
:select-tags="form.tagCodePaths"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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-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>
|
||||
@@ -78,7 +82,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, toRefs, watch } from 'vue';
|
||||
import { reactive, ref, toRefs, watchEffect } from 'vue';
|
||||
import { machineApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
@@ -88,6 +92,7 @@ import { MachineProtocolEnum } from './enums';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import { ResourceCodePattern } from '@/common/pattern';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { getTagPath } from '../component/tag';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -105,7 +110,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
|
||||
const rules = {
|
||||
tagId: [
|
||||
tagCodePaths: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择标签',
|
||||
@@ -159,7 +164,7 @@ const defaultForm = {
|
||||
protocol: MachineProtocolEnum.Ssh.value,
|
||||
name: null,
|
||||
authCerts: [],
|
||||
tagId: [],
|
||||
tagCodePaths: [],
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
enableRecorder: -1,
|
||||
@@ -178,16 +183,17 @@ const { dialogVisible, form, submitForm } = toRefs(state);
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
state.form = defaultForm;
|
||||
return;
|
||||
}
|
||||
if (newValue.machine) {
|
||||
state.form = { ...newValue.machine };
|
||||
state.form.tagId = newValue.machine.tags.map((t: any) => t.tagId);
|
||||
state.form.authCerts = newValue.machine.authCerts || [];
|
||||
const machine: any = props.machine;
|
||||
if (machine) {
|
||||
state.form = { ...machine };
|
||||
state.form.tagCodePaths = machine.tags.map((t: any) => t.codePath);
|
||||
state.form.authCerts = machine.authCerts || [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -230,6 +236,7 @@ const getReqForm = () => {
|
||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
reqForm.tagCodePaths = state.form.tagCodePaths.map((t: any) => getTagPath(t)) as any;
|
||||
return reqForm;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
<el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签" required>
|
||||
<el-form-item ref="tagSelectRef" prop="tagCodePaths" label="标签" required>
|
||||
<tag-tree-select
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
(tagCodePaths) => {
|
||||
form.tagCodePaths = tagCodePaths;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:select-tags="form.tagId"
|
||||
:select-tags="form.tagCodePaths"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -21,7 +21,7 @@
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入编号 (数字字母下划线), 不可修改"
|
||||
placeholder="请输入编号 (大小写字母、数字、_-.:), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
@@ -59,12 +59,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { toRefs, reactive, ref, watchEffect } from 'vue';
|
||||
import { mongoApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import { ResourceCodePattern } from '@/common/pattern';
|
||||
import { getTagPath } from '../component/tag';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -82,7 +83,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
|
||||
const rules = {
|
||||
tagId: [
|
||||
tagCodePaths: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择标签',
|
||||
@@ -129,7 +130,7 @@ const state = reactive({
|
||||
name: null,
|
||||
uri: null,
|
||||
sshTunnelMachineId: null as any,
|
||||
tagId: [],
|
||||
tagCodePaths: [],
|
||||
},
|
||||
submitForm: {},
|
||||
});
|
||||
@@ -139,15 +140,16 @@ const { dialogVisible, tabActiveName, form, submitForm } = toRefs(state);
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = mongoApi.testConn.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveMongoExec } = mongoApi.saveMongo.useApi(submitForm);
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.mongo) {
|
||||
state.form = { ...newValue.mongo };
|
||||
state.form.tagId = newValue.mongo.tags.map((t: any) => t.tagId);
|
||||
const mongo: any = props.mongo;
|
||||
if (mongo) {
|
||||
state.form = { ...mongo };
|
||||
state.form.tagCodePaths = mongo.tags.map((t: any) => t.codePath);
|
||||
} else {
|
||||
state.form = { db: 0 } as any;
|
||||
}
|
||||
@@ -158,6 +160,7 @@ const getReqForm = () => {
|
||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
reqForm.tagCodePaths = state.form.tagCodePaths.map((t: any) => getTagPath(t)) as any;
|
||||
return reqForm;
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<mongo-run-command v-model:visible="usersVisible" :id="state.dbOps.dbId" />
|
||||
|
||||
<mongo-edit
|
||||
@val-change="search"
|
||||
@val-change="search()"
|
||||
:title="mongoEditDialog.title"
|
||||
v-model:visible="mongoEditDialog.visible"
|
||||
v-model:mongo="mongoEditDialog.data"
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
<el-form :model="form" ref="redisForm" :rules="rules" label-width="auto">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form-item ref="tagSelectRef" prop="tagId" label="标签" required>
|
||||
<el-form-item ref="tagSelectRef" prop="tagCodePaths" label="标签" required>
|
||||
<tag-tree-select
|
||||
@change-tag="
|
||||
(tagIds) => {
|
||||
form.tagId = tagIds;
|
||||
(tagCodePaths) => {
|
||||
form.tagCodePaths = tagCodePaths;
|
||||
tagSelectRef.validate();
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:select-tags="form.tagId"
|
||||
:select-tags="form.tagCodePaths"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-input
|
||||
:disabled="form.id"
|
||||
v-model.trim="form.code"
|
||||
placeholder="请输入编号 (数字字母下划线), 不可修改"
|
||||
placeholder="请输入编号 (大小写字母、数字、_-.:), 不可修改"
|
||||
auto-complete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
@@ -108,7 +108,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { toRefs, reactive, ref, watchEffect } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
@@ -116,6 +116,7 @@ import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
|
||||
import { ResourceCodePattern } from '@/common/pattern';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import { getTagPath } from '../component/tag';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -132,7 +133,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:visible', 'val-change', 'cancel']);
|
||||
|
||||
const rules = {
|
||||
tagId: [
|
||||
tagCodePaths: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择标签',
|
||||
@@ -190,7 +191,7 @@ const state = reactive({
|
||||
form: {
|
||||
id: null,
|
||||
code: '',
|
||||
tagId: [],
|
||||
tagCodePaths: [],
|
||||
name: null,
|
||||
mode: 'standalone',
|
||||
host: '',
|
||||
@@ -211,15 +212,16 @@ const { dialogVisible, tabActiveName, form, submitForm, dbList } = toRefs(state)
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
|
||||
|
||||
watch(props, async (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.redis) {
|
||||
state.form = { ...newValue.redis };
|
||||
state.form.tagId = newValue.redis.tags.map((t: any) => t.tagId);
|
||||
const redis: any = props.redis;
|
||||
if (redis) {
|
||||
state.form = { ...redis };
|
||||
state.form.tagCodePaths = redis.tags.map((t: any) => t.codePath);
|
||||
convertDb(state.form.db);
|
||||
} else {
|
||||
state.form = { db: '0' } as any;
|
||||
@@ -247,6 +249,7 @@ const getReqForm = async () => {
|
||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
reqForm.tagCodePaths = state.form.tagCodePaths.map((t: any) => getTagPath(t)) as any;
|
||||
return reqForm;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import { AuthCertCiphertextTypeEnum, AuthCertTypeEnum } from './enums';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue';
|
||||
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
@@ -51,6 +51,7 @@ const state = reactive({
|
||||
},
|
||||
searchItems: [
|
||||
SearchItem.input('name', '凭证名称'),
|
||||
SearchItem.select('resourceType', '资源类型').withEnum(ResourceTypeEnum),
|
||||
SearchItem.select('type', '凭证类型').withEnum(AuthCertTypeEnum),
|
||||
SearchItem.select('ciphertextType', '密文类型').withEnum(AuthCertCiphertextTypeEnum),
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="30" min-size="25" max-size="35">
|
||||
<div class="card pd5 mr5">
|
||||
<el-input v-model="filterTag" clearable placeholder="关键字过滤(右击操作)" style="width: 200px; margin-right: 10px" />
|
||||
<el-input v-model="filterTag" clearable placeholder="关键字过滤(右击节点操作)" style="width: 200px; margin-right: 10px" />
|
||||
<el-button
|
||||
v-if="useUserInfo().userInfo.username == 'admin'"
|
||||
v-auth="'tag:save'"
|
||||
@@ -42,7 +42,10 @@
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
|
||||
<SvgIcon
|
||||
:name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon"
|
||||
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
|
||||
/>
|
||||
|
||||
<span class="ml5">
|
||||
{{ data.code }}
|
||||
@@ -94,7 +97,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :disabled="currentTag.type != TagResourceTypeEnum.Tag.value" :label="`数据库 (${resourceCount.db || 0})`" :name="DbTag">
|
||||
<DbList lazy ref="dbListRef" />
|
||||
<InstanceList lazy ref="dbInstanceListRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane
|
||||
@@ -152,10 +155,10 @@ import { Splitpanes, Pane } from 'splitpanes';
|
||||
import MachineList from '../machine/MachineList.vue';
|
||||
import RedisList from '../redis/RedisList.vue';
|
||||
import MongoList from '../mongo/MongoList.vue';
|
||||
import DbList from '../db/DbList.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import InstanceList from '../db/InstanceList.vue';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
@@ -169,7 +172,7 @@ const tagTreeRef: any = ref(null);
|
||||
const filterTag = ref('');
|
||||
const contextmenuRef = ref();
|
||||
const machineListRef: Ref<any> = ref(null);
|
||||
const dbListRef: Ref<any> = ref(null);
|
||||
const dbInstanceListRef: Ref<any> = ref(null);
|
||||
const redisListRef: Ref<any> = ref(null);
|
||||
const mongoListRef: Ref<any> = ref(null);
|
||||
|
||||
@@ -305,7 +308,7 @@ const setNowTabData = () => {
|
||||
machineListRef.value.search(tagPath);
|
||||
break;
|
||||
case DbTag:
|
||||
dbListRef.value.search(tagPath);
|
||||
dbInstanceListRef.value.search(tagPath);
|
||||
break;
|
||||
case RedisTag:
|
||||
redisListRef.value.search(tagPath);
|
||||
@@ -320,7 +323,7 @@ const setNowTabData = () => {
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return data.codePath.includes(value) || data.name.includes(value);
|
||||
return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
|
||||
@@ -48,41 +48,45 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="tag" label="标签">
|
||||
<el-scrollbar style="height: calc(100vh - 300px); border: 1px solid var(--el-border-color)">
|
||||
<el-tree
|
||||
ref="tagTreeRef"
|
||||
style="width: 100%"
|
||||
:data="state.tags"
|
||||
:default-expanded-keys="state.addTeamDialog.form.tags"
|
||||
:default-checked-keys="state.addTeamDialog.form.tags"
|
||||
multiple
|
||||
:render-after-expand="true"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'codePath',
|
||||
children: 'children',
|
||||
disabled: 'disabled',
|
||||
}"
|
||||
@check="tagTreeNodeCheck"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
|
||||
<div class="w100" style="border: 1px solid var(--el-border-color)">
|
||||
<el-input v-model="filterTag" clearable placeholder="输入关键字过滤" size="small" />
|
||||
<el-scrollbar style="height: calc(100vh - 330px)">
|
||||
<el-tree
|
||||
ref="tagTreeRef"
|
||||
style="width: 100%"
|
||||
:data="state.tags"
|
||||
:default-expanded-keys="state.addTeamDialog.form.tags"
|
||||
:default-checked-keys="state.addTeamDialog.form.tags"
|
||||
multiple
|
||||
:render-after-expand="true"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'codePath',
|
||||
children: 'children',
|
||||
disabled: 'disabled',
|
||||
}"
|
||||
@check="tagTreeNodeCheck"
|
||||
:filter-node-method="filterNode"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
|
||||
|
||||
<span class="font13 ml5">
|
||||
{{ data.code }}
|
||||
<span style="color: #3c8dbc">【</span>
|
||||
{{ data.name }}
|
||||
<span style="color: #3c8dbc">】</span>
|
||||
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
|
||||
<span class="font13 ml5">
|
||||
{{ data.code }}
|
||||
<span style="color: #3c8dbc">【</span>
|
||||
{{ data.name }}
|
||||
<span style="color: #3c8dbc">】</span>
|
||||
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }} </el-tag>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -127,7 +131,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
|
||||
import { ref, toRefs, reactive, onMounted, Ref, watch } from 'vue';
|
||||
import { tagApi } from './api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { notBlank } from '@/common/assert';
|
||||
@@ -143,6 +147,7 @@ const teamForm: any = ref(null);
|
||||
const tagTreeRef: any = ref(null);
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
const showMemPageTableRef: Ref<any> = ref(null);
|
||||
const filterTag = ref('');
|
||||
|
||||
const searchItems = [SearchItem.input('name', '团队名称')];
|
||||
const columns = [
|
||||
@@ -206,6 +211,17 @@ const search = async () => {
|
||||
pageTableRef.value.search();
|
||||
};
|
||||
|
||||
watch(filterTag, (val) => {
|
||||
tagTreeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
|
||||
};
|
||||
|
||||
const showSaveTeamDialog = async (data: any) => {
|
||||
state.tags = await tagApi.getTagTrees.request(null);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { toRefs, reactive, watch, ref, watchEffect } from 'vue';
|
||||
import { accountApi } from '../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { AccountUsernamePattern } from '@/common/pattern';
|
||||
@@ -101,6 +101,18 @@ watch(props, (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const account: any = props.account;
|
||||
if (account) {
|
||||
state.form = { ...account };
|
||||
state.edit = true;
|
||||
} else {
|
||||
state.edit = false;
|
||||
state.form = {} as any;
|
||||
}
|
||||
state.dialogVisible = props.visible;
|
||||
});
|
||||
|
||||
const btnOk = async () => {
|
||||
accountForm.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, watch } from 'vue';
|
||||
import { ref, toRefs, reactive, watch, watchEffect } from 'vue';
|
||||
import { configApi, accountApi } from '../api';
|
||||
import { DynamicFormEdit } from '@/components/dynamic-form';
|
||||
|
||||
@@ -82,14 +82,14 @@ const { dvisible, params, form } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveConfigExec } = configApi.save.useApi(form);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dvisible = newValue.visible;
|
||||
watchEffect(() => {
|
||||
state.dvisible = props.visible;
|
||||
if (!state.dvisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.data) {
|
||||
state.form = { ...newValue.data };
|
||||
if (props.data) {
|
||||
state.form = { ...(props.data as any) };
|
||||
if (state.form.params) {
|
||||
state.params = JSON.parse(state.form.params);
|
||||
} else {
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
e
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
@@ -140,7 +140,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, watch } from 'vue';
|
||||
import { ref, toRefs, reactive, watchEffect } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { resourceApi } from '../api';
|
||||
import { ResourceTypeEnum } from '../enums';
|
||||
@@ -229,10 +229,10 @@ const { dialogVisible, form, submitForm } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveResouceExec } = resourceApi.save.useApi(submitForm);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
if (newValue.data) {
|
||||
state.form = { ...newValue.data };
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (props.data) {
|
||||
state.form = { ...(props.data as any) };
|
||||
} else {
|
||||
state.form = {} as any;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, watch } from 'vue';
|
||||
import { ref, toRefs, reactive, watchEffect } from 'vue';
|
||||
import { roleApi } from '../api';
|
||||
import { RoleStatusEnum } from '../enums';
|
||||
|
||||
@@ -63,10 +63,10 @@ const { dvisible, form } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveRoleExec } = roleApi.save.useApi(form);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dvisible = newValue.visible;
|
||||
if (newValue.data) {
|
||||
state.form = { ...newValue.data };
|
||||
watchEffect(() => {
|
||||
state.dvisible = props.visible;
|
||||
if (props.data) {
|
||||
state.form = { ...(props.data as any) };
|
||||
} else {
|
||||
state.form = {} as any;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user