feat: 使用标签替代项目

This commit is contained in:
meilin.huang
2022-10-26 20:49:29 +08:00
parent 6c197edddd
commit e925a808c4
201 changed files with 3302 additions and 2140 deletions

View File

@@ -1,4 +1,4 @@
export function dateFormat(fmt: string, date: Date) {
export function dateFormat2(fmt: string, date: Date) {
let ret;
const opt = {
"y+": date.getFullYear().toString(), // 年
@@ -19,5 +19,9 @@ export function dateFormat(fmt: string, date: Date) {
}
export function dateStrFormat(fmt: string, dateStr: string) {
return dateFormat(fmt, new Date(dateStr))
return dateFormat2(fmt, new Date(dateStr))
}
export function dateFormat(dateStr: string) {
return dateFormat2('yyyy-MM-dd HH:mm:ss',new Date(dateStr))
}

View File

@@ -1,5 +1,5 @@
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage.ts';
import { dateFormat } from '@/common/utils/date.ts'
import { dateFormat2 } from '@/common/utils/date.ts'
// 页面添加水印效果
const setWatermark = (str: any) => {
@@ -46,7 +46,7 @@ const watermark = {
setTimeout(() => {
const userinfo = getUserInfo4Session()
if (userinfo && getUseWatermark4Session()) {
set(`${userinfo.username} ${dateFormat('yyyy-MM-dd HH:mm:ss', new Date())}`)
set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`)
} else {
del();
}

View File

@@ -14,8 +14,9 @@ export const imports = {
"SyslogList": () => import('@/views/system/syslog/SyslogList.vue'),
"ConfigList": () => import('@/views/system/config/ConfigList.vue'),
// project
"ProjectList": () => import('@/views/ops/project/ProjectList.vue'),
// tag
"TagTreeList": () => import('@/views/ops/tag/TagTreeList.vue'),
"TeamList": () => import('@/views/ops/tag/TeamList.vue'),
// db
"DbList": () => import('@/views/ops/db/DbList.vue'),
"SqlExec": () => import('@/views/ops/db'),

View File

@@ -1,6 +1,6 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/views/layout/index.vue'
import RouterParent from '@/views/layout/routerView/parent.vue';
// import RouterParent from '@/views/layout/routerView/parent.vue';
// 定义动态路由
export const dynamicRoutes = [
@@ -12,119 +12,108 @@ export const dynamicRoutes = [
meta: {
isKeepAlive: true,
},
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
// iframe链接
link: '',
// 是否在菜单栏显示,默认显示
isHide: false,
isKeepAlive: true,
// tag标签是否不可删除
isAffix: true,
// 是否为iframe
isIframe: false,
icon: 'el-icon-s-home',
},
},
{
path: '/sys',
name: 'Resource',
redirect: '/sys/resources',
meta: {
title: '系统管理',
// 资源code用于校验用户是否拥有该资源权限
code: 'sys',
// isKeepAlive: true,
icon: 'el-icon-monitor',
},
children: [
{
path: 'sys/resources',
name: 'ResourceList',
component: () => import('@/views/system/resource'),
meta: {
title: '资源管理',
code: 'resource:list',
isKeepAlive: true,
icon: 'el-icon-menu',
},
},
{
path: 'sys/roles',
name: 'RoleList',
component: () => import('@/views/system/role'),
meta: {
title: '角色管理',
code: 'role:list',
isKeepAlive: true,
icon: 'el-icon-menu',
},
},
{
path: 'sys/accounts',
name: 'ResourceList',
component: () => import('@/views/system/account'),
meta: {
title: '账号管理',
code: 'account:list',
isKeepAlive: true,
icon: 'el-icon-menu',
},
},
],
},
{
path: '/machine',
name: 'Machine',
redirect: '/machine/list',
meta: {
title: '机器管理',
// 资源code用于校验用户是否拥有该资源权限
code: 'machine',
// isKeepAlive: true,
icon: 'el-icon-monitor',
},
children: [
{
path: '/list',
name: 'MachineList',
component: () => import('@/views/ops/machine'),
meta: {
title: '机器列表',
code: 'machine:list',
isKeepAlive: true,
icon: 'el-icon-menu',
},
},
],
},
{
path: '/personal',
name: 'personal',
component: () => import('@/views/personal/index.vue'),
meta: {
title: '个人中心',
isKeepAlive: true,
icon: 'el-icon-user',
},
},
{
path: '/iframes',
name: 'layoutIfameView',
component: RouterParent,
meta: {
title: 'iframe',
link: 'https://gitee.com/lyt-top/vue-next-admin',
isIframe: true,
icon: 'el-icon-menu',
},
},
],
// children: [
// {
// path: '/home',
// name: 'home',
// component: () => import('@/views/home/index.vue'),
// meta: {
// title: '首页',
// // iframe链接
// link: '',
// // 是否在菜单栏显示,默认显示
// isHide: false,
// isKeepAlive: true,
// // tag标签是否不可删除
// isAffix: true,
// // 是否为iframe
// isIframe: false,
// icon: 'el-icon-s-home',
// },
// },
// {
// path: '/sys',
// name: 'Resource',
// redirect: '/sys/resources',
// meta: {
// title: '系统管理',
// // 资源code用于校验用户是否拥有该资源权限
// code: 'sys',
// // isKeepAlive: true,
// icon: 'el-icon-monitor',
// },
// children: [
// {
// path: 'sys/resources',
// name: 'ResourceList',
// component: () => import('@/views/system/resource'),
// meta: {
// title: '资源管理',
// code: 'resource:list',
// isKeepAlive: true,
// icon: 'el-icon-menu',
// },
// },
// {
// path: 'sys/roles',
// name: 'RoleList',
// component: () => import('@/views/system/role'),
// meta: {
// title: '角色管理',
// code: 'role:list',
// isKeepAlive: true,
// icon: 'el-icon-menu',
// },
// },
// {
// path: 'sys/accounts',
// name: 'ResourceList',
// component: () => import('@/views/system/account'),
// meta: {
// title: '账号管理',
// code: 'account:list',
// isKeepAlive: true,
// icon: 'el-icon-menu',
// },
// },
// ],
// },
// {
// path: '/machine',
// name: 'Machine',
// redirect: '/machine/list',
// meta: {
// title: '机器管理',
// // 资源code用于校验用户是否拥有该资源权限
// code: 'machine',
// // isKeepAlive: true,
// icon: 'el-icon-monitor',
// },
// children: [
// {
// path: '/list',
// name: 'MachineList',
// component: () => import('@/views/ops/machine'),
// meta: {
// title: '机器列表',
// code: 'machine:list',
// isKeepAlive: true,
// icon: 'el-icon-menu',
// },
// },
// ],
// },
// {
// path: '/personal',
// name: 'personal',
// component: () => import('@/views/personal/index.vue'),
// meta: {
// title: '个人中心',
// isKeepAlive: true,
// icon: 'el-icon-user',
// },
// },
// ],
},
];
@@ -165,17 +154,6 @@ export const staticRoutes: Array<RouteRecordRaw> = [
titleRename: true,
},
},
{
path: '/machine/terminal-rec',
name: 'machineTerminalRec',
component: () => import('@/views/ops/machine/MachineRec.vue'),
meta: {
// 将路径 'xxx?name=名字' 里的name字段值替换到title里
title: '终端回放 | {name}',
// 是否根据query对标题名进行参数替换即最终显示为终端_机器名
titleRename: true,
},
},
];
// 定义404界面

View File

@@ -43,25 +43,25 @@ export default {
const state = reactive({
topCardItemList: [
{
title: '项目数',
id: 'projectNum',
color: '#FEBB50',
},
{
title: 'Linux机器数',
title: 'Linux机器',
id: 'machineNum',
color: '#F95959',
},
{
title: '数据库总数',
title: '数据库',
id: 'dbNum',
color: '#8595F4',
},
{
title: 'redis总数',
title: 'redis',
id: 'redisNum',
color: '#1abc9c',
},
{
title: 'Mongo',
id: 'mongoNum',
color: '#FEBB50',
},
],
});
@@ -74,7 +74,7 @@ export default {
const initNumCountUp = async () => {
const res: any = await indexApi.getIndexCount.request();
nextTick(() => {
new CountUp('projectNum', res.projectNum).start();
new CountUp('mongoNum', res.mongoNum).start();
new CountUp('machineNum', res.machineNum).start();
new CountUp('dbNum', res.dbNum).start();
new CountUp('redisNum', res.redisNum).start();
@@ -87,8 +87,8 @@ export default {
router.push('/personal');
break;
}
case 'projectNum': {
router.push('/project/projects');
case 'mongoNum': {
router.push('/mongo/mongo-data-operation');
break;
}
case 'machineNum': {

View File

@@ -0,0 +1,81 @@
<template>
<div>
<el-tree-select
@check="changeTag"
style="width: 100%"
v-model="selectTags"
:data="tags"
:render-after-expand="true"
:default-expanded-keys="[selectTags]"
show-checkbox
check-strictly
node-key="id"
:props="{
value: 'id',
label: 'codePath',
children: 'children',
}"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
{{ 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-select>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { tagApi } from '../tag/api';
export default defineComponent({
name: 'TagSelect',
props: {
tagId: {
type: Number,
},
tagPath: {
type: String,
},
},
setup(props: any, { emit }) {
const state = reactive({
tags: [],
// 单选则为id多选为id数组
selectTags: null as any,
});
onMounted(async () => {
if (props.tagId) {
state.selectTags = props.tagId;
}
state.tags = await tagApi.getTagTrees.request(null);
});
const changeTag = (tag: any, checkInfo: any) => {
if (checkInfo.checkedNodes.length > 0) {
emit('update:tagId', tag.id);
emit('update:tagPath', tag.codePath);
emit('changeTag', tag);
} else {
emit('update:tagId', null);
emit('update:tagPath', null);
}
};
return {
...toRefs(state),
changeTag,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -2,17 +2,10 @@
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="envId" label="环境:" required>
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="name" label="别名:" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
@@ -76,6 +69,10 @@
</el-col>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
@@ -108,14 +105,17 @@
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
import { dbApi } from './api';
import { projectApi } from '../project/api.ts';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'DbEdit',
components: {
TagSelect,
},
props: {
visible: {
type: Boolean,
@@ -139,10 +139,14 @@ export default defineComponent({
envs: [],
allDatabases: [] as any,
databaseList: [] as any,
sshTunnelMachineList: [],
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: null as any,
type: null,
name: null,
host: '',
port: 3306,
username: null,
password: null,
@@ -152,6 +156,7 @@ export default defineComponent({
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
@@ -218,7 +223,6 @@ export default defineComponent({
}
state.projects = newValue.projects;
if (newValue.db) {
getEnvs(newValue.db.projectId);
state.form = { ...newValue.db };
// 将数据库名使用空格切割,获取所有数据库列表
state.databaseList = newValue.db.database.split(' ');
@@ -244,22 +248,6 @@ export default defineComponent({
}
};
const getEnvs = async (projectId: any) => {
state.envs = await projectApi.projectEnvs.request({ projectId });
};
const changeProject = (projectId: number) => {
for (let p of state.projects as any) {
if (p.id == projectId) {
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};
const changeEnv = (envId: number) => {
for (let p of state.envs as any) {
if (p.id == envId) {
@@ -272,7 +260,7 @@ export default defineComponent({
const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
state.allDatabases = await dbApi.getAllDatabase.request(reqForm);
ElMessage.success('获取成功, 请选择需要管理操作的数据库')
ElMessage.success('获取成功, 请选择需要管理操作的数据库');
};
const getDbPwd = async () => {
@@ -324,7 +312,6 @@ export default defineComponent({
getDbPwd,
changeDatabase,
getSshTunnelMachines,
changeProject,
changeEnv,
btnOk,
cancel,

View File

@@ -7,8 +7,8 @@
>删除</el-button
>
<div style="float: right">
<el-select @focus="getProjects" v-model="query.projectId" placeholder="请选择项目" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button v-waves type="primary" icon="search" @click="search()" class="ml5">查询</el-button>
</div>
@@ -20,8 +20,7 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="project" label="项目" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="env" label="环境" min-width="100"></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
<template #default="scope">
@@ -42,13 +41,14 @@
size="small"
v-for="db in scope.row.dbs"
:key="db"
style="cursor: pointer; margin-left: 3px; margin-bottom: 3px;"
style="cursor: pointer; margin-left: 3px; margin-bottom: 3px"
>{{ db }}</el-tag
>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
@@ -190,7 +190,7 @@
<el-table-column prop="creator" label="执行人" min-width="60" show-overflow-tooltip> </el-table-column>
<el-table-column prop="createTime" label="执行时间" show-overflow-tooltip>
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="60" show-overflow-tooltip> </el-table-column>
@@ -249,7 +249,6 @@
<db-edit
@val-change="valChange"
:projects="projects"
:title="dbEditDialog.title"
v-model:visible="dbEditDialog.visible"
v-model:db="dbEditDialog.data"
@@ -266,11 +265,12 @@ import DbEdit from './DbEdit.vue';
import CreateTable from './CreateTable.vue';
import { dbApi } from './api';
import enums from './enums';
import { projectApi } from '../project/api.ts';
import SqlExecBox from './component/SqlExecBox.ts';
import config from '@/common/config';
import { getSession } from '@/common/utils/storage';
import { isTrue } from '@/common/assert';
import { tagApi } from '../tag/api.ts';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'DbList',
@@ -286,7 +286,7 @@ export default defineComponent({
saveDb: 'db:save',
delDb: 'db:del',
},
projects: [],
tags: [],
chooseId: null,
/**
* 选中的数据
@@ -296,6 +296,7 @@ export default defineComponent({
* 查询条件
*/
query: {
tagPath: null,
pageNum: 1,
pageSize: 10,
},
@@ -406,12 +407,11 @@ export default defineComponent({
search();
};
const getProjects = async () => {
state.projects = await projectApi.accountProjects.request(null);
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const editDb = async (isAdd = false) => {
await getProjects();
if (isAdd) {
state.dbEditDialog.data = null;
state.dbEditDialog.title = '新增数据库资源';
@@ -616,7 +616,8 @@ export default defineComponent({
return {
...toRefs(state),
getProjects,
dateFormat,
getTags,
filterTableInfos,
enums,
search,

View File

@@ -3,46 +3,56 @@
<div class="toolbar">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv">
<template #default>
<el-form-item label="资源">
<el-select v-model="dbId" placeholder="请选择资源实例" @change="changeDbInstance" filterable style="width: 150px">
<el-option v-for="item in dbs" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
`${item.host}:${item.port} ${item.type}`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select @change="changeTag" @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" filterable style="width: 220px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="数据库">
<el-select
v-model="db"
placeholder="请选择数据库"
@change="changeDb"
@clear="clearDb"
clearable
filterable
style="width: 150px"
<el-form-item label="资源">
<el-select v-model="dbId" placeholder="请选择资源实例" @change="changeDbInstance" filterable style="width: 220px">
<el-option v-for="item in dbs" :key="item.id" :label="`${item.name} [${item.tagPath}]`" :value="item.id">
<span style="float: left">{{ `${item.name} [${item.tagPath}]` }}</span>
<span style="float: rignt; color: #8492a6; margin-left: 10px; font-size: 13px">{{
`${item.host}:${item.port} ${item.type}`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="数据库">
<el-select
v-model="db"
placeholder="请选择数据库"
@change="changeDb"
@clear="clearDb"
clearable
filterable
style="width: 150px"
>
<el-option v-for="item in databaseList" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label-width="20" label="表">
<el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable style="width: 250px">
<el-option
v-for="item in tableMetadata"
:key="item.tableName"
:label="item.tableName + (item.tableComment != '' ? `【${item.tableComment}】` : '')"
:value="item.tableName"
>
<el-option v-for="item in databaseList" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
</el-option>
</el-select>
</el-form-item>
</el-form>
<el-form-item label-width="20" label="表">
<el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable style="width: 250px">
<el-option
v-for="item in tableMetadata"
:key="item.tableName"
:label="item.tableName + (item.tableComment != '' ? `【${item.tableComment}】` : '')"
:value="item.tableName"
>
</el-option>
</el-select>
</el-form-item>
<!-- <project-env-select @changeTag="changeTag">
<template #default>
</template>
</project-env-select>
</project-env-select> -->
</el-col>
</el-row>
</div>
@@ -316,17 +326,15 @@ import 'codemirror/addon/hint/sql-hint.js';
import { format as sqlFormatter } from 'sql-formatter';
import { notBlank, notEmpty, isTrue } from '@/common/assert';
import { ElMessage, ElMessageBox } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import config from '@/common/config';
import { getSession } from '@/common/utils/storage';
import SqlExecBox from './component/SqlExecBox';
import { dateStrFormat } from '@/common/utils/date.ts';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'SqlExec',
components: {
ProjectEnvSelect,
},
components: {},
setup() {
const codeTextarea: any = ref(null);
const token = getSession('token');
@@ -336,11 +344,12 @@ export default defineComponent({
const state = reactive({
token: token,
defalutLimit: 20, // 默认查询数量
dbs: [], // 数据库实例列表
tags: [],
dbs: [] as any, // 数据库实例列表
databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库
db: '', // 当前操作的数据库
dbType: '',
tables: [],
tables: [] as any,
dbId: null, // 当前选中操作的数据库实例
tableName: '',
tableMetadata: [],
@@ -349,7 +358,7 @@ export default defineComponent({
activeName: 'Query',
queryTabName: 'Query',
nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用
dataTabs: {}, // 点击表信息后执行结果数据展示tabs
dataTabs: {} as any, // 点击表信息后执行结果数据展示tabs
dataTabsTableHeight: 600,
// 查询tab
queryTab: {
@@ -366,8 +375,8 @@ export default defineComponent({
},
params: {
pageNum: 1,
pageSize: 10,
envId: null,
pageSize: 100,
tagPath: null,
},
conditionDialog: {
title: '',
@@ -440,16 +449,17 @@ export default defineComponent({
/**
* 项目及环境更改后的回调事件
*/
const changeProjectEnv = (projectId: any, envId: any) => {
const changeTag = () => {
state.dbs = [];
state.dbId = null;
state.db = '';
state.databaseList = [];
clearDb();
if (envId != null) {
state.params.envId = envId;
search();
}
search();
};
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const onBeforeChange = (instance: any, changeObj: any) => {
@@ -1221,8 +1231,9 @@ export default defineComponent({
return {
...toRefs(state),
getTags,
codeTextarea,
changeProjectEnv,
changeTag,
changeTable,
cellClick,
onRunSql,

View File

@@ -2,17 +2,15 @@
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="38%">
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="ip" label="ip:" required>
<el-col :span="18">
<el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"></el-input>
<el-input :disabled="form.id != 0" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
@@ -91,9 +89,13 @@ import { machineApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'MachineEdit',
components: {
TagSelect,
},
props: {
visible: {
type: Boolean,
@@ -112,12 +114,15 @@ export default defineComponent({
const machineForm: any = ref(null);
const state = reactive({
dialogVisible: false,
projects: [],
sshTunnelMachineList: [],
projects: [] as any,
sshTunnelMachineList: [] as any,
tags: [],
selectTags: [],
form: {
id: null,
projectId: null,
projectName: null,
tagId: null as any,
tagPath: '',
ip: null,
name: null,
authMethod: 1,
port: 22,
@@ -131,17 +136,10 @@ export default defineComponent({
pwd: '',
btnLoading: false,
rules: {
projectId: [
tagId: [
{
required: true,
message: '请选择项目',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
message: '请选择标签',
trigger: ['change', 'blur'],
},
],
@@ -206,14 +204,6 @@ export default defineComponent({
state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id });
};
const changeProject = (projectId: number) => {
for (let p of state.projects as any) {
if (p.id == projectId) {
state.form.projectName = p.name;
}
}
};
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
@@ -258,7 +248,6 @@ export default defineComponent({
machineForm,
getSshTunnelMachines,
getPwd,
changeProject,
btnOk,
cancel,
};

View File

@@ -3,21 +3,15 @@
<el-card>
<div>
<el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加</el-button>
<el-button
v-auth="'machine:update'"
type="primary"
icon="edit"
:disabled="currentId == null"
@click="openFormDialog(currentData)"
plain
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId" @click="openFormDialog(currentData)" plain
>编辑</el-button
>
<el-button v-auth="'machine:del'" :disabled="currentId == null" @click="deleteMachine(currentId)" type="danger" icon="delete"
<el-button v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger" icon="delete"
>删除</el-button
>
<div style="float: right">
<el-select @focus="getProjects" v-model="params.projectId" placeholder="请选择项目" @clear="search" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-input
class="ml5"
@@ -66,11 +60,11 @@
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="120"></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="165">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建者" min-width="80"></el-table-column>
@@ -170,7 +164,6 @@
<machine-edit
:title="machineEditDialog.title"
:projects="projects"
v-model:visible="machineEditDialog.visible"
v-model:machine="machineEditDialog.data"
@valChange="submitSuccess"
@@ -187,6 +180,8 @@
:machineId="machineStatsDialog.machineId"
:title="machineStatsDialog.title"
></machine-stats>
<machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId" :title="machineRecDialog.title"></machine-rec>
</div>
</template>
@@ -195,12 +190,14 @@ import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api';
import { projectApi } from '../project/api.ts';
import { tagApi } from '../tag/api.ts';
import ServiceManage from './ServiceManage.vue';
import FileManage from './FileManage.vue';
import MachineEdit from './MachineEdit.vue';
import ProcessList from './ProcessList.vue';
import MachineStats from './MachineStats.vue';
import MachineRec from './MachineRec.vue';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MachineList',
@@ -210,17 +207,19 @@ export default defineComponent({
FileManage,
MachineEdit,
MachineStats,
MachineRec,
},
setup() {
const router = useRouter();
const state = reactive({
projects: [],
tags: [] as any,
stats: '',
params: {
pageNum: 1,
pageSize: 10,
ip: null,
name: null,
tagPath: null,
},
// 列表数据
data: {
@@ -228,7 +227,7 @@ export default defineComponent({
total: 10,
},
// 当前选中数据id
currentId: null,
currentId: 0,
currentData: null,
serviceDialog: {
visible: false,
@@ -252,7 +251,7 @@ export default defineComponent({
},
machineEditDialog: {
visible: false,
data: null,
data: null as any,
title: '新增机器',
},
machineRecDialog: {
@@ -296,12 +295,11 @@ export default defineComponent({
search();
};
const getProjects = async () => {
state.projects = await projectApi.accountProjects.request(null);
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const openFormDialog = async (machine: any) => {
await getProjects();
let dialogTitle;
if (machine) {
state.machineEditDialog.data = state.currentData as any;
@@ -324,7 +322,7 @@ export default defineComponent({
});
await machineApi.del.request({ id });
ElMessage.success('操作成功');
state.currentId = null;
state.currentId = 0;
state.currentData = null;
search();
} catch (err) {}
@@ -353,7 +351,7 @@ export default defineComponent({
};
const submitSuccess = () => {
state.currentId = null;
state.currentId = 0;
state.currentData = null;
search();
};
@@ -380,20 +378,16 @@ export default defineComponent({
};
const showRec = (row: any) => {
const { href } = router.resolve({
path: `/machine/terminal-rec`,
query: {
id: row.id,
name: `${row.name}[${row.ip}]-终端回放记录`,
},
});
window.open(href, '_blank');
state.machineRecDialog.title = `${row.name}[${row.ip}]-终端回放记录`;
state.machineRecDialog.machineId = row.id;
state.machineRecDialog.visible = true;
};
return {
...toRefs(state),
dateFormat,
choose,
getProjects,
getTags,
showTerminal,
openFormDialog,
deleteMachine,

View File

@@ -1,30 +1,37 @@
<template>
<div>
<div class="toolbar">
<span style="dispaly: inline-block" class="ml10">{{ title }}</span>
<el-divider direction="vertical" border-style="dashed" />
<el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable>
<el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-select class="ml10" @change="getRecs" filterable v-model="user" placeholder="请选择操作人">
<el-option v-for="item in users" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-select class="ml10" @change="playRec" filterable v-model="rec" placeholder="请选择操作记录">
<el-option v-for="item in recs" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-divider direction="vertical" border-style="dashed" />
快捷键-> space[空格键]: 暂停/播放 | f: 全屏/取消全屏
</div>
<div ref="playerRef" id="rc-player"></div>
<div id="terminalRecDialog">
<el-dialog
:title="title"
v-model="dialogVisible"
:before-close="handleClose"
:close-on-click-modal="false"
:destroy-on-close="true"
width="70%"
>
<div class="toolbar">
<el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable>
<el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-select class="ml10" @change="getRecs" filterable v-model="user" placeholder="请选择操作人">
<el-option v-for="item in users" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-select class="ml10" @change="playRec" filterable v-model="rec" placeholder="请选择操作记录">
<el-option v-for="item in recs" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-divider direction="vertical" border-style="dashed" />
快捷键-> space[空格键]: 暂停/播放
</div>
<div ref="playerRef" id="rc-player"></div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, onMounted, ref, reactive, defineComponent } from 'vue';
import { toRefs, watch, ref, reactive, defineComponent } from 'vue';
import { machineApi } from './api';
import * as AsciinemaPlayer from 'asciinema-player';
import 'asciinema-player/dist/bundle/asciinema-player.css';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'MachineRec',
components: {},
@@ -34,7 +41,6 @@ export default defineComponent({
title: { type: String },
},
setup(props: any, context) {
const route = useRoute();
const playerRef = ref(null);
const state = reactive({
dialogVisible: false,
@@ -48,10 +54,14 @@ export default defineComponent({
rec: '',
});
onMounted(() => {
state.machineId = Number.parseInt(route.query.id as string);
state.title = route.query.name as string;
getOperateDate();
watch(props, async (newValue) => {
const visible = newValue.visible;
if (visible) {
state.machineId = newValue.machineId;
state.title = newValue.title;
await getOperateDate();
}
state.dialogVisible = visible;
});
const getOperateDate = async () => {
@@ -118,3 +128,10 @@ export default defineComponent({
},
});
</script>
<style lang="scss">
#terminalRecDialog {
.el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
padding: 0px !important;
}
}
</style>

View File

@@ -3,36 +3,45 @@
<div class="toolbar">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv">
<template #default>
<el-form-item label="实例" label-width="40px">
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo">
<el-option v-for="item in mongoList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ ` [${item.uri}]` }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select
@change="changeTag"
@focus="getTags"
v-model="query.tagPath"
placeholder="请选择标签"
filterable
style="width: 250px"
>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="实例" label-width="40px">
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo">
<el-option v-for="item in mongoList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ ` [${item.uri}]` }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase" filterable>
<el-option v-for="item in databases" :key="item.Name" :label="item.Name" :value="item.Name">
<span style="float: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase" filterable>
<el-option v-for="item in databases" :key="item.Name" :label="item.Name" :value="item.Name">
<span style="float: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection" filterable>
<el-option v-for="item in collections" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
</template>
</project-env-select>
<el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection" filterable>
<el-option v-for="item in collections" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
@@ -64,12 +73,7 @@
<el-divider direction="vertical" border-style="dashed" />
<el-link
@click="onSaveDoc(item.value)"
:underline="false"
type="warning"
icon="DocumentChecked"
></el-link>
<el-link @click="onSaveDoc(item.value)" :underline="false" type="warning" icon="DocumentChecked"></el-link>
<el-divider direction="vertical" border-style="dashed" />
@@ -132,36 +136,38 @@
import { mongoApi } from './api';
import { toRefs, ref, reactive, defineComponent } from 'vue';
import { ElMessage } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { formatByteSize } from '@/common/utils/format';
import JsonEdit from '@/components/jsonedit/index.vue';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'MongoDataOp',
components: {
ProjectEnvSelect,
JsonEdit,
},
setup() {
const findParamInputRef: any = ref(null);
const state = reactive({
loading: false,
mongoList: [],
tags: [],
mongoList: [] as any,
query: {
envId: 0,
tagPath: null,
},
mongoId: null, // 当前选择操作的mongo
database: '', // 当前选择操作的库
collection: '', //当前选中的collection
activeName: '', // 当前操作的tab
databases: [],
collections: [],
dataTabs: {}, // 数据tabs
databases: [] as any,
collections: [] as any,
dataTabs: {} as any, // 数据tabs
findDialog: {
visible: false,
findParam: {
limit: 0,
skip: 0,
filter: '',
sort: '',
},
@@ -178,24 +184,27 @@ export default defineComponent({
});
const searchMongo = async () => {
notNull(state.query.envId, '请先选择项目环境');
notNull(state.query.tagPath, '请先选择标签');
const res = await mongoApi.mongoList.request(state.query);
state.mongoList = res.list;
};
const changeProjectEnv = (projectId: any, envId: any) => {
const changeTag = (tagPath: string) => {
state.databases = [];
state.collections = [];
state.mongoId = null;
state.collection = '';
state.database = '';
state.dataTabs = {};
if (envId != null) {
state.query.envId = envId;
if (tagPath != null) {
searchMongo();
}
};
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const changeMongo = () => {
state.databases = [];
state.collections = [];
@@ -419,7 +428,8 @@ export default defineComponent({
return {
...toRefs(state),
findParamInputRef,
changeProjectEnv,
getTags,
changeTag,
changeMongo,
changeDatabase,
changeCollection,

View File

@@ -2,17 +2,10 @@
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="38%" :destroy-on-close="true">
<el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="envId" label="环境" required>
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
@@ -58,19 +51,19 @@
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
import { mongoApi } from './api';
import { projectApi } from '../project/api.ts';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'MongoEdit',
components: {
TagSelect,
},
props: {
visible: {
type: Boolean,
},
projects: {
type: Array,
},
mongo: {
type: [Boolean, Object],
},
@@ -82,33 +75,22 @@ export default defineComponent({
const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
projects: [],
envs: [],
sshTunnelMachineList: [],
sshTunnelMachineList: [] as any,
form: {
id: null,
name: null,
uri: null,
enableSshTunnel: -1,
sshTunnelMachineId: null,
project: null,
projectId: null,
envId: null,
env: null,
tagId: null as any,
tagPath: null as any,
},
btnLoading: false,
rules: {
projectId: [
tagId: [
{
required: true,
message: '请选择项目',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
message: '请选择标签',
trigger: ['change', 'blur'],
},
],
@@ -134,12 +116,9 @@ export default defineComponent({
if (!state.dialogVisible) {
return;
}
state.projects = newValue.projects;
if (newValue.mongo) {
getEnvs(newValue.mongo.projectId);
state.form = { ...newValue.mongo };
} else {
state.envs = [];
state.form = { db: 0 } as any;
}
getSshTunnelMachines();
@@ -152,30 +131,6 @@ export default defineComponent({
}
};
const getEnvs = async (projectId: any) => {
state.envs = await projectApi.projectEnvs.request({ projectId });
};
const changeProject = (projectId: number) => {
for (let p of state.projects as any) {
if (p.id == projectId) {
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};
const changeEnv = (envId: number) => {
for (let p of state.envs as any) {
if (p.id == envId) {
state.form.env = p.name;
}
}
};
const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
@@ -206,9 +161,7 @@ export default defineComponent({
return {
...toRefs(state),
mongoForm,
changeProject,
getSshTunnelMachines,
changeEnv,
btnOk,
cancel,
};

View File

@@ -5,8 +5,8 @@
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除</el-button>
<div style="float: right">
<el-select @focus="getProjects" v-model="query.projectId" placeholder="请选择项目" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
@@ -18,8 +18,7 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="project" label="项目" width></el-table-column>
<el-table-column prop="env" label="环境" width></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="名称" width></el-table-column>
<el-table-column prop="uri" label="连接uri" min-width="150" show-overflow-tooltip>
<template #default="scope">
@@ -28,7 +27,7 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人"></el-table-column>
@@ -180,7 +179,6 @@
<mongo-edit
@val-change="valChange"
:projects="projects"
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
@@ -192,9 +190,10 @@
import { mongoApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { projectApi } from '../project/api.ts';
import { tagApi } from '../tag/api.ts';
import MongoEdit from './MongoEdit.vue';
import { formatByteSize } from '@/common/utils/format';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MongoList',
@@ -203,7 +202,7 @@ export default defineComponent({
},
setup() {
const state = reactive({
projects: [],
tags: [],
list: [],
total: 0,
currentId: null,
@@ -211,12 +210,11 @@ export default defineComponent({
query: {
pageNum: 1,
pageSize: 10,
prjectId: null,
clusterId: null,
tagPath: null,
},
mongoEditDialog: {
visible: false,
data: null,
data: null as any,
title: '新增mongo',
},
databaseDialog: {
@@ -225,7 +223,7 @@ export default defineComponent({
title: '',
statsDialog: {
visible: false,
data: {},
data: {} as any,
title: '',
},
},
@@ -236,7 +234,7 @@ export default defineComponent({
title: '',
statsDialog: {
visible: false,
data: {},
data: {} as any,
title: '',
},
},
@@ -370,12 +368,11 @@ export default defineComponent({
state.total = res.total;
};
const getProjects = async () => {
state.projects = await projectApi.accountProjects.request(null);
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const editMongo = async (isAdd = false) => {
await getProjects();
if (isAdd) {
state.mongoEditDialog.data = null;
state.mongoEditDialog.title = '新增mongo';
@@ -394,7 +391,8 @@ export default defineComponent({
return {
...toRefs(state),
getProjects,
dateFormat,
getTags,
search,
handlePageChange,
choose,

View File

@@ -1,443 +0,0 @@
<template>
<div class="project-list">
<el-card>
<div>
<el-button @click="showAddProjectDialog" v-auth="permissions.saveProject" type="primary" icon="plus">添加</el-button>
<el-button
@click="showAddProjectDialog(chooseData)"
v-auth="permissions.saveProject"
:disabled="chooseId == null"
type="primary"
icon="edit"
>编辑</el-button
>
<el-button @click="showMembers(chooseData)" :disabled="chooseId == null" type="success" icon="user">成员管理</el-button>
<el-button @click="showEnv(chooseData)" :disabled="chooseId == null" type="info" icon="setting">环境管理</el-button>
<el-button v-auth="permissions.delProject" @click="delProject" :disabled="chooseId == null" type="danger" icon="delete"
>删除</el-button
>
<div style="float: right">
<el-input class="mr2" placeholder="请输入项目名!" style="width: 200px" v-model="query.name" @clear="search" clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
</div>
<el-table :data="projects" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="项目名"></el-table-column>
<el-table-column prop="remark" label="描述" min-width="180px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建者"> </el-table-column>
<!-- <el-table-column label="查看更多" min-width="80px">
<template #default="scope">
<el-link @click.prevent="showMembers(scope.row)" type="success">成员</el-link>
<el-link class="ml5" @click.prevent="showEnv(scope.row)" type="info">环境</el-link>
</template>
</el-table-column> -->
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row>
</el-card>
<el-dialog width="400px" title="项目编辑" :before-close="cancelAddProject" v-model="addProjectDialog.visible">
<el-form :model="addProjectDialog.form" label-width="70px">
<el-form-item prop="name" label="项目名:" required>
<el-input :disabled="addProjectDialog.form.id ? true : false" v-model="addProjectDialog.form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="描述:">
<el-input v-model="addProjectDialog.form.remark" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelAddProject()"> </el-button>
<el-button @click="addProject" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog width="500px" :title="showEnvDialog.title" v-model="showEnvDialog.visible">
<div class="toolbar">
<el-button @click="showAddEnvDialog" v-auth="permissions.saveMember" type="primary" icon="plus">添加</el-button>
<el-button @click="deleteEnv" v-auth="permissions.delProject" :disabled="showEnvDialog.chooseId == null" type="danger" icon="delete"
>删除</el-button
>
</div>
<el-table @current-change="chooseEnv" border :data="showEnvDialog.envs">
<el-table-column label="选择" width="50px">
<template #default="scope">
<el-radio v-model="showEnvDialog.chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column property="name" label="环境名" width="125"></el-table-column>
<el-table-column property="remark" label="描述" width="125"></el-table-column>
<el-table-column property="createTime" label="创建时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
<el-dialog width="400px" title="添加环境" :before-close="cancelAddEnv" v-model="showEnvDialog.addVisible">
<el-form :model="showEnvDialog.envForm" label-width="70px">
<el-form-item prop="name" label="环境名:" required>
<el-input v-model="showEnvDialog.envForm.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="描述:">
<el-input v-model="showEnvDialog.envForm.remark" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelAddEnv()"> </el-button>
<el-button v-auth="permissions.saveEnv" @click="addEnv" type="primary" :loading="btnLoading"> </el-button>
</div>
</template>
</el-dialog>
</el-dialog>
<el-dialog width="500px" :title="showMemDialog.title" v-model="showMemDialog.visible">
<div class="toolbar">
<el-button v-auth="permissions.saveMember" @click="showAddMemberDialog()" type="primary" icon="plus">添加</el-button>
<el-button v-auth="permissions.delMember" @click="deleteMember" :disabled="showMemDialog.chooseId == null" type="danger" icon="delete"
>移除</el-button
>
</div>
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list">
<el-table-column label="选择" width="50px">
<template #default="scope">
<el-radio v-model="showMemDialog.chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column property="username" label="账号" width="125"></el-table-column>
<el-table-column property="createTime" label="加入时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column property="creator" label="分配者" width="125"></el-table-column>
</el-table>
<el-pagination
@current-change="setMemebers"
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="showMemDialog.members.total"
v-model:current-page="showMemDialog.query.pageNum"
:page-size="showMemDialog.query.pageSize"
/>
<el-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
<el-form :model="showMemDialog.memForm" label-width="70px">
<el-form-item label="账号:">
<el-select
style="width: 100%"
remote
:remote-method="getAccount"
v-model="showMemDialog.memForm.accountId"
filterable
placeholder="请输入账号模糊搜索并选择"
>
<el-option v-for="item in showMemDialog.accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="描述:">
<el-input v-model="showEnvDialog.envForm.remark" auto-complete="off"></el-input>
</el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelAddMember()"> </el-button>
<el-button v-auth="permissions.saveMember" @click="addMember" type="primary" :loading="btnLoading"> </el-button>
</div>
</template>
</el-dialog>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import { projectApi } from './api';
import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { notEmpty, notNull } from '@/common/assert';
export default defineComponent({
name: 'ProjectList',
components: {},
setup() {
const state = reactive({
permissions: {
saveProject: 'project:save',
delProject: 'project:del',
saveMember: 'project:member:add',
delMember: 'project:member:del',
saveEnv: 'project:env:add',
},
query: {
pageNum: 1,
pageSize: 10,
name: null,
},
total: 0,
projects: [],
btnLoading: false,
chooseId: null as any,
chooseData: null as any,
addProjectDialog: {
title: '新增项目',
visible: false,
form: { name: '', remark: '' },
},
showEnvDialog: {
visible: false,
chooseId: null,
chooseData: null,
envs: [],
title: '',
addVisible: false,
envForm: {
name: '',
remark: '',
projectId: 0,
},
},
showMemDialog: {
visible: false,
chooseId: null,
chooseData: null,
query: {
pageSize: 8,
pageNum: 1,
projectId: null,
},
members: {
list: [],
total: null,
},
title: '',
addVisible: false,
memForm: {},
accounts: [],
},
});
onMounted(() => {
search();
});
const search = async () => {
let res = await projectApi.projects.request(state.query);
state.projects = res.list;
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const showAddProjectDialog = (data: any) => {
if (data) {
state.addProjectDialog.form = { ...data };
} else {
state.addProjectDialog.form = {} as any;
}
state.addProjectDialog.visible = true;
};
const cancelAddProject = () => {
state.addProjectDialog.visible = false;
state.addProjectDialog.form = {} as any;
};
const addProject = async () => {
const form = state.addProjectDialog.form as any;
notEmpty(form.name, '项目名不能为空');
notEmpty(form.remark, '项目描述不能为空');
await projectApi.saveProject.request(form);
ElMessage.success('保存成功');
search();
cancelAddProject();
};
const delProject = async () => {
try {
await ElMessageBox.confirm(`确定删除该项目?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await projectApi.delProject.request({ id: state.chooseId });
ElMessage.success('删除成功');
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) {}
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const showMembers = async (project: any) => {
state.showMemDialog.query.projectId = project.id;
await setMemebers();
state.showMemDialog.title = `${project.name}的成员信息`;
state.showMemDialog.visible = true;
};
/**
* 选中成员
*/
const chooseMember = (item: any) => {
if (!item) {
return;
}
state.showMemDialog.chooseData = item;
state.showMemDialog.chooseId = item.id;
};
const deleteMember = async () => {
notNull(state.showMemDialog.chooseData, '请选选择成员');
await projectApi.deleteProjectMem.request(state.showMemDialog.chooseData);
ElMessage.success('移除成功');
// 重新赋值成员列表
setMemebers();
};
/**
* 设置成员列表信息
*/
const setMemebers = async () => {
const res = await projectApi.projectMems.request(state.showMemDialog.query);
state.showMemDialog.members.list = res.list;
state.showMemDialog.members.total = res.total;
};
const showEnv = async (project: any) => {
state.showEnvDialog.envs = await projectApi.projectEnvs.request({ projectId: project.id });
state.showEnvDialog.title = `${project.name}的环境信息`;
state.showEnvDialog.visible = true;
};
const chooseEnv = (item: any) => {
if (!item) {
return;
}
state.showEnvDialog.chooseData = item;
state.showEnvDialog.chooseId = item.id;
};
const deleteEnv = async () => {
notNull(state.showEnvDialog.chooseData, '请选选择环境');
await projectApi.delProjectEnvs.request({ id: state.showEnvDialog.chooseId });
ElMessage.success('删除成功');
state.showEnvDialog.envs = await projectApi.projectEnvs.request({ projectId: state.chooseId });
};
const showAddMemberDialog = () => {
state.showMemDialog.addVisible = true;
};
const addMember = async () => {
const memForm = state.showMemDialog.memForm as any;
memForm.projectId = state.chooseData.id;
notEmpty(memForm.accountId, '请先选择账号');
await projectApi.saveProjectMem.request(memForm);
ElMessage.success('保存成功');
setMemebers();
cancelAddMember();
};
const cancelAddMember = () => {
state.showMemDialog.memForm = {};
state.showMemDialog.addVisible = false;
state.showMemDialog.chooseData = null;
state.showMemDialog.chooseId = null;
};
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {
state.showMemDialog.accounts = res.list;
});
};
const showAddEnvDialog = () => {
state.showEnvDialog.addVisible = true;
};
const addEnv = async () => {
const envForm = state.showEnvDialog.envForm;
envForm.projectId = state.chooseData.id;
await projectApi.saveProjectEnv.request(envForm);
ElMessage.success('保存成功');
state.showEnvDialog.envs = await projectApi.projectEnvs.request({ projectId: envForm.projectId });
cancelAddEnv();
};
const cancelAddEnv = () => {
state.showEnvDialog.envForm = {} as any;
state.showEnvDialog.addVisible = false;
};
return {
...toRefs(state),
search,
handlePageChange,
choose,
showAddProjectDialog,
addProject,
delProject,
cancelAddProject,
showMembers,
setMemebers,
showEnv,
deleteEnv,
showAddMemberDialog,
addMember,
chooseMember,
deleteMember,
cancelAddMember,
showAddEnvDialog,
chooseEnv,
addEnv,
cancelAddEnv,
getAccount,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -1,17 +0,0 @@
import Api from '@/common/Api';
export const projectApi = {
// 获取账号可访问的项目列表
accountProjects: Api.create("/accounts/projects", 'get'),
projects: Api.create("/projects", 'get'),
saveProject: Api.create("/projects", 'post'),
delProject: Api.create("/projects", 'delete'),
// 获取项目下的环境信息
projectEnvs: Api.create("/projects/{projectId}/envs", 'get'),
delProjectEnvs: Api.create("/projects/envs", 'delete'),
saveProjectEnv: Api.create("/projects/{projectId}/envs", 'post'),
// 获取项目下的成员信息
projectMems: Api.create("/projects/{projectId}/members", 'get'),
saveProjectMem: Api.create("/projects/{projectId}/members", 'post'),
deleteProjectMem: Api.create("/projects/{projectId}/members/{accountId}", 'delete'),
}

View File

@@ -4,34 +4,57 @@
<div style="float: left">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearRedis">
<template #default>
<el-form-item label="redis" label-width="40px">
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis" @clear="clearRedis" clearable>
<el-option v-for="item in redisList" :key="item.id" :label="item.host" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="scanParam.db" @change="changeDb" placeholder="库" style="width: 85px">
<el-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select>
</el-form-item>
</template>
</project-env-select>
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select
@change="changeTag"
@focus="getTags"
v-model="query.tagPath"
placeholder="请选择标签"
filterable
style="width: 250px"
>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="redis" label-width="40px">
<el-select
v-model="scanParam.id"
placeholder="请选择redis"
@change="changeRedis"
@clear="clearRedis"
clearable
style="width: 250px"
>
<el-option
v-for="item in redisList"
:key="item.id"
:label="`${item.name ? item.name : ''} [${item.host}]`"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="scanParam.db" @change="changeDb" placeholder="库" style="width: 85px">
<el-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
<el-col class="mt10">
<el-form class="search-form" label-position="right" :inline="true" label-width="60px">
<el-form-item label="key" label-width="40px">
<el-input
placeholder="match 支持*模糊key"
style="width: 240px"
style="width: 250px"
v-model="scanParam.match"
@clear="clear()"
clearable
></el-input>
</el-form-item>
<el-form-item label="count" label-width="60px">
<el-input placeholder="count" style="width: 62px" v-model.number="scanParam.count"></el-input>
<el-form-item label="count" label-width="40px">
<el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="searchKey()" type="success" icon="search" plain></el-button>
@@ -127,12 +150,12 @@
import { redisApi } from './api';
import { toRefs, reactive, defineComponent } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import HashValue from './HashValue.vue';
import StringValue from './StringValue.vue';
import SetValue from './SetValue.vue';
import ListValue from './ListValue.vue';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'DataOperation',
@@ -141,15 +164,15 @@ export default defineComponent({
HashValue,
SetValue,
ListValue,
ProjectEnvSelect,
},
setup() {
const state = reactive({
loading: false,
redisList: [],
tags: [],
redisList: [] as any,
dbList: [],
query: {
envId: 0,
tagPath: null,
},
scanParam: {
id: null,
@@ -185,19 +208,22 @@ export default defineComponent({
});
const searchRedis = async () => {
notNull(state.query.envId, '请先选择项目环境');
notBlank(state.query.tagPath, '请先选择标签');
const res = await redisApi.redisList.request(state.query);
state.redisList = res.list;
};
const changeProjectEnv = (projectId: any, envId: any) => {
const changeTag = (tagPath: string) => {
clearRedis();
if (envId != null) {
state.query.envId = envId;
if (tagPath != null) {
searchRedis();
}
};
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const changeRedis = (id: number) => {
resetScanParam(id);
state.scanParam.db = null;
@@ -385,7 +411,8 @@ export default defineComponent({
return {
...toRefs(state),
changeProjectEnv,
getTags,
changeTag,
changeRedis,
changeDb,
clearRedis,

View File

@@ -2,16 +2,11 @@
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="envId" label="环境:" required>
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入redis名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="mode" label="mode:" required>
<el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
@@ -88,9 +83,13 @@ import { projectApi } from '../project/api.ts';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'RedisEdit',
components: {
TagSelect,
},
props: {
visible: {
type: Boolean,
@@ -114,6 +113,8 @@ export default defineComponent({
sshTunnelMachineList: [],
form: {
id: null,
tagId: null as any,
tatPath: null as any,
name: null,
mode: 'standalone',
host: '',
@@ -176,7 +177,6 @@ export default defineComponent({
}
state.projects = newValue.projects;
if (newValue.redis) {
getEnvs(newValue.redis.projectId);
state.form = { ...newValue.redis };
convertDb(state.form.db);
} else {
@@ -205,34 +205,10 @@ export default defineComponent({
}
};
const getEnvs = async (projectId: any) => {
state.envs = await projectApi.projectEnvs.request({ projectId });
};
const getPwd = async () => {
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
};
const changeProject = (projectId: number) => {
for (let p of state.projects as any) {
if (p.id == projectId) {
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};
const changeEnv = (envId: number) => {
for (let p of state.envs as any) {
if (p.id == envId) {
state.form.env = p.name;
}
}
};
const btnOk = async () => {
redisForm.value.validate(async (valid: boolean) => {
if (valid) {
@@ -270,8 +246,6 @@ export default defineComponent({
changeDb,
getSshTunnelMachines,
getPwd,
changeProject,
changeEnv,
btnOk,
cancel,
};

View File

@@ -5,8 +5,8 @@
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除</el-button>
<div style="float: right">
<el-select @focus="getProjects" v-model="query.projectId" placeholder="请选择项目" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
@@ -18,14 +18,14 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="project" label="项目" min-width="100"></el-table-column>
<el-table-column prop="env" label="环境" min-width="100"></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="名称" min-width="100"></el-table-column>
<el-table-column prop="host" label="host:port" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column prop="mode" label="mode" min-width="100"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="160">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人" min-width="100"></el-table-column>
@@ -138,7 +138,7 @@
<redis-edit
@val-change="valChange"
:projects="projects"
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"
@@ -151,8 +151,9 @@ import Info from './Info.vue';
import { redisApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { projectApi } from '../project/api.ts';
import { tagApi } from '../tag/api.ts';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'RedisList',
@@ -162,15 +163,15 @@ export default defineComponent({
},
setup() {
const state = reactive({
projects: [],
tags: [],
redisTable: [],
total: 0,
currentId: null,
currentData: null,
query: {
tagPath: null,
pageNum: 1,
pageSize: 10,
prjectId: null,
clusterId: null,
},
redisInfo: {
@@ -263,12 +264,11 @@ export default defineComponent({
state.total = res.total;
};
const getProjects = async () => {
state.projects = await projectApi.accountProjects.request(null);
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
const editRedis = async (isAdd = false) => {
await getProjects();
if (isAdd) {
state.redisEditDialog.data = null;
state.redisEditDialog.title = '新增redis';
@@ -287,7 +287,8 @@ export default defineComponent({
return {
...toRefs(state),
getProjects,
dateFormat,
getTags,
search,
handlePageChange,
choose,

View File

@@ -0,0 +1,300 @@
<template>
<div class="menu">
<div class="toolbar">
<el-button v-auth="'tag:save'" type="primary" icon="plus" @click="showSaveTabDialog(null)">添加</el-button>
<div style="float: right">
<el-tooltip effect="dark" placement="top">
<template #content>
1. 用于将资产进行归类
<br />2. 可在团队管理中进行分配用于资源隔离
<br />3. 父标签可访问及操作所有子标签关联的资源
</template>
<span>标签作用<el-icon><question-filled /></el-icon></span>
</el-tooltip>
</div>
</div>
<el-tree
class="none-select"
:indent="38"
node-key="id"
:props="props"
:data="data"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys"
:expand-on-click-node="false"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
{{ 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>
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info" :underline="false" />
<el-link v-auth="'tag:save'" @click.prevent="showEditTagDialog(data)" class="ml5" type="primary" icon="edit" :underline="false" />
<el-link
v-auth="'tag:save'"
@click.prevent="showSaveTabDialog(data)"
icon="circle-plus"
:underline="false"
type="success"
class="ml5"
/>
<!-- <el-link
v-auth="'resource:changeStatus'"
@click.prevent="changeStatus(data, -1)"
v-if="data.status === 1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
icon="circle-close"
:underline="false"
type="warning"
class="ml5"
/>
<el-link
v-auth="'resource:changeStatus'"
@click.prevent="changeStatus(data, 1)"
v-if="data.status === -1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
type="success"
icon="circle-check"
:underline="false"
plain
class="ml5"
/> -->
<el-link
v-auth="'tag:del'"
@click.prevent="deleteTag(data)"
v-if="data.children == null"
type="danger"
icon="delete"
:underline="false"
plain
class="ml5"
/>
</span>
</template>
</el-tree>
<el-dialog width="500px" :title="saveTabDialog.title" :before-close="cancelSaveTag" v-model="saveTabDialog.visible">
<el-form ref="tagForm" :rules="rules" :model="saveTabDialog.form" label-width="70px">
<el-form-item prop="code" label="标识:" required>
<el-input :disabled="saveTabDialog.form.id ? true : false" v-model="saveTabDialog.form.code" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model="saveTabDialog.form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="备注:">
<el-input v-model="saveTabDialog.form.remark" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelSaveTag()"> </el-button>
<el-button @click="saveTag" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="infoDialog.visible">
<el-descriptions title="节点信息" :column="2" border>
<el-descriptions-item label="code">{{ infoDialog.data.code }}</el-descriptions-item>
<el-descriptions-item label="code路径">{{ infoDialog.data.codePath }}</el-descriptions-item>
<el-descriptions-item label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}</el-descriptions-item>
<el-descriptions-item label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, ref, reactive, onMounted, defineComponent } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from './api';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'TagTreeList',
components: {},
setup() {
const tagForm: any = ref(null);
const state = reactive({
saveTabDialog: {
title: '新增标签',
visible: false,
form: { id: 0, pid: 0, code: '', name: '', remark: '' },
},
//资源信息弹出框对象
infoDialog: {
title: '',
visible: false,
// 资源类型选择是否选
data: null as any,
},
data: [],
props: {
label: 'name',
children: 'children',
},
// 展开的节点
defaultExpandedKeys: [] as any[],
rules: {
code: [
{ required: true, message: '标识符不能为空', trigger: 'blur' },
// {
// pattern: /^\w+$/g,
// message: '标识符只能为空数字字母下划线等',
// trigger: 'blur',
// },
],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
},
});
onMounted(() => {
search();
});
const search = async () => {
let res = await tagApi.getTagTrees.request(null);
state.data = res;
};
const info = async (data: any) => {
state.infoDialog.data = data;
state.infoDialog.visible = true;
};
const showSaveTabDialog = (data: any) => {
if (data) {
state.saveTabDialog.form.pid = data.id;
state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
} else {
state.saveTabDialog.title = '新增根标签信息';
}
state.saveTabDialog.visible = true;
};
const showEditTagDialog = (data: any) => {
state.saveTabDialog.form.id = data.id;
state.saveTabDialog.form.code = data.code;
state.saveTabDialog.form.name = data.name;
state.saveTabDialog.form.remark = data.remark;
state.saveTabDialog.title = `修改 [${data.codePath}] 信息`;
state.saveTabDialog.visible = true;
};
const saveTag = async () => {
tagForm.value.validate(async (valid: any) => {
if (valid) {
const form = state.saveTabDialog.form;
await tagApi.saveTagTree.request(form);
ElMessage.success('保存成功');
search();
cancelSaveTag();
}
});
};
const cancelSaveTag = () => {
state.saveTabDialog.visible = false;
state.saveTabDialog.form = {} as any;
tagForm.value.resetFields();
};
const deleteTag = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.codePath}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await tagApi.delTagTree.request({ id: data.id });
ElMessage.success('删除成功!');
search();
});
};
// const changeStatus = async (data: any, status: any) => {
// await resourceApi.changeStatus.request({
// id: data.id,
// status: status,
// });
// data.status = status;
// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
// };
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id;
if (!state.defaultExpandedKeys.includes(id)) {
state.defaultExpandedKeys.push(id);
}
};
// 关闭节点
const handleNodeCollapse = (data: any, node: any) => {
removeDeafultExpandId(node.data.id);
let childNodes = node.childNodes;
for (let cn of childNodes) {
if (cn.expanded) {
removeDeafultExpandId(cn.data.id);
}
// 递归删除展开的子节点节点id
handleNodeCollapse(data, cn);
}
};
const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) {
state.defaultExpandedKeys.splice(index, 1);
}
};
return {
...toRefs(state),
dateFormat,
tagForm,
info,
saveTag,
showSaveTabDialog,
showEditTagDialog,
cancelSaveTag,
deleteTag,
handleNodeExpand,
handleNodeCollapse,
};
},
});
</script>
<style lang="scss">
.menu {
height: 100%;
.el-tree-node__content {
height: 40px;
line-height: 40px;
}
}
.none-select {
moz-user-select: -moz-none;
-moz-user-select: none;
-o-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>

View File

@@ -0,0 +1,440 @@
<template>
<div class="role-list">
<el-card>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:save'" :disabled="chooseId == null" @click="showSaveTeamDialog(chooseData)" type="primary" icon="edit"
>编辑</el-button
>
<el-button v-auth="'team:del'" :disabled="chooseId == null" @click="deleteTeam(chooseData)" type="danger" icon="delete">删除</el-button>
<div style="float: right">
<el-input placeholder="请输入团队名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search" clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="data" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="团队名称"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="160px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建者"> </el-table-column>
<el-table-column label="操作" min-width="80px">
<template #default="scope">
<el-link @click.prevent="showMembers(scope.row)" :underline="false" type="primary">成员</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click.prevent="showTags(scope.row)" :underline="false" type="success">标签</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row>
</el-card>
<el-dialog width="400px" title="团队编辑" :before-close="cancelSaveTeam" v-model="addTeamDialog.visible">
<el-form ref="teamForm" :model="addTeamDialog.form" label-width="70px">
<el-form-item prop="name" label="团队名:" required>
<el-input v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="备注:">
<el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelSaveTeam()"> </el-button>
<el-button @click="saveTeam" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog width="500px" :title="showTagDialog.title" :before-close="closeTagDialog" v-model="showTagDialog.visible">
<el-form label-width="70px">
<el-form-item prop="project" label="标签:">
<el-tree-select
ref="tagTreeRef"
style="width: 100%"
v-model="showTagDialog.tagTreeTeams"
:data="showTagDialog.tags"
:default-expanded-keys="showTagDialog.tagTreeTeams"
multiple
:render-after-expand="true"
show-checkbox
check-strictly
node-key="id"
:props="showTagDialog.props"
@check="tagTreeNodeCheck"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
{{ 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-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeTagDialog()"> </el-button>
<el-button v-auth="'team:tag:save'" @click="saveTags()" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog width="600px" :title="showMemDialog.title" v-model="showMemDialog.visible">
<div class="toolbar">
<el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus">添加</el-button>
<el-button v-auth="'team:member:del'" @click="deleteMember" :disabled="showMemDialog.chooseId == null" type="danger" icon="delete">移除</el-button>
</div>
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list">
<el-table-column label="选择" width="50px">
<template #default="scope">
<el-radio v-model="showMemDialog.chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column property="username" label="账号" width="135"></el-table-column>
<el-table-column property="createTime" label="加入时间">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column property="creator" label="分配者" width="135"></el-table-column>
</el-table>
<el-pagination
@current-change="setMemebers"
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="showMemDialog.members.total"
v-model:current-page="showMemDialog.query.pageNum"
:page-size="showMemDialog.query.pageSize"
/>
<el-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
<el-form :model="showMemDialog.memForm" label-width="70px">
<el-form-item label="账号:">
<el-select
style="width: 100%"
remote
:remote-method="getAccount"
v-model="showMemDialog.memForm.accountId"
filterable
placeholder="请输入账号模糊搜索并选择"
>
<el-option v-for="item in showMemDialog.accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelAddMember()"> </el-button>
<el-button @click="addMember" type="primary"> </el-button>
</div>
</template>
</el-dialog>
</el-dialog>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
import { tagApi } from './api';
import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import { notBlank } from '@/common/assert';
export default defineComponent({
name: 'TeamList',
components: {},
setup() {
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
addTeamDialog: {
title: '新增团队',
visible: false,
form: { id: 0, name: '', remark: '' },
},
query: {
pageNum: 1,
pageSize: 10,
name: null,
},
total: 0,
data: [],
chooseId: 0,
chooseData: null,
showMemDialog: {
visible: false,
chooseId: 0,
chooseData: null,
query: {
pageSize: 8,
pageNum: 1,
teamId: null,
},
members: {
list: [],
total: null,
},
title: '',
addVisible: false,
memForm: {
accountId: null as any,
teamId: 0,
},
accounts: Array(),
},
showTagDialog: {
title: '项目信息',
visible: false,
tags: [],
teamId: 0,
tagTreeTeams: [] as any,
props: {
value: 'id',
label: 'codePath',
children: 'children',
},
},
});
onMounted(() => {
search();
});
const search = async () => {
let res = await tagApi.getTeams.request(state.query);
state.data = res.list;
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const showSaveTeamDialog = (data: any) => {
if (data) {
state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name;
state.addTeamDialog.form.remark = data.remark;
state.addTeamDialog.title = `修改 [${data.codePath}] 信息`;
}
state.addTeamDialog.visible = true;
};
const saveTeam = async () => {
teamForm.value.validate(async (valid: any) => {
if (valid) {
const form = state.addTeamDialog.form;
await tagApi.saveTeam.request(form);
ElMessage.success('保存成功');
search();
cancelSaveTeam();
}
});
};
const cancelSaveTeam = () => {
state.addTeamDialog.visible = false;
state.addTeamDialog.form = {} as any;
teamForm.value.resetFields();
};
const deleteTeam = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await tagApi.delTeam.request({ id: data.id });
ElMessage.success('删除成功!');
search();
});
};
/********** 团队成员相关 ***********/
const showMembers = async (team: any) => {
state.showMemDialog.query.teamId = team.id;
await setMemebers();
state.showMemDialog.title = `[${team.name}] 成员信息`;
state.showMemDialog.visible = true;
};
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {
state.showMemDialog.accounts = res.list;
});
};
/**
* 选中成员
*/
const chooseMember = (item: any) => {
if (!item) {
return;
}
state.showMemDialog.chooseData = item;
state.showMemDialog.chooseId = item.id;
};
const deleteMember = async () => {
await tagApi.delTeamMem.request(state.showMemDialog.chooseData);
ElMessage.success('移除成功');
// 重新赋值成员列表
setMemebers();
};
/**
* 设置成员列表信息
*/
const setMemebers = async () => {
const res = await tagApi.getTeamMem.request(state.showMemDialog.query);
state.showMemDialog.members.list = res.list;
state.showMemDialog.members.total = res.total;
};
const showAddMemberDialog = () => {
state.showMemDialog.addVisible = true;
};
const addMember = async () => {
const memForm = state.showMemDialog.memForm;
memForm.teamId = state.chooseId;
notBlank(memForm.accountId, '请先选择账号');
await tagApi.saveTeamMem.request(memForm);
ElMessage.success('保存成功');
setMemebers();
cancelAddMember();
};
const cancelAddMember = () => {
state.showMemDialog.memForm = {} as any;
state.showMemDialog.addVisible = false;
state.showMemDialog.chooseData = null;
state.showMemDialog.chooseId = 0;
};
/********** 标签相关 ***********/
const showTags = async (team: any) => {
state.showTagDialog.tags = await tagApi.getTagTrees.request(null);
state.showTagDialog.tagTreeTeams = await tagApi.getTeamTagIds.request({ teamId: team.id });
state.showTagDialog.title = `[${team.name}] 项目信息`;
state.showTagDialog.teamId = team.id;
state.showTagDialog.visible = true;
};
const closeTagDialog = () => {
state.showTagDialog.visible = false;
setTimeout(() => {
state.showTagDialog.tagTreeTeams = [];
}, 500);
};
const saveTags = async () => {
await tagApi.saveTeamTags.request({
teamId: state.showTagDialog.teamId,
tagIds: state.showTagDialog.tagTreeTeams,
});
ElMessage.success('保存成功');
closeTagDialog();
};
const tagTreeNodeCheck = (data: any, checkInfo: any) => {
const node = tagTreeRef.value.getNode(data.id);
console.log(node);
// state.showTagDialog.tagTreeTeams = [16]
if (node.checked) {
if (node.parent) {
console.log(node.parent);
// removeCheckedTagId(node.parent.key);
tagTreeRef.value.setChecked(node.parent, false, false);
}
// // parentNode = node.parent
// for (let parentNode of node.parent) {
// parentNode.setChecked(false);
// }
}
console.log(data);
console.log(checkInfo);
};
// function removeCheckedTagId(id: any) {
// console.log(state.showTagDialog.tagTreeTeams);
// for (let i = 0; i < state.showTagDialog.tagTreeTeams.length; i++) {
// if (state.showTagDialog.tagTreeTeams[i] == id) {
// console.log('has id', id);
// state.showTagDialog.tagTreeTeams.splice(i, 1);
// }
// }
// console.log(state.showTagDialog.tagTreeTeams);
// }
return {
...toRefs(state),
teamForm,
tagTreeRef,
dateFormat,
choose,
search,
handlePageChange,
showSaveTeamDialog,
saveTeam,
cancelSaveTeam,
deleteTeam,
showMembers,
setMemebers,
getAccount,
showAddMemberDialog,
addMember,
cancelAddMember,
chooseMember,
deleteMember,
showTags,
closeTagDialog,
saveTags,
tagTreeNodeCheck,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,19 @@
import Api from '@/common/Api';
export const tagApi = {
getAccountTags: Api.create("/tag-trees/account-has", 'get'),
getTagTrees: Api.create("/tag-trees", 'get'),
saveTagTree: Api.create("/tag-trees", 'post'),
delTagTree: Api.create("/tag-trees/{id}", 'delete'),
getTeams: Api.create("/teams", 'get'),
saveTeam: Api.create("/teams", 'post'),
delTeam: Api.create("/teams/{id}", 'delete'),
getTeamMem: Api.create("/teams/{teamId}/members", 'get'),
saveTeamMem: Api.create("/teams/{teamId}/members", 'post'),
delTeamMem: Api.create("/teams/{teamId}/members/{accountId}", 'delete'),
getTeamTagIds: Api.create("/teams/{teamId}/tags", 'get'),
saveTeamTags: Api.create("/teams/{teamId}/tags", 'post'),
}

View File

@@ -73,6 +73,7 @@ export default defineComponent({
state.form = { ...newValue.account };
state.edit = true;
} else {
state.edit = false;
state.form = {} as any;
}
state.dialogVisible = newValue.visible;

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="750px" :destroy-on-close="true">
<el-form ref="configForm" :model="form" label-width="90px">
<el-form-item prop="name" label="配置项:" required>
<el-input v-model="form.name"></el-input>
@@ -8,9 +8,28 @@
<el-form-item prop="key" label="配置key:" required>
<el-input :disabled="form.id != null" v-model="form.key"></el-input>
</el-form-item>
<el-form-item prop="value" label="配置值:" required>
<el-input v-model="form.value"></el-input>
<el-row style="margin-left: 30px; margin-bottom: 5px">
<el-button @click="onAddParam" size="small" type="success">新增配置项</el-button>
</el-row>
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
<el-row>
<el-col :span="5"><el-input v-model="param.model" placeholder="model"></el-input></el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.name" placeholder="字段名"></el-input></el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.placeholder" placeholder="字段说明"></el-input></el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="2"><el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button></el-col>
</el-row>
</el-form-item>
<!-- <el-form-item prop="value" label="配置值:" required>
<el-input v-model="form.value"></el-input>
</el-form-item> -->
<el-form-item label="备注:">
<el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
</el-form-item>
@@ -46,10 +65,12 @@ export default defineComponent({
const configForm: any = ref(null);
const state = reactive({
dvisible: false,
params: [] as any,
form: {
id: null,
name: '',
key: '',
params: '',
value: '',
remark: '',
},
@@ -60,11 +81,25 @@ export default defineComponent({
state.dvisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
if (state.form.params) {
state.params = JSON.parse(state.form.params);
} else {
state.params = [];
}
} else {
state.form = {} as any;
state.params = [];
}
});
const onAddParam = () => {
state.params.push({ name: '', model: '', placeholder: '' });
};
const onDeleteParam = (idx: number) => {
state.params.splice(idx, 1);
};
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
@@ -75,6 +110,9 @@ export default defineComponent({
const btnOk = async () => {
configForm.value.validate(async (valid: boolean) => {
if (valid) {
if (state.params) {
state.form.params = JSON.stringify(state.params);
}
await configApi.save.request(state.form);
emit('val-change', state.form);
cancel();
@@ -88,6 +126,8 @@ export default defineComponent({
return {
...toRefs(state),
onAddParam,
onDeleteParam,
configForm,
btnOk,
cancel,

View File

@@ -16,12 +16,25 @@
<el-table-column prop="key" label="配置key"></el-table-column>
<el-table-column prop="value" label="配置值" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间">
<el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope">
<el-link
:disabled="scope.row.status == -1"
type="warning"
@click="showSetConfigDialog(scope.row)"
plain
size="small"
:underline="false"
>配置</el-link
>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
@@ -35,6 +48,42 @@
</el-row>
</el-card>
<el-dialog :before-close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="500px">
<el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsForm" :model="paramsDialog.params" label-width="90px">
<el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
<el-input
v-if="!item.options"
v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder"
autocomplete="off"
clearable
></el-input>
<el-select
v-else
v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder"
filterable
autocomplete="off"
clearable
style="width: 100%"
>
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
</el-select>
</el-form-item>
</el-form>
<el-form v-else ref="paramsForm" label-width="90px">
<el-form-item label="配置值" required>
<el-input v-model="paramsDialog.params" :placeholder="paramsDialog.config.remark" autocomplete="off" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeSetConfigDialog()"> </el-button>
<el-button type="primary" @click="setConfig()"> </el-button>
</span>
</template>
</el-dialog>
<config-edit :title="configEdit.title" v-model:visible="configEdit.visible" :data="configEdit.config" @val-change="configEditChange" />
</div>
</template>
@@ -62,6 +111,12 @@ export default defineComponent({
configs: [],
chooseId: null,
chooseData: null,
paramsDialog: {
visible: false,
config: null as any,
params: {},
paramsFormItem: [] as any,
},
configEdit: {
title: '配置修改',
visible: false,
@@ -84,6 +139,62 @@ export default defineComponent({
search();
};
const showSetConfigDialog = (row: any) => {
state.paramsDialog.config = row;
// 存在配置项则弹窗提示输入对应的配置项
if (row.params) {
state.paramsDialog.paramsFormItem = JSON.parse(row.params);
if (state.paramsDialog.paramsFormItem && state.paramsDialog.paramsFormItem.length > 0) {
if (row.value) {
state.paramsDialog.params = JSON.parse(row.value);
}
}
} else {
state.paramsDialog.params = row.value;
}
state.paramsDialog.visible = true;
};
const closeSetConfigDialog = () => {
state.paramsDialog.visible = false;
setTimeout(() => {
state.paramsDialog.config = {};
state.paramsDialog.params = {};
state.paramsDialog.paramsFormItem = [];
}, 300);
};
const setConfig = async () => {
let paramsValue = state.paramsDialog.params;
if (state.paramsDialog.paramsFormItem.length > 0) {
// 如果配置项删除则需要将value中对应的字段移除
for (let paramKey in paramsValue) {
if (!hasParam(paramKey, state.paramsDialog.paramsFormItem)) {
delete paramsValue[paramKey];
}
}
paramsValue = JSON.stringify(paramsValue);
}
await configApi.save.request({
id: state.paramsDialog.config.id,
key: state.paramsDialog.config.key,
name: state.paramsDialog.config.name,
value: paramsValue,
});
ElMessage.success('保存成功');
closeSetConfigDialog();
search();
};
const hasParam = (paramKey: string, paramItems: any) => {
for (let paramItem of paramItems) {
if (paramItem.model == paramKey) {
return true;
}
}
return false;
};
const choose = (item: any) => {
if (!item) {
return;
@@ -93,7 +204,7 @@ export default defineComponent({
};
const configEditChange = () => {
ElMessage.success('修改成功');
ElMessage.success('保存成功');
state.chooseId = null;
state.chooseData = null;
search();
@@ -111,6 +222,9 @@ export default defineComponent({
return {
...toRefs(state),
showSetConfigDialog,
closeSetConfigDialog,
setConfig,
search,
handlePageChange,
choose,

View File

@@ -15,7 +15,7 @@ require (
github.com/sirupsen/logrus v1.9.0
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.9.1 // mongo
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // ssh
golang.org/x/crypto v0.1.0 // ssh
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.4.1
@@ -50,10 +50,10 @@ require (
github.com/xdg-go/stringprep v1.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -7,9 +7,9 @@ import (
db_router "mayfly-go/internal/db/router"
machine_router "mayfly-go/internal/machine/router"
mongo_router "mayfly-go/internal/mongo/router"
project_router "mayfly-go/internal/project/router"
redis_router "mayfly-go/internal/redis/router"
sys_router "mayfly-go/internal/sys/router"
tag_router "mayfly-go/internal/tag/router"
"mayfly-go/pkg/config"
"mayfly-go/pkg/middleware"
"mayfly-go/static"
@@ -74,7 +74,8 @@ func InitRouter() *gin.Engine {
sys_router.Init(api)
project_router.Init(api)
// project_router.Init(api)
tag_router.Init(api)
machine_router.Init(api)
db_router.Init(api)
redis_router.Init(api)

View File

@@ -3,28 +3,43 @@ package api
import (
dbapp "mayfly-go/internal/db/application"
dbentity "mayfly-go/internal/db/domain/entity"
machineentity "mayfly-go/internal/machine/domain/entity"
projectentity "mayfly-go/internal/project/domain/entity"
redisentity "mayfly-go/internal/redis/domain/entity"
machineapp "mayfly-go/internal/machine/application"
projectapp "mayfly-go/internal/project/application"
machineentity "mayfly-go/internal/machine/domain/entity"
mongoapp "mayfly-go/internal/mongo/application"
mongoentity "mayfly-go/internal/mongo/domain/entity"
redisapp "mayfly-go/internal/redis/application"
redisentity "mayfly-go/internal/redis/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
)
type Index struct {
ProjectApp projectapp.Project
TagApp tagapp.TagTree
MachineApp machineapp.Machine
DbApp dbapp.Db
RedisApp redisapp.Redis
MongoApp mongoapp.Mongo
}
func (i *Index) Count(rc *ctx.ReqCtx) {
accountId := rc.LoginAccount.Id
tagIds := i.TagApp.ListTagIdByAccountId(accountId)
var mongoNum int64
var redisNum int64
var dbNum int64
var machienNum int64
if len(tagIds) > 0 {
mongoNum = i.MongoApp.Count(&mongoentity.MongoQuery{TagIds: tagIds})
machienNum = i.MachineApp.Count(&machineentity.MachineQuery{TagIds: tagIds})
dbNum = i.DbApp.Count(&dbentity.DbQuery{TagIds: tagIds})
redisNum = i.RedisApp.Count(&redisentity.RedisQuery{TagIds: tagIds})
}
rc.ResData = map[string]interface{}{
"projectNum": i.ProjectApp.Count(new(projectentity.Project)),
"machineNum": i.MachineApp.Count(new(machineentity.Machine)),
"dbNum": i.DbApp.Count(new(dbentity.Db)),
"redisNum": i.RedisApp.Count(new(redisentity.Redis)),
"mongoNum": mongoNum,
"machineNum": machienNum,
"dbNum": dbNum,
"redisNum": redisNum,
}
}

View File

@@ -4,8 +4,9 @@ import (
"mayfly-go/internal/common/api"
dbapp "mayfly-go/internal/db/application"
machineapp "mayfly-go/internal/machine/application"
projectapp "mayfly-go/internal/project/application"
mongoapp "mayfly-go/internal/mongo/application"
redisapp "mayfly-go/internal/redis/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -14,10 +15,11 @@ import (
func InitIndexRouter(router *gin.RouterGroup) {
index := router.Group("common/index")
i := &api.Index{
ProjectApp: projectapp.GetProjectApp(),
TagApp: tagapp.GetTagTreeApp(),
MachineApp: machineapp.GetMachineApp(),
DbApp: dbapp.GetDbApp(),
RedisApp: redisapp.GetRedisApp(),
MongoApp: mongoapp.GetMongoApp(),
}
{
// 首页基本信息统计

View File

@@ -7,8 +7,8 @@ import (
"mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/domain/entity"
projectapp "mayfly-go/internal/project/application"
sysapp "mayfly-go/internal/sys/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
@@ -27,19 +27,24 @@ type Db struct {
DbApp application.Db
DbSqlExecApp application.DbSqlExec
MsgApp sysapp.Msg
ProjectApp projectapp.Project
TagApp tagapp.TagTree
}
const DEFAULT_ROW_SIZE = 1800
// @router /api/dbs [get]
func (d *Db) Dbs(rc *ctx.ReqCtx) {
g := rc.GinCtx
m := &entity.Db{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
condition := new(entity.DbQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
// 不存在可访问标签id即没有可操作数据
tagIds := d.TagApp.ListTagIdByAccountId(rc.LoginAccount.Id)
if len(tagIds) == 0 {
rc.ResData = model.EmptyPageResult()
return
}
m.CreatorId = rc.LoginAccount.Id
rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
condition.TagIds = tagIds
rc.ResData = d.DbApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
}
func (d *Db) Save(rc *ctx.ReqCtx) {
@@ -121,7 +126,7 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
id := GetDbId(g)
db := form.Db
dbInstance := d.DbApp.GetDbInstance(id, db)
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
// 去除前后空格及换行符
sql := strings.TrimFunc(form.Sql, func(r rune) bool {
@@ -163,7 +168,7 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
db := d.DbApp.GetDbInstance(dbId, db)
dbEntity := d.DbApp.GetById(dbId)
dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.Env)
dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.TagPath)
defer func() {
if err := recover(); err != nil {
@@ -174,7 +179,7 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
}
}()
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, db.TagPath), "%s")
tokens := sqlparser.NewTokenizer(file)
for {
@@ -208,7 +213,7 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
needData := dumpType == "2" || dumpType == "3"
dbInstance := d.DbApp.GetDbInstance(dbId, db)
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
now := time.Now()
filename := fmt.Sprintf("%s.%s.sql", db, now.Format("200601021504"))
@@ -294,7 +299,7 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
// @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbi.TagPath), "%s")
rc.ResData = dbi.GetMeta().GetTables()
}
@@ -305,14 +310,14 @@ func (d *Db) ColumnMA(rc *ctx.ReqCtx) {
biz.NotEmpty(tn, "tableName不能为空")
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
// biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
rc.ResData = dbi.GetMeta().GetColumns(tn)
}
// @router /api/db/:dbId/hint-tables [get]
func (d *Db) HintTables(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
// biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
dm := dbi.GetMeta()
// 获取所有表

View File

@@ -1,19 +1,18 @@
package form
type DbForm struct {
Id uint64
Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"`
Port int `binding:"required" json:"port"`
Username string `binding:"required" json:"username"`
Password string `json:"password"`
Params string `json:"params"`
Database string `json:"database"`
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
Id uint64
Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"`
Port int `binding:"required" json:"port"`
Username string `binding:"required" json:"username"`
Password string `json:"password"`
Params string `json:"params"`
Database string `json:"database"`
Remark string `json:"remark"`
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
EnableSshTunnel int8 `json:"enableSshTunnel"`
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"`

View File

@@ -12,10 +12,9 @@ type SelectDataDbVO struct {
Params *string `json:"params"`
Database *string `json:"database"`
Username *string `json:"username"`
ProjectId *int64 `json:"projectId"`
Project *string `json:"project"`
Env *string `json:"env"`
EnvId *int64 `json:"envId"`
Remark *string `json:"remark"`
TagId *int64 `json:"tagId"`
TagPath *string `json:"tagPath"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`

View File

@@ -27,9 +27,9 @@ import (
type Db interface {
// 分页获取
GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Db) int64
Count(condition *entity.DbQuery) int64
// 根据条件获取
GetDbBy(condition *entity.Db, cols ...string) error
@@ -64,11 +64,11 @@ type dbAppImpl struct {
}
// 分页获取数据库信息列表
func (d *dbAppImpl) GetPageList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return d.dbRepo.GetDbList(condition, pageParam, toEntity, orderBy...)
}
func (d *dbAppImpl) Count(condition *entity.Db) int64 {
func (d *dbAppImpl) Count(condition *entity.DbQuery) int64 {
return d.dbRepo.Count(condition)
}
@@ -92,7 +92,7 @@ func (d *dbAppImpl) Save(dbEntity *entity.Db) {
}
// 查找是否存在该库
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, EnvId: dbEntity.EnvId}
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, TagId: dbEntity.TagId}
err := d.GetDbBy(oldDb)
if dbEntity.Id == 0 {
@@ -194,7 +194,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
cacheKey := GetDbCacheKey(id, db)
dbi := &DbInstance{Id: cacheKey, Type: d.Type, ProjectId: d.ProjectId, sshTunnelMachineId: d.SshTunnelMachineId}
dbi := &DbInstance{Id: cacheKey, Type: d.Type, TagPath: d.TagPath, sshTunnelMachineId: d.SshTunnelMachineId}
DB, err := GetDbConn(d, db)
if err != nil {
@@ -206,7 +206,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
// 最大连接周期超过时间的连接就close
// DB.SetConnMaxLifetime(100 * time.Second)
// 设置最大连接数
DB.SetMaxOpenConns(2)
DB.SetMaxOpenConns(5)
// 设置闲置连接数
DB.SetMaxIdleConns(1)
@@ -224,7 +224,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
type DbInstance struct {
Id string
Type string
ProjectId uint64
TagPath string
db *sql.DB
sshTunnelMachineId uint64
}

View File

@@ -20,6 +20,7 @@ const (
PGSQL_COLUMN_MA = `SELECT
C.relname AS "tableName",
A.attname AS "columnName",
tc.is_nullable AS "nullable",
concat_ws ( '', t.typname, SUBSTRING ( format_type ( a.atttypid, a.atttypmod ) FROM '\(.*\)' ) ) AS "columnType",
d.description AS "columnComment"
FROM
@@ -28,6 +29,7 @@ const (
LEFT JOIN pg_class c ON A.attrelid = c.oid
LEFT JOIN pg_namespace pn ON c.relnamespace = pn.oid
LEFT JOIN pg_type t ON a.atttypid = t.oid
JOIN information_schema.columns tc ON tc.column_name = a.attname AND tc.table_name = C.relname AND tc.table_schema = pn.nspname
WHERE
A.attnum >= 0
AND pn.nspname = (select current_schema())

View File

@@ -9,19 +9,18 @@ import (
type Db struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
Params string `json:"params"`
ProjectId uint64
Project string
EnvId uint64
Env string
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
Params string `json:"params"`
Remark string `json:"remark"`
TagId uint64
TagPath string
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id

View File

@@ -0,0 +1,24 @@
package entity
import "mayfly-go/pkg/model"
// 数据库查询实体,不与数据库表字段一一对应
type DbQuery struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
Params string `json:"params"`
Remark string `json:"remark"`
TagId uint64
ProjectIds []uint64
TagIds []uint64
TagPathLike string
}

View File

@@ -7,9 +7,9 @@ import (
type Db interface {
// 分页获取机器信息列表
GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Db) int64
Count(condition *entity.DbQuery) int64
// 根据条件获取账号信息
GetDb(condition *entity.Db, cols ...string) error

View File

@@ -6,6 +6,8 @@ import (
"mayfly-go/internal/db/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strings"
)
type dbRepoImpl struct{}
@@ -15,30 +17,33 @@ func newDbRepo() repository.Db {
}
// 分页获取数据库信息列表
func (d *dbRepoImpl) GetDbList(condition *entity.Db, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_db d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
}
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_db d WHERE 1 = 1 "
if condition.Host != "" {
sql = sql + " AND d.host LIKE '%" + condition.Host + "%'"
}
if condition.Database != "" {
sql = sql + " AND d.database LIKE '%" + condition.Database + "%'"
}
if len(condition.TagIds) > 0 {
sql = sql + " AND d.tag_id IN " + fmt.Sprintf("(%s)", strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
}
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (d *dbRepoImpl) Count(condition *entity.Db) int64 {
return model.CountBy(condition)
func (d *dbRepoImpl) Count(condition *entity.DbQuery) int64 {
where := make(map[string]interface{})
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}
if condition.TagId != 0 {
where["tag_id"] = condition.TagId
}
return model.CountByMap(new(entity.Db), where)
}
// 根据条件获取账号信息

View File

@@ -23,5 +23,5 @@ func (d *dbSqlExecRepoImpl) DeleteBy(condition *entity.DbSqlExec) {
// 分页获取
func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}

View File

@@ -3,8 +3,8 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/application"
projectapp "mayfly-go/internal/project/application"
sysapp "mayfly-go/internal/sys/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -17,7 +17,7 @@ func InitDbRouter(router *gin.RouterGroup) {
DbApp: application.GetDbApp(),
DbSqlExecApp: application.GetDbSqlExecApp(),
MsgApp: sysapp.GetMsgApp(),
ProjectApp: projectapp.GetProjectApp(),
TagApp: tagapp.GetTagTreeApp(),
}
// 获取所有数据库列表
db.GET("", func(c *gin.Context) {

View File

@@ -2,8 +2,6 @@ package form
type MachineForm struct {
Id uint64 `json:"id"`
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name string `json:"name" binding:"required"`
Ip string `json:"ip" binding:"required"` // IP地址
Username string `json:"username" binding:"required"` // 用户名
@@ -14,6 +12,8 @@ type MachineForm struct {
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
type MachineRunForm struct {

View File

@@ -8,11 +8,12 @@ import (
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/infrastructure/machine"
projectapp "mayfly-go/internal/project/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/config"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"mayfly-go/pkg/ws"
"os"
@@ -27,16 +28,22 @@ import (
type Machine struct {
MachineApp application.Machine
ProjectApp projectapp.Project
TagApp tagapp.TagTree
}
func (m *Machine) Machines(rc *ctx.ReqCtx) {
condition := new(entity.Machine)
// 使用创建者id模拟账号成员id
condition.CreatorId = rc.LoginAccount.Id
condition := new(entity.MachineQuery)
condition.Ip = rc.GinCtx.Query("ip")
condition.Name = rc.GinCtx.Query("name")
condition.ProjectId = uint64(ginx.QueryInt(rc.GinCtx, "projectId", 0))
condition.TagPathLike = rc.GinCtx.Query("tagPath")
// 不存在可访问标签id即没有可操作数据
tagIds := m.TagApp.ListTagIdByAccountId(rc.LoginAccount.Id)
if len(tagIds) == 0 {
rc.ResData = model.EmptyPageResult()
return
}
condition.TagIds = tagIds
res := m.MachineApp.GetMachineList(condition, ginx.GetPageParam(rc.GinCtx), new([]*vo.MachineVO))
if res.Total == 0 {
@@ -130,7 +137,7 @@ func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
cmd += "| head -n " + count
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().TagPath), "%s")
res, err := cli.Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
@@ -143,7 +150,7 @@ func (m *Machine) KillProcess(rc *ctx.ReqCtx) {
biz.NotEmpty(pid, "进程id不能为空")
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().TagPath), "%s")
_, err := cli.Run("sudo kill -9 " + pid)
biz.ErrIsNilAppendErr(err, "终止进程失败: %s")
@@ -168,7 +175,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
}
cli := m.MachineApp.GetCli(GetMachineId(g))
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().TagPath), "%s")
cols := ginx.QueryInt(g, "cols", 80)
rows := ginx.QueryInt(g, "rows", 40)

View File

@@ -6,7 +6,7 @@ import (
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/domain/entity"
projectapp "mayfly-go/internal/project/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
@@ -19,7 +19,7 @@ import (
type MachineScript struct {
MachineScriptApp application.MachineScript
MachineApp application.Machine
ProjectApp projectapp.Project
TagApp tagapp.TagTree
}
func (m *MachineScript) MachineScripts(rc *ctx.ReqCtx) {
@@ -65,7 +65,7 @@ func (m *MachineScript) RunMachineScript(rc *ctx.ReqCtx) {
script = utils.TemplateParse(ms.Script, utils.Json2Map(params))
}
cli := m.MachineApp.GetCli(machineId)
biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().TagPath), "%s")
res, err := cli.Run(script)
// 记录请求参数

View File

@@ -2,22 +2,8 @@ package vo
import "time"
type AccountVO struct {
//models.BaseModel
Id *int64 `json:"id"`
Username *string `json:"username"`
CreateTime *string `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
// Role *RoleVO `json:"roles"`
//Status int8 `json:"status"`
}
type MachineVO struct {
//models.BaseModel
Id *uint64 `json:"id"`
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name *string `json:"name"`
Username *string `json:"username"`
Ip *string `json:"ip"`
@@ -35,6 +21,8 @@ type MachineVO struct {
HasCli bool `json:"hasCli" gorm:"-"`
Remark *string `json:"remark"`
EnableRecorder int8 `json:"enableRecorder"`
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
type MachineScriptVO struct {

View File

@@ -19,7 +19,7 @@ type Machine interface {
// 调整机器状态
ChangeStatus(id uint64, status int8)
Count(condition *entity.Machine) int64
Count(condition *entity.MachineQuery) int64
Delete(id uint64)
@@ -27,7 +27,7 @@ type Machine interface {
GetById(id uint64, cols ...string) *entity.Machine
// 分页获取机器信息列表
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
// 获取机器连接
GetCli(id uint64) *machine.Cli
@@ -46,11 +46,11 @@ type machineAppImpl struct {
}
// 分页获取机器信息列表
func (m *machineAppImpl) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return m.machineRepo.GetMachineList(condition, pageParam, toEntity, orderBy...)
}
func (m *machineAppImpl) Count(condition *entity.Machine) int64 {
func (m *machineAppImpl) Count(condition *entity.MachineQuery) int64 {
return m.machineRepo.Count(condition)
}

View File

@@ -7,8 +7,7 @@ import (
type Machine struct {
model.Model
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
@@ -20,6 +19,8 @@ type Machine struct {
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
const (

View File

@@ -0,0 +1,16 @@
package entity
import "mayfly-go/pkg/model"
// 机器任务配置
type MachineTaskConfig struct {
model.Model
Name string `json:"name"`
Cron string `json:"cron"` // cron表达式
Script string `json:"script"` // 任务内容
Status string `json:"status"`
EnableNotify int `json:"enableNotify"` // 是否启用通知
NotifyTemplate string `json:"notifyTemplate"` // 通知模板
Remark string `json:"remark"` // 备注
}

View File

@@ -0,0 +1,25 @@
package entity
import "mayfly-go/pkg/model"
type MachineQuery struct {
model.Model
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
AuthMethod int8 `json:"authMethod"` // 授权认证方式
Password string `json:"-"`
Port int `json:"port"` // 端口号
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
ProjectIds []uint64
TagId uint64
TagPathLike string
TagIds []uint64
}

View File

@@ -7,9 +7,9 @@ import (
type Machine interface {
// 分页获取机器信息列表
GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Machine) int64
Count(condition *entity.MachineQuery) int64
// 根据条件获取账号信息
GetMachine(condition *entity.Machine, cols ...string) error

View File

@@ -0,0 +1,22 @@
package repository
import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/pkg/model"
)
type MachineTaskConfig interface {
GetPageList(condition *entity.MachineTaskConfig, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
// 根据条件获取
GetBy(condition *entity.MachineTaskConfig, cols ...string) error
// 根据id获取
GetById(id uint64, cols ...string) *entity.MachineTaskConfig
Delete(id uint64)
Create(entity *entity.MachineTaskConfig)
UpdateById(entity *entity.MachineTaskConfig)
}

View File

@@ -6,6 +6,8 @@ import (
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strings"
)
type machineRepoImpl struct{}
@@ -15,27 +17,34 @@ func newMachineRepo() repository.Machine {
}
// 分页获取机器信息列表
func (m *machineRepoImpl) GetMachineList(condition *entity.Machine, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT m.* FROM t_machine m JOIN t_project_member pm ON m.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND m.project_id = %d", sql, condition.ProjectId)
}
func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT m.* FROM t_machine m WHERE 1 = 1 "
if condition.Ip != "" {
sql = sql + " AND m.ip LIKE '%" + condition.Ip + "%'"
}
if condition.Name != "" {
sql = sql + " AND m.name LIKE '%" + condition.Name + "%'"
}
sql = sql + " ORDER BY m.project_id, m.create_time DESC"
if len(condition.TagIds) > 0 {
sql = fmt.Sprintf("%s AND m.tag_id IN (%s) ", sql, strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
}
if condition.TagPathLike != "" {
sql = sql + " AND m.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY m.tag_id, m.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (m *machineRepoImpl) Count(condition *entity.Machine) int64 {
return model.CountBy(condition)
func (m *machineRepoImpl) Count(condition *entity.MachineQuery) int64 {
where := make(map[string]interface{})
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}
if condition.TagId != 0 {
where["tag_id"] = condition.TagId
}
return model.CountByMap(new(entity.Machine), where)
}
// 根据条件获取账号信息

View File

@@ -15,7 +15,7 @@ func newMachineFileRepo() repository.MachineFile {
// 分页获取机器文件信息列表
func (m *machineFileRepoImpl) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息

View File

@@ -15,7 +15,7 @@ func newMachineScriptRepo() repository.MachineScript {
// 分页获取机器信息列表
func (m *machineScriptRepoImpl) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}
// 根据条件获取账号信息

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
projectapp "mayfly-go/internal/project/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -12,7 +12,7 @@ import (
func InitMachineRouter(router *gin.RouterGroup) {
m := &api.Machine{
MachineApp: application.GetMachineApp(),
ProjectApp: projectapp.GetProjectApp(),
TagApp: tagapp.GetTagTreeApp(),
}
machines := router.Group("machines")

View File

@@ -3,7 +3,7 @@ package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
projectapp "mayfly-go/internal/project/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -15,7 +15,7 @@ func InitMachineScriptRouter(router *gin.RouterGroup) {
ms := &api.MachineScript{
MachineScriptApp: application.GetMachineScriptApp(),
MachineApp: application.GetMachineApp(),
ProjectApp: projectapp.GetProjectApp(),
TagApp: tagapp.GetTagTreeApp(),
}
// 获取指定机器脚本列表

View File

@@ -6,10 +6,8 @@ type Mongo struct {
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `binding:"required" json:"name"`
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
TagId uint64 `binding:"required" json:"tagId"`
TagPath string `json:"tagPath"`
}
type MongoCommand struct {

View File

@@ -5,9 +5,11 @@ import (
"mayfly-go/internal/mongo/api/form"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"regexp"
"strconv"
@@ -20,15 +22,21 @@ import (
type Mongo struct {
MongoApp application.Mongo
TagApp tagapp.TagTree
}
func (m *Mongo) Mongos(rc *ctx.ReqCtx) {
g := rc.GinCtx
mc := &entity.Mongo{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
condition := new(entity.MongoQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
// 不存在可访问标签id即没有可操作数据
tagIds := m.TagApp.ListTagIdByAccountId(rc.LoginAccount.Id)
if len(tagIds) == 0 {
rc.ResData = model.EmptyPageResult()
return
}
mc.CreatorId = rc.LoginAccount.Id
rc.ResData = m.MongoApp.GetPageList(mc, ginx.GetPageParam(rc.GinCtx), new([]entity.Mongo))
condition.TagIds = tagIds
rc.ResData = m.MongoApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]entity.Mongo))
}
func (m *Mongo) Save(rc *ctx.ReqCtx) {

View File

@@ -22,9 +22,9 @@ import (
type Mongo interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Mongo) int64
Count(condition *entity.MongoQuery) int64
// 根据条件获取
GetBy(condition *entity.Mongo, cols ...string) error
@@ -53,11 +53,11 @@ type mongoAppImpl struct {
}
// 分页获取数据库信息列表
func (d *mongoAppImpl) GetPageList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *mongoAppImpl) GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return d.mongoRepo.GetList(condition, pageParam, toEntity, orderBy...)
}
func (d *mongoAppImpl) Count(condition *entity.Mongo) int64 {
func (d *mongoAppImpl) Count(condition *entity.MongoQuery) int64 {
return d.mongoRepo.Count(condition)
}
@@ -141,7 +141,7 @@ func DeleteMongoCache(mongoId uint64) {
type MongoInstance struct {
Id uint64
ProjectId uint64
TagPath string
Cli *mongo.Client
sshTunnelMachineId uint64
}
@@ -160,7 +160,7 @@ func connect(me *entity.Mongo) (*MongoInstance, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
mongoInstance := &MongoInstance{Id: me.Id, ProjectId: me.ProjectId}
mongoInstance := &MongoInstance{Id: me.Id, TagPath: me.TagPath}
mongoOptions := options.Client().ApplyURI(me.Uri).
SetMaxPoolSize(1)

View File

@@ -9,8 +9,6 @@ type Mongo struct {
Uri string `orm:"column(uri)" json:"uri"`
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
ProjectId uint64 `json:"projectId"`
Project string `json:"project"`
EnvId uint64 `json:"envId"`
Env string `json:"env"`
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}

View File

@@ -0,0 +1,17 @@
package entity
import "mayfly-go/pkg/model"
type MongoQuery struct {
model.Model
Name string
Uri string
EnableSshTunnel int8 // 是否启用ssh隧道
SshTunnelMachineId uint64 // ssh隧道机器id
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
TagIds []uint64
TagPathLike string
}

View File

@@ -7,9 +7,9 @@ import (
type Mongo interface {
// 分页获取列表
GetList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Mongo) int64
Count(condition *entity.MongoQuery) int64
// 根据条件获取
Get(condition *entity.Mongo, cols ...string) error

View File

@@ -6,6 +6,8 @@ import (
"mayfly-go/internal/mongo/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strings"
)
type mongoRepoImpl struct{}
@@ -15,24 +17,28 @@ func newMongoRepo() repository.Mongo {
}
// 分页获取数据库信息列表
func (d *mongoRepoImpl) GetList(condition *entity.Mongo, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_mongo d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_mongo d WHERE 1=1 "
if len(condition.TagIds) > 0 {
sql = sql + " AND d.tag_id IN " + fmt.Sprintf("(%s)", strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (d *mongoRepoImpl) Count(condition *entity.Mongo) int64 {
return model.CountBy(condition)
func (d *mongoRepoImpl) Count(condition *entity.MongoQuery) int64 {
where := make(map[string]interface{})
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}
if condition.TagId != 0 {
where["tag_id"] = condition.TagId
}
return model.CountByMap(new(entity.Mongo), where)
}
// 根据条件获取

View File

@@ -3,6 +3,7 @@ package router
import (
"mayfly-go/internal/mongo/api"
"mayfly-go/internal/mongo/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -13,6 +14,7 @@ func InitMongoRouter(router *gin.RouterGroup) {
{
ma := &api.Mongo{
MongoApp: application.GetMongoApp(),
TagApp: tagapp.GetTagTreeApp(),
}
// 获取所有mongo列表
@@ -60,7 +62,7 @@ func InitMongoRouter(router *gin.RouterGroup) {
})
// 执行mongo update by id命令
updateDocById := ctx.NewLogInfo("mongo-更新文档")
updateDocById := ctx.NewLogInfo("mongo-更新文档").WithSave(true)
m.POST(":id/command/update-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(updateDocById).
@@ -68,7 +70,7 @@ func InitMongoRouter(router *gin.RouterGroup) {
})
// 执行mongo delete by id命令
deleteDoc := ctx.NewLogInfo("mongo-删除文档")
deleteDoc := ctx.NewLogInfo("mongo-删除文档").WithSave(true)
m.POST(":id/command/delete-by-id", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(deleteDoc).
@@ -76,7 +78,7 @@ func InitMongoRouter(router *gin.RouterGroup) {
})
// 执行mongo insert 命令
insertDoc := ctx.NewLogInfo("mongo-新增文档")
insertDoc := ctx.NewLogInfo("mongo-新增文档").WithSave(true)
m.POST(":id/command/insert", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).
WithLog(insertDoc).

View File

@@ -1,110 +0,0 @@
package api
import (
"fmt"
"mayfly-go/internal/project/api/vo"
projectapp "mayfly-go/internal/project/application"
"mayfly-go/internal/project/domain/entity"
sys_applicaiton "mayfly-go/internal/sys/application"
sys_entity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
)
type Project struct {
ProjectApp projectapp.Project
AccountApp sys_applicaiton.Account
}
// 获取当前登录用户可以访问的项目列表
func (p *Project) GetProjectsByLoginAccount(rc *ctx.ReqCtx) {
// 获取登录用户拥有的项目ids
projectMembers := &[]entity.ProjectMember{}
p.ProjectApp.ListMember(&entity.ProjectMember{AccountId: rc.LoginAccount.Id}, projectMembers)
var pids []uint64
for _, pm := range *projectMembers {
pids = append(pids, pm.ProjectId)
}
// 获取项目信息
projects := &vo.AccountProjects{}
p.ProjectApp.ListProjectByIds(pids, projects)
rc.ResData = projects
}
func (p *Project) GetProjects(rc *ctx.ReqCtx) {
condition := &entity.Project{}
ginx.BindQuery(rc.GinCtx, condition)
// condition.Name = rc.GinCtx.Query("name")
rc.ResData = p.ProjectApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]entity.Project))
}
func (p *Project) SaveProject(rc *ctx.ReqCtx) {
project := &entity.Project{}
ginx.BindJsonAndValid(rc.GinCtx, project)
rc.ReqParam = fmt.Sprintf("projectId: %d, projectName: %s, remark: %s", project.Id, project.Name, project.Remark)
project.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProject(project)
}
func (p *Project) DelProject(rc *ctx.ReqCtx) {
p.ProjectApp.DelProject(uint64(ginx.QueryInt(rc.GinCtx, "id", 0)))
}
func (p *Project) DelProjectEnv(rc *ctx.ReqCtx) {
p.ProjectApp.DelProjectEnv(uint64(ginx.QueryInt(rc.GinCtx, "id", 0)))
}
// 获取项目下的环境信息
func (p *Project) GetProjectEnvs(rc *ctx.ReqCtx) {
projectEnvs := &[]entity.ProjectEnv{}
p.ProjectApp.ListEnvByProjectId(uint64(ginx.PathParamInt(rc.GinCtx, "projectId")), projectEnvs)
rc.ResData = projectEnvs
}
// 保存项目下的环境信息
func (p *Project) SaveProjectEnvs(rc *ctx.ReqCtx) {
projectEnv := &entity.ProjectEnv{}
ginx.BindJsonAndValid(rc.GinCtx, projectEnv)
rc.ReqParam = projectEnv
projectEnv.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProjectEnv(projectEnv)
}
// 获取项目下的成员信息
func (p *Project) GetProjectMembers(rc *ctx.ReqCtx) {
projectMems := &[]entity.ProjectMember{}
rc.ResData = p.ProjectApp.GetMemberPage(&entity.ProjectMember{ProjectId: uint64(ginx.PathParamInt(rc.GinCtx, "projectId"))},
ginx.GetPageParam(rc.GinCtx), projectMems)
}
// 保存项目的成员信息
func (p *Project) SaveProjectMember(rc *ctx.ReqCtx) {
projectMem := &entity.ProjectMember{}
ginx.BindJsonAndValid(rc.GinCtx, projectMem)
rc.ReqParam = fmt.Sprintf("projectId: %d, username: %s", projectMem.ProjectId, projectMem.Username)
// 校验账号并赋值username
account := &sys_entity.Account{}
account.Id = projectMem.AccountId
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
projectMem.Username = account.Username
projectMem.SetBaseInfo(rc.LoginAccount)
p.ProjectApp.SaveProjectMember(projectMem)
}
// 删除项目成员
func (p *Project) DelProjectMember(rc *ctx.ReqCtx) {
g := rc.GinCtx
pid := ginx.PathParamInt(g, "projectId")
aid := ginx.PathParamInt(g, "accountId")
rc.ReqParam = fmt.Sprintf("projectId: %d, accountId: %d", pid, aid)
p.ProjectApp.DeleteMember(uint64(pid), uint64(aid))
}

View File

@@ -1,25 +0,0 @@
package application
import (
dbapp "mayfly-go/internal/db/application"
machineapp "mayfly-go/internal/machine/application"
mongoapp "mayfly-go/internal/mongo/application"
"mayfly-go/internal/project/infrastructure/persistence"
redisapp "mayfly-go/internal/redis/application"
)
var (
projectApp Project = newProjectApp(
persistence.GetProjectRepo(),
persistence.GetProjectEnvRepo(),
persistence.GetProjectMemberRepo(),
machineapp.GetMachineApp(),
redisapp.GetRedisApp(),
dbapp.GetDbApp(),
mongoapp.GetMongoApp(),
)
)
func GetProjectApp() Project {
return projectApp
}

View File

@@ -1,156 +0,0 @@
package application
import (
dbapp "mayfly-go/internal/db/application"
dbentity "mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
machineentity "mayfly-go/internal/machine/domain/entity"
mongoapp "mayfly-go/internal/mongo/application"
mongoentity "mayfly-go/internal/mongo/domain/entity"
"mayfly-go/internal/project/domain/entity"
"mayfly-go/internal/project/domain/repository"
redisapp "mayfly-go/internal/redis/application"
redisentity "mayfly-go/internal/redis/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type Project interface {
// 分页获取项目信息列表
GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Project) int64
ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string)
SaveProject(project *entity.Project)
DelProject(id uint64)
DelProjectEnv(id uint64)
// 根据项目id获取所有该项目下的环境信息列表
ListEnvByProjectId(projectId uint64, listPtr interface{})
// 保存项目环境信息
SaveProjectEnv(projectEnv *entity.ProjectEnv)
// 根据条件获取项目成员信息
ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string)
SaveProjectMember(pm *entity.ProjectMember)
// 根据条件获取项目成员信息
GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
DeleteMember(projectId, accountId uint64)
// 账号是否有权限访问该项目关联的资源信息
CanAccess(accountId, projectId uint64) error
}
func newProjectApp(projectRepo repository.Project,
projectEnvRepo repository.ProjectEnv,
projectMemberRepo repository.ProjectMemeber,
machineApp machineapp.Machine,
redisApp redisapp.Redis,
dbApp dbapp.Db,
mongoApp mongoapp.Mongo) Project {
return &projectAppImpl{
projectRepo: projectRepo,
projectEnvRepo: projectEnvRepo,
projectMemberRepo: projectMemberRepo,
machineApp: machineApp,
redisApp: redisApp,
dbApp: dbApp,
mongoApp: mongoApp,
}
}
type projectAppImpl struct {
projectRepo repository.Project
projectEnvRepo repository.ProjectEnv
projectMemberRepo repository.ProjectMemeber
machineApp machineapp.Machine
redisApp redisapp.Redis
mongoApp mongoapp.Mongo
dbApp dbapp.Db
}
// 分页获取项目信息列表
func (p *projectAppImpl) GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *projectAppImpl) Count(condition *entity.Project) int64 {
return p.projectRepo.Count(condition)
}
func (p *projectAppImpl) ListProjectByIds(ids []uint64, toEntity interface{}, orderBy ...string) {
p.projectRepo.GetByIdIn(ids, toEntity, orderBy...)
}
func (p *projectAppImpl) SaveProject(project *entity.Project) {
if project.Id == 0 {
p.projectRepo.Save(project)
} else {
// 防止误传导致项目名更新
project.Name = ""
p.projectRepo.Update(project)
}
}
func (p *projectAppImpl) DelProject(id uint64) {
biz.IsTrue(p.machineApp.Count(&machineentity.Machine{ProjectId: id}) == 0, "请先删除该项目关联的机器信息")
biz.IsTrue(p.redisApp.Count(&redisentity.Redis{ProjectId: id}) == 0, "请先删除该项目关联的redis信息")
biz.IsTrue(p.dbApp.Count(&dbentity.Db{ProjectId: id}) == 0, "请先删除该项目关联的数据库信息")
biz.IsTrue(p.mongoApp.Count(&mongoentity.Mongo{ProjectId: id}) == 0, "请先删除该项目关联的Mongo信息")
p.projectRepo.Delete(id)
p.projectEnvRepo.DeleteEnvs(id)
p.projectMemberRepo.DeleteMems(id)
}
// 根据项目id获取所有该项目下的环境信息列表
func (p *projectAppImpl) ListEnvByProjectId(projectId uint64, listPtr interface{}) {
p.projectEnvRepo.ListEnv(&entity.ProjectEnv{ProjectId: projectId}, listPtr)
}
// 保存项目环境信息
func (p *projectAppImpl) SaveProjectEnv(projectEnv *entity.ProjectEnv) {
p.projectEnvRepo.Save(projectEnv)
}
// 删除项目环境信息
func (p *projectAppImpl) DelProjectEnv(id uint64) {
biz.IsTrue(p.redisApp.Count(&redisentity.Redis{EnvId: id}) == 0, "请先删除该项目环境关联的redis信息")
biz.IsTrue(p.dbApp.Count(&dbentity.Db{EnvId: id}) == 0, "请先删除该项目环境关联的数据库信息")
biz.IsTrue(p.mongoApp.Count(&mongoentity.Mongo{ProjectId: id}) == 0, "请先删除该项目环境关联的Mongo信息")
p.projectEnvRepo.DeleteEnv(id)
}
// 根据条件获取项目成员信息
func (p *projectAppImpl) ListMember(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string) {
p.projectMemberRepo.ListMemeber(condition, toEntity, orderBy...)
}
func (p *projectAppImpl) SaveProjectMember(pm *entity.ProjectMember) {
pms := new([]entity.ProjectMember)
p.ListMember(&entity.ProjectMember{ProjectId: pm.ProjectId, AccountId: pm.AccountId}, pms)
biz.IsTrue(len(*pms) == 0, "该成员已存在")
p.projectMemberRepo.Save(pm)
}
func (p *projectAppImpl) GetMemberPage(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *projectAppImpl) DeleteMember(projectId, accountId uint64) {
p.projectMemberRepo.DeleteByPidMid(projectId, accountId)
}
func (p *projectAppImpl) CanAccess(accountId, projectId uint64) error {
if p.projectMemberRepo.IsExist(projectId, accountId) {
return nil
}
return biz.NewBizErr("您无权操作该资源")
}

View File

@@ -1,11 +0,0 @@
package entity
import "mayfly-go/pkg/model"
// 项目环境
type ProjectEnv struct {
model.Model
Name string `json:"name"` // 环境名
ProjectId uint64 `json:"projectId"` // 项目id
Remark string `json:"remark"` // 备注说明
}

View File

@@ -1,11 +0,0 @@
package entity
import "mayfly-go/pkg/model"
// 项目成员,用于对项目下组件的访问控制
type ProjectMember struct {
model.Model
AccountId uint64 `json:"accountId"` // 账号
Username string `json:"username"` // 账号用户名
ProjectId uint64 `json:"projectId"` // 项目id
}

View File

@@ -1,20 +0,0 @@
package repository
import (
"mayfly-go/internal/project/domain/entity"
"mayfly-go/pkg/model"
)
type Project interface {
GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Project) int64
GetByIdIn(ids []uint64, toEntity interface{}, orderBy ...string)
Save(p *entity.Project)
Update(project *entity.Project)
Delete(id uint64)
}

View File

@@ -1,14 +0,0 @@
package repository
import "mayfly-go/internal/project/domain/entity"
type ProjectEnv interface {
// 获取项目环境列表
ListEnv(condition *entity.ProjectEnv, toEntity interface{}, orderBy ...string)
Save(entity *entity.ProjectEnv)
DeleteEnvs(projectId uint64)
DeleteEnv(envId uint64)
}

View File

@@ -1,24 +0,0 @@
package repository
import (
"mayfly-go/internal/project/domain/entity"
"mayfly-go/pkg/model"
)
type ProjectMemeber interface {
// 获取项目成员列表
ListMemeber(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string)
Save(mp *entity.ProjectMember)
GetPageList(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
// 根据成员id和项目id删除关联关系
DeleteByPidMid(projectId, accountId uint64)
DeleteMems(projectId uint64)
// 是否存在指定的项目成员关联信息
IsExist(projectId, accountId uint64) bool
}

View File

@@ -1,21 +0,0 @@
package persistence
import "mayfly-go/internal/project/domain/repository"
var (
projectRepo repository.Project = newProjectRepo()
projectEnvRepo repository.ProjectEnv = newProjectEnvRepo()
projectMemberRepo repository.ProjectMemeber = newProjectMemberRepo()
)
func GetProjectRepo() repository.Project {
return projectRepo
}
func GetProjectEnvRepo() repository.ProjectEnv {
return projectEnvRepo
}
func GetProjectMemberRepo() repository.ProjectMemeber {
return projectMemberRepo
}

View File

@@ -1,38 +0,0 @@
package persistence
import (
"mayfly-go/internal/project/domain/entity"
"mayfly-go/internal/project/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectRepoImpl struct{}
func newProjectRepo() repository.Project {
return new(projectRepoImpl)
}
func (p *projectRepoImpl) GetPageList(condition *entity.Project, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
func (p *projectRepoImpl) Count(condition *entity.Project) int64 {
return model.CountBy(condition)
}
func (p *projectRepoImpl) GetByIdIn(ids []uint64, toEntity interface{}, orderBy ...string) {
model.GetByIdIn(new(entity.Project), toEntity, ids, orderBy...)
}
func (p *projectRepoImpl) Save(project *entity.Project) {
biz.ErrIsNil(model.Insert(project), "保存项目失败")
}
func (p *projectRepoImpl) Update(project *entity.Project) {
biz.ErrIsNil(model.UpdateById(project), "更新项目信息")
}
func (p *projectRepoImpl) Delete(id uint64) {
model.DeleteById(new(entity.Project), id)
}

View File

@@ -1,30 +0,0 @@
package persistence
import (
"mayfly-go/internal/project/domain/entity"
"mayfly-go/internal/project/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectEnvRepoImpl struct{}
func newProjectEnvRepo() repository.ProjectEnv {
return new(projectEnvRepoImpl)
}
func (p *projectEnvRepoImpl) ListEnv(condition *entity.ProjectEnv, toEntity interface{}, orderBy ...string) {
model.ListByOrder(condition, toEntity, orderBy...)
}
func (p *projectEnvRepoImpl) Save(entity *entity.ProjectEnv) {
biz.ErrIsNilAppendErr(model.Insert(entity), "保存环境失败:%s")
}
func (p *projectEnvRepoImpl) DeleteEnvs(projectId uint64) {
model.DeleteByCondition(&entity.ProjectEnv{ProjectId: projectId})
}
func (p *projectEnvRepoImpl) DeleteEnv(envId uint64) {
model.DeleteById(new(entity.ProjectEnv), envId)
}

View File

@@ -1,38 +0,0 @@
package persistence
import (
"mayfly-go/internal/project/domain/entity"
"mayfly-go/internal/project/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type projectMemberRepoImpl struct{}
func newProjectMemberRepo() repository.ProjectMemeber {
return new(projectMemberRepoImpl)
}
func (p *projectMemberRepoImpl) ListMemeber(condition *entity.ProjectMember, toEntity interface{}, orderBy ...string) {
model.ListByOrder(condition, toEntity, orderBy...)
}
func (p *projectMemberRepoImpl) Save(pm *entity.ProjectMember) {
biz.ErrIsNilAppendErr(model.Insert(pm), "保存项目成员失败:%s")
}
func (p *projectMemberRepoImpl) GetPageList(condition *entity.ProjectMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
}
func (p *projectMemberRepoImpl) DeleteByPidMid(projectId, accountId uint64) {
model.DeleteByCondition(&entity.ProjectMember{ProjectId: projectId, AccountId: accountId})
}
func (p *projectMemberRepoImpl) DeleteMems(projectId uint64) {
model.DeleteByCondition(&entity.ProjectMember{ProjectId: projectId})
}
func (p *projectMemberRepoImpl) IsExist(projectId, accountId uint64) bool {
return model.CountBy(&entity.ProjectMember{ProjectId: projectId, AccountId: accountId}) > 0
}

View File

@@ -1,90 +0,0 @@
package router
import (
"mayfly-go/internal/project/api"
projectapp "mayfly-go/internal/project/application"
sysapp "mayfly-go/internal/sys/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
)
func InitProjectRouter(router *gin.RouterGroup) {
m := &api.Project{
ProjectApp: projectapp.GetProjectApp(),
AccountApp: sysapp.GetAccountApp()}
project := router.Group("/projects")
{
router.GET("/accounts/projects", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectsByLoginAccount)
})
// 获取项目列表
project.GET("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjects)
})
saveProjectLog := ctx.NewLogInfo("保存项目信息").WithSave(true)
savePP := ctx.NewPermission("project:save")
// 保存项目下的环境信息
project.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectLog).
WithRequiredPermission(savePP).
Handle(m.SaveProject)
})
delProjectLog := ctx.NewLogInfo("删除项目信息").WithSave(true)
delPP := ctx.NewPermission("project:del")
// 删除项目
project.DELETE("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectLog).
WithRequiredPermission(delPP).
Handle(m.DelProject)
})
// 获取项目下的环境信息列表
project.GET("/:projectId/envs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectEnvs)
})
saveProjectEnvLog := ctx.NewLogInfo("新增项目环境信息").WithSave(true)
savePeP := ctx.NewPermission("project:env:add")
// 保存项目下的环境信息
project.POST("/:projectId/envs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectEnvLog).
WithRequiredPermission(savePeP).
Handle(m.SaveProjectEnvs)
})
delProjectEnvLog := ctx.NewLogInfo("删除项目环境信息").WithSave(true)
project.DELETE("/envs", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectEnvLog).
WithRequiredPermission(delPP).
Handle(m.DelProjectEnv)
})
// 获取项目下的成员信息列表
project.GET("/:projectId/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(m.GetProjectMembers)
})
// 保存项目成员
saveProjectMemLog := ctx.NewLogInfo("新增项目成员").WithSave(true)
savePmP := ctx.NewPermission("project:member:add")
project.POST("/:projectId/members", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(saveProjectMemLog).
WithRequiredPermission(savePmP).
Handle(m.SaveProjectMember)
})
// 删除项目成员
delProjectMemLog := ctx.NewLogInfo("删除项目成员").WithSave(true)
savePmdP := ctx.NewPermission("project:member:del")
project.DELETE("/:projectId/members/:accountId", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).WithLog(delProjectMemLog).
WithRequiredPermission(savePmdP).
Handle(m.DelProjectMember)
})
}
}

View File

@@ -2,16 +2,15 @@ package form
type Redis struct {
Id uint64
Name string `json:"name"`
Host string `binding:"required" json:"host"`
Password string `json:"password"`
Mode string `json:"mode"`
Db string `json:"db"`
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"`
Env string `json:"env"`
EnvId uint64 `binding:"required" json:"envId"`
TagId uint64 `binding:"required" json:"tagId"`
TagPath string `json:"tagPath"`
Remark string `json:"remark"`
}

View File

@@ -2,14 +2,15 @@ package api
import (
"context"
projectapp "mayfly-go/internal/project/application"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/api/vo"
"mayfly-go/internal/redis/application"
"mayfly-go/internal/redis/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strconv"
"strings"
@@ -20,17 +21,22 @@ import (
)
type Redis struct {
RedisApp application.Redis
ProjectApp projectapp.Project
RedisApp application.Redis
TagApp tagapp.TagTree
}
func (r *Redis) RedisList(rc *ctx.ReqCtx) {
g := rc.GinCtx
m := &entity.Redis{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
condition := new(entity.RedisQuery)
condition.TagPathLike = rc.GinCtx.Query("tagPath")
// 不存在可访问标签id即没有可操作数据
tagIds := r.TagApp.ListTagIdByAccountId(rc.LoginAccount.Id)
if len(tagIds) == 0 {
rc.ResData = model.EmptyPageResult()
return
}
m.CreatorId = rc.LoginAccount.Id
rc.ResData = r.RedisApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.Redis))
condition.TagIds = tagIds
rc.ResData = r.RedisApp.GetPageList(condition, ginx.GetPageParam(rc.GinCtx), new([]vo.Redis))
}
func (r *Redis) Save(rc *ctx.ReqCtx) {
@@ -184,7 +190,7 @@ func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
func (r *Redis) Scan(rc *ctx.ReqCtx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
form := &form.RedisScanForm{}
ginx.BindJsonAndValid(rc.GinCtx, form)
@@ -263,7 +269,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
biz.NotEmpty(key, "key不能为空")
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
rc.ReqParam = key
ri.GetCmdable().Del(context.Background(), key)
@@ -275,7 +281,7 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
biz.NotEmpty(key, "key不能为空")
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
return ri, key
}
@@ -293,7 +299,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
ginx.BindJsonAndValid(g, keyValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
str, err := ri.GetCmdable().Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
@@ -345,7 +351,7 @@ func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
ginx.BindJsonAndValid(g, hashValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
cmd := ri.GetCmdable()
key := hashValue.Key
@@ -372,7 +378,7 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
ginx.BindJsonAndValid(g, keyvalue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
cmd := ri.GetCmdable()
key := keyvalue.Key
@@ -411,7 +417,7 @@ func (r *Redis) SaveListValue(rc *ctx.ReqCtx) {
ginx.BindJsonAndValid(g, listValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
cmd := ri.GetCmdable()
key := listValue.Key
@@ -431,7 +437,7 @@ func (r *Redis) SetListValue(rc *ctx.ReqCtx) {
ginx.BindJsonAndValid(g, listSetValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.TagPath), "%s")
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
biz.ErrIsNilAppendErr(err, "list set失败: %s")

View File

@@ -3,8 +3,8 @@ package vo
import "time"
type Redis struct {
Id *int64 `json:"id"`
// Name *string `json:"name"`
Id *int64 `json:"id"`
Name *string `json:"name"`
Host *string `json:"host"`
Db string `json:"db"`
ProjectId *int64 `json:"projectId"`
@@ -13,8 +13,8 @@ type Redis struct {
EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
Remark *string `json:"remark"`
Env *string `json:"env"`
EnvId *int64 `json:"envId"`
TagId *uint64 `json:"tagId"`
TagPath *string `json:"tagPath"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`

View File

@@ -23,9 +23,9 @@ import (
type Redis interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Redis) int64
Count(condition *entity.RedisQuery) int64
// 根据id获取
GetById(id uint64, cols ...string) *entity.Redis
@@ -55,11 +55,11 @@ type redisAppImpl struct {
}
// 分页获取机器脚本信息列表
func (r *redisAppImpl) GetPageList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (r *redisAppImpl) GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return r.redisRepo.GetRedisList(condition, pageParam, toEntity, orderBy...)
}
func (r *redisAppImpl) Count(condition *entity.Redis) int64 {
func (r *redisAppImpl) Count(condition *entity.RedisQuery) int64 {
return r.redisRepo.Count(condition)
}
@@ -172,7 +172,7 @@ func getRedisCacheKey(id uint64, db int) string {
}
func getRedisCient(re *entity.Redis, db int) *RedisInstance {
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), ProjectId: re.ProjectId, Mode: re.Mode}
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), TagPath: re.TagPath, Mode: re.Mode}
redisOptions := &redis.Options{
Addr: re.Host,
@@ -191,7 +191,7 @@ func getRedisCient(re *entity.Redis, db int) *RedisInstance {
}
func getRedisClusterClient(re *entity.Redis) *RedisInstance {
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, 0), ProjectId: re.ProjectId, Mode: re.Mode}
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, 0), TagPath: re.TagPath, Mode: re.Mode}
redisClusterOptions := &redis.ClusterOptions{
Addrs: strings.Split(re.Host, ","),
@@ -207,7 +207,7 @@ func getRedisClusterClient(re *entity.Redis) *RedisInstance {
}
func getRedisSentinelCient(re *entity.Redis, db int) *RedisInstance {
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), ProjectId: re.ProjectId, Mode: re.Mode}
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), TagPath: re.TagPath, Mode: re.Mode}
// sentinel模式host为 masterName=host:port,host:port
masterNameAndHosts := strings.Split(re.Host, "=")
sentinelOptions := &redis.FailoverOptions{
@@ -294,7 +294,7 @@ func TestRedisConnection(re *entity.Redis) {
// redis实例
type RedisInstance struct {
Id string
ProjectId uint64
TagPath string
Mode string
Cli *redis.Client
ClusterCli *redis.ClusterClient

View File

@@ -0,0 +1,20 @@
package entity
import "mayfly-go/pkg/model"
type RedisQuery struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"`
Db string `orm:"column(database)" json:"db"`
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
TagId uint64
TagIds []uint64
TagPathLike string
}

View File

@@ -8,6 +8,7 @@ import (
type Redis struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"`
@@ -15,10 +16,8 @@ type Redis struct {
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
ProjectId uint64
Project string
EnvId uint64
Env string
TagId uint64
TagPath string
}
const (

View File

@@ -7,9 +7,9 @@ import (
type Redis interface {
// 分页获取机器信息列表
GetRedisList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Count(condition *entity.Redis) int64
Count(condition *entity.RedisQuery) int64
// 根据id获取
GetById(id uint64, cols ...string) *entity.Redis

View File

@@ -6,6 +6,8 @@ import (
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strings"
)
type redisRepoImpl struct{}
@@ -15,27 +17,32 @@ func newRedisRepo() repository.Redis {
}
// 分页获取机器信息列表
func (r *redisRepoImpl) GetRedisList(condition *entity.Redis, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_redis d JOIN t_project_member pm ON d.project_id = pm.project_id WHERE 1 = 1 "
if condition.CreatorId != 0 {
// 使用创建者id模拟项目成员id
sql = fmt.Sprintf("%s AND pm.account_id = %d", sql, condition.CreatorId)
}
if condition.ProjectId != 0 {
sql = fmt.Sprintf("%s AND d.project_id = %d", sql, condition.ProjectId)
}
if condition.EnvId != 0 {
sql = fmt.Sprintf("%s AND d.env_id = %d", sql, condition.EnvId)
}
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_redis d WHERE 1=1 "
if condition.Host != "" {
sql = sql + " AND d.host LIKE '%" + condition.Host + "%'"
}
if len(condition.TagIds) > 0 {
sql = sql + " AND d.tag_id IN " + fmt.Sprintf("(%s)", strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
}
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (r *redisRepoImpl) Count(condition *entity.Redis) int64 {
return model.CountBy(condition)
func (r *redisRepoImpl) Count(condition *entity.RedisQuery) int64 {
where := make(map[string]interface{})
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}
if condition.TagId != 0 {
where["tag_id"] = condition.TagId
}
return model.CountByMap(new(entity.Redis), where)
}
// 根据id获取

View File

@@ -1,9 +1,9 @@
package router
import (
projectapp "mayfly-go/internal/project/application"
"mayfly-go/internal/redis/api"
"mayfly-go/internal/redis/application"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/ctx"
"github.com/gin-gonic/gin"
@@ -13,8 +13,8 @@ func InitRedisRouter(router *gin.RouterGroup) {
redis := router.Group("redis")
{
rs := &api.Redis{
RedisApp: application.GetRedisApp(),
ProjectApp: projectapp.GetProjectApp(),
RedisApp: application.GetRedisApp(),
TagApp: tagapp.GetTagTreeApp(),
}
// 获取redis list

View File

@@ -2,8 +2,9 @@ package form
type ConfigForm struct {
Id int
Name string `binding:"required"`
Key string `binding:"required"`
Name string
Key string
Params string
Value string
Remark string `json:"remark"`
}

View File

@@ -1,6 +1,9 @@
package entity
import "mayfly-go/pkg/model"
import (
"encoding/json"
"mayfly-go/pkg/model"
)
const (
ConfigKeyUseLoginCaptcha string = "UseLoginCaptcha" // 是否使用登录验证码
@@ -10,6 +13,7 @@ type Config struct {
model.Model
Name string `json:"name"` // 配置名
Key string `json:"key"` // 配置key
Params string `json:"params"`
Value string `json:"value"`
Remark string `json:"remark"`
}
@@ -27,3 +31,13 @@ func (c *Config) BoolValue(defaultValue bool) bool {
}
return c.Value == "1"
}
// 值返回json map
func (c *Config) GetJsonMap() map[string]string {
var res map[string]string
if c.Id == 0 || c.Value == "" {
return res
}
_ = json.Unmarshal([]byte(c.Value), &res)
return res
}

View File

@@ -14,7 +14,7 @@ func newConfigRepo() repository.Config {
}
func (m *configRepoImpl) GetPageList(condition *entity.Config, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity)
return model.GetPage(pageParam, condition, condition, toEntity)
}
func (m *configRepoImpl) Insert(config *entity.Config) {

View File

@@ -14,7 +14,7 @@ func newMsgRepo() repository.Msg {
}
func (m *msgRepoImpl) GetPageList(condition *entity.Msg, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity)
return model.GetPage(pageParam, condition, condition, toEntity)
}
func (m *msgRepoImpl) Insert(account *entity.Msg) {

View File

@@ -14,7 +14,7 @@ func newRoleRepo() repository.Role {
}
func (m *roleRepoImpl) GetPageList(condition *entity.Role, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}
func (m *roleRepoImpl) Delete(id uint64) {

View File

@@ -13,7 +13,7 @@ func newSyslogRepo() repository.Syslog {
}
func (m *syslogRepoImpl) GetPageList(condition *entity.Syslog, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, toEntity, orderBy...)
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}
func (m *syslogRepoImpl) Insert(syslog *entity.Syslog) {

View File

@@ -0,0 +1,6 @@
package form
type TagTreeTeam struct {
TeamId uint64 `json:"teamId"`
TagIds []uint64 `json:"tagIds"`
}

View File

@@ -0,0 +1,39 @@
package api
import (
"fmt"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
)
type TagTree struct {
TagTreeApp application.TagTree
}
func (p *TagTree) GetAccountTags(rc *ctx.ReqCtx) {
rc.ResData = p.TagTreeApp.ListTagByAccountId(rc.LoginAccount.Id)
}
func (p *TagTree) GetTagTree(rc *ctx.ReqCtx) {
var tagTrees vo.TagTreeVOS
p.TagTreeApp.ListByQuery(new(entity.TagTreeQuery), &tagTrees)
rc.ResData = tagTrees.ToTrees(0)
}
func (p *TagTree) SaveTagTree(rc *ctx.ReqCtx) {
projectTree := &entity.TagTree{}
ginx.BindJsonAndValid(rc.GinCtx, projectTree)
loginAccount := rc.LoginAccount
projectTree.SetBaseInfo(loginAccount)
p.TagTreeApp.Save(projectTree)
rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, codePath: %s", projectTree.Id, projectTree.Name, projectTree.CodePath)
}
func (p *TagTree) DelTagTree(rc *ctx.ReqCtx) {
p.TagTreeApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}

View File

@@ -0,0 +1,133 @@
package api
import (
"fmt"
sys_applicaiton "mayfly-go/internal/sys/application"
sys_entity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ctx"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/utils"
)
type Team struct {
TeamApp application.Team
TagApp application.TagTree
AccountApp sys_applicaiton.Account
}
func (p *Team) GetTeams(rc *ctx.ReqCtx) {
teams := &[]entity.Team{}
rc.ResData = p.TeamApp.GetPageList(&entity.Team{}, ginx.GetPageParam(rc.GinCtx), teams)
}
func (p *Team) SaveTeam(rc *ctx.ReqCtx) {
team := &entity.Team{}
ginx.BindJsonAndValid(rc.GinCtx, team)
isAdd := team.Id == 0
loginAccount := rc.LoginAccount
team.SetBaseInfo(loginAccount)
p.TeamApp.Save(team)
// 如果是新增团队则默认将自己加入该团队
if isAdd {
teamMem := &entity.TeamMember{}
teamMem.AccountId = loginAccount.Id
teamMem.Username = loginAccount.Username
teamMem.TeamId = team.Id
teamMem.SetBaseInfo(rc.LoginAccount)
p.TeamApp.SaveMember(teamMem)
}
}
func (p *Team) DelTeam(rc *ctx.ReqCtx) {
p.TeamApp.Delete(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 获取团队的成员信息
func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) {
teamMems := &[]entity.TeamMember{}
rc.ResData = p.TeamApp.GetMemberPage(&entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))},
ginx.GetPageParam(rc.GinCtx), teamMems)
}
// 保存团队信息
func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) {
projectMem := &entity.TeamMember{}
ginx.BindJsonAndValid(rc.GinCtx, projectMem)
rc.ReqParam = fmt.Sprintf("projectId: %d, username: %s", projectMem.TeamId, projectMem.Username)
// 校验账号并赋值username
account := &sys_entity.Account{}
account.Id = projectMem.AccountId
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
projectMem.Username = account.Username
projectMem.SetBaseInfo(rc.LoginAccount)
p.TeamApp.SaveMember(projectMem)
}
// 删除团队成员
func (p *Team) DelTeamMember(rc *ctx.ReqCtx) {
g := rc.GinCtx
tid := ginx.PathParamInt(g, "id")
aid := ginx.PathParamInt(g, "accountId")
rc.ReqParam = fmt.Sprintf("teamId: %d, accountId: %d", tid, aid)
p.TeamApp.DeleteMember(uint64(tid), uint64(aid))
}
// 获取团队关联的标签id
func (p *Team) GetTagIds(rc *ctx.ReqCtx) {
rc.ResData = p.TeamApp.ListTagIds(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}
// 保存团队关联标签信息
func (p *Team) SaveTags(rc *ctx.ReqCtx) {
g := rc.GinCtx
var form form.TagTreeTeam
ginx.BindJsonAndValid(g, &form)
teamId := form.TeamId
// 将[]uint64转为[]interface{}
oIds := p.TeamApp.ListTagIds(teamId)
var oldIds []interface{}
for _, v := range oIds {
oldIds = append(oldIds, v)
}
var newIds []interface{}
for _, v := range form.TagIds {
newIds = append(newIds, v)
}
// 比较新旧两合集
addIds, delIds, _ := utils.ArrayCompare(newIds, oldIds, func(i1, i2 interface{}) bool {
return i1.(uint64) == i2.(uint64)
})
loginAccount := rc.LoginAccount
for _, v := range addIds {
tagId := v.(uint64)
tag := p.TagApp.GetById(tagId)
biz.NotNil(tag, "存在非法标签id")
ptt := &entity.TagTreeTeam{TeamId: teamId, TagId: tagId, TagPath: tag.CodePath}
ptt.SetBaseInfo(loginAccount)
p.TeamApp.SaveTag(ptt)
}
for _, v := range delIds {
p.TeamApp.DeleteTag(teamId, v.(uint64))
}
rc.ReqParam = form
}

View File

@@ -0,0 +1,50 @@
package vo
import "time"
type TagTreeVO struct {
Id int `json:"id"`
Pid int `json:"pid"`
Name string `json:"name"`
Code string `json:"code"`
CodePath string `json:"codePath"`
Remark string `json:"remark"`
Creator string `json:"creator"`
CreateTime time.Time `json:"createTime"`
Modifier string `json:"modifier"`
UpdateTime time.Time `json:"updateTime"`
}
type TagTreeVOS []TagTreeVO
type TagTreeItem struct {
TagTreeVO
Children []TagTreeItem `json:"children"`
}
func (m *TagTreeVOS) ToTrees(pid int) []TagTreeItem {
var resourceTree []TagTreeItem
list := m.findChildren(pid)
if len(list) == 0 {
return resourceTree
}
for _, v := range list {
Children := m.ToTrees(int(v.Id))
resourceTree = append(resourceTree, TagTreeItem{v, Children})
}
return resourceTree
}
func (m *TagTreeVOS) findChildren(pid int) []TagTreeVO {
child := []TagTreeVO{}
for _, v := range *m {
if v.Pid == pid {
child = append(child, v)
}
}
return child
}

View File

@@ -0,0 +1,34 @@
package application
import (
dbapp "mayfly-go/internal/db/application"
machineapp "mayfly-go/internal/machine/application"
mongoapp "mayfly-go/internal/mongo/application"
redisapp "mayfly-go/internal/redis/application"
"mayfly-go/internal/tag/infrastructure/persistence"
)
var (
tagTreeApp TagTree = newTagTreeApp(
persistence.GetTagTreeRepo(),
persistence.GetTagTreeTeamRepo(),
machineapp.GetMachineApp(),
redisapp.GetRedisApp(),
dbapp.GetDbApp(),
mongoapp.GetMongoApp(),
)
teamApp Team = newTeamApp(
persistence.GetTeamRepo(),
persistence.GetTeamMemberRepo(),
persistence.GetTagTreeTeamRepo(),
)
)
func GetTagTreeApp() TagTree {
return tagTreeApp
}
func GetTeamApp() Team {
return teamApp
}

View File

@@ -0,0 +1,150 @@
package application
import (
"fmt"
dbapp "mayfly-go/internal/db/application"
dbentity "mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
machineentity "mayfly-go/internal/machine/domain/entity"
mongoapp "mayfly-go/internal/mongo/application"
mongoentity "mayfly-go/internal/mongo/domain/entity"
redisapp "mayfly-go/internal/redis/application"
redisentity "mayfly-go/internal/redis/domain/entity"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/biz"
"strings"
)
type TagTree interface {
ListByQuery(condition *entity.TagTreeQuery, toEntity interface{})
GetById(id uint64) *entity.TagTree
Save(project *entity.TagTree)
Delete(id uint64)
// 获取账号id拥有的可访问的标签id
ListTagIdByAccountId(accountId uint64) []uint64
// 获取以指定tagPath数组开头的所有标签id
ListTagIdByPath(tagPath ...string) []uint64
// 根据账号id获取其可访问标签信息
ListTagByAccountId(accountId uint64) []string
// 账号是否有权限访问该标签关联的资源信息
CanAccess(accountId uint64, tagPath string) error
}
func newTagTreeApp(tagTreeRepo repository.TagTree,
tagTreeTeamRepo repository.TagTreeTeam,
machineApp machineapp.Machine,
redisApp redisapp.Redis,
dbApp dbapp.Db,
mongoApp mongoapp.Mongo) TagTree {
return &tagTreeAppImpl{
tagTreeRepo: tagTreeRepo,
tagTreeTeamRepo: tagTreeTeamRepo,
machineApp: machineApp,
redisApp: redisApp,
dbApp: dbApp,
mongoApp: mongoApp,
}
}
type tagTreeAppImpl struct {
tagTreeRepo repository.TagTree
tagTreeTeamRepo repository.TagTreeTeam
machineApp machineapp.Machine
redisApp redisapp.Redis
mongoApp mongoapp.Mongo
dbApp dbapp.Db
}
func (p *tagTreeAppImpl) Save(tag *entity.TagTree) {
// 新建项目树节点信息
if tag.Id == 0 {
biz.IsTrue(!strings.Contains(tag.Code, entity.CodePathSeparator), "标识符不能包含'/'")
if tag.Pid != 0 {
parentTag := p.tagTreeRepo.SelectById(tag.Pid)
biz.NotNil(parentTag, "父节点不存在")
tag.CodePath = parentTag.CodePath + entity.CodePathSeparator + tag.Code
} else {
tag.CodePath = tag.Code
}
// 判断该路径是否存在
var hasLikeTags []entity.TagTree
p.tagTreeRepo.SelectByCondition(&entity.TagTreeQuery{CodePathLike: tag.CodePath}, &hasLikeTags)
biz.IsTrue(len(hasLikeTags) == 0, "已存在该标签路径开头的标签, 请修改该标识code")
// 校验同级标签是否有以该code为开头的标识符
p.tagTreeRepo.SelectByCondition(&entity.TagTreeQuery{Pid: tag.Pid}, &hasLikeTags)
for _, v := range hasLikeTags {
if strings.HasPrefix(tag.Code, v.Code) {
panic(biz.NewBizErr(fmt.Sprintf("同级标签下的[%s]与[%s]存在相似开头字符, 请修改该标识code", v.Code, tag.Code)))
}
}
p.tagTreeRepo.Insert(tag)
return
}
// 防止误传导致被更新
tag.Code = ""
tag.CodePath = ""
p.tagTreeRepo.UpdateById(tag)
}
func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity interface{}) {
p.tagTreeRepo.SelectByCondition(condition, toEntity)
}
func (p *tagTreeAppImpl) GetById(tagId uint64) *entity.TagTree {
return p.tagTreeRepo.SelectById(tagId)
}
func (p *tagTreeAppImpl) ListTagIdByAccountId(accountId uint64) []uint64 {
// 获取该账号可操作的标签路径
return p.ListTagIdByPath(p.ListTagByAccountId(accountId)...)
}
func (p *tagTreeAppImpl) ListTagIdByPath(tagPaths ...string) []uint64 {
tagIds := make([]uint64, 0)
if len(tagPaths) == 0 {
return tagIds
}
var tags []entity.TagTree
p.tagTreeRepo.SelectByCondition(&entity.TagTreeQuery{CodePathLikes: tagPaths}, &tags)
for _, v := range tags {
tagIds = append(tagIds, v.Id)
}
return tagIds
}
func (p *tagTreeAppImpl) ListTagByAccountId(accountId uint64) []string {
return p.tagTreeTeamRepo.SelectTagPathsByAccountId(accountId)
}
func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath string) error {
tagPaths := p.ListTagByAccountId(accountId)
// 判断该资源标签是否为该账号拥有的标签或其子标签
for _, v := range tagPaths {
if strings.HasPrefix(tagPath, v) {
return nil
}
}
return biz.NewBizErr("您无权操作该资源")
}
func (p *tagTreeAppImpl) Delete(id uint64) {
biz.IsTrue(p.machineApp.Count(&machineentity.MachineQuery{TagId: id}) == 0, "请先删除该项目关联的机器信息")
biz.IsTrue(p.redisApp.Count(&redisentity.RedisQuery{TagId: id}) == 0, "请先删除该项目关联的redis信息")
biz.IsTrue(p.dbApp.Count(&dbentity.DbQuery{TagId: id}) == 0, "请先删除该项目关联的数据库信息")
biz.IsTrue(p.mongoApp.Count(&mongoentity.MongoQuery{TagId: id}) == 0, "请先删除该项目关联的Mongo信息")
p.tagTreeRepo.Delete(id)
// 删除该标签关联的团队信息
p.tagTreeTeamRepo.DeleteBy(&entity.TagTreeTeam{TagId: id})
}

View File

@@ -0,0 +1,111 @@
package application
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type Team interface {
// 分页获取项目团队信息列表
GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Save(projectTeam *entity.Team)
Delete(id uint64)
//--------------- 团队成员相关接口 ---------------
GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
SaveMember(projectTeamMember *entity.TeamMember)
DeleteMember(teamId, accountId uint64)
// 账号是否有权限访问该项目关联的资源信息
// CanAccess(accountId, projectId uint64) error
//--------------- 关联项目相关接口 ---------------
ListTagIds(teamId uint64) []uint64
SaveTag(tagTeam *entity.TagTreeTeam)
DeleteTag(teamId, projectId uint64)
}
func newTeamApp(projectTeamRepo repository.Team,
projectTeamMemberRepo repository.TeamMember,
tagTreeTeamRepo repository.TagTreeTeam,
) Team {
return &projectTeamAppImpl{
projectTeamRepo: projectTeamRepo,
projectTeamMemberRepo: projectTeamMemberRepo,
tagTreeTeamRepo: tagTreeTeamRepo,
}
}
type projectTeamAppImpl struct {
projectTeamRepo repository.Team
projectTeamMemberRepo repository.TeamMember
tagTreeTeamRepo repository.TagTreeTeam
}
func (p *projectTeamAppImpl) GetPageList(condition *entity.Team, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectTeamRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
func (p *projectTeamAppImpl) Save(projectTeam *entity.Team) {
if projectTeam.Id == 0 {
p.projectTeamRepo.Insert(projectTeam)
} else {
p.projectTeamRepo.UpdateById(projectTeam)
}
}
func (p *projectTeamAppImpl) Delete(id uint64) {
p.projectTeamRepo.Delete(id)
p.projectTeamMemberRepo.DeleteBy(&entity.TeamMember{TeamId: id})
}
// --------------- 团队成员相关接口 ---------------
func (p *projectTeamAppImpl) GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectTeamMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
// 保存团队成员信息
func (p *projectTeamAppImpl) SaveMember(projectTeamMember *entity.TeamMember) {
projectTeamMember.Id = 0
biz.IsTrue(!p.projectTeamMemberRepo.IsExist(projectTeamMember.TeamId, projectTeamMember.AccountId), "该成员已存在")
p.projectTeamMemberRepo.Save(projectTeamMember)
}
// 删除团队成员信息
func (p *projectTeamAppImpl) DeleteMember(teamId, accountId uint64) {
p.projectTeamMemberRepo.DeleteBy(&entity.TeamMember{TeamId: teamId, AccountId: accountId})
}
//--------------- 关联项目相关接口 ---------------
func (p *projectTeamAppImpl) ListTagIds(teamId uint64) []uint64 {
projects := &[]entity.TagTreeTeam{}
p.tagTreeTeamRepo.ListProject(&entity.TagTreeTeam{TeamId: teamId}, projects)
ids := make([]uint64, 0)
for _, v := range *projects {
ids = append(ids, v.TagId)
}
return ids
}
// 保存关联项目信息
func (p *projectTeamAppImpl) SaveTag(projectTreeTeam *entity.TagTreeTeam) {
projectTreeTeam.Id = 0
p.tagTreeTeamRepo.Save(projectTreeTeam)
}
// 删除关联项目信息
func (p *projectTeamAppImpl) DeleteTag(teamId, tagId uint64) {
p.tagTreeTeamRepo.DeleteBy(&entity.TagTreeTeam{TeamId: teamId, TagId: tagId})
}

Some files were not shown because too many files have changed in this diff Show More