Merge remote-tracking branch 'upstream/dev' into dev_link_merge

# Conflicts:
#	mayfly_go_web/src/views/ops/db/DbList.vue
#	mayfly_go_web/src/views/ops/db/SqlExec.vue
#	mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue
#	mayfly_go_web/src/views/ops/mongo/MongoList.vue
#	mayfly_go_web/src/views/ops/redis/DataOperation.vue
#	mayfly_go_web/src/views/ops/redis/RedisList.vue
This commit is contained in:
刘宗洋
2022-10-27 10:53:29 +08:00
201 changed files with 3317 additions and 2187 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">
@@ -40,17 +39,23 @@
<el-icon class="el-input__icon"><search-icon /></el-icon>
</template>
</el-input>
<div class="el-tag--plain el-tag--success"
v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative"
<el-tag
@click="showTableInfo(scope.row, db)"
effect="plain"
type="success"
size="small"
v-for="db in filterDb.list"
:key="db"
style="cursor: pointer; margin-left: 3px; margin-bottom: 3px;"
>{{ db }}</el-tag
>
<el-link type="success" plain size="small" :underline="false" @click="showTableInfo(scope.row, db)">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false" @click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作</el-link>
</div>
</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>
@@ -192,7 +197,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>
@@ -251,7 +256,6 @@
<db-edit
@val-change="valChange"
:projects="projects"
:title="dbEditDialog.title"
v-model:visible="dbEditDialog.visible"
v-model:db="dbEditDialog.data"
@@ -268,7 +272,6 @@ 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';
@@ -276,6 +279,8 @@ import { isTrue } from '@/common/assert';
import { Search as SearchIcon } from '@element-plus/icons-vue'
import router from '@/router';
import {store} from '@/store';
import { tagApi } from '../tag/api.ts';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'DbList',
@@ -293,7 +298,7 @@ export default defineComponent({
saveDb: 'db:save',
delDb: 'db:del',
},
projects: [],
tags: [],
chooseId: null,
/**
* 选中的数据
@@ -303,6 +308,7 @@ export default defineComponent({
* 查询条件
*/
query: {
tagPath: null,
projectId:null,
pageNum: 1,
pageSize: 10,
@@ -419,12 +425,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 = '新增数据库资源';
@@ -661,7 +666,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" :data="{ projectId, envId:params.envId }">
<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>
<!-- <project-env-select @changeTag="changeTag">
<template #default>
<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>
</template>
</project-env-select>
</project-env-select> -->
</el-col>
</el-row>
</div>
@@ -316,18 +326,16 @@ 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 { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'SqlExec',
components: {
ProjectEnvSelect,
},
components: {},
setup() {
const store = useStore();
const codeTextarea: any = ref(null);
@@ -339,11 +347,12 @@ export default defineComponent({
projectId: null,
token: token,
defalutLimit: 20, // 默认查询数量
dbs: [], // 数据库实例列表
tags: [],
dbs: [] as any, // 数据库实例列表
databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库
db: '', // 当前操作的数据库
dbType: '',
tables: [],
tables: [] as any,
dbId: null, // 当前选中操作的数据库实例
tableName: '',
tableMetadata: [],
@@ -352,7 +361,7 @@ export default defineComponent({
activeName: 'Query',
queryTabName: 'Query',
nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用
dataTabs: {}, // 点击表信息后执行结果数据展示tabs
dataTabs: {} as any, // 点击表信息后执行结果数据展示tabs
dataTabsTableHeight: 600,
// 查询tab
queryTab: {
@@ -369,8 +378,8 @@ export default defineComponent({
},
params: {
pageNum: 1,
pageSize: 10,
envId: null,
pageSize: 100,
tagPath: null,
},
conditionDialog: {
title: '',
@@ -443,16 +452,17 @@ export default defineComponent({
/**
* 项目及环境更改后的回调事件
*/
const changeProjectEnv = (projectId: any, envId: any) => {
const changeTag = (projectId: any, envId: any) => {
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,7 +1231,7 @@ export default defineComponent({
const res = await dbApi.dbs.request(state.params);
state.dbs = res.list;
};
// 加载选中的db
const setSelects = async (sqlExecInfo: any) =>{
// 保存sql
@@ -1248,16 +1258,17 @@ export default defineComponent({
if(sqlExecInfo.dbOptInfo.envId){
setSelects(sqlExecInfo)
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.sqlExecInfo,async (newValue) => {
await setSelects(newValue)
})
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" :data="{ projectId, envId:query.envId }">
<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" />
@@ -130,41 +134,40 @@
<script lang="ts">
import { mongoApi } from './api';
import {toRefs, ref, reactive, defineComponent, watch} from 'vue';
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 { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'MongoDataOp',
components: {
ProjectEnvSelect,
JsonEdit,
},
setup() {
const store = useStore();
const findParamInputRef: any = ref(null);
const state = reactive({
projectId: null,
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: '',
},
@@ -181,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,38 +425,11 @@ export default defineComponent({
delete state.dataTabs[targetName];
};
// 加载选中的db
const setSelects = async (mongoDbOptInfo: any) =>{
// 设置项目id和环境id
const { projectId, envId, dbId, db} = mongoDbOptInfo.dbOptInfo;
state.projectId = projectId;
state.query.envId = envId
await searchMongo();
state.mongoId = dbId
await getDatabases();
state.database = db
await getCollections();
if(state.collection){
state.collection = ''
state.dataTabs = {}
}
}
// 判断如果有数据则加载下拉选项
let mongoDbOptInfo = store.state.mongoDbOptInfo
if(mongoDbOptInfo.dbOptInfo.envId){
setSelects(mongoDbOptInfo)
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.mongoDbOptInfo,async (newValue) => {
await setSelects(newValue)
})
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>
@@ -182,7 +181,6 @@
<mongo-edit
@val-change="valChange"
:projects="projects"
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
@@ -194,11 +192,12 @@
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 {store} from '@/store';
import router from '@/router';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MongoList',
@@ -207,7 +206,8 @@ export default defineComponent({
},
setup() {
const state = reactive({
dbOps: {
tags: [],
dbOps: {
projectId: null,
envId: null,
dbId: 0,
@@ -221,12 +221,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: {
@@ -235,7 +234,7 @@ export default defineComponent({
title: '',
statsDialog: {
visible: false,
data: {},
data: {} as any,
title: '',
},
},
@@ -246,7 +245,7 @@ export default defineComponent({
title: '',
statsDialog: {
visible: false,
data: {},
data: {} as any,
title: '',
},
},
@@ -280,7 +279,7 @@ export default defineComponent({
state.dbOps.projectId = projectId
state.dbOps.envId = envId
state.dbOps.dbId = id
state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
state.databaseDialog.title = `数据库列表`;
state.databaseDialog.visible = true;
@@ -385,12 +384,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';
@@ -406,10 +404,10 @@ export default defineComponent({
state.currentData = null;
search();
};
const openDataOps = ( row: any) => {
state.dbOps.db = row.Name
let data = {
projectId: state.dbOps.projectId,
envId: state.dbOps.envId,
@@ -427,7 +425,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,39 +4,57 @@
<div style="float: left">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearRedis" :data="{ projectId, envId:query.envId }" >
<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" :value="item.id" :label=" item.remark ">
<span style="float: left">{{ item.remark }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
`${item.host}`
}}</span>
</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>
@@ -132,13 +150,11 @@
import { redisApi } from './api';
import {toRefs, reactive, defineComponent, watch} 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 { useStore } from '@/store/index.ts';
export default defineComponent({
name: 'DataOperation',
@@ -147,17 +163,17 @@ export default defineComponent({
HashValue,
SetValue,
ListValue,
ProjectEnvSelect,
},
setup() {
let store = useStore();
const state = reactive({
projectId: null,
loading: false,
redisList: [],
tags: [],
redisList: [] as any,
dbList: [],
query: {
envId: 0,
tagPath: null,
},
scanParam: {
id: null,
@@ -193,19 +209,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 = '';
@@ -419,7 +438,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>
@@ -137,7 +137,7 @@
<redis-edit
@val-change="valChange"
:projects="projects"
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"
@@ -150,8 +150,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';
import {store} from '@/store';
import router from '@/router';
@@ -163,15 +164,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: {
@@ -264,12 +265,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';
@@ -304,7 +304,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,