mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 机器新增命令过滤配置、首页功能完善(操作记录与快捷操作)
This commit is contained in:
@@ -11,18 +11,17 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"asciinema-player": "^3.7.0",
|
||||
"asciinema-player": "^3.7.1",
|
||||
"axios": "^1.6.2",
|
||||
"clipboard": "^2.0.11",
|
||||
"countup.js": "^2.8.0",
|
||||
"cropperjs": "^1.6.1",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.7.1",
|
||||
"element-plus": "^2.7.2",
|
||||
"js-base64": "^3.7.7",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.47.0",
|
||||
"monaco-editor": "^0.48.0",
|
||||
"monaco-sql-languages": "^0.11.0",
|
||||
"monaco-themes": "^0.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
@@ -34,7 +33,7 @@
|
||||
"sql-formatter": "^15.0.2",
|
||||
"trzsz": "^1.1.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.4.23",
|
||||
"vue": "^3.4.25",
|
||||
"vue-router": "^4.3.2",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
|
||||
29
mayfly_go_web/src/store/autoOpenResource.ts
Normal file
29
mayfly_go_web/src/store/autoOpenResource.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
* 自动打开资源
|
||||
*/
|
||||
export const useAutoOpenResource = defineStore('autoOpenResource', {
|
||||
state: () => ({
|
||||
autoOpenResource: {
|
||||
machineCodePath: '',
|
||||
dbCodePath: '',
|
||||
redisCodePath: '',
|
||||
mongoCodePath: '',
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
setMachineCodePath(codePath: string) {
|
||||
this.autoOpenResource.machineCodePath = codePath;
|
||||
},
|
||||
setDbCodePath(codePath: string) {
|
||||
this.autoOpenResource.dbCodePath = codePath;
|
||||
},
|
||||
setRedisCodePath(codePath: string) {
|
||||
this.autoOpenResource.redisCodePath = codePath;
|
||||
},
|
||||
setMongoCodePath(codePath: string) {
|
||||
this.autoOpenResource.mongoCodePath = codePath;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,137 +1,541 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<div class="home-container personal">
|
||||
<el-row :gutter="15">
|
||||
<el-col :sm="6" class="mb15">
|
||||
<div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
|
||||
<div class="flex-margin flex">
|
||||
<img :src="userInfo.photo" />
|
||||
<div class="home-card-first-right ml15">
|
||||
<div class="flex-margin">
|
||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}` }}</div>
|
||||
</div>
|
||||
<!-- 个人信息 -->
|
||||
<el-col :xs="24" :sm="16">
|
||||
<el-card shadow="hover" header="个人信息">
|
||||
<div class="personal-user">
|
||||
<div class="personal-user-left">
|
||||
<el-upload class="h100 personal-user-left-upload" action="" multiple :limit="1">
|
||||
<img :src="userInfo.photo" />
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="personal-user-right">
|
||||
<el-row>
|
||||
<el-col :span="24" class="personal-title mb18"
|
||||
>{{ currentTime }},{{ userInfo.name }},生活变的再糟糕,也不妨碍我变得更好!
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">用户名:</div>
|
||||
<div class="personal-item-value">{{ userInfo.username }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">角色:</div>
|
||||
<div class="personal-item-value">{{ roleInfo }}</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">上次登录IP:</div>
|
||||
<div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">上次登录时间:</div>
|
||||
<div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
|
||||
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
|
||||
<div class="home-card-item-flex">
|
||||
<div class="home-card-item-title pb3">{{ v.title }}</div>
|
||||
<div class="home-card-item-title-num pb6" :id="v.id"></div>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<el-col :xs="24" :sm="8" class="pl15 personal-info">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<span>消息通知</span>
|
||||
<span @click="showMsgs" class="personal-info-more">更多</span>
|
||||
</template>
|
||||
<div class="personal-info-box">
|
||||
<ul class="personal-info-ul">
|
||||
<li v-for="(v, k) in state.msgs as any" :key="k" class="personal-info-li">
|
||||
<a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="mt20 resource-info">
|
||||
<el-col :sm="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="pointer-icon" @click="toPage('machine')">
|
||||
<div class="resource-num">
|
||||
<SvgIcon
|
||||
class="mb5 mr5"
|
||||
:size="28"
|
||||
:name="TagResourceTypeEnum.Machine.extra.icon"
|
||||
:color="TagResourceTypeEnum.Machine.extra.iconColor"
|
||||
/>
|
||||
<span>{{ state.machine.num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :sm="24">
|
||||
<el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small">
|
||||
<el-table-column prop="createTime" show-overflow-tooltip width="135">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<TagCodePath :path="scope.row.codePath" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="30">
|
||||
<template #default="scope">
|
||||
<el-link @click="toPage('machine', scope.row.codePath)" type="primary" icon="Position"></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :sm="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="pointer-icon" @click="toPage('db')">
|
||||
<div class="resource-num">
|
||||
<SvgIcon class="mb5 mr5" :size="28" :name="TagResourceTypeEnum.Db.extra.icon" :color="TagResourceTypeEnum.Db.extra.iconColor" />
|
||||
<span>{{ state.db.num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :sm="24">
|
||||
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small">
|
||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<TagCodePath :path="scope.row.codePath" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="30">
|
||||
<template #default="scope">
|
||||
<el-link @click="toPage('db', scope.row.codePath)" type="primary" icon="Position"></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="mt20 resource-info">
|
||||
<el-col :sm="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="pointer-icon" @click="toPage('redis')">
|
||||
<div class="resource-num">
|
||||
<SvgIcon
|
||||
class="mb5 mr5"
|
||||
:size="28"
|
||||
:name="TagResourceTypeEnum.Redis.extra.icon"
|
||||
:color="TagResourceTypeEnum.Redis.extra.iconColor"
|
||||
/>
|
||||
<span>{{ state.redis.num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :sm="24">
|
||||
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small">
|
||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<TagCodePath :path="scope.row.codePath" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="30">
|
||||
<template #default="scope">
|
||||
<el-link @click="toPage('redis', scope.row.codePath)" type="primary" icon="Position"></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :sm="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="pointer-icon" @click="toPage('mongo')">
|
||||
<div class="resource-num">
|
||||
<SvgIcon
|
||||
class="mb5 mr5"
|
||||
:size="28"
|
||||
:name="TagResourceTypeEnum.Mongo.extra.icon"
|
||||
:color="TagResourceTypeEnum.Mongo.extra.iconColor"
|
||||
/>
|
||||
<span>{{ state.mongo.num }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :sm="24">
|
||||
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small">
|
||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<TagCodePath :path="scope.row.codePath" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="30">
|
||||
<template #default="scope">
|
||||
<el-link @click="toPage('mongo', scope.row.codePath)" type="primary" icon="Position"></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog width="900px" title="消息" v-model="msgDialog.visible">
|
||||
<el-table border :data="msgDialog.msgs.list" size="small">
|
||||
<el-table-column property="type" label="类型" width="60">
|
||||
<template #default="scope">
|
||||
{{ getMsgTypeDesc(scope.row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="msg" label="消息"></el-table-column>
|
||||
<el-table-column property="createTime" label="时间" width="150">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row type="flex" class="mt5" justify="center">
|
||||
<el-pagination
|
||||
small
|
||||
@current-change="searchMsg"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="msgDialog.msgs.total"
|
||||
v-model:current-page="msgDialog.query.pageNum"
|
||||
:page-size="msgDialog.query.pageSize"
|
||||
/>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
|
||||
import { toRefs, reactive, onMounted, computed } from 'vue';
|
||||
// import * as echarts from 'echarts';
|
||||
import { CountUp } from 'countup.js';
|
||||
import { formatAxis } from '@/common/utils/format';
|
||||
import { indexApi } from './api';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { personApi } from '../personal/api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { resourceOpLogApi } from '../ops/tag/api';
|
||||
import TagCodePath from '../ops/component/TagCodePath.vue';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
|
||||
const router = useRouter();
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
|
||||
const state = reactive({
|
||||
topCardItemList: [
|
||||
{
|
||||
title: 'Linux机器',
|
||||
id: 'machineNum',
|
||||
color: '#F95959',
|
||||
accountInfo: {
|
||||
roles: [],
|
||||
},
|
||||
msgs: [],
|
||||
msgDialog: {
|
||||
visible: false,
|
||||
query: {
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
},
|
||||
{
|
||||
title: '数据库',
|
||||
id: 'dbNum',
|
||||
color: '#8595F4',
|
||||
msgs: {
|
||||
list: [],
|
||||
total: null,
|
||||
},
|
||||
{
|
||||
title: 'redis',
|
||||
id: 'redisNum',
|
||||
color: '#1abc9c',
|
||||
},
|
||||
{
|
||||
title: 'Mongo',
|
||||
id: 'mongoNum',
|
||||
color: '#FEBB50',
|
||||
},
|
||||
],
|
||||
},
|
||||
resourceOpTableHeight: 180,
|
||||
defaultLogSize: 5,
|
||||
machine: {
|
||||
num: 0,
|
||||
opLogs: [],
|
||||
},
|
||||
db: {
|
||||
num: 0,
|
||||
opLogs: [],
|
||||
},
|
||||
redis: {
|
||||
num: 0,
|
||||
opLogs: [],
|
||||
},
|
||||
mongo: {
|
||||
num: 0,
|
||||
opLogs: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { topCardItemList } = toRefs(state);
|
||||
const { msgDialog } = toRefs(state);
|
||||
|
||||
const roleInfo = computed(() => {
|
||||
if (state.accountInfo.roles.length == 0) {
|
||||
return '';
|
||||
}
|
||||
return state.accountInfo.roles.map((val: any) => val.roleName).join('、');
|
||||
});
|
||||
|
||||
// 当前时间提示语
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initData();
|
||||
getAccountInfo();
|
||||
|
||||
getMsgs().then((res) => {
|
||||
state.msgs = res.list;
|
||||
});
|
||||
});
|
||||
|
||||
const showMsgs = async () => {
|
||||
state.msgDialog.query.pageNum = 1;
|
||||
searchMsg();
|
||||
state.msgDialog.visible = true;
|
||||
};
|
||||
|
||||
const searchMsg = async () => {
|
||||
state.msgDialog.msgs = await getMsgs();
|
||||
};
|
||||
|
||||
const getMsgTypeDesc = (type: number) => {
|
||||
if (type == 1) {
|
||||
return '登录';
|
||||
}
|
||||
if (type == 2) {
|
||||
return '通知';
|
||||
}
|
||||
};
|
||||
|
||||
const getAccountInfo = async () => {
|
||||
state.accountInfo = await personApi.accountInfo.request();
|
||||
};
|
||||
|
||||
const getMsgs = async () => {
|
||||
return await personApi.getMsgs.request(state.msgDialog.query);
|
||||
};
|
||||
|
||||
// 初始化数字滚动
|
||||
const initNumCountUp = async () => {
|
||||
indexApi.machineDashbord.request().then((res: any) => {
|
||||
nextTick(() => {
|
||||
new CountUp('machineNum', res.machineNum).start();
|
||||
const initData = async () => {
|
||||
resourceOpLogApi.getAccountResourceOpLogs
|
||||
.request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
|
||||
.then((res: any) => {
|
||||
state.machine.opLogs = res.list;
|
||||
});
|
||||
|
||||
resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize }).then((res: any) => {
|
||||
state.db.opLogs = res.list;
|
||||
});
|
||||
|
||||
resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.Redis.value, pageSize: state.defaultLogSize }).then((res: any) => {
|
||||
state.redis.opLogs = res.list;
|
||||
});
|
||||
|
||||
resourceOpLogApi.getAccountResourceOpLogs.request({ resourceType: TagResourceTypeEnum.Mongo.value, pageSize: state.defaultLogSize }).then((res: any) => {
|
||||
state.mongo.opLogs = res.list;
|
||||
});
|
||||
|
||||
indexApi.machineDashbord.request().then((res: any) => {
|
||||
state.machine.num = res.machineNum;
|
||||
});
|
||||
|
||||
indexApi.dbDashbord.request().then((res: any) => {
|
||||
nextTick(() => {
|
||||
new CountUp('dbNum', res.dbNum).start();
|
||||
});
|
||||
state.db.num = res.dbNum;
|
||||
});
|
||||
|
||||
indexApi.redisDashbord.request().then((res: any) => {
|
||||
nextTick(() => {
|
||||
new CountUp('redisNum', res.redisNum).start();
|
||||
});
|
||||
state.redis.num = res.redisNum;
|
||||
});
|
||||
|
||||
indexApi.mongoDashbord.request().then((res: any) => {
|
||||
nextTick(() => {
|
||||
new CountUp('mongoNum', res.mongoNum).start();
|
||||
});
|
||||
state.mongo.num = res.mongoNum;
|
||||
});
|
||||
};
|
||||
|
||||
const toPage = (item: any) => {
|
||||
switch (item.id) {
|
||||
const toPage = (item: any, codePath = '') => {
|
||||
let path;
|
||||
switch (item) {
|
||||
case 'personal': {
|
||||
router.push('/personal');
|
||||
break;
|
||||
}
|
||||
case 'mongoNum': {
|
||||
router.push('/mongo/mongo-data-operation');
|
||||
case 'mongo': {
|
||||
useAutoOpenResource().setMongoCodePath(codePath);
|
||||
path = '/mongo/mongo-data-operation';
|
||||
break;
|
||||
}
|
||||
case 'machineNum': {
|
||||
router.push('/machine/machines-op');
|
||||
case 'machine': {
|
||||
useAutoOpenResource().setMachineCodePath(codePath);
|
||||
path = '/machine/machines-op';
|
||||
break;
|
||||
}
|
||||
case 'dbNum': {
|
||||
router.push('/dbms/sql-exec');
|
||||
case 'db': {
|
||||
useAutoOpenResource().setDbCodePath(codePath);
|
||||
path = '/dbms/sql-exec';
|
||||
break;
|
||||
}
|
||||
case 'redisNum': {
|
||||
router.push('/redis/data-operation');
|
||||
case 'redis': {
|
||||
useAutoOpenResource().setRedisCodePath(codePath);
|
||||
path = '/redis/data-operation';
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initNumCountUp();
|
||||
// initHomeLaboratory();
|
||||
// initHomeOvertime();
|
||||
});
|
||||
router.push({ path });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/theme/mixins/index.scss';
|
||||
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.personal-user-left {
|
||||
width: 100px;
|
||||
height: 130px;
|
||||
border-radius: 3px;
|
||||
|
||||
::v-deep(.el-upload) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-user-left-upload {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-user-right {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
|
||||
.personal-item-label {
|
||||
color: gray;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
.personal-info-more {
|
||||
float: right;
|
||||
color: gray;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info-box {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-info-ul {
|
||||
list-style: none;
|
||||
|
||||
.personal-info-li {
|
||||
font-size: 13px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
color: grey;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& a:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-info {
|
||||
text-align: center;
|
||||
|
||||
::v-deep(.el-card__header) {
|
||||
padding: 2px 20px;
|
||||
}
|
||||
|
||||
.resource-num {
|
||||
font-weight: 700;
|
||||
font-size: 2vw;
|
||||
}
|
||||
}
|
||||
|
||||
.home-container {
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -182,7 +586,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.home-card-item-title-num {
|
||||
font-size: 18px;
|
||||
font-size: 2vw;
|
||||
}
|
||||
|
||||
.home-card-item-tip-num {
|
||||
@@ -190,124 +594,5 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-card-first {
|
||||
background: var(--bg-main-color);
|
||||
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 100%;
|
||||
border: 2px solid var(--el-color-primary-light-5);
|
||||
}
|
||||
|
||||
.home-card-first-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.home-card-first-right-msg {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-monitor {
|
||||
height: 200px;
|
||||
|
||||
.flex-warp-item {
|
||||
width: 50%;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
|
||||
.flex-warp-item-box {
|
||||
margin: auto;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-warning-card {
|
||||
height: 292px;
|
||||
|
||||
::v-deep(.el-card) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic {
|
||||
height: 200px;
|
||||
|
||||
.home-dynamic-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
|
||||
&:first-of-type {
|
||||
.home-dynamic-item-line {
|
||||
i {
|
||||
color: orange !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-left {
|
||||
text-align: right;
|
||||
|
||||
.home-dynamic-item-left-time1 {
|
||||
}
|
||||
|
||||
.home-dynamic-item-left-time2 {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-line {
|
||||
height: 60px;
|
||||
border-right: 2px dashed #dfdfdf;
|
||||
margin: 0 20px;
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: -6px;
|
||||
transform: rotate(46deg);
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-right {
|
||||
flex: 1;
|
||||
|
||||
.home-dynamic-item-right-title {
|
||||
i {
|
||||
margin-right: 5px;
|
||||
border: 1px solid #dfdfdf;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100%;
|
||||
padding: 3px 2px 2px;
|
||||
text-align: center;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-right-label {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -191,12 +191,13 @@ const showResourceEdit = computed(() => {
|
||||
return state.form.type != AuthCertTypeEnum.Public.value && !props.resourceEdit;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.authCert,
|
||||
(val: any) => {
|
||||
setForm(val);
|
||||
watch(dialogVisible, (val: any) => {
|
||||
if (val) {
|
||||
setForm(props.authCert);
|
||||
} else {
|
||||
cancelEdit();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const setForm = (val: any) => {
|
||||
val = { ...val };
|
||||
@@ -246,10 +247,11 @@ const getCiphertext = async () => {
|
||||
|
||||
const cancelEdit = () => {
|
||||
dialogVisible.value = false;
|
||||
emit('cancel');
|
||||
|
||||
setTimeout(() => {
|
||||
acForm.value?.resetFields();
|
||||
state.form = { ...DefaultForm };
|
||||
acForm.value?.resetFields();
|
||||
emit('cancel');
|
||||
}, 300);
|
||||
};
|
||||
|
||||
|
||||
@@ -113,9 +113,6 @@ const deleteRow = (idx: any) => {
|
||||
|
||||
const cancelEdit = () => {
|
||||
state.dvisible = false;
|
||||
setTimeout(() => {
|
||||
state.form = {};
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const btnOk = async (authCert: any) => {
|
||||
|
||||
87
mayfly_go_web/src/views/ops/component/TagCodePath.vue
Normal file
87
mayfly_go_web/src/views/ops/component/TagCodePath.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div v-if="paths">
|
||||
<el-row v-for="(path, idx) in paths?.slice(0, 1)" :key="idx">
|
||||
<span v-for="item in parseTagPath(path)" :key="item.code">
|
||||
<SvgIcon
|
||||
:name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon"
|
||||
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.iconColor"
|
||||
class="mr2"
|
||||
/>
|
||||
<span> {{ item.code }}</span>
|
||||
<SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
|
||||
</span>
|
||||
|
||||
<!-- 展示剩余的标签信息 -->
|
||||
<el-popover :show-after="300" v-if="paths.length > 1 && idx == 0" placement="bottom" width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<SvgIcon class="mt5 ml5" color="var(--el-color-primary)" name="MoreFilled" />
|
||||
</template>
|
||||
|
||||
<el-row v-for="i in paths.slice(1)" :key="i">
|
||||
<span v-for="item in parseTagPath(i)" :key="item.code">
|
||||
<SvgIcon
|
||||
:name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon"
|
||||
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.iconColor"
|
||||
class="mr2"
|
||||
/>
|
||||
<span> {{ item.code }}</span>
|
||||
<SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
|
||||
</span>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
path: {
|
||||
type: [String, Array<string>],
|
||||
},
|
||||
});
|
||||
|
||||
const paths = computed(() => {
|
||||
if (Array.isArray(props.path)) {
|
||||
return props.path;
|
||||
}
|
||||
|
||||
return [props.path];
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
@@ -203,8 +203,14 @@ const getNode = (nodeKey: any) => {
|
||||
return node;
|
||||
};
|
||||
|
||||
const setCurrentKey = (nodeKey: any) => {
|
||||
treeRef.value.setCurrentKey(nodeKey);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
reloadNode,
|
||||
getNode,
|
||||
setCurrentKey,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
153
mayfly_go_web/src/views/ops/component/TagTreeCheck.vue
Executable file
153
mayfly_go_web/src/views/ops/component/TagTreeCheck.vue
Executable file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="w100" style="border: 1px solid var(--el-border-color)">
|
||||
<el-input v-model="filterTag" clearable placeholder="输入关键字过滤" size="small" />
|
||||
<el-scrollbar :style="{ height: props.height }">
|
||||
<el-tree
|
||||
v-bind="$attrs"
|
||||
ref="tagTreeRef"
|
||||
style="width: 100%"
|
||||
:data="state.tags"
|
||||
:default-expanded-keys="checkedTags"
|
||||
:default-checked-keys="checkedTags"
|
||||
multiple
|
||||
:render-after-expand="true"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
:node-key="$props.nodeKey"
|
||||
:props="{
|
||||
value: $props.nodeKey,
|
||||
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"
|
||||
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.iconColor"
|
||||
/>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumValue from '@/common/Enum';
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'calc(100vh - 330px)',
|
||||
},
|
||||
tagType: {
|
||||
type: Number,
|
||||
default: TagResourceTypeEnum.Tag.value,
|
||||
},
|
||||
nodeKey: {
|
||||
type: String,
|
||||
default: 'codePath',
|
||||
},
|
||||
});
|
||||
|
||||
const checkedTags = defineModel<Array<any>>('modelValue', {
|
||||
default: () => [],
|
||||
});
|
||||
|
||||
const tagTreeRef: any = ref(null);
|
||||
const filterTag = ref('');
|
||||
|
||||
const state = reactive({
|
||||
tags: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
|
||||
|
||||
setTimeout(() => {
|
||||
const checkedNodes = tagTreeRef.value.getCheckedNodes();
|
||||
console.log('check nodes: ', checkedNodes);
|
||||
// 禁用选中节点的所有父节点,不可选中
|
||||
for (let checkNodeData of checkedNodes) {
|
||||
disableParentNodes(tagTreeRef.value.getNode(checkNodeData.codePath).parent);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
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 tagTreeNodeCheck = (data: any) => {
|
||||
const node = tagTreeRef.value.getNode(data.codePath);
|
||||
console.log('check node: ', node);
|
||||
|
||||
if (node.checked) {
|
||||
// 如果选中了子节点,则需要将父节点全部取消选中,并禁用父节点
|
||||
unCheckParentNodes(node.parent);
|
||||
disableParentNodes(node.parent);
|
||||
} else {
|
||||
// 如果取消了选中,则需要根据条件恢复父节点的选中状态
|
||||
disableParentNodes(node.parent, false);
|
||||
}
|
||||
|
||||
// 更新绑定的值
|
||||
checkedTags.value = tagTreeRef.value.getCheckedKeys(false);
|
||||
};
|
||||
|
||||
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>
|
||||
@@ -6,11 +6,9 @@
|
||||
@change="changeTag"
|
||||
:data="tags"
|
||||
placeholder="请选择关联标签"
|
||||
:render-after-expand="true"
|
||||
:default-expanded-keys="[state.selectTags]"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
show-checkbox
|
||||
node-key="codePath"
|
||||
:check-strictly="props.checkStrictly"
|
||||
:props="{
|
||||
value: 'codePath',
|
||||
label: 'codePath',
|
||||
@@ -19,6 +17,7 @@
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" class="mr2" />
|
||||
<span style="font-size: 13px">
|
||||
{{ data.code }}
|
||||
<span style="color: #3c8dbc">【</span>
|
||||
@@ -33,25 +32,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { toRefs, reactive, onMounted, computed } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumValue from '@/common/Enum';
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
|
||||
|
||||
const props = defineProps({
|
||||
selectTags: {
|
||||
type: [Array<any>],
|
||||
type: [Array<any>, Object],
|
||||
},
|
||||
tagType: {
|
||||
type: Number,
|
||||
default: TagResourceTypeEnum.Tag.value,
|
||||
},
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
@@ -62,6 +58,16 @@ const state = reactive({
|
||||
|
||||
const { tags } = toRefs(state);
|
||||
|
||||
const defaultExpandedKeys = computed(() => {
|
||||
if (Array.isArray(state.selectTags)) {
|
||||
// 如果 state.selectTags 是数组,直接返回
|
||||
return state.selectTags;
|
||||
}
|
||||
|
||||
// 如果 state.selectTags 不是数组,转换为包含 state.selectTags 的数组
|
||||
return [state.selectTags];
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
state.selectTags = props.selectTags;
|
||||
state.tags = await tagApi.getTagTrees.request({ type: props.tagType });
|
||||
|
||||
@@ -171,3 +171,31 @@ export function getTagPathSearchItem(resourceType: number) {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签路径获取对应的类型与编号数组
|
||||
* @param codePath 编号路径 tag1/tag2/1|xxx/11|yyy/
|
||||
* @returns {1: ['xxx'], 11: ['yyy']}
|
||||
*/
|
||||
export function getTagTypeCodeByPath(codePath: string) {
|
||||
const result = {};
|
||||
const parts = codePath.split('/'); // 切分字符串并保留数字和对应的值部分
|
||||
|
||||
for (let part of parts) {
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
let [key, value] = part.split('|'); // 分割数字和值部分
|
||||
// 如果不存在第二个参数,则说明为标签类型
|
||||
if (!value) {
|
||||
value = key;
|
||||
key = '-1';
|
||||
}
|
||||
if (!result[key]) {
|
||||
result[key] = [];
|
||||
}
|
||||
result[key].push(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="db-sql-exec">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.DbName.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
|
||||
<tag-tree
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
: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
|
||||
@@ -167,11 +172,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
||||
import { NodeType, TagTreeNode } from '../component/tag';
|
||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { dbApi } from './api';
|
||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
@@ -183,6 +188,8 @@ import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
|
||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||
@@ -258,7 +265,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
|
||||
await sleep(100);
|
||||
return dbInfos?.map((x: any) => {
|
||||
x.tagPath = parentNode.key;
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
||||
return new TagTreeNode(`${x.code}`, x.name, NodeTypeDbInst).withParams(x);
|
||||
});
|
||||
})
|
||||
.withContextMenuItems([ContextmenuItemRefresh]);
|
||||
@@ -267,6 +274,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
|
||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
const dbs = params.database.split(' ')?.sort();
|
||||
|
||||
return dbs.map((x: any) => {
|
||||
return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
|
||||
.withParams({
|
||||
@@ -418,6 +426,7 @@ const tagTreeRef: any = ref(null);
|
||||
|
||||
const tabs: Map<string, TabInfo> = new Map();
|
||||
const state = reactive({
|
||||
defaultExpendKey: [] as any,
|
||||
/**
|
||||
* 当前操作的数据库实例
|
||||
*/
|
||||
@@ -452,7 +461,11 @@ const serverInfoReqParam = ref({
|
||||
});
|
||||
const { execute: getDbServerInfo, isFetching: loadingServerInfo, data: dbServerInfo } = dbApi.getInstanceServerInfo.useApi<any>(serverInfoReqParam);
|
||||
|
||||
const autoOpenResourceStore = useAutoOpenResource();
|
||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
|
||||
|
||||
onMounted(() => {
|
||||
autoOpenDb(autoOpenResource.value.dbCodePath);
|
||||
setHeight();
|
||||
// 监听浏览器窗口大小变化,更新对应组件高度
|
||||
useEventListener(window, 'resize', setHeight);
|
||||
@@ -462,6 +475,31 @@ onBeforeUnmount(() => {
|
||||
dispposeCompletionItemProvider('sql');
|
||||
});
|
||||
|
||||
watch(
|
||||
() => autoOpenResource.value.dbCodePath,
|
||||
(codePath: any) => {
|
||||
autoOpenDb(codePath);
|
||||
}
|
||||
);
|
||||
|
||||
const autoOpenDb = (codePath: string) => {
|
||||
if (!codePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeAndCodes = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const dbCode = typeAndCodes[TagResourceTypeEnum.DbName.value][0];
|
||||
state.defaultExpendKey = [tagPath, dbCode];
|
||||
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setDbCodePath('');
|
||||
tagTreeRef.value.setCurrentKey(dbCode);
|
||||
}, 600);
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置editor高度和数据表高度
|
||||
*/
|
||||
@@ -807,7 +845,7 @@ const getNowDbInfo = () => {
|
||||
}
|
||||
|
||||
.db-op {
|
||||
height: calc(100vh - 108px);
|
||||
height: calc(100vh - 106px);
|
||||
}
|
||||
|
||||
#data-exec {
|
||||
|
||||
@@ -106,7 +106,9 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||
const emit = defineEmits(['cancel', 'val-change']);
|
||||
|
||||
const dialogVisible = defineModel<boolean>('visible', { default: false });
|
||||
|
||||
const rules = {
|
||||
tagCodePaths: [
|
||||
@@ -170,23 +172,19 @@ const defaultForm = {
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
sshTunnelMachineList: [] as any,
|
||||
form: defaultForm,
|
||||
submitForm: {} as any,
|
||||
pwd: '',
|
||||
});
|
||||
|
||||
const { dialogVisible, form, submitForm } = toRefs(state);
|
||||
const { form, submitForm } = toRefs(state);
|
||||
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
|
||||
|
||||
watchEffect(() => {
|
||||
state.dialogVisible = props.visible;
|
||||
if (!state.dialogVisible) {
|
||||
state.form = { ...defaultForm };
|
||||
state.form.authCerts = [];
|
||||
if (!dialogVisible.value) {
|
||||
return;
|
||||
}
|
||||
const machine: any = props.machine;
|
||||
@@ -194,6 +192,9 @@ watchEffect(() => {
|
||||
state.form = { ...machine };
|
||||
state.form.tagCodePaths = machine.tags.map((t: any) => t.codePath);
|
||||
state.form.authCerts = machine.authCerts || [];
|
||||
} else {
|
||||
state.form = { ...defaultForm };
|
||||
state.form.authCerts = [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -250,7 +251,7 @@ const handleChangeProtocol = (val: any) => {
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
dialogVisible.value = false;
|
||||
emit('cancel');
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
ref="tagTreeRef"
|
||||
:resource-type="TagResourceTypeEnum.MachineAuthCert.value"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
>
|
||||
<template #prefix="{ data }">
|
||||
<SvgIcon
|
||||
@@ -153,13 +154,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, nextTick, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { NodeType, TagTreeNode } from '../component/tag';
|
||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import { ContextmenuItem } from '@/components/contextmenu/index';
|
||||
@@ -169,6 +170,8 @@ import MachineRdp from '@/components/terminal-rdp/MachineRdp.vue';
|
||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import { MachineProtocolEnum } from './enums';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
// 组件
|
||||
const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
|
||||
@@ -196,6 +199,7 @@ class MachineNodeType {
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
defaultExpendKey: [] as any,
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 0,
|
||||
@@ -252,6 +256,9 @@ const { infoDialog, serviceDialog, processDialog, fileDialog, machineStatsDialog
|
||||
|
||||
const tagTreeRef: any = ref(null);
|
||||
|
||||
const autoOpenResourceStore = useAutoOpenResource();
|
||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
|
||||
|
||||
let openIds = {};
|
||||
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (node: TagTreeNode) => {
|
||||
@@ -263,7 +270,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
||||
// 把list 根据name字段排序
|
||||
res.list = res.list.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
return res.list.map((x: any) =>
|
||||
new TagTreeNode(x.id, x.name, NodeTypeMachine)
|
||||
new TagTreeNode(x.code, x.name, NodeTypeMachine)
|
||||
.withParams(x)
|
||||
.withDisabled(x.status == -1 && x.protocol == MachineProtocolEnum.Ssh.value)
|
||||
.withIcon({
|
||||
@@ -279,7 +286,7 @@ const NodeTypeMachine = new NodeType(MachineNodeType.Machine)
|
||||
// 获取授权凭证列表
|
||||
const authCerts = machine.authCerts;
|
||||
return authCerts.map((x: any) =>
|
||||
new TagTreeNode(x.id, x.username, NodeTypeAuthCert)
|
||||
new TagTreeNode(x.name, x.username, NodeTypeAuthCert)
|
||||
.withParams({ ...machine, selectAuthCert: x })
|
||||
.withDisabled(machine.status == -1 && machine.protocol == MachineProtocolEnum.Ssh.value)
|
||||
.withIcon({
|
||||
@@ -323,6 +330,47 @@ const NodeTypeAuthCert = new NodeType(MachineNodeType.AuthCert)
|
||||
.withOnClick((node: any) => serviceManager(node.params)),
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => autoOpenResource.value.machineCodePath,
|
||||
(codePath: any) => {
|
||||
autoOpenTerminal(codePath);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.activeTermName,
|
||||
(newValue, oldValue) => {
|
||||
oldValue && terminalRefs[oldValue]?.blur && terminalRefs[oldValue]?.blur();
|
||||
terminalRefs[newValue]?.focus && terminalRefs[newValue]?.focus();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
autoOpenTerminal(autoOpenResource.value.machineCodePath);
|
||||
});
|
||||
|
||||
const autoOpenTerminal = (codePath: string) => {
|
||||
if (!codePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeAndCodes = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const machineCode = typeAndCodes[TagResourceTypeEnum.Machine.value][0];
|
||||
state.defaultExpendKey = [tagPath, machineCode];
|
||||
|
||||
const authCertName = typeAndCodes[TagResourceTypeEnum.MachineAuthCert.value][0];
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setMachineCodePath('');
|
||||
tagTreeRef.value.setCurrentKey(authCertName);
|
||||
|
||||
const acNode = tagTreeRef.value.getNode(authCertName);
|
||||
openTerminal(acNode.data.params);
|
||||
}, 600);
|
||||
};
|
||||
|
||||
const openTerminal = (machine: any, ex?: boolean) => {
|
||||
// 授权凭证名
|
||||
const ac = machine.selectAuthCert.name;
|
||||
@@ -465,15 +513,6 @@ const onRemoveTab = (targetName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => state.activeTermName,
|
||||
(newValue, oldValue) => {
|
||||
console.log('oldValue', oldValue);
|
||||
oldValue && terminalRefs[oldValue]?.blur && terminalRefs[oldValue]?.blur();
|
||||
terminalRefs[newValue]?.focus && terminalRefs[newValue]?.focus();
|
||||
}
|
||||
);
|
||||
|
||||
const terminalStatusChange = (key: string, status: TerminalStatus) => {
|
||||
state.tabs.get(key).status = status;
|
||||
};
|
||||
|
||||
@@ -58,6 +58,12 @@ export const cronJobApi = {
|
||||
execList: Api.newGet('/machine-cronjobs/execs'),
|
||||
};
|
||||
|
||||
export const cmdConfApi = {
|
||||
list: Api.newGet('/machine/security/cmd-confs'),
|
||||
save: Api.newPost('/machine/security/cmd-confs'),
|
||||
delete: Api.newDelete('/machine/security/cmd-confs/{id}'),
|
||||
};
|
||||
|
||||
export function getMachineTerminalSocketUrl(authCertName: any) {
|
||||
return `${config.baseWsUrl}/machines/terminal/${authCertName}?${joinClientParams()}`;
|
||||
}
|
||||
|
||||
222
mayfly_go_web/src/views/ops/machine/security/CmdConfList.vue
Normal file
222
mayfly_go_web/src/views/ops/machine/security/CmdConfList.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table :data="cmdConfs" stripe>
|
||||
<el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100px"> </el-table-column>
|
||||
<el-table-column prop="cmds" label="过滤命令" min-width="320px" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag class="ml2 mt2" v-for="cmd in scope.row.cmds" :key="cmd" type="danger">
|
||||
{{ cmd }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="codePaths" label="关联机器" min-width="220px" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<TagCodePath :path="scope.row.tags.map((tag: any) => tag.codePath)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
|
||||
<el-table-column prop="creator" label="创建者" show-overflow-tooltip width="100px"> </el-table-column>
|
||||
|
||||
<el-table-column label="操作" min-wdith="100px">
|
||||
<template #header>
|
||||
<el-text tag="b">操作</el-text>
|
||||
<el-button v-auth="'cmdconf:save'" class="ml5" type="primary" circle size="small" icon="Plus" @click="openFormDialog(false)"> </el-button>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button v-auth="'cmdconf:save'" @click="openFormDialog(scope.row)" type="primary" link>编辑</el-button>
|
||||
<el-button v-auth="'cmdconf:del'" @click="deleteCmdConf(scope.row)" type="danger" link>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-drawer title="命令配置" v-model="dialogVisible" :show-close="false" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<template #header>
|
||||
<DrawerHeader header="命令配置" :back="cancelEdit" />
|
||||
</template>
|
||||
|
||||
<el-form ref="formRef" :model="state.form" :rules="rules" label-width="auto">
|
||||
<el-form-item prop="name" label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="名称"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="cmds" label="过滤命令" required>
|
||||
<el-row>
|
||||
<el-tag
|
||||
class="ml2 mt2"
|
||||
v-for="tag in form.cmds"
|
||||
:key="tag"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="handleCmdClose(tag)"
|
||||
type="danger"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-input
|
||||
v-if="state.inputCmdVisible"
|
||||
ref="cmdInputRef"
|
||||
v-model="state.cmdInputValue"
|
||||
class="mt3"
|
||||
size="small"
|
||||
@keyup.enter="handleCmdInputConfirm"
|
||||
@blur="handleCmdInputConfirm"
|
||||
placeholder="请输入命令正则表达式"
|
||||
/>
|
||||
<el-button v-else class="ml2 mt2" size="small" @click="showCmdInput"> + 新建命令 </el-button>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" label="关联机器">
|
||||
<tag-tree-check height="calc(100vh - 430px)" :tag-type="TagResourceTypeEnum.MachineAuthCert.value" v-model="form.codePaths" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="submiting" @click="cancelEdit">取 消</el-button>
|
||||
<el-button v-auth="'cmdconf:save'" type="primary" :loading="submiting" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, onMounted, nextTick } from 'vue';
|
||||
import TagTreeCheck from '../../component/TagTreeCheck.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { cmdConfApi } from '../api';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import TagCodePath from '../../component/TagCodePath.vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
const rules = {
|
||||
tags: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择关联的机器',
|
||||
trigger: ['change'],
|
||||
},
|
||||
],
|
||||
cmds: [
|
||||
{
|
||||
required: true,
|
||||
message: '请创建命令',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const tagSelectRef: any = ref(null);
|
||||
const formRef: any = ref(null);
|
||||
const cmdInputRef: any = ref(null);
|
||||
|
||||
const DefaultForm = {
|
||||
id: 0,
|
||||
name: '',
|
||||
codePaths: [],
|
||||
cmds: [] as any,
|
||||
remark: '',
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
cmdConfs: [],
|
||||
dialogVisible: false,
|
||||
form: DefaultForm,
|
||||
submiting: false,
|
||||
inputCmdVisible: false,
|
||||
cmdInputValue: '',
|
||||
});
|
||||
|
||||
const { cmdConfs, dialogVisible, form, submiting } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
getCmdConfs();
|
||||
});
|
||||
|
||||
const getCmdConfs = async () => {
|
||||
state.cmdConfs = await cmdConfApi.list.request();
|
||||
};
|
||||
|
||||
const handleCmdClose = (tag: string) => {
|
||||
state.form.cmds.splice(state.form.cmds.indexOf(tag), 1);
|
||||
};
|
||||
|
||||
const showCmdInput = () => {
|
||||
state.inputCmdVisible = true;
|
||||
nextTick(() => {
|
||||
cmdInputRef.value!.input!.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const handleCmdInputConfirm = () => {
|
||||
if (state.cmdInputValue) {
|
||||
state.form.cmds.push(state.cmdInputValue);
|
||||
}
|
||||
state.inputCmdVisible = false;
|
||||
state.cmdInputValue = '';
|
||||
};
|
||||
|
||||
const openFormDialog = (data: any) => {
|
||||
if (!data) {
|
||||
state.form = { ...DefaultForm };
|
||||
} else {
|
||||
state.form = _.cloneDeep(data);
|
||||
state.form.codePaths = data.tags.map((tag: any) => tag.codePath);
|
||||
}
|
||||
state.dialogVisible = true;
|
||||
};
|
||||
|
||||
const deleteCmdConf = async (data: any) => {
|
||||
await ElMessageBox.confirm(`确定删除该[${data.name}]命令配置?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
|
||||
await cmdConfApi.delete.request({ id: data.id });
|
||||
ElMessage.success('操作成功');
|
||||
getCmdConfs();
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
state.dialogVisible = false;
|
||||
// 取消表单的校验
|
||||
setTimeout(() => {
|
||||
state.form = { ...DefaultForm };
|
||||
formRef.value.resetFields();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const submitForm = () => {
|
||||
formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
state.submiting = true;
|
||||
await cmdConfApi.save.request(state.form);
|
||||
ElMessage.success('操作成功');
|
||||
|
||||
cancelEdit();
|
||||
getCmdConfs();
|
||||
} finally {
|
||||
state.submiting = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style></style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-change="handleTabChange">
|
||||
<el-tab-pane label="命令配置" :name="CmdConfTab">
|
||||
<CmdConfList />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
|
||||
|
||||
const CmdConfList = defineAsyncComponent(() => import('./CmdConfList.vue'));
|
||||
|
||||
const CmdConfTab = 'cmdConf';
|
||||
|
||||
const state = reactive({
|
||||
activeName: CmdConfTab,
|
||||
cmdConfs: [],
|
||||
});
|
||||
|
||||
const { activeName } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
state.activeName = CmdConfTab;
|
||||
});
|
||||
|
||||
const handleTabChange = (tabName: any) => {
|
||||
if (tabName == CmdConfTab) {
|
||||
console.log('get cmd confs');
|
||||
}
|
||||
console.log(tabName);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="flex-all-center">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Mongo.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<tag-tree
|
||||
ref="tagTreeRef"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
:resource-type="TagResourceTypeEnum.Mongo.value"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
>
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == MongoNodeType.Mongo">
|
||||
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
|
||||
@@ -168,16 +173,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { mongoApi } from './api';
|
||||
import { computed, defineAsyncComponent, reactive, ref, toRefs } from 'vue';
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { isTrue, notBlank } from '@/common/assert';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
|
||||
|
||||
@@ -207,7 +214,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
||||
await sleep(100);
|
||||
return mongoInfos?.map((x: any) => {
|
||||
x.tagPath = parentNode.key;
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeMongo).withParams(x);
|
||||
return new TagTreeNode(`${x.code}`, x.name, NodeTypeMongo).withParams(x);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,7 +257,10 @@ const NodeTypeColl = new NodeType(MongoNodeType.Coll).withNodeClickFunc((nodeDat
|
||||
});
|
||||
|
||||
const findParamInputRef: any = ref(null);
|
||||
const tagTreeRef: any = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
defaultExpendKey: [] as any,
|
||||
tags: [],
|
||||
mongoList: [] as any,
|
||||
activeName: '', // 当前操作的tab
|
||||
@@ -282,10 +292,42 @@ const state = reactive({
|
||||
|
||||
const { findDialog, docEditDialog } = toRefs(state);
|
||||
|
||||
const autoOpenResourceStore = useAutoOpenResource();
|
||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
|
||||
|
||||
const nowColl = computed(() => {
|
||||
return getNowDataTab();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => autoOpenResource.value.mongoCodePath,
|
||||
(codePath: any) => {
|
||||
autoOpenMongo(codePath);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
autoOpenMongo(autoOpenResource.value.mongoCodePath);
|
||||
});
|
||||
|
||||
const autoOpenMongo = (codePath: string) => {
|
||||
if (!codePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeAndCodes = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const mongoCode = typeAndCodes[TagResourceTypeEnum.Mongo.value][0];
|
||||
state.defaultExpendKey = [tagPath, mongoCode];
|
||||
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setMongoCodePath('');
|
||||
tagTreeRef.value.setCurrentKey(mongoCode);
|
||||
}, 600);
|
||||
};
|
||||
|
||||
const changeCollection = async (id: any, schema: string, collection: string) => {
|
||||
const label = `${id}:\`${schema}\`.${collection}`;
|
||||
let dataTab = state.dataTabs[label];
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="redis-data-op flex-all-center">
|
||||
<Splitpanes class="default-theme">
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree :resource-type="TagResourceTypeEnum.Redis.value" :tag-path-node-type="NodeTypeTagPath">
|
||||
<tag-tree
|
||||
ref="tagTreeRef"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
:resource-type="TagResourceTypeEnum.Redis.value"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
>
|
||||
<template #prefix="{ data }">
|
||||
<span v-if="data.type.value == RedisNodeType.Redis">
|
||||
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
|
||||
@@ -178,11 +183,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { redisApi } from './api';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref } from 'vue';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { TagTreeNode, NodeType } from '../component/tag';
|
||||
import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
@@ -190,6 +195,8 @@ import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
import { RedisInst } from './redis';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
|
||||
@@ -230,7 +237,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
||||
await sleep(100);
|
||||
return redisInfos.map((x: any) => {
|
||||
x.tagPath = parentNode.key;
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeRedis).withParams(x);
|
||||
return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -288,9 +295,11 @@ const treeProps = {
|
||||
const defaultCount = 250;
|
||||
|
||||
const keyTreeRef: any = ref(null);
|
||||
const tagTreeRef: any = ref(null);
|
||||
const redisInst: Ref<RedisInst> = ref(new RedisInst());
|
||||
|
||||
const state = reactive({
|
||||
defaultExpendKey: [] as any,
|
||||
tags: [],
|
||||
redisList: [] as any,
|
||||
dbList: [],
|
||||
@@ -331,7 +340,37 @@ const state = reactive({
|
||||
|
||||
const { scanParam, keyTreeData, newKeyDialog } = toRefs(state);
|
||||
|
||||
onMounted(async () => {});
|
||||
const autoOpenResourceStore = useAutoOpenResource();
|
||||
const { autoOpenResource } = storeToRefs(autoOpenResourceStore);
|
||||
|
||||
onMounted(async () => {
|
||||
autoOpenRedis(autoOpenResource.value.redisCodePath);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => autoOpenResource.value.redisCodePath,
|
||||
(codePath: any) => {
|
||||
autoOpenRedis(codePath);
|
||||
}
|
||||
);
|
||||
|
||||
const autoOpenRedis = (codePath: string) => {
|
||||
if (!codePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeAndCodes = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const redisCode = typeAndCodes[TagResourceTypeEnum.Redis.value][0];
|
||||
state.defaultExpendKey = [tagPath, redisCode];
|
||||
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setRedisCodePath('');
|
||||
tagTreeRef.value.setCurrentKey(redisCode);
|
||||
}, 600);
|
||||
};
|
||||
|
||||
const scan = async (appendKey = false) => {
|
||||
isTrue(state.scanParam.id != null, '请先选择redis');
|
||||
|
||||
@@ -75,11 +75,7 @@
|
||||
<el-descriptions-item label="code">{{ currentTag.code }}</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>
|
||||
<TagCodePath :path="currentTag.codePath" />
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
|
||||
@@ -163,6 +159,7 @@ import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import InstanceList from '../db/InstanceList.vue';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
@@ -345,38 +342,6 @@ const handleDrop = async (draggingNode: any, dropNode: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -14,11 +14,8 @@
|
||||
<el-button v-auth="'team:del'" :disabled="selectionData.length < 1" @click="deleteTeam()" type="danger" icon="delete">删除</el-button>
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<tag-info :tag-path="data.tagPath" />
|
||||
<span class="ml5">
|
||||
{{ data.tagPath }}
|
||||
</span>
|
||||
<template #tags="{ data }">
|
||||
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
@@ -48,45 +45,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="tag" label="标签">
|
||||
<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>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<TagTreeCheck v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -131,7 +90,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, onMounted, Ref, watch } from 'vue';
|
||||
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
|
||||
import { tagApi } from './api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { notBlank } from '@/common/assert';
|
||||
@@ -139,20 +98,19 @@ import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import TagTreeCheck from '../component/TagTreeCheck.vue';
|
||||
import TagCodePath from '../component/TagCodePath.vue';
|
||||
|
||||
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 = [
|
||||
TableColumn.new('name', '团队名称'),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('tags', '分配标签').isSlot().setAddWidth(40),
|
||||
TableColumn.new('creator', '创建者'),
|
||||
TableColumn.new('createTime', '创建时间').isTime(),
|
||||
TableColumn.new('modifier', '修改者'),
|
||||
@@ -162,10 +120,9 @@ const columns = [
|
||||
|
||||
const state = reactive({
|
||||
currentEditPermissions: false,
|
||||
tags: [],
|
||||
addTeamDialog: {
|
||||
visible: false,
|
||||
form: { id: 0, name: '', remark: '', tags: [] },
|
||||
form: { id: 0, name: '', remark: '', codePaths: [] },
|
||||
},
|
||||
query: {
|
||||
pageNum: 1,
|
||||
@@ -211,34 +168,13 @@ 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);
|
||||
|
||||
if (data) {
|
||||
state.addTeamDialog.form.id = data.id;
|
||||
state.addTeamDialog.form.name = data.name;
|
||||
state.addTeamDialog.form.remark = data.remark;
|
||||
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.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
|
||||
// state.addTeamDialog.form.tags = await tagApi.getRelateTagIds.request({ relateType: TagTreeRelateTypeEnum.Team.value, relateId: data.id });
|
||||
}
|
||||
|
||||
state.addTeamDialog.visible = true;
|
||||
@@ -248,7 +184,6 @@ const saveTeam = async () => {
|
||||
teamForm.value.validate(async (valid: any) => {
|
||||
if (valid) {
|
||||
const form = state.addTeamDialog.form;
|
||||
form.tags = tagTreeRef.value.getCheckedKeys(false);
|
||||
await tagApi.saveTeam.request(form);
|
||||
ElMessage.success('保存成功');
|
||||
search();
|
||||
@@ -318,48 +253,5 @@ const cancelAddMember = () => {
|
||||
state.showMemDialog.memForm = {} as any;
|
||||
state.showMemDialog.addVisible = false;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
@@ -9,6 +9,7 @@ export const tagApi = {
|
||||
|
||||
getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
|
||||
countTagResource: Api.newGet('/tag-trees/resources/count'),
|
||||
getRelateTagIds: Api.newGet('/tag-trees/relate/{relateType}/{relateId}'),
|
||||
|
||||
getTeams: Api.newGet('/teams'),
|
||||
saveTeam: Api.newPost('/teams'),
|
||||
@@ -17,8 +18,6 @@ export const tagApi = {
|
||||
getTeamMem: Api.newGet('/teams/{teamId}/members'),
|
||||
saveTeamMem: Api.newPost('/teams/{teamId}/members'),
|
||||
delTeamMem: Api.newDelete('/teams/{teamId}/members/{accountId}'),
|
||||
|
||||
getTeamTagIds: Api.newGet('/teams/{teamId}/tags'),
|
||||
};
|
||||
|
||||
export const resourceAuthCertApi = {
|
||||
@@ -27,3 +26,7 @@ export const resourceAuthCertApi = {
|
||||
save: Api.newPost('/auth-certs'),
|
||||
delete: Api.newDelete('/auth-certs/{id}'),
|
||||
};
|
||||
|
||||
export const resourceOpLogApi = {
|
||||
getAccountResourceOpLogs: Api.newGet('/resource-op-logs/account'),
|
||||
};
|
||||
|
||||
@@ -14,3 +14,7 @@ export const AuthCertCiphertextTypeEnum = {
|
||||
PrivateKey: EnumValue.of(2, '秘钥').tagTypeSuccess(),
|
||||
Public: EnumValue.of(-1, '公共凭证').tagTypeSuccess(),
|
||||
};
|
||||
|
||||
export const TagTreeRelateTypeEnum = {
|
||||
Team: EnumValue.of(1, '团队'),
|
||||
};
|
||||
|
||||
@@ -1,112 +1,6 @@
|
||||
<template>
|
||||
<div class="personal">
|
||||
<el-row>
|
||||
<!-- 个人信息 -->
|
||||
<el-col :xs="24" :sm="16">
|
||||
<el-card shadow="hover" header="个人信息">
|
||||
<div class="personal-user">
|
||||
<div class="personal-user-left">
|
||||
<el-upload class="h100 personal-user-left-upload" action="" multiple :limit="1">
|
||||
<img :src="userInfo.photo" />
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="personal-user-right">
|
||||
<el-row>
|
||||
<el-col :span="24" class="personal-title mb18"
|
||||
>{{ currentTime }},{{ userInfo.name }},生活变的再糟糕,也不妨碍我变得更好!
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">用户名:</div>
|
||||
<div class="personal-item-value">{{ userInfo.username }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">角色:</div>
|
||||
<div class="personal-item-value">{{ roleInfo }}</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">上次登录IP:</div>
|
||||
<div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||
<div class="personal-item-label">上次登录时间:</div>
|
||||
<div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<el-col :xs="24" :sm="8" class="pl15 personal-info">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<span>消息通知</span>
|
||||
<span @click="showMsgs" class="personal-info-more">更多</span>
|
||||
</template>
|
||||
<div class="personal-info-box">
|
||||
<ul class="personal-info-ul">
|
||||
<li v-for="(v, k) in msgDialog.msgs.list as any" :key="k" class="personal-info-li">
|
||||
<a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-dialog width="900px" title="消息" v-model="msgDialog.visible">
|
||||
<el-table border :data="msgDialog.msgs.list" size="small">
|
||||
<el-table-column property="type" label="类型" width="60">
|
||||
<template #default="scope">
|
||||
{{ getMsgTypeDesc(scope.row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="msg" label="消息"></el-table-column>
|
||||
<el-table-column property="createTime" label="时间" width="150">
|
||||
<template #default="scope">
|
||||
{{ dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row type="flex" class="mt5" justify="center">
|
||||
<el-pagination
|
||||
small
|
||||
@current-change="getMsgs"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="msgDialog.msgs.total"
|
||||
v-model:current-page="msgDialog.query.pageNum"
|
||||
:page-size="msgDialog.query.pageSize"
|
||||
/>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 营销推荐 -->
|
||||
<!-- <el-col :span="24">
|
||||
<el-card shadow="hover" class="mt15" header="营销推荐">
|
||||
<el-row :gutter="15" class="personal-recommend-row">
|
||||
<el-col :sm="6" v-for="(v, k) in recommendList" :key="k" class="personal-recommend-col">
|
||||
<div class="personal-recommend" :style="{ 'background-color': v.bg }">
|
||||
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
|
||||
<div class="personal-recommend-auto">
|
||||
<div>{{ v.title }}</div>
|
||||
<div class="personal-recommend-msg">{{ v.msg }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col> -->
|
||||
|
||||
<!-- 更新信息 -->
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" class="mt15 personal-edit" header="更新信息">
|
||||
@@ -142,28 +36,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<!-- <div class="personal-edit-safe-box">
|
||||
<div class="personal-edit-safe-item">
|
||||
<div class="personal-edit-safe-item-left">
|
||||
<div class="personal-edit-safe-item-left-label">密保手机</div>
|
||||
<div class="personal-edit-safe-item-left-value">已绑定手机:132****4108</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-item-right">
|
||||
<el-button type="text">立即修改</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-box">
|
||||
<div class="personal-edit-safe-item">
|
||||
<div class="personal-edit-safe-item-left">
|
||||
<div class="personal-edit-safe-item-left-label">密保问题</div>
|
||||
<div class="personal-edit-safe-item-left-value">已设置密保问题,账号安全大幅度提升</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-item-right">
|
||||
<el-button type="text">立即设置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -171,33 +43,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, computed, onMounted } from 'vue';
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { formatAxis } from '@/common/utils/format';
|
||||
import { personApi } from './api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
const state = reactive({
|
||||
accountInfo: {
|
||||
roles: [],
|
||||
},
|
||||
msgs: [],
|
||||
msgDialog: {
|
||||
visible: false,
|
||||
query: {
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
},
|
||||
msgs: {
|
||||
list: [],
|
||||
total: null,
|
||||
},
|
||||
},
|
||||
recommendList: [],
|
||||
accountForm: {
|
||||
password: '',
|
||||
@@ -208,27 +63,10 @@ const state = reactive({
|
||||
},
|
||||
});
|
||||
|
||||
const { msgDialog, accountForm, authStatus } = toRefs(state);
|
||||
|
||||
// 当前时间提示语
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
|
||||
const showMsgs = () => {
|
||||
state.msgDialog.visible = true;
|
||||
};
|
||||
|
||||
const roleInfo = computed(() => {
|
||||
if (state.accountInfo.roles.length == 0) {
|
||||
return '';
|
||||
}
|
||||
return state.accountInfo.roles.map((val: any) => val.name).join('、');
|
||||
});
|
||||
const { accountForm, authStatus } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
getAccountInfo();
|
||||
getMsgs();
|
||||
state.authStatus = await personApi.authStatus.request();
|
||||
});
|
||||
|
||||
@@ -277,162 +115,11 @@ const unbindOAuth2 = async () => {
|
||||
ElMessage.success('解绑成功');
|
||||
state.authStatus = await personApi.authStatus.request();
|
||||
};
|
||||
|
||||
const getMsgs = async () => {
|
||||
const res = await personApi.getMsgs.request(state.msgDialog.query);
|
||||
state.msgDialog.msgs = res;
|
||||
};
|
||||
|
||||
const getMsgTypeDesc = (type: number) => {
|
||||
if (type == 1) {
|
||||
return '登录';
|
||||
}
|
||||
if (type == 2) {
|
||||
return '通知';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../theme/mixins/index.scss';
|
||||
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.personal-user-left {
|
||||
width: 100px;
|
||||
height: 130px;
|
||||
border-radius: 3px;
|
||||
|
||||
::v-deep(.el-upload) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-user-left-upload {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-user-right {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
|
||||
.personal-item-label {
|
||||
color: gray;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
.personal-info-more {
|
||||
float: right;
|
||||
color: gray;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info-box {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-info-ul {
|
||||
list-style: none;
|
||||
|
||||
.personal-info-li {
|
||||
font-size: 13px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
color: grey;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& a:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-recommend-row {
|
||||
.personal-recommend-col {
|
||||
.personal-recommend {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
color: #ffffff;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
right: 0px !important;
|
||||
bottom: 0px !important;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
font-size: 70px;
|
||||
transform: rotate(-30deg);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
|
||||
.personal-recommend-auto {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5%;
|
||||
|
||||
.personal-recommend-msg {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-edit {
|
||||
.personal-edit-title {
|
||||
position: relative;
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
e
|
||||
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||
github.com/sijms/go-ora/v2 v2.8.12
|
||||
github.com/sijms/go-ora/v2 v2.8.13
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/veops/go-ansiterm v0.0.5
|
||||
go.mongodb.org/mongo-driver v1.15.0 // mongo
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
// gorm
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/gorm v1.25.9
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -20,7 +20,4 @@ const (
|
||||
ResourceTypeDb int8 = 2
|
||||
ResourceTypeRedis int8 = 3
|
||||
ResourceTypeMongo int8 = 4
|
||||
|
||||
// 删除机器的事件主题名
|
||||
DeleteMachineEventTopic = "machine:delete"
|
||||
)
|
||||
|
||||
@@ -12,11 +12,13 @@ import (
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/event"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -97,6 +99,8 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
biz.ErrIsNil(err)
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
|
||||
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.TagPath[0])
|
||||
|
||||
sqlBytes, err := base64.StdEncoding.DecodeString(form.Sql)
|
||||
biz.ErrIsNilAppendErr(err, "sql解码失败: %s")
|
||||
// 去除前后空格及换行符
|
||||
|
||||
@@ -23,6 +23,7 @@ type InstanceDbNamesForm struct {
|
||||
Host string `binding:"required" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Params string `json:"params"`
|
||||
Extra string `json:"extra"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"`
|
||||
AuthCert *tagentity.ResourceAuthCert `json:"authCert" binding:"required"` // 资产授权凭证信息
|
||||
}
|
||||
|
||||
6
server/internal/event/topic.go
Normal file
6
server/internal/event/topic.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package event
|
||||
|
||||
const (
|
||||
EventTopicDeleteMachine = "machine:delete" // 删除机器的事件主题名
|
||||
EventTopicResourceOp = "resource:op" // 资源操作主题
|
||||
)
|
||||
@@ -44,3 +44,14 @@ type MachineCronJobForm struct {
|
||||
MachineIds []uint64 `json:"machineIds"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type MachineCmdConfForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Cmds []string `json:"cmds"` // 命令配置
|
||||
Status int8 `json:"execCmds"` // 状态
|
||||
Stratege string `json:"stratege"` // 策略,空禁用
|
||||
Remark string `json:"remark"` // 备注
|
||||
|
||||
CodePaths []string `json:"codePaths"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/api/form"
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -192,7 +194,9 @@ func (m *Machine) WsSSH(g *gin.Context) {
|
||||
cli, err := m.MachineApp.NewCli(GetMachineAc(rc))
|
||||
biz.ErrIsNilAppendErr(err, mcm.GetErrorContentRn("获取客户端连接失败: %s"))
|
||||
defer cli.Close()
|
||||
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
|
||||
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), mcm.GetErrorContentRn("%s"))
|
||||
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, cli.Info.TagPath[0])
|
||||
|
||||
cols := rc.QueryIntDefault("cols", 80)
|
||||
rows := rc.QueryIntDefault("rows", 32)
|
||||
|
||||
49
server/internal/machine/api/machine_cmd_conf.go
Normal file
49
server/internal/machine/api/machine_cmd_conf.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/machine/api/form"
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type MachineCmdConf struct {
|
||||
MachineCmdConfApp application.MachineCmdConf `inject:""`
|
||||
TagTreeRelateApp tagapp.TagTreeRelate `inject:"TagTreeRelateApp"`
|
||||
}
|
||||
|
||||
func (m *MachineCmdConf) MachineCmdConfs(rc *req.Ctx) {
|
||||
cond := req.BindQuery(rc, new(entity.MachineCmdConf))
|
||||
|
||||
var vos []*vo.MachineCmdConfVO
|
||||
err := m.MachineCmdConfApp.ListByCond(cond, &vos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
m.TagTreeRelateApp.FillTagInfo(tagentity.TagRelateTypeMachineCmd, collx.ArrayMap(vos, func(mvo *vo.MachineCmdConfVO) tagentity.IRelateTag {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
rc.ResData = vos
|
||||
}
|
||||
|
||||
func (m *MachineCmdConf) Save(rc *req.Ctx) {
|
||||
cmdForm := new(form.MachineCmdConfForm)
|
||||
mcj := req.BindJsonAndCopyTo[*entity.MachineCmdConf](rc, cmdForm, new(entity.MachineCmdConf))
|
||||
rc.ReqParam = cmdForm
|
||||
|
||||
err := m.MachineCmdConfApp.SaveCmdConf(rc.MetaCtx, &application.SaveMachineCmdConfParam{
|
||||
CmdConf: mcj,
|
||||
CodePaths: cmdForm.CodePaths,
|
||||
})
|
||||
biz.ErrIsNil(err)
|
||||
}
|
||||
|
||||
func (m *MachineCmdConf) Delete(rc *req.Ctx) {
|
||||
m.MachineCmdConfApp.DeleteCmdConf(rc.MetaCtx, uint64(rc.PathParamInt("id")))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package vo
|
||||
|
||||
import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -85,3 +86,18 @@ func (s MachineFileInfos) Less(i, j int) bool {
|
||||
}
|
||||
return s[i].Name < s[j].Name
|
||||
}
|
||||
|
||||
type MachineCmdConfVO struct {
|
||||
tagentity.RelateTags // 标签信息
|
||||
model.Model
|
||||
|
||||
Name string `json:"name"`
|
||||
Cmds model.Slice[string] `json:"cmds"` // 命令配置
|
||||
Status int8 `json:"execCmds"` // 状态
|
||||
Stratege string `json:"stratege"` // 策略,空禁用
|
||||
Remark string `json:"remark"` // 备注
|
||||
}
|
||||
|
||||
func (mcc *MachineCmdConfVO) GetRelateId() uint64 {
|
||||
return mcc.Id
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ func InitIoc() {
|
||||
ioc.Register(new(machineScriptAppImpl), ioc.WithComponentName("MachineScriptApp"))
|
||||
ioc.Register(new(machineCronJobAppImpl), ioc.WithComponentName("MachineCronJobApp"))
|
||||
ioc.Register(new(machineTermOpAppImpl), ioc.WithComponentName("MachineTermOpApp"))
|
||||
ioc.Register(new(machineCmdConfAppImpl), ioc.WithComponentName("MachineCmdConfApp"))
|
||||
}
|
||||
|
||||
func GetMachineApp() Machine {
|
||||
|
||||
@@ -3,7 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
@@ -210,7 +210,7 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
mcm.DeleteCli(id)
|
||||
|
||||
// 发布机器删除事件
|
||||
global.EventBus.Publish(ctx, consts.DeleteMachineEventTopic, machine)
|
||||
global.EventBus.Publish(ctx, event.EventTopicDeleteMachine, machine)
|
||||
|
||||
resourceType := tagentity.TagTypeMachine
|
||||
return m.Tx(ctx,
|
||||
|
||||
98
server/internal/machine/application/machine_cmd_conf.go
Normal file
98
server/internal/machine/application/machine_cmd_conf.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type SaveMachineCmdConfParam struct {
|
||||
CmdConf *entity.MachineCmdConf
|
||||
CodePaths []string
|
||||
}
|
||||
|
||||
type MachineCmd struct {
|
||||
CmdRegexp *regexp.Regexp // 命令正则表达式
|
||||
Stratege string // 策略(拒绝或审批等)
|
||||
}
|
||||
|
||||
type MachineCmdConf interface {
|
||||
base.App[*entity.MachineCmdConf]
|
||||
|
||||
SaveCmdConf(ctx context.Context, cmdConf *SaveMachineCmdConfParam) error
|
||||
|
||||
DeleteCmdConf(ctx context.Context, id uint64) error
|
||||
|
||||
GetCmdConfsByMachineTags(tagPaths ...string) []*MachineCmd
|
||||
}
|
||||
|
||||
type machineCmdConfAppImpl struct {
|
||||
base.AppImpl[*entity.MachineCmdConf, repository.MachineCmdConf]
|
||||
|
||||
tagTreeRelateApp tagapp.TagTreeRelate `inject:"TagTreeRelateApp"`
|
||||
}
|
||||
|
||||
var _ (MachineCmdConf) = (*machineCmdConfAppImpl)(nil)
|
||||
|
||||
// 注入MachineCmdConfRepo
|
||||
func (m *machineCmdConfAppImpl) InjectMachineCmdConfRepo(repo repository.MachineCmdConf) {
|
||||
m.Repo = repo
|
||||
}
|
||||
|
||||
func (m *machineCmdConfAppImpl) SaveCmdConf(ctx context.Context, cmdConfParam *SaveMachineCmdConfParam) error {
|
||||
cmdConf := cmdConfParam.CmdConf
|
||||
|
||||
return m.Tx(ctx, func(ctx context.Context) error {
|
||||
return m.Save(ctx, cmdConf)
|
||||
}, func(ctx context.Context) error {
|
||||
return m.tagTreeRelateApp.RelateTag(ctx, tagentity.TagRelateTypeMachineCmd, cmdConf.Id, cmdConfParam.CodePaths...)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machineCmdConfAppImpl) DeleteCmdConf(ctx context.Context, id uint64) error {
|
||||
_, err := m.GetById(new(entity.MachineCmdConf), id)
|
||||
if err != nil {
|
||||
return errorx.NewBiz("该命令配置不存在")
|
||||
}
|
||||
|
||||
return m.Tx(ctx, func(ctx context.Context) error {
|
||||
return m.DeleteById(ctx, id)
|
||||
}, func(ctx context.Context) error {
|
||||
return m.tagTreeRelateApp.DeleteByCond(ctx, &tagentity.TagTreeRelate{
|
||||
RelateType: tagentity.TagRelateTypeMachineCmd,
|
||||
RelateId: id,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (m *machineCmdConfAppImpl) GetCmdConfsByMachineTags(tagPaths ...string) []*MachineCmd {
|
||||
var cmds []*MachineCmd
|
||||
cmdConfIds, err := m.tagTreeRelateApp.GetRelateIds(tagentity.TagRelateTypeMachineCmd, tagPaths...)
|
||||
if err != nil {
|
||||
logx.Errorf("获取命令配置信息失败: %s", err.Error())
|
||||
return cmds
|
||||
}
|
||||
if len(cmdConfIds) == 0 {
|
||||
return cmds
|
||||
}
|
||||
|
||||
var cmdConfs []*entity.MachineCmdConf
|
||||
m.GetByIdIn(&cmdConfs, cmdConfIds)
|
||||
|
||||
for _, cmdConf := range cmdConfs {
|
||||
for _, cmd := range cmdConf.Cmds {
|
||||
if p, err := regexp.Compile(cmd); err != nil {
|
||||
logx.Errorf("命令配置[%s],正则编译失败", cmd)
|
||||
} else {
|
||||
cmds = append(cmds, &MachineCmd{CmdRegexp: p})
|
||||
}
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
@@ -36,6 +36,8 @@ type MachineTermOp interface {
|
||||
|
||||
type machineTermOpAppImpl struct {
|
||||
base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp]
|
||||
|
||||
machineCmdConfApp MachineCmdConf `inject:"MachineCmdConfApp"`
|
||||
}
|
||||
|
||||
// 注入MachineTermOpRepo
|
||||
@@ -87,12 +89,17 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
|
||||
LogCmd: cli.Info.EnableRecorder == 1,
|
||||
}
|
||||
|
||||
// createTsParam.CmdFilterFuncs = []mcm.CmdFilterFunc{func(cmd string) error {
|
||||
// if strings.HasPrefix(cmd, "rm") {
|
||||
// return errorx.NewBiz("该命令已被禁用...")
|
||||
// }
|
||||
// return nil
|
||||
// }}
|
||||
cmdConfs := m.machineCmdConfApp.GetCmdConfsByMachineTags(cli.Info.TagPath...)
|
||||
if len(cmdConfs) > 0 {
|
||||
createTsParam.CmdFilterFuncs = []mcm.CmdFilterFunc{func(cmd string) error {
|
||||
for _, cmdConf := range cmdConfs {
|
||||
if cmdConf.CmdRegexp.Match([]byte(cmd)) {
|
||||
return errorx.NewBiz("该命令已被禁用...")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
mts, err := mcm.NewTerminalSession(createTsParam)
|
||||
if err != nil {
|
||||
|
||||
16
server/internal/machine/domain/entity/machine_cmd_conf.go
Normal file
16
server/internal/machine/domain/entity/machine_cmd_conf.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
// 机器命令过滤配置
|
||||
type MachineCmdConf struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name"`
|
||||
Cmds model.Slice[string] `json:"cmds"` // 命令配置
|
||||
Status int8 `json:"execCmds"` // 状态
|
||||
Stratege string `json:"stratege"` // 策略,空禁用
|
||||
Remark string `json:"remark"` // 备注
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type MachineCmdConf interface {
|
||||
base.Repo[*entity.MachineCmdConf]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type machineCmdConfRepoImpl struct {
|
||||
base.RepoImpl[*entity.MachineCmdConf]
|
||||
}
|
||||
|
||||
func newMachineCmdConfRepo() repository.MachineCmdConf {
|
||||
return &machineCmdConfRepoImpl{base.RepoImpl[*entity.MachineCmdConf]{M: new(entity.MachineCmdConf)}}
|
||||
}
|
||||
@@ -12,4 +12,5 @@ func InitIoc() {
|
||||
ioc.Register(newMachineCronJobExecRepo(), ioc.WithComponentName("MachineCronJobExecRepo"))
|
||||
ioc.Register(newMachineCronJobRelateRepo(), ioc.WithComponentName("MachineCronJobRelateRepo"))
|
||||
ioc.Register(newMachineTermOpRepoImpl(), ioc.WithComponentName("MachineTermOpRepo"))
|
||||
ioc.Register(newMachineCmdConfRepo(), ioc.WithComponentName("MachineCmdConfRepo"))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package init
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/infrastructure/persistence"
|
||||
@@ -28,17 +28,17 @@ func Init() {
|
||||
|
||||
application.GetMachineTermOpApp().TimerDeleteTermOp()
|
||||
|
||||
global.EventBus.Subscribe(consts.DeleteMachineEventTopic, "machineFile", func(ctx context.Context, event *eventbus.Event) error {
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineFile", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return application.GetMachineFileApp().DeleteByCond(ctx, &entity.MachineFile{MachineId: me.Id})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(consts.DeleteMachineEventTopic, "machineScript", func(ctx context.Context, event *eventbus.Event) error {
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineScript", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
return application.GetMachineScriptApp().DeleteByCond(ctx, &entity.MachineScript{MachineId: me.Id})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(consts.DeleteMachineEventTopic, "machineCronJob", func(ctx context.Context, event *eventbus.Event) error {
|
||||
global.EventBus.Subscribe(event.EventTopicDeleteMachine, "machineCronJob", func(ctx context.Context, event *eventbus.Event) error {
|
||||
me := event.Val.(*entity.Machine)
|
||||
var jobIds []uint64
|
||||
application.GetMachineCronJobApp().MachineRelateCronJobs(ctx, me.Id, jobIds)
|
||||
|
||||
27
server/internal/machine/router/machine_cmd_conf.go
Normal file
27
server/internal/machine/router/machine_cmd_conf.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/machine/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitMachineCmdConfRouter(router *gin.RouterGroup) {
|
||||
mccs := router.Group("machine/security/cmd-confs")
|
||||
|
||||
mcc := new(api.MachineCmdConf)
|
||||
biz.ErrIsNil(ioc.Inject(mcc))
|
||||
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("", mcc.MachineCmdConfs),
|
||||
|
||||
req.NewPost("", mcc.Save).Log(req.NewLogSave("机器命令配置-保存")).RequiredPermissionCode("cmdconf:save"),
|
||||
|
||||
req.NewDelete(":id", mcc.Delete).Log(req.NewLogSave("机器命令配置-删除")).RequiredPermissionCode("cmdconf:del"),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(mccs, reqs[:])
|
||||
}
|
||||
@@ -7,4 +7,5 @@ func Init(router *gin.RouterGroup) {
|
||||
InitMachineFileRouter(router)
|
||||
InitMachineScriptRouter(router)
|
||||
InitMachineCronJobRouter(router)
|
||||
InitMachineCmdConfRouter(router)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/mongo/api/form"
|
||||
"mayfly-go/internal/mongo/api/vo"
|
||||
"mayfly-go/internal/mongo/application"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
@@ -93,6 +95,9 @@ func (m *Mongo) Databases(rc *req.Ctx) {
|
||||
func (m *Mongo) Collections(rc *req.Ctx) {
|
||||
conn, err := m.MongoApp.GetMongoConn(m.GetMongoId(rc))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, conn.Info.TagPath[0])
|
||||
|
||||
db := rc.Query("database")
|
||||
biz.NotEmpty(db, "database不能为空")
|
||||
ctx := context.TODO()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/redis/api/form"
|
||||
"mayfly-go/internal/redis/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
@@ -14,8 +16,11 @@ func (r *Redis) RunCmd(rc *req.Ctx) {
|
||||
biz.IsTrue(len(cmdReq.Cmd) > 0, "redis命令不能为空")
|
||||
|
||||
redisConn := r.getRedisConn(rc)
|
||||
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.GetLoginAccount().Id, redisConn.Info.TagPath...), "%s")
|
||||
rc.ReqParam = collx.Kvs("redis", redisConn.Info, "cmd", cmdReq.Cmd)
|
||||
|
||||
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, redisConn.Info.TagPath[0])
|
||||
|
||||
res, err := r.RedisApp.RunCmd(rc.MetaCtx, redisConn, runCmdParam)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
|
||||
24
server/internal/tag/api/resource_op_log.go
Normal file
24
server/internal/tag/api/resource_op_log.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/application"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
)
|
||||
|
||||
type ResourceOpLog struct {
|
||||
ResourceOpLogApp application.ResourceOpLog `inject:""`
|
||||
}
|
||||
|
||||
func (r *ResourceOpLog) PageAccountOpLog(rc *req.Ctx) {
|
||||
cond := new(entity.ResourceOpLog)
|
||||
cond.ResourceCode = rc.Query("resourceCode")
|
||||
cond.ResourceType = int8(rc.QueryInt("resourceType"))
|
||||
cond.CreatorId = rc.GetLoginAccount().Id
|
||||
|
||||
var rols []*entity.ResourceOpLog
|
||||
res, err := r.ResourceOpLogApp.PageQuery(cond, rc.GetPageParam(), &rols)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
)
|
||||
|
||||
type TagTree struct {
|
||||
TagTreeApp application.TagTree `inject:""`
|
||||
TagTreeApp application.TagTree `inject:""`
|
||||
TagTreeRelateApp application.TagTreeRelate `inject:""`
|
||||
}
|
||||
|
||||
func (p *TagTree) GetTagTree(rc *req.Ctx) {
|
||||
@@ -123,3 +124,8 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
|
||||
"mongo": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, tagPath)),
|
||||
}
|
||||
}
|
||||
|
||||
// 获取关联的标签id
|
||||
func (p *TagTree) GetRelateTagIds(rc *req.Ctx) {
|
||||
rc.ResData = p.TagTreeRelateApp.GetTagPathsByRelate(entity.TagRelateType(rc.PathParamInt("relateType")), uint64(rc.PathParamInt("relateId")))
|
||||
}
|
||||
|
||||
@@ -10,22 +10,29 @@ import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type Team struct {
|
||||
TeamApp application.Team `inject:""`
|
||||
TagTreeApp application.TagTree `inject:""`
|
||||
AccountApp sys_applicaiton.Account `inject:""`
|
||||
TeamApp application.Team `inject:""`
|
||||
TagTreeApp application.TagTree `inject:""`
|
||||
TagTreeRelateApp application.TagTreeRelate `inject:""`
|
||||
AccountApp sys_applicaiton.Account `inject:""`
|
||||
}
|
||||
|
||||
func (p *Team) GetTeams(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage(rc, new(entity.TeamQuery))
|
||||
teams := &[]entity.Team{}
|
||||
res, err := p.TeamApp.GetPageList(queryCond, page, teams)
|
||||
var teams []*vo.Team
|
||||
res, err := p.TeamApp.GetPageList(queryCond, page, &teams)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
p.TagTreeRelateApp.FillTagInfo(entity.TagRelateTypeTeam, collx.ArrayMap(teams, func(mvo *vo.Team) entity.IRelateTag {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -89,8 +96,3 @@ func (p *Team) DelTeamMember(rc *req.Ctx) {
|
||||
|
||||
p.TeamApp.DeleteMember(rc.MetaCtx, uint64(tid), uint64(aid))
|
||||
}
|
||||
|
||||
// 获取团队关联的标签id
|
||||
func (p *Team) GetTagIds(rc *req.Ctx) {
|
||||
rc.ResData = p.TeamApp.ListTagIds(uint64(rc.PathParamInt("id")))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
package vo
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Team struct {
|
||||
model.Model
|
||||
entity.RelateTags // 标签信息
|
||||
|
||||
Name string `json:"name"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
}
|
||||
|
||||
func (t *Team) GetRelateId() uint64 {
|
||||
return t.Id
|
||||
}
|
||||
|
||||
// 团队成员信息
|
||||
type TeamMember struct {
|
||||
|
||||
@@ -8,4 +8,10 @@ func InitIoc() {
|
||||
ioc.Register(new(tagTreeAppImpl), ioc.WithComponentName("TagTreeApp"))
|
||||
ioc.Register(new(teamAppImpl), ioc.WithComponentName("TeamApp"))
|
||||
ioc.Register(new(resourceAuthCertAppImpl), ioc.WithComponentName("ResourceAuthCertApp"))
|
||||
ioc.Register(new(resourceOpLogAppImpl), ioc.WithComponentName("ResourceOpLogApp"))
|
||||
ioc.Register(new(tagTreeRelateAppImpl), ioc.WithComponentName("TagTreeRelateApp"))
|
||||
}
|
||||
|
||||
func GetResourceOpLogApp() ResourceOpLog {
|
||||
return ioc.Get[ResourceOpLog]("ResourceOpLogApp")
|
||||
}
|
||||
|
||||
58
server/internal/tag/application/resource_op_log.go
Normal file
58
server/internal/tag/application/resource_op_log.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ResourceOpLog interface {
|
||||
base.App[*entity.ResourceOpLog]
|
||||
|
||||
// AddResourceOpLog 新增资源操作记录
|
||||
AddResourceOpLog(ctx context.Context, codePath string) error
|
||||
}
|
||||
|
||||
type resourceOpLogAppImpl struct {
|
||||
base.AppImpl[*entity.ResourceOpLog, repository.ResourceOpLog]
|
||||
|
||||
tagTreeApp TagTree `inject:"TagTreeApp"`
|
||||
}
|
||||
|
||||
var _ (ResourceOpLog) = (*resourceOpLogAppImpl)(nil)
|
||||
|
||||
// 注入ResourceOpLogRepo
|
||||
func (rol *resourceOpLogAppImpl) InjectResourceOpLogRepo(resourceOpLogRepo repository.ResourceOpLog) {
|
||||
rol.Repo = resourceOpLogRepo
|
||||
}
|
||||
|
||||
func (rol *resourceOpLogAppImpl) AddResourceOpLog(ctx context.Context, codePath string) error {
|
||||
loginAccount := contextx.GetLoginAccount(ctx)
|
||||
if loginAccount == nil {
|
||||
return errorx.NewBiz("当前上下文不存在登录信息")
|
||||
}
|
||||
|
||||
var logs []*entity.ResourceOpLog
|
||||
if err := rol.ListByWheres(collx.Kvs("create_time > ?", time.Now().Add(-5*time.Minute), "creator_id = ?", loginAccount.Id, "code_path = ?", codePath), &logs); err != nil {
|
||||
return err
|
||||
}
|
||||
// 指定时间内多次操作则不记录
|
||||
if len(logs) > 0 {
|
||||
return nil
|
||||
}
|
||||
tagTree := &entity.TagTree{CodePath: codePath}
|
||||
if err := rol.tagTreeApp.GetBy(tagTree); err != nil {
|
||||
return errorx.NewBiz("资源不存在")
|
||||
}
|
||||
|
||||
return rol.Save(ctx, &entity.ResourceOpLog{
|
||||
ResourceCode: tagTree.Code,
|
||||
ResourceType: int8(tagTree.Type),
|
||||
CodePath: tagTree.CodePath,
|
||||
})
|
||||
}
|
||||
@@ -99,7 +99,7 @@ type TagTree interface {
|
||||
type tagTreeAppImpl struct {
|
||||
base.AppImpl[*entity.TagTree, repository.TagTree]
|
||||
|
||||
tagTreeTeamRepo repository.TagTreeTeam `inject:"TagTreeTeamRepo"`
|
||||
tagTreeRelateApp TagTreeRelate `inject:"TagTreeRelateApp"`
|
||||
}
|
||||
|
||||
// 注入TagTreeRepo
|
||||
@@ -450,7 +450,7 @@ func (p *tagTreeAppImpl) ListTagPathByTypeAndCode(resourceType int8, resourceCod
|
||||
}
|
||||
|
||||
func (p *tagTreeAppImpl) ListTagByAccountId(accountId uint64) []string {
|
||||
return p.tagTreeTeamRepo.SelectTagPathsByAccountId(accountId)
|
||||
return p.tagTreeRelateApp.GetTagPathsByAccountId(accountId)
|
||||
}
|
||||
|
||||
func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath ...string) error {
|
||||
@@ -486,7 +486,7 @@ func (p *tagTreeAppImpl) FillTagInfo(resourceTagType entity.TagType, resources .
|
||||
|
||||
for _, tr := range tagResources {
|
||||
// 赋值标签信息
|
||||
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{CodePath: tr.GetTagPath()})
|
||||
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Id, CodePath: tr.GetTagPath()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,6 +552,8 @@ func (p *tagTreeAppImpl) deleteByIds(ctx context.Context, tagIds []uint64) error
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除team关联的标签
|
||||
return p.tagTreeTeamRepo.DeleteByWheres(ctx, collx.M{"tag_id in ?": tagIds})
|
||||
// 删除与标签有关联信息的记录(如团队关联的标签等)
|
||||
return p.tagTreeRelateApp.DeleteByWheres(ctx, collx.M{
|
||||
"tag_id in ?": tagIds,
|
||||
})
|
||||
}
|
||||
|
||||
130
server/internal/tag/application/tag_tree_relate.go
Normal file
130
server/internal/tag/application/tag_tree_relate.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
)
|
||||
|
||||
type TagTreeRelate interface {
|
||||
base.App[*entity.TagTreeRelate]
|
||||
|
||||
// RelateTag 关联标签
|
||||
RelateTag(ctx context.Context, relateType entity.TagRelateType, relateId uint64, tagCodePaths ...string) error
|
||||
|
||||
// GetRelateIds 根据标签路径获取对应关联的id
|
||||
GetRelateIds(relateType entity.TagRelateType, tagPaths ...string) ([]uint64, error)
|
||||
|
||||
// GetTagPathsByAccountId 根据账号id获取该账号可操作的标签code路径
|
||||
GetTagPathsByAccountId(accountId uint64) []string
|
||||
|
||||
// GetTagPathsByRelate 根据关联信息获取关联的标签codePaths
|
||||
GetTagPathsByRelate(relateType entity.TagRelateType, relateId uint64) []string
|
||||
|
||||
// FillTagInfo 填充关联的标签信息
|
||||
FillTagInfo(relateType entity.TagRelateType, relates ...entity.IRelateTag)
|
||||
}
|
||||
|
||||
type tagTreeRelateAppImpl struct {
|
||||
base.AppImpl[*entity.TagTreeRelate, repository.TagTreeRelate]
|
||||
|
||||
tagTreeRelateRepo repository.TagTreeRelate `inject:"TagTreeRelateRepo"`
|
||||
|
||||
tagTreeApp TagTree `inject:"TagTreeApp"`
|
||||
}
|
||||
|
||||
var _ (TagTreeRelate) = (*tagTreeRelateAppImpl)(nil)
|
||||
|
||||
// 注入TagTreeRelateRepo
|
||||
func (p *tagTreeRelateAppImpl) InjectTagTreeRelateRepo(tagTreeRelateRepo repository.TagTreeRelate) {
|
||||
p.Repo = tagTreeRelateRepo
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateAppImpl) RelateTag(ctx context.Context, relateType entity.TagRelateType, relateId uint64, tagCodePaths ...string) error {
|
||||
var tags []*entity.TagTree
|
||||
tr.tagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePaths: tagCodePaths}, &tags)
|
||||
if len(tags) != len(tagCodePaths) {
|
||||
return errorx.NewBiz("存在错误标签路径")
|
||||
}
|
||||
|
||||
var oldRelates []*entity.TagTreeRelate
|
||||
tr.ListByCond(&entity.TagTreeRelate{RelateType: relateType, RelateId: relateId}, &oldRelates)
|
||||
oldTagIds := collx.ArrayMap[*entity.TagTreeRelate, uint64](oldRelates, func(val *entity.TagTreeRelate) uint64 {
|
||||
return val.TagId
|
||||
})
|
||||
newTagIds := collx.ArrayMap[*entity.TagTree, uint64](tags, func(val *entity.TagTree) uint64 {
|
||||
return val.Id
|
||||
})
|
||||
addTagIds, delTagIds, _ := collx.ArrayCompare(newTagIds, oldTagIds)
|
||||
|
||||
if len(addTagIds) > 0 {
|
||||
trs := make([]*entity.TagTreeRelate, 0)
|
||||
for _, tagId := range addTagIds {
|
||||
trs = append(trs, &entity.TagTreeRelate{
|
||||
TagId: tagId,
|
||||
RelateType: relateType,
|
||||
RelateId: relateId,
|
||||
})
|
||||
}
|
||||
if err := tr.BatchInsert(ctx, trs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(delTagIds) > 0 {
|
||||
if err := tr.DeleteByWheres(ctx, collx.Kvs("relate_type=?", relateType, "relate_id=?", relateId, "tag_id in ?", delTagIds)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateAppImpl) GetRelateIds(relateType entity.TagRelateType, tagPaths ...string) ([]uint64, error) {
|
||||
poisibleTagPaths := make([]string, 0)
|
||||
for _, tagPath := range tagPaths {
|
||||
// 追加可能关联的标签路径,如tagPath = tag1/tag2/1|xxx/,需要获取所有关联的自身及父标签(tag1/ tag1/tag2/ tag1/tag2/1|xxx)
|
||||
poisibleTagPaths = append(poisibleTagPaths, entity.GetAllCodePath(tagPath)...)
|
||||
}
|
||||
return tr.tagTreeRelateRepo.SelectRelateIdsByTagPaths(relateType, poisibleTagPaths...)
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateAppImpl) GetTagPathsByAccountId(accountId uint64) []string {
|
||||
return tr.tagTreeRelateRepo.SelectTagPathsByAccountId(accountId)
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateAppImpl) GetTagPathsByRelate(relateType entity.TagRelateType, relateId uint64) []string {
|
||||
return tr.tagTreeRelateRepo.SelectTagPathsByRelate(relateType, relateId)
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateAppImpl) FillTagInfo(relateType entity.TagRelateType, relates ...entity.IRelateTag) {
|
||||
if len(relates) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 关联id -> 关联信息
|
||||
relateIds2Relate := collx.ArrayToMap(relates, func(rt entity.IRelateTag) uint64 {
|
||||
return rt.GetRelateId()
|
||||
})
|
||||
|
||||
var relateTags []*entity.TagTreeRelate
|
||||
tr.ListByWheres(collx.Kvs("relate_type=?", relateType, "relate_id in ?", collx.MapKeys(relateIds2Relate)), &relateTags)
|
||||
|
||||
tagIds := collx.ArrayMap(relateTags, func(rt *entity.TagTreeRelate) uint64 {
|
||||
return rt.TagId
|
||||
})
|
||||
var tags []*entity.TagTree
|
||||
tr.tagTreeApp.GetByIdIn(&tags, tagIds)
|
||||
|
||||
tagId2Tag := collx.ArrayToMap(tags, func(t *entity.TagTree) uint64 {
|
||||
return t.Id
|
||||
})
|
||||
for _, rt := range relateTags {
|
||||
// 赋值标签信息
|
||||
tag := tagId2Tag[rt.TagId]
|
||||
relateIds2Relate[rt.RelateId].SetTagInfo(entity.ResourceTag{CodePath: tag.CodePath, TagId: tag.Id})
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -20,7 +19,7 @@ type SaveTeamParam struct {
|
||||
Name string `json:"name" binding:"required"` // 名称
|
||||
Remark string `json:"remark"` // 备注说明
|
||||
|
||||
Tags []uint64 `json:"tags"` // 关联标签信息
|
||||
CodePaths []string `json:"codePaths"` // 关联标签信息
|
||||
}
|
||||
|
||||
type Team interface {
|
||||
@@ -42,17 +41,14 @@ type Team interface {
|
||||
|
||||
IsExistMember(teamId, accounId uint64) bool
|
||||
|
||||
//--------------- 关联项目相关接口 ---------------
|
||||
|
||||
ListTagIds(teamId uint64) []uint64
|
||||
|
||||
DeleteTag(tx context.Context, teamId, tagId uint64) error
|
||||
}
|
||||
|
||||
type teamAppImpl struct {
|
||||
teamRepo repository.Team `inject:"TeamRepo"`
|
||||
teamMemberRepo repository.TeamMember `inject:"TeamMemberRepo"`
|
||||
tagTreeTeamRepo repository.TagTreeTeam `inject:"TagTreeTeamRepo"`
|
||||
teamRepo repository.Team `inject:"TeamRepo"`
|
||||
teamMemberRepo repository.TeamMember `inject:"TeamMemberRepo"`
|
||||
|
||||
tagTreeRelateApp TagTreeRelate `inject:"TagTreeRelateApp"`
|
||||
}
|
||||
|
||||
func (p *teamAppImpl) GetPageList(condition *entity.TeamQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
@@ -89,35 +85,7 @@ func (p *teamAppImpl) Save(ctx context.Context, saveParam *SaveTeamParam) error
|
||||
}
|
||||
|
||||
// 保存团队关联的标签信息
|
||||
teamId := team.Id
|
||||
var addIds, delIds []uint64
|
||||
if saveParam.Id == 0 {
|
||||
addIds = saveParam.Tags
|
||||
} else {
|
||||
// 将[]uint64转为[]any
|
||||
oIds := p.ListTagIds(team.Id)
|
||||
// 比较新旧两合集
|
||||
addIds, delIds, _ = collx.ArrayCompare(saveParam.Tags, oIds)
|
||||
}
|
||||
|
||||
addTeamTags := make([]*entity.TagTreeTeam, 0)
|
||||
for _, v := range addIds {
|
||||
ptt := &entity.TagTreeTeam{TeamId: teamId, TagId: v}
|
||||
addTeamTags = append(addTeamTags, ptt)
|
||||
}
|
||||
if len(addTeamTags) > 0 {
|
||||
logx.DebugfContext(ctx, "团队[%s]新增关联的标签信息: [%v]", team.Name, addTeamTags)
|
||||
p.tagTreeTeamRepo.BatchInsert(ctx, addTeamTags)
|
||||
}
|
||||
|
||||
for _, v := range delIds {
|
||||
p.DeleteTag(ctx, teamId, v)
|
||||
}
|
||||
if len(delIds) > 0 {
|
||||
logx.DebugfContext(ctx, "团队[%s]删除关联的标签信息: [%v]", team.Name, delIds)
|
||||
}
|
||||
|
||||
return nil
|
||||
return p.tagTreeRelateApp.RelateTag(ctx, entity.TagRelateTypeTeam, team.Id, saveParam.CodePaths...)
|
||||
}
|
||||
|
||||
func (p *teamAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
@@ -129,7 +97,7 @@ func (p *teamAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||
return p.teamMemberRepo.DeleteByCondWithDb(ctx, db, &entity.TeamMember{TeamId: id})
|
||||
},
|
||||
func(db *gorm.DB) error {
|
||||
return p.tagTreeTeamRepo.DeleteByCondWithDb(ctx, db, &entity.TagTreeTeam{TeamId: id})
|
||||
return p.tagTreeRelateApp.DeleteByCondWithDb(ctx, db, &entity.TagTreeRelate{RelateType: entity.TagRelateTypeTeam, RelateId: id})
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -158,17 +126,7 @@ func (p *teamAppImpl) IsExistMember(teamId, accounId uint64) bool {
|
||||
|
||||
//--------------- 标签相关接口 ---------------
|
||||
|
||||
func (p *teamAppImpl) ListTagIds(teamId uint64) []uint64 {
|
||||
tags := &[]entity.TagTreeTeam{}
|
||||
p.tagTreeTeamRepo.ListByCondOrder(&entity.TagTreeTeam{TeamId: teamId}, tags)
|
||||
ids := make([]uint64, 0)
|
||||
for _, v := range *tags {
|
||||
ids = append(ids, v.TagId)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// 删除关联项目信息
|
||||
// 删除关联标签信息
|
||||
func (p *teamAppImpl) DeleteTag(ctx context.Context, teamId, tagId uint64) error {
|
||||
return p.tagTreeTeamRepo.DeleteByCond(ctx, &entity.TagTreeTeam{TeamId: teamId, TagId: tagId})
|
||||
return p.tagTreeRelateApp.DeleteByCond(ctx, &entity.TagTreeRelate{RelateType: entity.TagRelateTypeTeam, RelateId: teamId, TagId: tagId})
|
||||
}
|
||||
|
||||
12
server/internal/tag/domain/entity/resource_op_log.go
Normal file
12
server/internal/tag/domain/entity/resource_op_log.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
// 资源操作日志记录
|
||||
type ResourceOpLog struct {
|
||||
model.CreateModel
|
||||
|
||||
CodePath string `json:"codePath"` // 标签路径
|
||||
ResourceCode string `json:"resourceCode"` // 资源编号
|
||||
ResourceType int8 `json:"relateType"` // 资源类型
|
||||
}
|
||||
@@ -89,11 +89,13 @@ type ITagResource interface {
|
||||
|
||||
// 资源关联的标签信息
|
||||
type ResourceTag struct {
|
||||
TagId uint64 `json:"tagId" gorm:"-"`
|
||||
CodePath string `json:"codePath" gorm:"-"` // 标签路径
|
||||
}
|
||||
|
||||
func (r *ResourceTag) SetTagInfo(rt ResourceTag) {
|
||||
r.CodePath = rt.CodePath
|
||||
r.TagId = rt.TagId
|
||||
}
|
||||
|
||||
// 资源标签列表
|
||||
|
||||
40
server/internal/tag/domain/entity/tag_tree_relate.go
Normal file
40
server/internal/tag/domain/entity/tag_tree_relate.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
// 与标签树有关联关系的实体
|
||||
type TagTreeRelate struct {
|
||||
model.Model
|
||||
|
||||
TagId uint64 `json:"tagId"`
|
||||
RelateId uint64 `json:"relateId"` // 关联的id
|
||||
RelateType TagRelateType `json:"relateType"` // 关联的类型
|
||||
}
|
||||
|
||||
type TagRelateType int8
|
||||
|
||||
const (
|
||||
TagRelateTypeTeam TagRelateType = 1 // 关联团队
|
||||
TagRelateTypeMachineCmd TagRelateType = 2 // 关联机器命令配置
|
||||
)
|
||||
|
||||
// 关联标签信息,如果要实现填充关联标签信息,则结构体需要实现该接口
|
||||
type IRelateTag interface {
|
||||
// 获取关联id
|
||||
GetRelateId() uint64
|
||||
|
||||
// 赋值标签路径
|
||||
SetTagInfo(tag ResourceTag)
|
||||
}
|
||||
|
||||
// 关联的标签信息
|
||||
type RelateTags struct {
|
||||
Tags []ResourceTag `json:"tags" gorm:"-"` // 标签路径
|
||||
}
|
||||
|
||||
func (r *RelateTags) SetTagInfo(rt ResourceTag) {
|
||||
if r.Tags == nil {
|
||||
r.Tags = make([]ResourceTag, 0)
|
||||
}
|
||||
r.Tags = append(r.Tags, rt)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
// 标签树与团队关联信息
|
||||
type TagTreeTeam struct {
|
||||
model.Model
|
||||
|
||||
TagId uint64 `json:"tagId"`
|
||||
TeamId uint64 `json:"teamId"`
|
||||
}
|
||||
10
server/internal/tag/domain/repository/resource_op_log.go
Normal file
10
server/internal/tag/domain/repository/resource_op_log.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type ResourceOpLog interface {
|
||||
base.Repo[*entity.ResourceOpLog]
|
||||
}
|
||||
19
server/internal/tag/domain/repository/tag_tree_relate.go
Normal file
19
server/internal/tag/domain/repository/tag_tree_relate.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type TagTreeRelate interface {
|
||||
base.Repo[*entity.TagTreeRelate]
|
||||
|
||||
// SelectRelateIdsByTagPaths 根据标签路径查询相关联的id
|
||||
SelectRelateIdsByTagPaths(relateType entity.TagRelateType, tagPaths ...string) ([]uint64, error)
|
||||
|
||||
// SelectTagPathsByAccountId 根据账号id获取该账号可访问操作的标签codePaths(该方法调用较频繁,故不使用下列方法获取)
|
||||
SelectTagPathsByAccountId(accountId uint64) []string
|
||||
|
||||
// SelectTagPathsByRelate 根据关联信息查询对应的关联的标签路径
|
||||
SelectTagPathsByRelate(relateType entity.TagRelateType, relateId uint64) []string
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type TagTreeTeam interface {
|
||||
base.Repo[*entity.TagTreeTeam]
|
||||
|
||||
SelectTagPathsByAccountId(accountId uint64) []string
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import (
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(newTagTreeRepo(), ioc.WithComponentName("TagTreeRepo"))
|
||||
ioc.Register(newTagTreeTeamRepo(), ioc.WithComponentName("TagTreeTeamRepo"))
|
||||
ioc.Register(newTeamRepo(), ioc.WithComponentName("TeamRepo"))
|
||||
ioc.Register(newTeamMemberRepo(), ioc.WithComponentName("TeamMemberRepo"))
|
||||
ioc.Register(newResourceAuthCertRepoImpl(), ioc.WithComponentName("ResourceAuthCertRepo"))
|
||||
ioc.Register(newResourceOpLogRepo(), ioc.WithComponentName("ResourceOpLogRepo"))
|
||||
ioc.Register(newTagTreeRelateRepo(), ioc.WithComponentName("TagTreeRelateRepo"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type resourceOpLogRepoImpl struct {
|
||||
base.RepoImpl[*entity.ResourceOpLog]
|
||||
}
|
||||
|
||||
func newResourceOpLogRepo() repository.ResourceOpLog {
|
||||
return &resourceOpLogRepoImpl{base.RepoImpl[*entity.ResourceOpLog]{M: new(entity.ResourceOpLog)}}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
)
|
||||
|
||||
type tagTreeRelateRepoImpl struct {
|
||||
base.RepoImpl[*entity.TagTreeRelate]
|
||||
}
|
||||
|
||||
func newTagTreeRelateRepo() repository.TagTreeRelate {
|
||||
return &tagTreeRelateRepoImpl{base.RepoImpl[*entity.TagTreeRelate]{M: new(entity.TagTreeRelate)}}
|
||||
}
|
||||
|
||||
// SelectRelateIdsByTagPaths 根据标签路径查询相关联的id
|
||||
func (tr *tagTreeRelateRepoImpl) SelectRelateIdsByTagPaths(relateType entity.TagRelateType, tagPaths ...string) ([]uint64, error) {
|
||||
var res []uint64
|
||||
sql := `
|
||||
SELECT
|
||||
t1.relate_id
|
||||
FROM
|
||||
t_tag_tree_relate t1
|
||||
JOIN t_tag_tree t ON
|
||||
t.id = t1.tag_id
|
||||
WHERE
|
||||
t1.relate_type = ?
|
||||
AND t.code_path in ?
|
||||
AND t.is_deleted = 0
|
||||
AND t1.is_deleted = 0
|
||||
ORDER BY
|
||||
t.code_path
|
||||
`
|
||||
if err := gormx.GetListBySql2Model(sql, &res, relateType, tagPaths); err != nil {
|
||||
return res, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (tr *tagTreeRelateRepoImpl) SelectTagPathsByAccountId(accountId uint64) []string {
|
||||
var res []string
|
||||
sql := `
|
||||
SELECT
|
||||
DISTINCT(t.code_path)
|
||||
FROM
|
||||
t_tag_tree_relate t1
|
||||
JOIN t_team_member t2 ON
|
||||
t1.relate_id = t2.team_id
|
||||
JOIN t_tag_tree t ON
|
||||
t.id = t1.tag_id
|
||||
WHERE
|
||||
t1.relate_type = ?
|
||||
AND t2.account_id = ?
|
||||
AND t1.is_deleted = 0
|
||||
AND t2.is_deleted = 0
|
||||
AND t.is_deleted = 0
|
||||
ORDER BY
|
||||
t.code_path
|
||||
`
|
||||
gormx.GetListBySql2Model(sql, &res, entity.TagRelateTypeTeam, accountId)
|
||||
return res
|
||||
}
|
||||
|
||||
// SelectTagPathsByRelate 根据关联信息查询对应的关联的标签路径
|
||||
func (tr *tagTreeRelateRepoImpl) SelectTagPathsByRelate(relateType entity.TagRelateType, relateId uint64) []string {
|
||||
var res []string
|
||||
sql := `
|
||||
SELECT
|
||||
DISTINCT(t.code_path)
|
||||
FROM
|
||||
t_tag_tree_relate t1
|
||||
JOIN t_tag_tree t ON
|
||||
t.id = t1.tag_id
|
||||
WHERE
|
||||
t1.relate_id = ?
|
||||
AND t1.relate_type = ?
|
||||
AND t.is_deleted = 0
|
||||
AND t1.is_deleted = 0
|
||||
ORDER BY
|
||||
t.code_path
|
||||
`
|
||||
gormx.GetListBySql2Model(sql, &res, relateId, relateType)
|
||||
return res
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/internal/tag/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
)
|
||||
|
||||
type tagTreeTeamRepoImpl struct {
|
||||
base.RepoImpl[*entity.TagTreeTeam]
|
||||
}
|
||||
|
||||
func newTagTreeTeamRepo() repository.TagTreeTeam {
|
||||
return &tagTreeTeamRepoImpl{base.RepoImpl[*entity.TagTreeTeam]{M: new(entity.TagTreeTeam)}}
|
||||
}
|
||||
|
||||
func (p *tagTreeTeamRepoImpl) SelectTagPathsByAccountId(accountId uint64) []string {
|
||||
var res []string
|
||||
sql := `
|
||||
SELECT
|
||||
DISTINCT(t.code_path)
|
||||
FROM
|
||||
t_tag_tree_team t1
|
||||
JOIN t_team_member t2 ON
|
||||
t1.team_id = t2.team_id
|
||||
JOIN t_tag_tree t ON
|
||||
t.id = t1.tag_id
|
||||
WHERE
|
||||
t2.account_id = ?
|
||||
AND t1.is_deleted = 0
|
||||
AND t2.is_deleted = 0
|
||||
AND t.is_deleted = 0
|
||||
ORDER BY
|
||||
t.code_path
|
||||
`
|
||||
gormx.GetListBySql2Model(sql, &res, accountId)
|
||||
return res
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/event"
|
||||
"mayfly-go/internal/tag/application"
|
||||
"mayfly-go/internal/tag/infrastructure/persistence"
|
||||
"mayfly-go/internal/tag/router"
|
||||
"mayfly-go/pkg/eventbus"
|
||||
"mayfly-go/pkg/global"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -13,4 +17,14 @@ func init() {
|
||||
application.InitIoc()
|
||||
})
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
initialize.AddInitFunc(Init)
|
||||
}
|
||||
|
||||
func Init() {
|
||||
|
||||
global.EventBus.SubscribeAsync(event.EventTopicResourceOp, "ResourceOpLogApp", func(ctx context.Context, event *eventbus.Event) error {
|
||||
codePath := event.Val.(string)
|
||||
return application.GetResourceOpLogApp().AddResourceOpLog(ctx, codePath)
|
||||
}, false)
|
||||
|
||||
}
|
||||
|
||||
24
server/internal/tag/router/resource_op_log.go
Normal file
24
server/internal/tag/router/resource_op_log.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/tag/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitResourceOpLogRouter(router *gin.RouterGroup) {
|
||||
m := new(api.ResourceOpLog)
|
||||
biz.ErrIsNil(ioc.Inject(m))
|
||||
|
||||
resourceOpLog := router.Group("/resource-op-logs")
|
||||
{
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("/account", m.PageAccountOpLog),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(resourceOpLog, reqs[:])
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@ func Init(router *gin.RouterGroup) {
|
||||
InitTagTreeRouter(router)
|
||||
InitTeamRouter(router)
|
||||
InitResourceAuthCertRouter(router)
|
||||
InitResourceOpLogRouter(router)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
|
||||
req.NewGet("/resources/:rtype/tag-paths", m.TagResources),
|
||||
|
||||
req.NewGet("/resources/count", m.CountTagResource),
|
||||
|
||||
// 获取关联的标签id列表
|
||||
req.NewGet("/relate/:relateType/:relateId", m.GetRelateTagIds),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(tagTree, reqs[:])
|
||||
|
||||
@@ -29,9 +29,6 @@ func InitTeamRouter(router *gin.RouterGroup) {
|
||||
req.NewPost("/:id/members", m.SaveTeamMember).Log(req.NewLogSave("团队-新增成员")).RequiredPermissionCode("team:member:save"),
|
||||
|
||||
req.NewDelete("/:id/members/:accountId", m.DelTeamMember).Log(req.NewLogSave("团队-删除成员")).RequiredPermissionCode("team:member:del"),
|
||||
|
||||
// 获取团队关联的标签id列表
|
||||
req.NewGet("/:id/tags", m.GetTagIds),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(team, reqs[:])
|
||||
|
||||
@@ -89,9 +89,6 @@ func T2022() *gormigrate.Migration {
|
||||
if err := tx.AutoMigrate(&entity7.TagTree{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.AutoMigrate(&entity7.TagTreeTeam{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.AutoMigrate(&entity7.Team{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -138,9 +138,18 @@ type Map[K comparable, V any] map[K]V
|
||||
|
||||
func (m *Map[K, V]) Scan(value any) error {
|
||||
return json.Unmarshal(value.([]byte), m)
|
||||
|
||||
}
|
||||
|
||||
func (m Map[K, V]) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
type Slice[T int | string | Map[string, any]] []T
|
||||
|
||||
func (s *Slice[T]) Scan(value any) error {
|
||||
return json.Unmarshal(value.([]byte), s)
|
||||
}
|
||||
|
||||
func (s Slice[T]) Value() (driver.Value, error) {
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
@@ -503,6 +503,25 @@ CREATE TABLE `t_machine_term_op` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器终端操作记录表';
|
||||
|
||||
DROP TABLE IF EXISTS `t_machine_cmd_conf`;
|
||||
CREATE TABLE `t_machine_cmd_conf` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '名称',
|
||||
`cmds` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '命令配置',
|
||||
`status` tinyint(4) DEFAULT NULL COMMENT '状态',
|
||||
`stratege` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '策略',
|
||||
`remark` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint(20) NOT NULL,
|
||||
`creator` varchar(36) COLLATE utf8_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint(20) NOT NULL,
|
||||
`modifier` varchar(36) COLLATE utf8_bin NOT NULL,
|
||||
`is_deleted` tinyint(4) DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='机器命令配置';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_mongo
|
||||
-- ----------------------------
|
||||
@@ -826,7 +845,9 @@ INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `we
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196723, 1709194669, 2, 1, '启停', 'db:transfer:status', 1709196723, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:04', '2024-02-29 16:52:04', 'SmLcpu6c/hGiLN1VT/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196737, 1709194669, 2, 1, '日志', 'db:transfer:log', 1709196737, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:17', '2024-02-29 16:52:17', 'SmLcpu6c/CZhNIbWg/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196755, 1709194669, 2, 1, '运行', 'db:transfer:run', 1709196755, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:36', '2024-02-29 16:52:36', 'SmLcpu6c/b6yHt6V2/', 0, NULL);
|
||||
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714032002, 1713875842, '12sSjal1/UnWIUhW0/0tJwC3Gf/', 2, 1, '命令配置-删除', 'cmdconf:del', 1714032002, 'null', 1, 'admin', 1, 'admin', '2024-04-25 16:00:02', '2024-04-25 16:00:02', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714031981, 1713875842, '12sSjal1/UnWIUhW0/tEzIKecl/', 2, 1, '命令配置-保存', 'cmdconf:save', 1714031981, 'null', 1, 'admin', 1, 'admin', '2024-04-25 15:59:41', '2024-04-25 15:59:41', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1713875842, 2, '12sSjal1/UnWIUhW0/', 1, 1, '安全配置', 'security', 1713875842, '{"component":"ops/machine/security/SecurityConfList","icon":"Setting","isKeepAlive":true,"routeName":"SecurityConfList"}', 1, 'admin', 1, 'admin', '2024-04-23 20:37:22', '2024-04-23 20:37:22', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -913,31 +934,29 @@ BEGIN;
|
||||
INSERT INTO `t_tag_tree` VALUES (1, -1, 'default', 'default/', '默认', '默认标签', '2022-10-26 20:04:19', 1, 'admin', '2022-10-26 20:04:19', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_tag_tree_team
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_tag_tree_team`;
|
||||
CREATE TABLE `t_tag_tree_team` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tag_id` bigint(20) NOT NULL COMMENT '项目树id',
|
||||
`team_id` bigint(20) NOT NULL COMMENT '团队id',
|
||||
DROP TABLE IF EXISTS `t_tag_tree_relate`;
|
||||
CREATE TABLE `t_tag_tree_relate` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tag_id` bigint NOT NULL COMMENT '标签树id',
|
||||
`relate_id` bigint NOT NULL COMMENT '关联',
|
||||
`relate_type` tinyint NOT NULL COMMENT '关联类型',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint(20) NOT NULL,
|
||||
`creator` varchar(36) NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) COLLATE utf8mb4_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint(20) NOT NULL,
|
||||
`modifier` varchar(36) NOT NULL,
|
||||
`is_deleted` tinyint(8) NOT NULL DEFAULT 0,
|
||||
`modifier_id` bigint NOT NULL,
|
||||
`modifier` varchar(36) COLLATE utf8mb4_bin NOT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tag_id` (`tag_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签树团队关联信息';
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='与标签树有关联关系的表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of t_tag_tree_team
|
||||
-- Records of t_tag_tree_relate
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `t_tag_tree_team` VALUES (1, 1, 1, '2022-10-26 20:04:45', 1, 'admin', '2022-10-26 20:04:45', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_tag_tree_relate` VALUES (1, 1, 1, 1, '2022-10-26 20:04:45', 1, 'admin', '2022-10-26 20:04:45', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -1019,6 +1038,21 @@ CREATE TABLE `t_resource_auth_cert` (
|
||||
KEY `idx_name` (`name`) USING BTREE
|
||||
) COMMENT='资源授权凭证表';
|
||||
|
||||
DROP TABLE IF EXISTS `t_resource_op_log`;
|
||||
CREATE TABLE `t_resource_op_log` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`code_path` varchar(600) NOT NULL COMMENT '资源标签路径',
|
||||
`resource_code` varchar(32) NOT NULL COMMENT '资源编号',
|
||||
`resource_type` tinyint NOT NULL COMMENT '资源类型',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) NOT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_resource_code` (`resource_code`) USING BTREE
|
||||
) ENGINE=InnoDB COMMENT='资源操作记录';
|
||||
|
||||
DROP TABLE IF EXISTS `t_flow_procdef`;
|
||||
-- 工单流程相关表
|
||||
CREATE TABLE `t_flow_procdef` (
|
||||
|
||||
84
server/resources/script/sql/v1.8/v1.8.2.sql
Normal file
84
server/resources/script/sql/v1.8/v1.8.2.sql
Normal file
@@ -0,0 +1,84 @@
|
||||
CREATE TABLE `t_tag_tree_relate` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`tag_id` bigint NOT NULL COMMENT '标签树id',
|
||||
`relate_id` bigint NOT NULL COMMENT '关联',
|
||||
`relate_type` tinyint NOT NULL COMMENT '关联类型',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) COLLATE utf8mb4_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint NOT NULL,
|
||||
`modifier` varchar(36) COLLATE utf8mb4_bin NOT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_tag_id` (`tag_id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='与标签树有关联关系的表';
|
||||
|
||||
|
||||
CREATE TABLE `t_machine_cmd_conf` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '名称',
|
||||
`cmds` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '命令配置',
|
||||
`status` tinyint(4) DEFAULT NULL COMMENT '状态',
|
||||
`stratege` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '策略',
|
||||
`remark` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint(20) NOT NULL,
|
||||
`creator` varchar(36) COLLATE utf8_bin NOT NULL,
|
||||
`update_time` datetime NOT NULL,
|
||||
`modifier_id` bigint(20) NOT NULL,
|
||||
`modifier` varchar(36) COLLATE utf8_bin NOT NULL,
|
||||
`is_deleted` tinyint(4) DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='机器命令配置';
|
||||
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714032002, 1713875842, '12sSjal1/UnWIUhW0/0tJwC3Gf/', 2, 1, '命令配置-删除', 'cmdconf:del', 1714032002, 'null', 1, 'admin', 1, 'admin', '2024-04-25 16:00:02', '2024-04-25 16:00:02', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714031981, 1713875842, '12sSjal1/UnWIUhW0/tEzIKecl/', 2, 1, '命令配置-保存', 'cmdconf:save', 1714031981, 'null', 1, 'admin', 1, 'admin', '2024-04-25 15:59:41', '2024-04-25 15:59:41', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1713875842, 2, '12sSjal1/UnWIUhW0/', 1, 1, '安全配置', 'security', 1713875842, '{"component":"ops/machine/security/SecurityConfList","icon":"Setting","isKeepAlive":true,"routeName":"SecurityConfList"}', 1, 'admin', 1, 'admin', '2024-04-23 20:37:22', '2024-04-23 20:37:22', 0, NULL);
|
||||
|
||||
INSERT
|
||||
INTO
|
||||
t_tag_tree_relate (tag_id,
|
||||
relate_id,
|
||||
relate_type,
|
||||
create_time,
|
||||
creator_id,
|
||||
creator,
|
||||
update_time,
|
||||
modifier_id,
|
||||
modifier,
|
||||
is_deleted )
|
||||
SELECT
|
||||
tt.tag_id ,
|
||||
tt.team_id ,
|
||||
1,
|
||||
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
|
||||
1,
|
||||
'admin',
|
||||
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
|
||||
1,
|
||||
'admin',
|
||||
0
|
||||
FROM
|
||||
`t_tag_tree_team` tt
|
||||
WHERE
|
||||
tt.`is_deleted` = 0;
|
||||
|
||||
DROP TABLE t_tag_tree_team;
|
||||
|
||||
|
||||
CREATE TABLE `t_resource_op_log` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`code_path` varchar(600) NOT NULL COMMENT '资源标签路径',
|
||||
`resource_code` varchar(32) NOT NULL COMMENT '资源编号',
|
||||
`resource_type` tinyint NOT NULL COMMENT '资源类型',
|
||||
`create_time` datetime NOT NULL,
|
||||
`creator_id` bigint NOT NULL,
|
||||
`creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT '0',
|
||||
`delete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_resource_code` (`resource_code`) USING BTREE
|
||||
) ENGINE=InnoDB COMMENT='资源操作记录';
|
||||
Reference in New Issue
Block a user