mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: 完善数据库信息保存以及项目、redis相关操作
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'http://localhost:8888/api'
|
||||
VITE_API_URL = 'http://api.mayflygo.1yue.net/api'
|
||||
@@ -18,6 +18,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./config.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script> -->
|
||||
</body>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"axios": "^0.21.1",
|
||||
"codemirror": "^5.61.0",
|
||||
"core-js": "^3.6.5",
|
||||
"countup.js": "^2.0.7",
|
||||
"cropperjs": "^1.5.11",
|
||||
"echarts": "^5.1.1",
|
||||
|
||||
3
mayfly_go_web/public/config.js
Normal file
3
mayfly_go_web/public/config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
window.globalConfig = {
|
||||
"BaseApiUrl": "http://localhost:8888/api"
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class Api {
|
||||
* 操作该权限,即请求对应的url
|
||||
* @param {Object} param 请求该权限的参数
|
||||
*/
|
||||
request(param: any): Promise<any> {
|
||||
request(param: any = null): Promise<any> {
|
||||
return request.send(this, param);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
class AssertError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message); // (1)
|
||||
super(message);
|
||||
// 错误类名
|
||||
this.name = "AssertError";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const config = {
|
||||
baseApiUrl: import.meta.env.VITE_API_URL
|
||||
baseApiUrl: (window as any).globalConfig.BaseApiUrl
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -2,7 +2,7 @@ import request from './request'
|
||||
|
||||
export default {
|
||||
login: (param: any) => request.request('POST', '/sys/accounts/login', param, null),
|
||||
captcha: () => request.request('GET', '/open/captcha', null, null),
|
||||
captcha: () => request.request('GET', '/sys/captcha', null, null),
|
||||
logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param, null),
|
||||
getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param, null)
|
||||
}
|
||||
@@ -53,6 +53,14 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "500px",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "auto",
|
||||
},
|
||||
canChangeMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -165,15 +173,14 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(newValue, oldValue) => {
|
||||
console.log('options change', newValue);
|
||||
for (const key in newValue) {
|
||||
coder.setOption(key, newValue[key]);
|
||||
}
|
||||
}
|
||||
);
|
||||
// watch(
|
||||
// () => props.options,
|
||||
// (newValue, oldValue) => {
|
||||
// for (const key in newValue) {
|
||||
// coder.setOption(key, newValue[key]);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
const init = () => {
|
||||
if (props.options) {
|
||||
@@ -195,6 +202,9 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
|
||||
coder.setSize(props.width, props.height);
|
||||
// editor.setSize('width','height');
|
||||
|
||||
// 修改编辑器的语法配置
|
||||
setMode(language.value);
|
||||
|
||||
@@ -285,6 +295,7 @@ export default defineComponent({
|
||||
coder.setValue(newVal);
|
||||
state.content = newVal;
|
||||
coder.scrollTo(scrollInfo.left, scrollInfo.top);
|
||||
refresh()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,6 +303,7 @@ export default defineComponent({
|
||||
...toRefs(state),
|
||||
textarea,
|
||||
changeMode,
|
||||
refresh,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
dynamicForm.validate((valid: boolean) => {
|
||||
dynamicForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
// 提交的表单数据
|
||||
const subform = { ...state.form };
|
||||
|
||||
@@ -8,5 +8,9 @@ export const imports = {
|
||||
"ResourceList": () => import('@/views/system/resource'),
|
||||
"RoleList": () => import('@/views/system/role'),
|
||||
"AccountList": () => import('@/views/system/account'),
|
||||
"SelectData": () => import('@/views/ops/db'),
|
||||
"ProjectList": () => import('@/views/ops/project/ProjectList.vue'),
|
||||
"DbList": () => import('@/views/ops/db/DbList.vue'),
|
||||
"SqlExec": () => import('@/views/ops/db'),
|
||||
"RedisList": () => import('@/views/ops/redis'),
|
||||
"DataOperation": () => import('@/views/ops/redis/DataOperation.vue'),
|
||||
}
|
||||
@@ -14,8 +14,8 @@ body,
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
font-weight: 500;
|
||||
font-family: Microsoft YaHei, Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, SimSun, sans-serif;
|
||||
font-weight: 450;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
background-color: #f8f8f8;
|
||||
@@ -274,7 +274,7 @@ body,
|
||||
|
||||
.toolbar {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
padding: 6px;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
line-height: 32px;
|
||||
@@ -283,4 +283,10 @@ body,
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
<div class="left-item-animation left-item-num">401</div>
|
||||
<div class="left-item-animation left-item-title">您未被授权,没有操作权限</div>
|
||||
<div class="left-item-animation left-item-title">您未被授权或登录超时,没有操作权限</div>
|
||||
<div class="left-item-animation left-item-msg"></div>
|
||||
<div class="left-item-animation left-item-btn">
|
||||
<el-button type="primary" round @click="onSetAuth">重新授权</el-button>
|
||||
<el-button type="primary" round @click="onSetAuth">重新登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<img :src="getUserInfos.photo" />
|
||||
<div class="home-card-first-right ml15">
|
||||
<div class="flex-margin">
|
||||
<div class="home-card-first-right-title">{{ currentTime }},admin!</div>
|
||||
<div class="home-card-first-right-msg mt5">超级管理</div>
|
||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}` }}</div>
|
||||
<!-- <div class="home-card-first-right-msg mt5">超级管理</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<el-form class="login-content-form">
|
||||
<el-form-item>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form">
|
||||
<el-form-item prop="username">
|
||||
<el-input type="text" placeholder="请输入用户名" prefix-icon="el-icon-user" v-model="loginForm.username" clearable autocomplete="off">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
@@ -15,29 +15,36 @@
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="16">
|
||||
<el-input
|
||||
type="text"
|
||||
maxlength="4"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
prefix-icon="el-icon-position"
|
||||
v-model="loginForm.code"
|
||||
v-model="loginForm.captcha"
|
||||
clearable
|
||||
autocomplete="off"
|
||||
@keyup.enter="onSignIn"
|
||||
@keyup.enter="login"
|
||||
></el-input>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="login-content-code">
|
||||
<span class="login-content-code-img">1234</span>
|
||||
<img
|
||||
class="login-content-code-img"
|
||||
@click="getCaptcha"
|
||||
width="130px"
|
||||
height="40px"
|
||||
:src="captchaImage"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="loading.signIn">
|
||||
<el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
|
||||
<span>登 录</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -45,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, computed } from 'vue';
|
||||
import { onMounted, ref, toRefs, reactive, defineComponent, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { initAllFun, initBackEndControlRoutesFun } from '@/router/index.ts';
|
||||
@@ -60,22 +67,51 @@ export default defineComponent({
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loginFormRef: any = ref(null);
|
||||
const state = reactive({
|
||||
captchaImage: '',
|
||||
loginForm: {
|
||||
username: 'test',
|
||||
password: '123456',
|
||||
code: '1234',
|
||||
captcha: '',
|
||||
cid: '',
|
||||
},
|
||||
rules: {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getCaptcha();
|
||||
});
|
||||
|
||||
const getCaptcha = async () => {
|
||||
let res: any = await openApi.captcha();
|
||||
state.captchaImage = res.base64Captcha;
|
||||
state.loginForm.cid = res.cid;
|
||||
};
|
||||
|
||||
// 时间获取
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
|
||||
// 校验登录表单并登录
|
||||
const login = () => {
|
||||
loginFormRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
onSignIn();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 登录
|
||||
const onSignIn = async () => {
|
||||
state.loading.signIn = true;
|
||||
@@ -87,6 +123,8 @@ export default defineComponent({
|
||||
setSession('menus', loginRes.menus);
|
||||
} catch (e) {
|
||||
state.loading.signIn = false;
|
||||
state.loginForm.captcha = '';
|
||||
getCaptcha();
|
||||
return;
|
||||
}
|
||||
// 用户信息模拟数据
|
||||
@@ -132,9 +170,12 @@ export default defineComponent({
|
||||
ElMessage.success(`${currentTimeInfo},欢迎回来!`);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return {
|
||||
getCaptcha,
|
||||
currentTime,
|
||||
onSignIn,
|
||||
loginFormRef,
|
||||
login,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
|
||||
84
mayfly_go_web/src/views/ops/component/ProjectEnvSelect.vue
Normal file
84
mayfly_go_web/src/views/ops/component/ProjectEnvSelect.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form class="search-form" label-position="right" :inline="true" label-width="60px" size="small">
|
||||
<el-form-item prop="project" label="项目" label-width="40px">
|
||||
<el-select v-model="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>
|
||||
|
||||
<el-form-item prop="env" label="环境" label-width="40px">
|
||||
<el-select style="width: 100px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
|
||||
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<slot></slot>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, watch, defineComponent, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { notEmpty } from '@/common/assert';
|
||||
import { projectApi } from '../project/api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProjectEnvSelect',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
machineId: {
|
||||
type: Number,
|
||||
},
|
||||
isCommon: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
projects: [] as any,
|
||||
envs: [] as any,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
});
|
||||
|
||||
watch(props, (newValue, oldValue) => {});
|
||||
|
||||
onMounted(async () => {
|
||||
state.projects = await projectApi.accountProjects.request(null);
|
||||
});
|
||||
|
||||
const changeProject = async (projectId: any) => {
|
||||
emit('update:projectId', projectId);
|
||||
emit('changeProjectEnv', state.projectId, null);
|
||||
state.envId = null;
|
||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
||||
};
|
||||
|
||||
const changeEnv = (envId: any) => {
|
||||
emit('update:envId', envId);
|
||||
emit('changeProjectEnv', state.projectId, envId);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeProject,
|
||||
changeEnv,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
240
mayfly_go_web/src/views/ops/db/DbEdit.vue
Normal file
240
mayfly_go_web/src/views/ops/db/DbEdit.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="85px" size="small">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<el-form-item prop="type" label="类型:" required>
|
||||
<el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
|
||||
<el-option key="item.id" label="mysql" value="mysql"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="host" label="host:" required>
|
||||
<el-input v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="port" label="port:" required>
|
||||
<el-input type="number" v-model.trim="form.port" placeholder="请输入端口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="database" label="数据库名:" required>
|
||||
<el-input v-model.trim="form.database" placeholder="请输入数据库名"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DbEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
},
|
||||
db: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const dbForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
projects: [],
|
||||
envs: [],
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
port: 3306,
|
||||
username: null,
|
||||
password: null,
|
||||
project: null,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入别名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据库类型',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
host: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入端口',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
database: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入数据库名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
state.projects = newValue.projects;
|
||||
if (newValue.db) {
|
||||
getEnvs(newValue.db.projectId);
|
||||
state.form = { ...newValue.db };
|
||||
} else {
|
||||
state.envs = [];
|
||||
state.form = { port: 3306 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
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.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 () => {
|
||||
dbForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
dbApi.saveDb.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
dbForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
dbForm,
|
||||
changeProject,
|
||||
changeEnv,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
196
mayfly_go_web/src/views/ops/db/DbList.vue
Normal file
196
mayfly_go_web/src/views/ops/db/DbList.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div class="db-list">
|
||||
<div class="toolbar">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form class="search-form" label-position="right" :inline="true" label-width="60px" size="small">
|
||||
<el-form-item prop="project" label="项目">
|
||||
<el-select 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>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="query.database" auto-complete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="search()">查询</el-button>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row class="mt5">
|
||||
<el-col>
|
||||
<el-button v-auth="permissions.saveDb" type="primary" icon="el-icon-plus" size="mini" @click="editDb(true)">添加</el-button>
|
||||
<el-button
|
||||
v-auth="permissions.saveDb"
|
||||
:disabled="chooseId == null"
|
||||
@click="editDb(false)"
|
||||
type="primary"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="permissions.delDb"
|
||||
:disabled="chooseId == null"
|
||||
@click="deleteDb(chooseId)"
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-table :data="datas" border ref="table" @current-change="choose" show-overflow-tooltip>
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="chooseId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</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="name" label="名称" min-width="200"></el-table-column>
|
||||
<el-table-column min-width="160" label="host:port">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.host}:${scope.row.port}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="类型" min-width="80"></el-table-column>
|
||||
<el-table-column prop="database" label="数据库" min-width="120"></el-table-column>
|
||||
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
|
||||
|
||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
||||
<el-table-column min-width="160" prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<db-edit @val-change="valChange" :projects="projects" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
import DbEdit from './DbEdit.vue';
|
||||
import { dbApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
export default defineComponent({
|
||||
name: 'DbList',
|
||||
components: {
|
||||
ProjectEnvSelect,
|
||||
DbEdit,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
permissions: {
|
||||
saveDb: 'db:save',
|
||||
delDb: 'db:del',
|
||||
},
|
||||
projects: [],
|
||||
chooseId: null,
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
chooseData: null,
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
datas: [],
|
||||
total: 0,
|
||||
dbEditDialog: {
|
||||
visible: false,
|
||||
data: null,
|
||||
title: '新增数据库',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
search();
|
||||
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list;
|
||||
});
|
||||
|
||||
const choose = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.chooseId = item.id;
|
||||
state.chooseData = item;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
let res: any = await dbApi.dbs.request(state.query);
|
||||
state.datas = res.list;
|
||||
state.total = res.total;
|
||||
};
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
state.query.pageNum = curPage;
|
||||
search();
|
||||
};
|
||||
|
||||
const editDb = (isAdd = false) => {
|
||||
if (isAdd) {
|
||||
state.dbEditDialog.data = null;
|
||||
state.dbEditDialog.title = '新增数据库';
|
||||
} else {
|
||||
state.dbEditDialog.data = state.chooseData;
|
||||
state.dbEditDialog.title = '修改数据库';
|
||||
}
|
||||
state.dbEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
search();
|
||||
};
|
||||
|
||||
const deleteDb = async (id: number) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该库?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await dbApi.deleteDb.request({ id });
|
||||
ElMessage.success('删除成功');
|
||||
state.chooseData = null;
|
||||
state.chooseId = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
// enums,
|
||||
search,
|
||||
choose,
|
||||
handlePageChange,
|
||||
editDb,
|
||||
valChange,
|
||||
deleteDb,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -1,15 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<div class="fl">
|
||||
<el-select size="small" v-model="dbId" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable filterable>
|
||||
<el-option v-for="item in dbs" :key="item.id" :label="`${item.name} [${dbTypeName(item.type)}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24">
|
||||
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearDb">
|
||||
<template #default>
|
||||
<el-form-item label="数据库">
|
||||
<el-select v-model="dbId" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable filterable>
|
||||
<el-option v-for="item in dbs" :key="item.id" :label="item.database" :value="item.id">
|
||||
<span style="float: left">{{ item.database }}</span>
|
||||
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
|
||||
`${item.name} [${item.type}]`
|
||||
}}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</project-env-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<el-container style="height: 50%; border: 1px solid #eee; margin-top: 1px">
|
||||
<el-aside width="70%" style="background-color: rgb(238, 241, 246)">
|
||||
<el-container style="border: 1px solid #eee; margin-top: 1px">
|
||||
<el-aside id="sqlcontent" width="65%" style="background-color: rgb(238, 241, 246)">
|
||||
<div class="toolbar">
|
||||
<div class="fl">
|
||||
<el-button @click="runSql" type="success" icon="el-icon-video-play" size="mini" plain>执行</el-button>
|
||||
@@ -19,12 +32,12 @@
|
||||
<el-button @click="saveSql" type="primary" icon="el-icon-document-add" size="mini" plain>保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<codemirror class="codesql" ref="cmEditor" language="sql" v-model="sql" :options="cmOptions" />
|
||||
<codemirror @beforeChange="onBeforeChange" class="codesql" ref="cmEditor" language="sql" v-model="sql" :options="cmOptions" />
|
||||
</el-aside>
|
||||
|
||||
<el-container style="margin-left: 2px">
|
||||
<el-header style="text-align: left; height: 45px; font-size: 12px; padding: 0px">
|
||||
<el-select v-model="tableName" placeholder="请选择表" @change="changeTable" clearable filterable style="width: 99%">
|
||||
<el-select v-model="tableName" placeholder="请选择表" @change="changeTable" filterable style="width: 99%">
|
||||
<el-option
|
||||
v-for="item in tableMetadata"
|
||||
:key="item.tableName"
|
||||
@@ -35,20 +48,22 @@
|
||||
</el-select>
|
||||
</el-header>
|
||||
|
||||
<el-main style="padding: 0px; height: 100%; overflow: hidden">
|
||||
<el-main style="padding: 0px; overflow: hidden">
|
||||
<el-table :data="columnMetadata" height="100%" size="mini">
|
||||
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnComment" label="备注" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
|
||||
</el-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
<el-table style="margin-top: 1px" :data="selectRes.data" size="mini" max-height="300" stripe border>
|
||||
|
||||
<el-table style="margin-top: 1px" :data="execRes.data" size="mini" max-height="300" :empty-text="execRes.emptyResText" stripe border>
|
||||
<el-table-column
|
||||
min-width="92"
|
||||
min-width="100"
|
||||
:width="flexColumnWidth(item, execRes.data)"
|
||||
align="center"
|
||||
v-for="item in selectRes.tableColumn"
|
||||
v-for="item in execRes.tableColumn"
|
||||
:key="item"
|
||||
:prop="item"
|
||||
:label="item"
|
||||
@@ -56,20 +71,11 @@
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- <el-pagination
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="data.total"
|
||||
:current-page.sync="params.pageNum"
|
||||
:page-size="params.pageSize"
|
||||
/> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent, ref } from 'vue';
|
||||
import { toRefs, reactive, computed, defineComponent, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
|
||||
import 'codemirror/theme/ambiance.css';
|
||||
@@ -79,48 +85,56 @@ import 'codemirror/lib/codemirror.css';
|
||||
// 引入主题后还需要在 options 中指定主题才会生效
|
||||
import 'codemirror/theme/base16-light.css';
|
||||
|
||||
// require('codemirror/addon/edit/matchbrackets')
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import { codemirror } from '@/components/codemirror';
|
||||
// import 'codemirror/mode/sql/sql.js';
|
||||
// import 'codemirror/addon/hint/show-hint.js';
|
||||
// import 'codemirror/addon/hint/sql-hint.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
import 'codemirror/addon/hint/sql-hint.js';
|
||||
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
import { notEmpty } from '@/common/assert';
|
||||
import { notNull, notEmpty } from '@/common/assert';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SelectData',
|
||||
name: 'SqlExec',
|
||||
components: {
|
||||
codemirror,
|
||||
ProjectEnvSelect,
|
||||
},
|
||||
setup() {
|
||||
const cmEditor: any = ref(null);
|
||||
const state = reactive({
|
||||
dbs: [],
|
||||
tables: [],
|
||||
dbId: '',
|
||||
dbId: null,
|
||||
tableName: '',
|
||||
tableMetadata: [],
|
||||
columnMetadata: [],
|
||||
sql: '',
|
||||
selectRes: {
|
||||
sqlTabs: {
|
||||
tabs: [] as any,
|
||||
active: '',
|
||||
index: 1,
|
||||
},
|
||||
execRes: {
|
||||
tableColumn: [],
|
||||
data: [],
|
||||
emptyResText: '没有数据',
|
||||
},
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
envId: null,
|
||||
},
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'text/x-sql',
|
||||
// theme: 'cobalt',
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
indentWithTabs: true,
|
||||
smartIndent: true,
|
||||
// matchBrackets: true,
|
||||
matchBrackets: true,
|
||||
theme: 'base16-light',
|
||||
autofocus: true,
|
||||
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
|
||||
@@ -137,14 +151,19 @@ export default defineComponent({
|
||||
return cmEditor.value.coder;
|
||||
});
|
||||
|
||||
const dbTypeName = (type: any) => {
|
||||
return 'mysql';
|
||||
/**
|
||||
* 项目及环境更改后的回调事件
|
||||
*/
|
||||
const changeProjectEnv = (projectId: any, envId: any) => {
|
||||
state.dbs = [];
|
||||
state.dbId = null;
|
||||
clearDb();
|
||||
if (envId != null) {
|
||||
state.params.envId = envId;
|
||||
search();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
||||
/**
|
||||
* 输入字符给提示
|
||||
*/
|
||||
@@ -154,29 +173,92 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const onBeforeChange = (instance: any, changeObj: any) => {
|
||||
var text = changeObj.text[0];
|
||||
// 将sql提示去除
|
||||
changeObj.text[0] = text.split(' ')[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行sql
|
||||
*/
|
||||
const runSql = async () => {
|
||||
notEmpty(state.dbId, '请先选择数据库');
|
||||
notNull(state.dbId, '请先选择数据库');
|
||||
// 没有选中的文本,则为全部文本
|
||||
let selectSql = getSql();
|
||||
notEmpty(selectSql, '内容不能为空');
|
||||
const res = await dbApi.selectData.request({
|
||||
let sql = getSql();
|
||||
notNull(sql, '内容不能为空');
|
||||
|
||||
state.execRes.tableColumn = [];
|
||||
state.execRes.data = [];
|
||||
state.execRes.emptyResText = '查询中...';
|
||||
|
||||
const res = await dbApi.sqlExec.request({
|
||||
id: state.dbId,
|
||||
selectSql: selectSql,
|
||||
sql: sql,
|
||||
});
|
||||
let tableColumn: any;
|
||||
let data;
|
||||
if (res.length > 0) {
|
||||
tableColumn = Object.keys(res[0]);
|
||||
data = res;
|
||||
} else {
|
||||
tableColumn = [];
|
||||
data = [];
|
||||
state.execRes.emptyResText = '没有数据';
|
||||
state.execRes.tableColumn = res.colNames;
|
||||
state.execRes.data = res.res;
|
||||
};
|
||||
|
||||
const flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
|
||||
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
|
||||
// flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
|
||||
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
|
||||
str = str + '';
|
||||
let columnContent = '';
|
||||
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
|
||||
return;
|
||||
}
|
||||
state.selectRes.tableColumn = tableColumn;
|
||||
state.selectRes.data = data;
|
||||
if (!str || !str.length || str.length === 0 || str === undefined) {
|
||||
return;
|
||||
}
|
||||
if (flag === 'equal') {
|
||||
// 获取该列中第一个不为空的数据(内容)
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
if (tableData[i][str].length > 0) {
|
||||
columnContent = tableData[i][str];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 获取该列中最长的数据(内容)
|
||||
let index = 0;
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
if (tableData[i][str] === null) {
|
||||
return;
|
||||
}
|
||||
const now_temp = tableData[i][str] + '';
|
||||
const max_temp = tableData[index][str] + '';
|
||||
if (now_temp.length > max_temp.length) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
columnContent = tableData[index][str];
|
||||
}
|
||||
// 以下分配的单位长度可根据实际需求进行调整
|
||||
let flexWidth = 0;
|
||||
for (const char of columnContent) {
|
||||
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
|
||||
// 如果是英文字符,为字符分配8个单位宽度
|
||||
flexWidth += 8;
|
||||
} else if (char >= '\u4e00' && char <= '\u9fa5') {
|
||||
// 如果是中文字符,为字符分配15个单位宽度
|
||||
flexWidth += 15;
|
||||
} else {
|
||||
// 其他种类字符,为字符分配8个单位宽度
|
||||
flexWidth += 8;
|
||||
}
|
||||
}
|
||||
if (flexWidth < 80) {
|
||||
// 设置最小宽度
|
||||
flexWidth = 80;
|
||||
}
|
||||
if (flexWidth > 350) {
|
||||
// 设置最大宽度
|
||||
flexWidth = 350;
|
||||
}
|
||||
return flexWidth + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,7 +275,7 @@ export default defineComponent({
|
||||
|
||||
const saveSql = async () => {
|
||||
notEmpty(state.sql, 'sql内容不能为空');
|
||||
notEmpty(state.dbId, '请先选择数据库');
|
||||
notNull(state.dbId, '请先选择数据库');
|
||||
await dbApi.saveSql.request({ id: state.dbId, sql: state.sql, type: 1 });
|
||||
ElMessage.success('保存成功');
|
||||
};
|
||||
@@ -235,9 +317,10 @@ export default defineComponent({
|
||||
state.tableName = '';
|
||||
state.tableMetadata = [];
|
||||
state.columnMetadata = [];
|
||||
state.selectRes.data = [];
|
||||
state.selectRes.tableColumn = [];
|
||||
state.execRes.data = [];
|
||||
state.execRes.tableColumn = [];
|
||||
state.sql = '';
|
||||
state.cmOptions.hintOptions.tables = [];
|
||||
};
|
||||
|
||||
// 选择表事件
|
||||
@@ -268,7 +351,7 @@ export default defineComponent({
|
||||
codemirror.value.replaceSelection(sqlFormatter.format(selectSql));
|
||||
} else {
|
||||
/* 将sql内容进行格式后放入编辑器中*/
|
||||
codemirror.value.setValue(sqlFormatter.format(state.sql));
|
||||
state.sql = sqlFormatter.format(sqlFormatter.format(state.sql));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,14 +363,16 @@ export default defineComponent({
|
||||
return {
|
||||
...toRefs(state),
|
||||
cmEditor,
|
||||
dbTypeName,
|
||||
changeProjectEnv,
|
||||
inputRead,
|
||||
changeTable,
|
||||
runSql,
|
||||
flexColumnWidth,
|
||||
saveSql,
|
||||
changeDb,
|
||||
clearDb,
|
||||
formatSql,
|
||||
onBeforeChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -298,4 +383,9 @@ export default defineComponent({
|
||||
font-size: 10pt;
|
||||
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
|
||||
}
|
||||
#sqlcontent {
|
||||
.CodeMirror {
|
||||
height: 300px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,11 +3,13 @@ import Api from '@/common/Api';
|
||||
export const dbApi = {
|
||||
// 获取权限列表
|
||||
dbs: Api.create("/dbs", 'get'),
|
||||
saveDb: Api.create("/dbs", 'post'),
|
||||
deleteDb: Api.create("/dbs/{id}", 'delete'),
|
||||
tableMetadata: Api.create("/dbs/{id}/t-metadata", 'get'),
|
||||
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
|
||||
// 获取表即列提示
|
||||
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
|
||||
selectData: Api.create("/dbs/{id}/select", 'get'),
|
||||
sqlExec: Api.create("/dbs/{id}/exec-sql", 'get'),
|
||||
// 保存sql
|
||||
saveSql: Api.create("/dbs/{id}/sql", 'post'),
|
||||
// 获取保存的sql
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './SelectData.vue';
|
||||
export { default } from './SqlExec.vue';
|
||||
182
mayfly_go_web/src/views/ops/machine/MachineEdit.vue
Normal file
182
mayfly_go_web/src/views/ops/machine/MachineEdit.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px" size="small">
|
||||
<!-- <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>
|
||||
|
||||
<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>
|
||||
<el-form-item prop="ip" label="ip:" required>
|
||||
<el-input v-model.trim="form.ip" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="port" label="port:" required>
|
||||
<el-input type="number" v-model.trim="form.port" placeholder="请输入端口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, onMounted, defineComponent, ref } from 'vue';
|
||||
import { machineApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MachineEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
machine: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const machineForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
port: 22,
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入别名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
ip: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入端口',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
if (newValue.machine) {
|
||||
state.form = { ...newValue.machine };
|
||||
} else {
|
||||
state.form = { port: 22 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
const btnOk = async () => {
|
||||
machineForm.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
machineApi.saveMachine.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
machineForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
machineForm,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -42,16 +42,19 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" width></el-table-column>
|
||||
<el-table-column prop="ip" label="IP" width></el-table-column>
|
||||
<el-table-column prop="port" label="端口" :min-width="40"></el-table-column>
|
||||
<el-table-column prop="username" label="用户名" :min-width="40"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" :min-width="100">
|
||||
<el-table-column prop="ip" label="ip:port" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ `${scope.row.ip}:${scope.row.port}` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="用户名" :min-width="45"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建者" :min-width="50"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间" :min-width="100">
|
||||
<el-table-column prop="creator" label="创建者" min-width="50"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.updateTime) }}
|
||||
</template>
|
||||
@@ -59,7 +62,7 @@
|
||||
<el-table-column prop="modifier" label="修改者" :min-width="50"></el-table-column>
|
||||
<el-table-column label="操作" min-width="200px">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" @click="monitor(scope.row.id)" icom="el-icon-tickets" size="mini" plain>监控</el-button>
|
||||
<!-- <el-button type="primary" @click="monitor(scope.row.id)" icom="el-icon-tickets" size="mini" plain>监控</el-button> -->
|
||||
<el-button type="success" @click="serviceManager(scope.row)" size="mini" plain>脚本管理</el-button>
|
||||
<el-button v-auth="'machine:terminal'" type="success" @click="showTerminal(scope.row)" size="mini" plain>终端</el-button>
|
||||
</template>
|
||||
@@ -75,6 +78,12 @@
|
||||
:page-size="params.pageSize"
|
||||
/>
|
||||
|
||||
<machine-edit
|
||||
:title="machineEditDialog.title"
|
||||
v-model:visible="machineEditDialog.visible"
|
||||
v-model:machine="machineEditDialog.data"
|
||||
></machine-edit>
|
||||
|
||||
<!-- <el-dialog @close="closeMonitor" title="监控信息" v-model="monitorDialog.visible" width="60%">
|
||||
<monitor ref="monitorDialogRef" :machineId="monitorDialog.machineId" />
|
||||
</el-dialog> -->
|
||||
@@ -82,40 +91,32 @@
|
||||
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
|
||||
|
||||
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
|
||||
|
||||
<dynamic-form-dialog
|
||||
v-model:visible="formDialog.visible"
|
||||
:title="formDialog.title"
|
||||
:formInfo="formDialog.formInfo"
|
||||
v-model:formData="formDialog.formData"
|
||||
@submitSuccess="submitSuccess"
|
||||
></dynamic-form-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { DynamicFormDialog } from '@/components/dynamic-form';
|
||||
// import Monitor from './Monitor.vue';
|
||||
import { machineApi } from './api';
|
||||
import SshTerminal from './SshTerminal.vue';
|
||||
import ServiceManage from './ServiceManage.vue';
|
||||
import FileManage from './FileManage.vue';
|
||||
import MachineEdit from './MachineEdit.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MachineList',
|
||||
components: {
|
||||
// Monitor,
|
||||
SshTerminal,
|
||||
ServiceManage,
|
||||
FileManage,
|
||||
DynamicFormDialog,
|
||||
MachineEdit,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
// const monitorDialogRef = ref();
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageNum: 1,
|
||||
@@ -149,86 +150,10 @@ export default defineComponent({
|
||||
visible: false,
|
||||
machineId: 0,
|
||||
},
|
||||
formDialog: {
|
||||
machineEditDialog: {
|
||||
visible: false,
|
||||
title: '',
|
||||
formInfo: {
|
||||
createApi: machineApi.save,
|
||||
updateApi: machineApi.save,
|
||||
formRows: [
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
label: '名称:',
|
||||
name: 'name',
|
||||
placeholder: '请输入名称',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
label: 'ip:',
|
||||
name: 'ip',
|
||||
placeholder: '请输入ip',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入ip',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
label: '端口号:',
|
||||
name: 'port',
|
||||
placeholder: '请输入端口号',
|
||||
inputType: 'number',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入ip',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
label: '用户名:',
|
||||
name: 'username',
|
||||
placeholder: '请输入用户名',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
label: '密码:',
|
||||
name: 'password',
|
||||
placeholder: '请输入密码',
|
||||
inputType: 'password',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
formData: { port: 22 },
|
||||
data: null,
|
||||
title: '新增机器',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -261,7 +186,6 @@ export default defineComponent({
|
||||
// };
|
||||
|
||||
const showTerminal = (row: any) => {
|
||||
// router.push(`/machine/${row.id}/terminal?id=${row.id}&name=${row.name}&time=${new Date().getTime()}`);
|
||||
const { href } = router.resolve({
|
||||
path: `/machine/terminal`,
|
||||
query: {
|
||||
@@ -275,21 +199,30 @@ export default defineComponent({
|
||||
const openFormDialog = (redis: any) => {
|
||||
let dialogTitle;
|
||||
if (redis) {
|
||||
state.formDialog.formData = state.currentData as any;
|
||||
state.machineEditDialog.data = state.currentData as any;
|
||||
dialogTitle = '编辑机器';
|
||||
} else {
|
||||
state.formDialog.formData = { port: 22 };
|
||||
state.machineEditDialog.data = { port: 22 } as any;
|
||||
dialogTitle = '添加机器';
|
||||
}
|
||||
|
||||
state.formDialog.title = dialogTitle;
|
||||
state.formDialog.visible = true;
|
||||
state.machineEditDialog.title = dialogTitle;
|
||||
state.machineEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const deleteMachine = async (id: number) => {
|
||||
await machineApi.del.request({ id });
|
||||
ElMessage.success('操作成功');
|
||||
search();
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await machineApi.del.request({ id });
|
||||
ElMessage.success('操作成功');
|
||||
state.currentId = null;
|
||||
state.currentData = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const serviceManager = (row: any) => {
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="params" label="参数">
|
||||
<el-input v-model.trim="form.params" placeholder="参数数组json,若无可不填"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="script" label="内容" id="content">
|
||||
<codemirror ref="cmEditor" v-model="form.script" language="shell" />
|
||||
</el-form-item>
|
||||
@@ -31,7 +35,15 @@
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk" size="mini" :disabled="submitDisabled">保 存</el-button>
|
||||
<el-button
|
||||
v-auth="'machine:script:save'"
|
||||
type="primary"
|
||||
:loading="btnLoading"
|
||||
@click="btnOk"
|
||||
size="mini"
|
||||
:disabled="submitDisabled"
|
||||
>保 存</el-button
|
||||
>
|
||||
<el-button @click="cancel()" :disabled="submitDisabled" size="mini">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,6 +95,7 @@ export default defineComponent({
|
||||
machineId: 0,
|
||||
description: '',
|
||||
script: '',
|
||||
params: null,
|
||||
type: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" :min-width="50"> </el-table-column>
|
||||
<el-table-column prop="name" label="名称" :min-width="70"> </el-table-column>
|
||||
<el-table-column prop="description" label="描述" :min-width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="name" label="类型" :min-width="50">
|
||||
<template #default="scope">
|
||||
@@ -64,6 +64,19 @@
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
|
||||
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="70px" size="mini">
|
||||
<el-form-item v-for="item in scriptParamsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
|
||||
<el-input v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="hasParamsRun(currentData)" size="mini">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="执行结果" v-model="resultDialog.visible" width="40%">
|
||||
<div style="white-space: pre-line; padding: 10px; color: #000000">
|
||||
{{ resultDialog.result }}
|
||||
@@ -94,7 +107,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent } from 'vue';
|
||||
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import SshTerminal from './SshTerminal.vue';
|
||||
import { machineApi } from './api';
|
||||
@@ -113,6 +126,7 @@ export default defineComponent({
|
||||
title: { type: String },
|
||||
},
|
||||
setup(props: any, context) {
|
||||
const paramsForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
type: 0,
|
||||
@@ -125,6 +139,11 @@ export default defineComponent({
|
||||
machineId: 9999999,
|
||||
},
|
||||
scriptTable: [],
|
||||
scriptParamsDialog: {
|
||||
visible: false,
|
||||
params: {},
|
||||
paramsFormItem: [],
|
||||
},
|
||||
resultDialog: {
|
||||
visible: false,
|
||||
result: '',
|
||||
@@ -152,13 +171,43 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const runScript = async (script: any) => {
|
||||
// 如果存在参数,则弹窗输入参数后执行
|
||||
if (script.params) {
|
||||
state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params);
|
||||
state.scriptParamsDialog.visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
run(script);
|
||||
};
|
||||
|
||||
// 有参数的脚本执行函数
|
||||
const hasParamsRun = async (script: any) => {
|
||||
// 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
|
||||
if (state.scriptParamsDialog.visible) {
|
||||
paramsForm.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
run(script);
|
||||
state.scriptParamsDialog.params = {};
|
||||
state.scriptParamsDialog.visible = false;
|
||||
paramsForm.value.resetFields();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const run = async (script: any) => {
|
||||
const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value;
|
||||
// 如果脚本类型为有结果类型,则显示结果信息
|
||||
if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) {
|
||||
const res = await machineApi.runScript.request({
|
||||
machineId: props.machineId,
|
||||
scriptId: script.id,
|
||||
params: state.scriptParamsDialog.params,
|
||||
});
|
||||
|
||||
if (noResult) {
|
||||
ElMessage.success('执行完成');
|
||||
return;
|
||||
@@ -241,9 +290,11 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
paramsForm,
|
||||
enums,
|
||||
getScripts,
|
||||
runScript,
|
||||
hasParamsRun,
|
||||
closeTermnial,
|
||||
choose,
|
||||
editScript,
|
||||
|
||||
@@ -6,9 +6,9 @@ export const machineApi = {
|
||||
info: Api.create("/machines/{id}/sysinfo", 'get'),
|
||||
top: Api.create("/machines/{id}/top", 'get'),
|
||||
// 保存按钮
|
||||
save: Api.create("/machines", 'post'),
|
||||
saveMachine: Api.create("/machines", 'post'),
|
||||
// 删除机器
|
||||
del: Api.create("/devops/machines/{id}", 'delete'),
|
||||
del: Api.create("/machines/delete/{id}", 'delete'),
|
||||
scripts: Api.create("/machines/{machineId}/scripts", 'get'),
|
||||
runScript: Api.create("/machines/{machineId}/scripts/{scriptId}/run", 'get'),
|
||||
saveScript: Api.create("/machines/{machineId}/scripts", 'post'),
|
||||
|
||||
413
mayfly_go_web/src/views/ops/project/ProjectList.vue
Normal file
413
mayfly_go_web/src/views/ops/project/ProjectList.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<div class="project-list">
|
||||
<div class="toolbar">
|
||||
<el-button @click="showAddProjectDialog" v-auth="permissions.saveProject" type="primary" icon="el-icon-plus" size="mini">添加</el-button>
|
||||
<el-button
|
||||
@click="showAddProjectDialog(chooseData)"
|
||||
v-auth="permissions.saveProject"
|
||||
:disabled="chooseId == null"
|
||||
type="primary"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button @click="showMembers(chooseData)" :disabled="chooseId == null" type="success" icon="el-icon-setting" size="mini"
|
||||
>成员管理</el-button
|
||||
>
|
||||
|
||||
<el-button @click="showEnv(chooseData)" :disabled="chooseId == null" type="info" icon="el-icon-setting" size="mini">环境管理</el-button>
|
||||
|
||||
<el-button v-auth="'role:del'" :disabled="chooseId == null" type="danger" icon="el-icon-delete" size="mini">删除</el-button>
|
||||
|
||||
<div style="float: right">
|
||||
<el-input
|
||||
class="mr2"
|
||||
placeholder="请输入项目名!"
|
||||
size="small"
|
||||
style="width: 140px"
|
||||
v-model="query.name"
|
||||
@clear="search"
|
||||
clearable
|
||||
></el-input>
|
||||
<el-button @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="projects" @current-change="choose" border ref="table" style="width: 100%">
|
||||
<el-table-column label="选择" width="50px">
|
||||
<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-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<el-dialog width="400px" title="项目编辑" :before-close="cancelAddProject" v-model="addProjectDialog.visible">
|
||||
<el-form :model="addProjectDialog.form" size="small" label-width="70px">
|
||||
<el-form-item label="项目名:" required>
|
||||
<el-input :disabled="addProjectDialog.form.id" 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="addProject" type="primary" size="small">确 定</el-button>
|
||||
<el-button @click="cancelAddProject()" size="small">取 消</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="el-icon-plus" size="mini">添加</el-button>
|
||||
<!-- <el-button v-auth="'role:update'" :disabled="chooseId == null" type="danger" icon="el-icon-delete" size="mini">删除</el-button> -->
|
||||
</div>
|
||||
<el-table border :data="showEnvDialog.envs" size="small">
|
||||
<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" size="small" label-width="70px">
|
||||
<el-form-item 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 v-auth="permissions.saveEnv" @click="addEnv" type="primary" :loading="btnLoading" size="small">确 定</el-button>
|
||||
<el-button @click="cancelAddEnv()" size="small">取 消</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="el-icon-plus" size="mini"
|
||||
>添加</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="permissions.delMember"
|
||||
@click="deleteMember"
|
||||
:disabled="showMemDialog.chooseId == null"
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
>移除</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list" size="small">
|
||||
<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" size="small" 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 v-auth="permissions.saveMember" @click="addMember" type="primary" :loading="btnLoading" size="small"
|
||||
>确 定</el-button
|
||||
>
|
||||
<el-button @click="cancelAddMember()" size="small">取 消</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';
|
||||
import { auth } from '../../../common/utils/authFunction';
|
||||
export default defineComponent({
|
||||
name: 'ProjectList',
|
||||
components: {},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
permissions: {
|
||||
saveProject: 'project:save',
|
||||
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,
|
||||
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 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 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;
|
||||
};
|
||||
|
||||
const roleEditChange = (data: any) => {
|
||||
ElMessage.success('修改成功!');
|
||||
search();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
search,
|
||||
handlePageChange,
|
||||
choose,
|
||||
showAddProjectDialog,
|
||||
addProject,
|
||||
cancelAddProject,
|
||||
showMembers,
|
||||
setMemebers,
|
||||
showEnv,
|
||||
showAddMemberDialog,
|
||||
addMember,
|
||||
chooseMember,
|
||||
deleteMember,
|
||||
cancelAddMember,
|
||||
showAddEnvDialog,
|
||||
addEnv,
|
||||
cancelAddEnv,
|
||||
getAccount,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
15
mayfly_go_web/src/views/ops/project/api.ts
Normal file
15
mayfly_go_web/src/views/ops/project/api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const projectApi = {
|
||||
// 获取账号可访问的项目列表
|
||||
accountProjects: Api.create("/accounts/projects", 'get'),
|
||||
projects: Api.create("/projects", 'get'),
|
||||
saveProject: Api.create("/projects", 'post'),
|
||||
// 获取项目下的环境信息
|
||||
projectEnvs: Api.create("/projects/{projectId}/envs", 'get'),
|
||||
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'),
|
||||
}
|
||||
281
mayfly_go_web/src/views/ops/redis/DataOperation.vue
Normal file
281
mayfly_go_web/src/views/ops/redis/DataOperation.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<div style="float: left">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24">
|
||||
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearRedis">
|
||||
<template #default>
|
||||
<el-form-item label="redis" label-width="40px">
|
||||
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis" @clear="clearRedis" clearable>
|
||||
<el-option v-for="item in redisList" :key="item.id" :label="item.host" :value="item.id">
|
||||
<span style="float: left">{{ item.host }}</span>
|
||||
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
|
||||
`库: [${item.db}]`
|
||||
}}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="key" label-width="40px">
|
||||
<el-input
|
||||
placeholder="支持*模糊key"
|
||||
style="width: 180px"
|
||||
v-model="scanParam.match"
|
||||
size="mini"
|
||||
@clear="clear()"
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="40px">
|
||||
<el-input placeholder="count" style="width: 62px" v-model="scanParam.count" size="mini"></el-input>
|
||||
</el-form-item>
|
||||
<el-button @click="searchKey()" type="success" icon="el-icon-search" size="mini" plain></el-button>
|
||||
<el-button @click="scan()" icon="el-icon-bottom" size="mini" plain>scan</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="save(false)" plain></el-button>
|
||||
</template>
|
||||
</project-env-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div style="float: right">
|
||||
<!-- <el-button @click="scan()" icon="el-icon-refresh" size="small" plain>刷新</el-button> -->
|
||||
<span>keys: {{ dbsize }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="keys" border stripe :highlight-current-row="true" style="cursor: pointer">
|
||||
<el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
|
||||
<el-table-column prop="type" label="type" width="80"> </el-table-column>
|
||||
<el-table-column prop="ttl" label="ttl(过期时间)" width="120">
|
||||
<template #default="scope">
|
||||
{{ ttlConveter(scope.row.ttl) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button @click="getValue(scope.row)" type="success" icon="el-icon-search" size="mini" plain>查看</el-button>
|
||||
<el-button @click="del(scope.row.key)" type="danger" size="mini" icon="el-icon-delete" plain>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="text-align: center; margin-top: 10px"></div>
|
||||
|
||||
<value-dialog v-model:visible="valueDialog.visible" :keyValue="valueDialog.value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import ValueDialog from './ValueDialog.vue';
|
||||
import { redisApi } from './api';
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
|
||||
import { isTrue, notNull } from '@/common/assert';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataOperation',
|
||||
components: {
|
||||
ValueDialog,
|
||||
ProjectEnvSelect,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
cluster: 0,
|
||||
redisList: [],
|
||||
query: {
|
||||
envId: 0,
|
||||
},
|
||||
// redis: {
|
||||
// id: 0,
|
||||
// info: '',
|
||||
// conf: '',
|
||||
// },
|
||||
scanParam: {
|
||||
id: null,
|
||||
cluster: 0,
|
||||
match: null,
|
||||
count: 10,
|
||||
cursor: 0,
|
||||
prevCursor: null,
|
||||
},
|
||||
valueDialog: {
|
||||
visible: false,
|
||||
value: {},
|
||||
},
|
||||
keys: [],
|
||||
dbsize: 0,
|
||||
});
|
||||
|
||||
const searchRedis = async () => {
|
||||
notNull(state.query.envId, '请先选择项目环境');
|
||||
const res = await redisApi.redisList.request(state.query);
|
||||
state.redisList = res.list;
|
||||
};
|
||||
|
||||
const changeProjectEnv = (projectId: any, envId: any) => {
|
||||
clearRedis();
|
||||
if (envId != null) {
|
||||
state.query.envId = envId;
|
||||
searchRedis();
|
||||
}
|
||||
};
|
||||
|
||||
const changeRedis = (redisId: any) => {
|
||||
resetScanParam();
|
||||
state.keys = [];
|
||||
state.dbsize = 0;
|
||||
searchKey();
|
||||
};
|
||||
|
||||
const scan = () => {
|
||||
isTrue(state.scanParam.id != null, '请先选择redis');
|
||||
isTrue(state.scanParam.count < 2001, 'count不能超过2000');
|
||||
|
||||
state.loading = true;
|
||||
state.scanParam.cluster = state.cluster == 0 ? 0 : 1;
|
||||
|
||||
redisApi.scan.request(state.scanParam).then((res) => {
|
||||
state.keys = res.keys;
|
||||
state.dbsize = res.dbSize;
|
||||
state.scanParam.cursor = res.cursor;
|
||||
state.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const searchKey = () => {
|
||||
state.scanParam.cursor = 0;
|
||||
scan();
|
||||
};
|
||||
|
||||
const clearRedis = () => {
|
||||
state.redisList = [];
|
||||
state.scanParam.id = null;
|
||||
resetScanParam();
|
||||
state.keys = [];
|
||||
state.dbsize = 0;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
resetScanParam();
|
||||
if (state.scanParam.id) {
|
||||
scan();
|
||||
}
|
||||
};
|
||||
|
||||
const resetScanParam = () => {
|
||||
state.scanParam.match = null;
|
||||
state.scanParam.cursor = 0;
|
||||
state.scanParam.count = 10;
|
||||
};
|
||||
|
||||
const getValue = async (row: any) => {
|
||||
let api: any;
|
||||
switch (row.type) {
|
||||
case 'string':
|
||||
api = redisApi.getStringValue;
|
||||
break;
|
||||
case 'hash':
|
||||
api = redisApi.getHashValue;
|
||||
break;
|
||||
case 'set':
|
||||
api = redisApi.getSetValue;
|
||||
break;
|
||||
default:
|
||||
api = redisApi.getStringValue;
|
||||
break;
|
||||
}
|
||||
const id = state.cluster == 0 ? state.scanParam.id : state.cluster;
|
||||
const res = await api.request({
|
||||
cluster: state.cluster,
|
||||
key: row.key,
|
||||
id,
|
||||
});
|
||||
|
||||
let timed = row.ttl == 18446744073709552000 ? 0 : row.ttl;
|
||||
state.valueDialog.value = { id: state.scanParam.id, key: row.key, value: res, timed: timed, type: row.type };
|
||||
state.valueDialog.visible = true;
|
||||
};
|
||||
|
||||
// closeValueDialog() {
|
||||
// this.valueDialog.visible = false
|
||||
// this.valueDialog.value = {}
|
||||
// }
|
||||
|
||||
const update = (key: string) => {};
|
||||
|
||||
const del = (key: string) => {
|
||||
ElMessageBox.confirm(`此操作将删除对应的key , 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
let id = state.cluster == 0 ? state.scanParam.id : state.cluster;
|
||||
redisApi.delKey
|
||||
.request({
|
||||
cluster: state.cluster,
|
||||
key,
|
||||
id,
|
||||
})
|
||||
.then((res) => {
|
||||
ElMessage.success('删除成功!');
|
||||
scan();
|
||||
});
|
||||
})
|
||||
.catch((err) => {});
|
||||
};
|
||||
|
||||
const ttlConveter = (ttl: any) => {
|
||||
if (ttl == 18446744073709552000) {
|
||||
return '永久';
|
||||
}
|
||||
if (!ttl) {
|
||||
ttl = 0;
|
||||
}
|
||||
let second = parseInt(ttl); // 秒
|
||||
let min = 0; // 分
|
||||
let hour = 0; // 小时
|
||||
let day = 0;
|
||||
if (second > 60) {
|
||||
min = parseInt(second / 60 + '');
|
||||
second = second % 60;
|
||||
if (min > 60) {
|
||||
hour = parseInt(min / 60 + '');
|
||||
min = min % 60;
|
||||
if (hour > 24) {
|
||||
day = parseInt(hour / 24 + '');
|
||||
hour = hour % 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = '' + second + 's';
|
||||
if (min > 0) {
|
||||
result = '' + min + 'm:' + result;
|
||||
}
|
||||
if (hour > 0) {
|
||||
result = '' + hour + 'h:' + result;
|
||||
}
|
||||
if (day > 0) {
|
||||
result = '' + day + 'd:' + result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeProjectEnv,
|
||||
changeRedis,
|
||||
clearRedis,
|
||||
searchKey,
|
||||
scan,
|
||||
clear,
|
||||
getValue,
|
||||
del,
|
||||
ttlConveter,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
200
mayfly_go_web/src/views/ops/redis/Info.vue
Normal file
200
mayfly_go_web/src/views/ops/redis/Info.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="true" width="35%" @close="close()">
|
||||
<el-collapse>
|
||||
<el-collapse-item title="Server(Redis服务器的一般信息)" name="server">
|
||||
<div class="row">
|
||||
<span class="title">redis_version(版本):</span>
|
||||
<span class="value">{{ info.Server.redis_version }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">tcp_port(端口):</span>
|
||||
<span class="value">{{ info.Server.tcp_port }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">redis_mode(模式):</span>
|
||||
<span class="value">{{ info.Server.redis_mode }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">os(宿主操作系统):</span>
|
||||
<span class="value">{{ info.Server.os }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">uptime_in_days(运行天数):</span>
|
||||
<span class="value">{{ info.Server.uptime_in_days }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">executable(可执行文件路径):</span>
|
||||
<span class="value">{{ info.Server.executable }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">config_file(配置文件路径):</span>
|
||||
<span class="value">{{ info.Server.config_file }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Clients(客户端连接)" name="client">
|
||||
<div class="row">
|
||||
<span class="title">connected_clients(已连接客户端数):</span>
|
||||
<span class="value">{{ info.Clients.connected_clients }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">blocked_clients(正在等待阻塞命令客户端数):</span>
|
||||
<span class="value">{{ info.Clients.blocked_clients }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="Keyspace(key信息)" name="keyspace">
|
||||
<div class="row" v-for="(value, key) in info.Keyspace" :key="key">
|
||||
<span class="title">{{ key }}: </span>
|
||||
<span class="value">{{ value }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Stats(统计)" name="state">
|
||||
<div class="row">
|
||||
<span class="title">total_commands_processed(总处理命令数):</span>
|
||||
<span class="value">{{ info.Stats.total_commands_processed }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
|
||||
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_net_input_bytes(网络入口流量字节数):</span>
|
||||
<span class="value">{{ info.Stats.total_net_input_bytes }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_net_output_bytes(网络出口流量字节数):</span>
|
||||
<span class="value">{{ info.Stats.total_net_output_bytes }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">expired_keys(过期key的总数量):</span>
|
||||
<span class="value">{{ info.Stats.expired_keys }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">instantaneous_ops_per_sec(当前qps):</span>
|
||||
<span class="value">{{ info.Stats.instantaneous_ops_per_sec }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Persistence(持久化)" name="persistence">
|
||||
<div class="row">
|
||||
<span class="title">aof_enabled(是否启用aof):</span>
|
||||
<span class="value">{{ info.Persistence.aof_enabled }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">loading(是否正在载入持久化文件):</span>
|
||||
<span class="value">{{ info.Persistence.loading }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Cluster(集群)" name="cluster">
|
||||
<div class="row">
|
||||
<span class="title">cluster_enabled(是否启用集群模式):</span>
|
||||
<span class="value">{{ info.Cluster.cluster_enabled }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Memory(内存消耗相关信息)" name="memory">
|
||||
<div class="row">
|
||||
<span class="title">used_memory(分配内存总量):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">maxmemory(最大内存配置):</span>
|
||||
<span class="value">{{ info.Memory.maxmemory }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_memory_rss(已分配的内存总量,操作系统角度):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_rss_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">mem_fragmentation_ratio(used_memory_rss和used_memory 之间的比率):</span>
|
||||
<span class="value">{{ info.Memory.mem_fragmentation_ratio }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_memory_peak(内存消耗峰值):</span>
|
||||
<span class="value">{{ info.Memory.used_memory_peak_human }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">total_system_memory(主机总内存):</span>
|
||||
<span class="value">{{ info.Memory.total_system_memory_human }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="CPU" name="cpu">
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_sys(由Redis服务器消耗的系统CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_sys }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_user(由Redis服务器消耗的用户CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_user }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_sys_children(由后台进程消耗的系统CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_sys_children }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="title">used_cpu_user_children(由后台进程消耗的用户CPU):</span>
|
||||
<span class="value">{{ info.CPU.used_cpu_user_children }}</span>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, watch, toRefs } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Info',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
info: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
state.visible = val;
|
||||
}
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
close,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.row .title {
|
||||
font-size: 12px;
|
||||
color: #8492a6;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.row .value {
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
190
mayfly_go_web/src/views/ops/redis/RedisEdit.vue
Normal file
190
mayfly_go_web/src/views/ops/redis/RedisEdit.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" :before-close="cancel" width="35%">
|
||||
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px" size="small">
|
||||
<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>
|
||||
|
||||
<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="host" label="host:" required>
|
||||
<el-input v-model.trim="form.host" placeholder="请输入host:port" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:">
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="db" label="库号:" required>
|
||||
<el-input v-model.trim="form.db" placeholder="请输入库号"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="btnLoading" @click="btnOk" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { projectApi } from '../project/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedisEdit',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
},
|
||||
redis: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const redisForm: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
projects: [],
|
||||
envs: [],
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
host: null,
|
||||
password: null,
|
||||
project: null,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
host: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入主机ip:port',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
db: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入库号',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
watch(props, async (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
state.projects = newValue.projects;
|
||||
if (newValue.redis) {
|
||||
getEnvs(newValue.redis.projectId);
|
||||
state.form = { ...newValue.redis };
|
||||
} else {
|
||||
state.envs = [];
|
||||
state.form = { db: 0 } as any;
|
||||
}
|
||||
});
|
||||
|
||||
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.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((valid: boolean) => {
|
||||
if (valid) {
|
||||
redisApi.saveRedis.request(state.form).then((res: any) => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
state.btnLoading = true;
|
||||
setTimeout(() => {
|
||||
state.btnLoading = false;
|
||||
}, 1000);
|
||||
|
||||
cancel();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
redisForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
redisForm,
|
||||
changeProject,
|
||||
changeEnv,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
369
mayfly_go_web/src/views/ops/redis/RedisList.vue
Normal file
369
mayfly_go_web/src/views/ops/redis/RedisList.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="editRedis(true)" plain>添加</el-button>
|
||||
<el-button type="primary" icon="el-icon-edit" :disabled="currentId == null" size="mini" @click="editRedis(false)" plain>编辑</el-button>
|
||||
<el-button type="danger" icon="el-icon-delete" :disabled="currentId == null" size="mini" @click="deleteRedis" plain>删除</el-button>
|
||||
<div style="float: right">
|
||||
<!-- <el-input placeholder="host" size="mini" style="width: 140px" v-model="query.host" @clear="search" plain clearable></el-input>
|
||||
<el-select v-model="params.clusterId" size="mini" clearable placeholder="集群选择">
|
||||
<el-option v-for="item in clusters" :key="item.id" :value="item.id" :label="item.name"></el-option>
|
||||
</el-select> -->
|
||||
<el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable size="small">
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
<el-button class="ml5" @click="search" type="success" icon="el-icon-search" size="mini"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="redisTable" stripe style="width: 100%" @current-change="choose">
|
||||
<el-table-column label="选择" width="50px">
|
||||
<template #default="scope">
|
||||
<el-radio v-model="currentId" :label="scope.row.id">
|
||||
<i></i>
|
||||
</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="host" label="host:port" width></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="操作" width>
|
||||
<template #default="scope">
|
||||
<el-button type="primary" @click="info(scope.row)" icon="el-icon-tickets" size="mini" plain>info</el-button>
|
||||
<!-- <el-button type="success" @click="manage(scope.row)" :ref="scope.row" size="mini" plain>数据管理</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@current-change="handlePageChange"
|
||||
style="text-align: center"
|
||||
background
|
||||
layout="prev, pager, next, total, jumper"
|
||||
:total="total"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
/>
|
||||
|
||||
<info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
|
||||
|
||||
<redis-edit
|
||||
@val-change="valChange"
|
||||
:projects="projects"
|
||||
:title="redisEditDialog.title"
|
||||
v-model:visible="redisEditDialog.visible"
|
||||
v-model:redis="redisEditDialog.data"
|
||||
></redis-edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
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 RedisEdit from './RedisEdit.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedisList',
|
||||
components: {
|
||||
Info,
|
||||
RedisEdit,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
projects: [],
|
||||
redisTable: [],
|
||||
total: 0,
|
||||
currentId: null,
|
||||
currentData: null,
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
prjectId: null,
|
||||
clusterId: null,
|
||||
},
|
||||
redisInfo: {
|
||||
url: '',
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: 0,
|
||||
name: '单机',
|
||||
},
|
||||
],
|
||||
infoDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
info: {
|
||||
Server: {},
|
||||
Keyspace: {},
|
||||
Clients: {},
|
||||
CPU: {},
|
||||
Memory: {},
|
||||
},
|
||||
},
|
||||
redisEditDialog: {
|
||||
visible: false,
|
||||
data: null,
|
||||
title: '新增redis',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
search();
|
||||
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list;
|
||||
});
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
state.query.pageNum = curPage;
|
||||
search();
|
||||
};
|
||||
|
||||
const choose = (item: any) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
state.currentId = item.id;
|
||||
state.currentData = item;
|
||||
};
|
||||
|
||||
// connect() {
|
||||
// Req.post('/open/redis/connect', this.form, res => {
|
||||
// this.redisInfo = res
|
||||
// })
|
||||
// }
|
||||
|
||||
const deleteRedis = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await redisApi.delRedis.request({ id: state.currentId });
|
||||
ElMessage.success('删除成功');
|
||||
state.currentData = null;
|
||||
state.currentId = null;
|
||||
search();
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const info = (redis: any) => {
|
||||
redisApi.redisInfo.request({ id: redis.id }).then((res: any) => {
|
||||
state.infoDialog.info = res;
|
||||
state.infoDialog.title = `'${redis.host}' info`;
|
||||
state.infoDialog.visible = true;
|
||||
});
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
const res = await redisApi.redisList.request(state.query);
|
||||
state.redisTable = res.list;
|
||||
state.total = res.total;
|
||||
};
|
||||
|
||||
const editRedis = (isAdd = false) => {
|
||||
if (isAdd) {
|
||||
state.redisEditDialog.data = null;
|
||||
state.redisEditDialog.title = '新增redis';
|
||||
} else {
|
||||
state.redisEditDialog.data = state.currentData;
|
||||
state.redisEditDialog.title = '修改redis';
|
||||
}
|
||||
state.redisEditDialog.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
search();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
search,
|
||||
handlePageChange,
|
||||
choose,
|
||||
info,
|
||||
deleteRedis,
|
||||
editRedis,
|
||||
valChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
// @Component({
|
||||
// name: 'RedisList',
|
||||
// components: {
|
||||
// Info,
|
||||
// DynamicFormDialog
|
||||
// }
|
||||
// })
|
||||
// export default class RedisList extends Vue {
|
||||
// validatePort = (rule: any, value: any, callback: any) => {
|
||||
// if (value > 65535 || value < 1) {
|
||||
// callback(new Error('端口号错误'))
|
||||
// }
|
||||
// callback()
|
||||
// }
|
||||
|
||||
// redisTable = []
|
||||
// permission = redisPermission
|
||||
// keyPermission = redisKeyPermission
|
||||
// currentId = null
|
||||
// currentData: any = null
|
||||
// params = {
|
||||
// host: null,
|
||||
// clusterId: null
|
||||
// }
|
||||
// redisInfo = {
|
||||
// url: ''
|
||||
// }
|
||||
// clusters = [
|
||||
// {
|
||||
// id: 0,
|
||||
// name: '单机'
|
||||
// }
|
||||
// ]
|
||||
// infoDialog = {
|
||||
// title: '',
|
||||
// visible: false,
|
||||
// info: {
|
||||
// Server: {},
|
||||
// Keyspace: {},
|
||||
// Clients: {},
|
||||
// CPU: {},
|
||||
// Memory: {}
|
||||
// }
|
||||
// }
|
||||
// formDialog = {
|
||||
// visible: false,
|
||||
// title: '',
|
||||
// formInfo: {
|
||||
// createApi: redisApi.save,
|
||||
// updateApi: redisApi.update,
|
||||
// formRows: [
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '主机:',
|
||||
// name: 'host',
|
||||
// placeholder: '请输入节点ip',
|
||||
// rules: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入节点ip',
|
||||
// trigger: ['blur', 'change']
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '端口号:',
|
||||
// name: 'port',
|
||||
// placeholder: '请输入节点端口号',
|
||||
// inputType: 'number',
|
||||
// rules: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入节点端口号',
|
||||
// trigger: ['blur', 'change']
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '密码:',
|
||||
// name: 'pwd',
|
||||
// placeholder: '请输入节点密码',
|
||||
// inputType: 'password'
|
||||
// }
|
||||
// ],
|
||||
// [
|
||||
// {
|
||||
// type: 'input',
|
||||
// label: '描述:',
|
||||
// name: 'description',
|
||||
// placeholder: '请输入节点描述',
|
||||
// inputType: 'textarea'
|
||||
// }
|
||||
// ]
|
||||
// ]
|
||||
// },
|
||||
// formData: { port: 6379 }
|
||||
// }
|
||||
|
||||
// mounted() {
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// choose(item: any) {
|
||||
// if (!item) {
|
||||
// return
|
||||
// }
|
||||
// this.currentId = item.id
|
||||
// this.currentData = item
|
||||
// }
|
||||
|
||||
// // connect() {
|
||||
// // Req.post('/open/redis/connect', this.form, res => {
|
||||
// // this.redisInfo = res
|
||||
// // })
|
||||
// // }
|
||||
|
||||
// async deleteNode() {
|
||||
// await redisApi.del.request({ id: this.currentId })
|
||||
// this.$message.success('删除成功')
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// manage(row: any) {
|
||||
// this.$router.push(`/redis_operation/${row.clusterId}/${row.id}`)
|
||||
// }
|
||||
|
||||
// info(redis: any) {
|
||||
// redisApi.info.request({ id: redis.id }).then(res => {
|
||||
// this.infoDialog.info = res
|
||||
// this.infoDialog.title = `'${redis.host}' info`
|
||||
// this.infoDialog.visible = true
|
||||
// })
|
||||
// }
|
||||
|
||||
// search() {
|
||||
// redisApi.list.request(this.params).then(res => {
|
||||
// this.redisTable = res
|
||||
// })
|
||||
// }
|
||||
|
||||
// openFormDialog(redis: any) {
|
||||
// let dialogTitle
|
||||
// if (redis) {
|
||||
// this.formDialog.formData = this.currentData
|
||||
// dialogTitle = '编辑redis节点'
|
||||
// } else {
|
||||
// this.formDialog.formData = { port: 6379 }
|
||||
// dialogTitle = '添加redis节点'
|
||||
// }
|
||||
|
||||
// this.formDialog.title = dialogTitle
|
||||
// this.formDialog.visible = true
|
||||
// }
|
||||
|
||||
// submitSuccess() {
|
||||
// this.currentId = null
|
||||
// this.currentData = null
|
||||
// this.search()
|
||||
// }
|
||||
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
76
mayfly_go_web/src/views/ops/redis/ValueDialog.vue
Normal file
76
mayfly_go_web/src/views/ops/redis/ValueDialog.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<el-dialog :title="keyValue.key" v-model="visible" :before-close="cancel" :show-close="false" width="750px">
|
||||
<el-form>
|
||||
<el-form-item>
|
||||
<el-input v-model="keyValue.value" type="textarea" :autosize="{ minRows: 10, maxRows: 20 }" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="saveValue" type="primary" size="mini">确 定</el-button>
|
||||
<el-button @click="cancel()" size="mini">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, watch, toRefs } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { isTrue } from '@/common/assert';
|
||||
export default defineComponent({
|
||||
name: 'ValueDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
keyValue: {
|
||||
type: [String, Object],
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
keyValue: {} as any,
|
||||
});
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
state.visible = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.keyValue,
|
||||
(val) => {
|
||||
state.keyValue = val;
|
||||
if (state.keyValue.type != 'string') {
|
||||
state.keyValue.value = JSON.stringify(val.value, undefined, 2)
|
||||
}
|
||||
// state.keyValue.value = JSON.stringify(val.value, undefined, 2)
|
||||
}
|
||||
);
|
||||
|
||||
const saveValue = async () => {
|
||||
isTrue(state.keyValue.type == 'string', "暂不支持除string外其他类型修改")
|
||||
await redisApi.saveStringValue.request(state.keyValue);
|
||||
ElMessage.success('保存成功');
|
||||
cancel();
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
saveValue,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
17
mayfly_go_web/src/views/ops/redis/api.ts
Normal file
17
mayfly_go_web/src/views/ops/redis/api.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const redisApi = {
|
||||
redisList : Api.create("/redis", 'get'),
|
||||
redisInfo: Api.create("/redis/{id}/info", 'get'),
|
||||
saveRedis: Api.create("/redis", 'post'),
|
||||
delRedis: Api.create("/redis/{id}", 'delete'),
|
||||
// 获取权限列表
|
||||
scan: Api.create("/redis/{id}/scan/{cursor}/{count}", 'get'),
|
||||
getStringValue: Api.create("/redis/{id}/string-value", 'get'),
|
||||
saveStringValue: Api.create("/redis/{id}/string-value", 'post'),
|
||||
getHashValue: Api.create("/redis/{id}/hash-value", 'get'),
|
||||
getSetValue: Api.create("/redis/{id}/set-value", 'get'),
|
||||
saveHashValue: Api.create("/redis/{id}/hash-value", 'post'),
|
||||
del: Api.create("/redis/{id}/scan/{cursor}/{count}", 'delete'),
|
||||
delKey: Api.create("/redis/{id}/key", 'delete'),
|
||||
}
|
||||
1
mayfly_go_web/src/views/ops/redis/index.ts
Normal file
1
mayfly_go_web/src/views/ops/redis/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './RedisList.vue';
|
||||
@@ -3,14 +3,14 @@
|
||||
<el-dialog :title="title" v-model="visible" :show-close="false" width="35%">
|
||||
<el-form :model="form" ref="accountForm" :rules="rules" label-width="85px" size="small">
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入用户名" auto-complete="off"></el-input>
|
||||
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:" required>
|
||||
<!-- <el-form-item prop="password" label="密码:" required>
|
||||
<el-input type="password" v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!edit" label="确认密码:" required>
|
||||
<el-input type="password" v-model.trim="form.repassword" placeholder="请输入确认密码" autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -61,13 +61,13 @@ export default defineComponent({
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
// password: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请输入密码',
|
||||
// trigger: ['change', 'blur'],
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
>
|
||||
<div style="float: right">
|
||||
<el-input
|
||||
class="mr2"
|
||||
placeholder="请输入账号名"
|
||||
size="small"
|
||||
style="width: 140px"
|
||||
@@ -45,8 +46,13 @@
|
||||
<el-tag v-if="scope.row.status == -1" type="danger" size="mini">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.lastLoginTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- <el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column> -->
|
||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
||||
<el-table-column min-width="160" prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
@@ -58,8 +64,8 @@
|
||||
{{ $filters.dateFormat(scope.row.updateTime) }}
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间"></el-table-column>
|
||||
<el-table-column min-width="120" prop="remark" label="备注" show-overflow-tooltip></el-table-column>
|
||||
|
||||
<!-- <el-table-column min-width="120" prop="remark" label="备注" show-overflow-tooltip></el-table-column> -->
|
||||
<el-table-column label="查看更多" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-link @click.prevent="showRoles(scope.row)" type="success">角色</el-link>
|
||||
@@ -70,14 +76,21 @@
|
||||
|
||||
<el-table-column label="操作" min-width="200px">
|
||||
<template #default="scope">
|
||||
<el-button v-auth="'account:changeStatus'" v-if="scope.row.status == 1" type="danger" icom="el-icon-tickets" size="mini" plain
|
||||
<el-button
|
||||
v-auth="'account:changeStatus'"
|
||||
@click="changeStatus(scope.row)"
|
||||
v-if="scope.row.status == 1"
|
||||
type="danger"
|
||||
icom="el-icon-tickets"
|
||||
size="mini"
|
||||
plain
|
||||
>禁用</el-button
|
||||
>
|
||||
<el-button
|
||||
v-auth="'account:changeStatus'"
|
||||
v-if="scope.row.status == -1"
|
||||
type="success"
|
||||
@click="serviceManager(scope.row)"
|
||||
@click="changeStatus(scope.row)"
|
||||
size="mini"
|
||||
plain
|
||||
>启用</el-button
|
||||
@@ -136,7 +149,7 @@ import RoleEdit from './RoleEdit.vue';
|
||||
import AccountEdit from './AccountEdit.vue';
|
||||
import enums from '../enums';
|
||||
import { accountApi } from '../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
export default defineComponent({
|
||||
name: 'AccountList',
|
||||
components: {
|
||||
@@ -223,13 +236,13 @@ export default defineComponent({
|
||||
|
||||
const changeStatus = async (row: any) => {
|
||||
let id = row.id;
|
||||
let status = row.status ? 1 : -1;
|
||||
// await accountApi.changeStatus.request({
|
||||
// id,
|
||||
// status,
|
||||
// });
|
||||
// ElMessage.success('操作成功');
|
||||
// search();
|
||||
let status = row.status == -1 ? 1 : -1;
|
||||
await accountApi.changeStatus.request({
|
||||
id,
|
||||
status,
|
||||
});
|
||||
ElMessage.success('操作成功');
|
||||
search();
|
||||
};
|
||||
|
||||
const handlePageChange = (curPage: number) => {
|
||||
@@ -267,12 +280,17 @@ export default defineComponent({
|
||||
|
||||
const deleteAccount = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除该账号?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await accountApi.del.request({ id: state.chooseId });
|
||||
ElMessage.success('删除成功');
|
||||
state.chooseData = null;
|
||||
state.chooseId = null;
|
||||
search();
|
||||
} catch (error) {
|
||||
ElMessage.error('刪除失败');
|
||||
}
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="allRole" border ref="roleTable" @select="select" style="width: 100%">
|
||||
<el-table-column type="selection" width="40"></el-table-column>
|
||||
<el-table-column :selectable="selectable" type="selection" width="40"></el-table-column>
|
||||
<el-table-column prop="name" label="角色名称"></el-table-column>
|
||||
<el-table-column prop="code" label="角色code"></el-table-column>
|
||||
<el-table-column prop="remark" label="角色描述">
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark ? scope.row.remark : '暂无描述' }}
|
||||
@@ -92,6 +93,11 @@ export default defineComponent({
|
||||
search();
|
||||
};
|
||||
|
||||
const selectable = (row: any) => {
|
||||
// 角色code不以COMMON开头才可勾选
|
||||
return row.code.indexOf('COMMON') != 0;
|
||||
};
|
||||
|
||||
const select = (val: any, row: any) => {
|
||||
let roles = state.roles;
|
||||
// 如果账号的角色id存在则为取消该角色(删除角色id列表中的该记录id),否则为新增角色
|
||||
@@ -164,6 +170,7 @@ export default defineComponent({
|
||||
roleTable,
|
||||
search,
|
||||
handlePageChange,
|
||||
selectable,
|
||||
select,
|
||||
btnOk,
|
||||
cancel,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const accountApi = {
|
||||
save: Api.create("/sys/accounts", 'post'),
|
||||
update: Api.create("/sys/accounts/{id}", 'put'),
|
||||
del: Api.create("/sys/accounts/{id}", 'delete'),
|
||||
changeStatus: Api.create("/sys/accounts/{id}/{status}", 'put'),
|
||||
changeStatus: Api.create("/sys/accounts/change-status/{id}/{status}", 'put'),
|
||||
roleIds: Api.create("/sys/accounts/{id}/roleIds", 'get'),
|
||||
roles: Api.create("/sys/accounts/{id}/roles", 'get'),
|
||||
resources: Api.create("/sys/accounts/{id}/resources", 'get'),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code" label="path|code">
|
||||
<el-input v-model.trim="form.code" placeholder="菜单为路由path"></el-input>
|
||||
<el-input v-model.trim="form.code" placeholder="菜单不带/自动拼接父路径"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="序号" prop="weight" required>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<el-form-item label="角色名称:" required>
|
||||
<el-input v-model="form.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色code:" required>
|
||||
<el-input :disabled="form.id" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色描述:">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="角色名称"></el-table-column>
|
||||
<el-table-column prop="code" label="角色code"></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">
|
||||
|
||||
Reference in New Issue
Block a user