mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
feat: 新增数据库sql执行记录&执行前原值等信息
This commit is contained in:
@@ -292,10 +292,7 @@ export default defineComponent({
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
resetInputDb();
|
||||
dbForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -59,8 +59,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- <el-table-column fixed="right" label="更多信息" min-width="100">
|
||||
</el-table-column> -->
|
||||
<el-table-column label="操作" min-width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">SQL执行记录</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||
<el-pagination
|
||||
@@ -124,6 +127,54 @@
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
width="90%"
|
||||
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
|
||||
:before-close="onBeforeCloseSqlExecDialog"
|
||||
v-model="sqlExecLogDialog.visible"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<el-input v-model="sqlExecLogDialog.query.db" placeholder="请输入数据库名" clearable style="width: 150px" />
|
||||
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5" style="width: 150px" />
|
||||
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
|
||||
<el-option label="UPDATE" :value="1"> </el-option>
|
||||
<el-option label="DELETE" :value="2"> </el-option>
|
||||
<el-option label="INSERT" :value="3"> </el-option>
|
||||
</el-select>
|
||||
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
|
||||
</div>
|
||||
<el-table border stripe :data="sqlExecLogDialog.data" size="small">
|
||||
<el-table-column prop="db" label="数据库" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.type == 1" color="#E4F5EB" size="small">UPDATE</el-tag>
|
||||
<el-tag v-if="scope.row.type == 2" color="#F9E2AE" size="small">DELETE</el-tag>
|
||||
<el-tag v-if="scope.row.type == 3" color="#A8DEE0" size="small">INSERT</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="oldValue" label="原值" min-width="150" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="creator" label="执行人" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="createTime" label="执行时间" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||
<el-pagination
|
||||
style="text-align: right"
|
||||
@current-change="handleSqlExecPageChange"
|
||||
:total="sqlExecLogDialog.total"
|
||||
layout="prev, pager, next, total, jumper"
|
||||
v-model:current-page="sqlExecLogDialog.query.pageNum"
|
||||
:page-size="sqlExecLogDialog.query.pageSize"
|
||||
></el-pagination>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
|
||||
<el-table border stripe :data="columnDialog.columns" size="small">
|
||||
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
|
||||
@@ -195,7 +246,18 @@ export default defineComponent({
|
||||
},
|
||||
datas: [],
|
||||
total: 0,
|
||||
|
||||
// sql执行记录弹框
|
||||
sqlExecLogDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
query: {
|
||||
dbId: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 12,
|
||||
},
|
||||
},
|
||||
chooseTableName: '',
|
||||
tableInfoDialog: {
|
||||
visible: false,
|
||||
@@ -284,6 +346,32 @@ export default defineComponent({
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const onShowSqlExec = async (row: any) => {
|
||||
state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`;
|
||||
state.sqlExecLogDialog.query.dbId = row.id;
|
||||
searchSqlExecLog();
|
||||
state.sqlExecLogDialog.visible = true;
|
||||
};
|
||||
|
||||
const onBeforeCloseSqlExecDialog = () => {
|
||||
state.sqlExecLogDialog.visible = false;
|
||||
state.sqlExecLogDialog.data = [];
|
||||
state.sqlExecLogDialog.total = 0;
|
||||
state.sqlExecLogDialog.query.dbId = 0;
|
||||
state.sqlExecLogDialog.query.pageNum = 1;
|
||||
};
|
||||
|
||||
const searchSqlExecLog = async () => {
|
||||
const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query);
|
||||
state.sqlExecLogDialog.data = res.list;
|
||||
state.sqlExecLogDialog.total = res.total;
|
||||
};
|
||||
|
||||
const handleSqlExecPageChange = (curPage: number) => {
|
||||
state.sqlExecLogDialog.query.pageNum = curPage;
|
||||
searchSqlExecLog();
|
||||
};
|
||||
|
||||
const showTableInfo = async (row: any, db: string) => {
|
||||
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
|
||||
state.dbId = row.id;
|
||||
@@ -360,6 +448,10 @@ export default defineComponent({
|
||||
editDb,
|
||||
valChange,
|
||||
deleteDb,
|
||||
onShowSqlExec,
|
||||
onBeforeCloseSqlExecDialog,
|
||||
handleSqlExecPageChange,
|
||||
searchSqlExecLog,
|
||||
showTableInfo,
|
||||
closeTableInfo,
|
||||
showColumns,
|
||||
|
||||
@@ -239,7 +239,6 @@ import 'codemirror/theme/base16-light.css';
|
||||
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import _CodeMirror from 'codemirror';
|
||||
// import 'codemirror/mode/sql/sql.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
import 'codemirror/addon/hint/sql-hint.js';
|
||||
|
||||
@@ -390,7 +389,44 @@ export default defineComponent({
|
||||
let sql = getSql();
|
||||
isTrue(sql && sql.trim(), '请选中需要执行的sql');
|
||||
|
||||
state.queryTab.loading = true;
|
||||
// 去除字符串前的空格、换行等
|
||||
sql = sql.replace(/(^\s*)/g, '');
|
||||
let execRemark = '';
|
||||
let canRun = true;
|
||||
if (
|
||||
sql.startsWith('update') ||
|
||||
sql.startsWith('UPDATE') ||
|
||||
sql.startsWith('INSERT') ||
|
||||
sql.startsWith('insert') ||
|
||||
sql.startsWith('DELETE') ||
|
||||
sql.startsWith('delete')
|
||||
) {
|
||||
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
|
||||
inputErrorMessage: '请输入执行该sql的备注信息',
|
||||
});
|
||||
execRemark = res.value;
|
||||
if (!execRemark) {
|
||||
canRun = false;
|
||||
}
|
||||
}
|
||||
if (!canRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
state.queryTab.loading = true;
|
||||
const colAndData: any = await runSql(sql, execRemark);
|
||||
state.queryTab.execRes.data = colAndData.res;
|
||||
state.queryTab.execRes.tableColumn = colAndData.colNames;
|
||||
state.queryTab.loading = false;
|
||||
} catch (e: any) {
|
||||
state.queryTab.loading = false;
|
||||
}
|
||||
closeExecBtns();
|
||||
|
||||
// 即只有以该字符串开头的sql才可修改表数据内容
|
||||
if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) {
|
||||
state.queryTab.selectionDatas = [];
|
||||
@@ -407,16 +443,6 @@ export default defineComponent({
|
||||
state.queryTab.nowTableName = '';
|
||||
state.nowTableName = '';
|
||||
}
|
||||
|
||||
try {
|
||||
const colAndData: any = await runSql(sql);
|
||||
state.queryTab.execRes.data = colAndData.res;
|
||||
state.queryTab.execRes.tableColumn = colAndData.colNames;
|
||||
state.queryTab.loading = false;
|
||||
} catch (e: any) {
|
||||
state.queryTab.loading = false;
|
||||
}
|
||||
closeExecBtns();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -424,12 +450,40 @@ export default defineComponent({
|
||||
*
|
||||
* @param sql 执行的sql
|
||||
*/
|
||||
const runSql = (sql: string) => {
|
||||
return dbApi.sqlExec.request({
|
||||
const runSql = async (sql: string, remark: string = '') => {
|
||||
return await dbApi.sqlExec.request({
|
||||
id: state.dbId,
|
||||
db: state.db,
|
||||
sql: sql.trim(),
|
||||
remark,
|
||||
});
|
||||
// const sqlTrim = sql.trim();
|
||||
// let remark = '';
|
||||
// let canRun = true;
|
||||
// const needRemark = ['update', 'UPDATE', 'delete', 'DELETE', 'INSERT', 'insert'].indexOf(sqlTrim.split(' ')[0]);
|
||||
// if (needRemark) {
|
||||
// const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
// });
|
||||
// remark = res.value;
|
||||
// if (!remark) {
|
||||
// canRun = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!canRun) {
|
||||
// return;
|
||||
// }
|
||||
// try {
|
||||
// state.queryTab.loading = true;
|
||||
// return await dbApi.sqlExec.request({
|
||||
// id: state.dbId,
|
||||
// db: state.db,
|
||||
// sql: sqlTrim,
|
||||
// remark,
|
||||
// });
|
||||
// } catch (e: any) {}
|
||||
};
|
||||
|
||||
const removeDataTab = (targetName: string) => {
|
||||
@@ -497,8 +551,9 @@ export default defineComponent({
|
||||
if (flag === 'equal') {
|
||||
// 获取该列中第一个不为空的数据(内容)
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
if (tableData[i][str].length > 0) {
|
||||
columnContent = tableData[i][str];
|
||||
// 转为字符串后比较
|
||||
if ((tableData[i][str] + '').length > 0) {
|
||||
columnContent = tableData[i][str] + '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -515,7 +570,7 @@ export default defineComponent({
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
columnContent = tableData[index][str];
|
||||
columnContent = tableData[index][str] + '';
|
||||
}
|
||||
const contentWidth: number = getContentWidth(columnContent);
|
||||
// 获取列名称的长度 加上排序图标长度
|
||||
@@ -899,7 +954,8 @@ export default defineComponent({
|
||||
if (!state.nowTableName || !property) {
|
||||
return;
|
||||
}
|
||||
let text = row[property];
|
||||
// 转为字符串比较,可能存在数字等
|
||||
let text = row[property] + '';
|
||||
let div = cell.children[0];
|
||||
if (div) {
|
||||
let input = document.createElement('input');
|
||||
|
||||
@@ -12,7 +12,7 @@ export const dbApi = {
|
||||
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
|
||||
// 获取表即列提示
|
||||
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
|
||||
sqlExec: Api.create("/dbs/{id}/exec-sql", 'get'),
|
||||
sqlExec: Api.create("/dbs/{id}/exec-sql", 'post'),
|
||||
// 保存sql
|
||||
saveSql: Api.create("/dbs/{id}/sql", 'post'),
|
||||
// 获取保存的sql
|
||||
@@ -20,4 +20,6 @@ export const dbApi = {
|
||||
// 获取保存的sql names
|
||||
getSqlNames: Api.create("/dbs/{id}/sql-names", 'get'),
|
||||
deleteDbSql: Api.create("/dbs/{id}/sql", 'delete'),
|
||||
// 获取数据库sql执行记录
|
||||
getSqlExecs: Api.create("/dbs/{id}/sql-execs", 'get'),
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
|
||||
<el-input v-model="remark" placeholder="请输入执行备注" class="mb5" />
|
||||
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -15,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
import { dbApi } from '../api';
|
||||
import { ElDialog, ElButton } from 'element-plus';
|
||||
import { ElDialog, ElButton, ElInput, ElMessage } from 'element-plus';
|
||||
// import base style
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
// 引入主题后还需要在 options 中指定主题才会生效
|
||||
@@ -32,6 +33,7 @@ export default defineComponent({
|
||||
codemirror,
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElInput,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
@@ -53,6 +55,7 @@ export default defineComponent({
|
||||
sqlValue: '',
|
||||
dbId: 0,
|
||||
db: '',
|
||||
remark: '',
|
||||
btnLoading: false,
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
@@ -77,11 +80,17 @@ export default defineComponent({
|
||||
* 执行sql
|
||||
*/
|
||||
const runSql = async () => {
|
||||
if (!state.remark) {
|
||||
ElMessage.error('请输入执行的备注信息');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
state.btnLoading = true;
|
||||
await dbApi.sqlExec.request({
|
||||
id: state.dbId,
|
||||
db: state.db,
|
||||
remark: state.remark,
|
||||
sql: state.sqlValue.trim(),
|
||||
});
|
||||
runSuccess = true;
|
||||
@@ -104,6 +113,7 @@ export default defineComponent({
|
||||
setTimeout(() => {
|
||||
state.dbId = 0;
|
||||
state.sqlValue = '';
|
||||
state.remark = '';
|
||||
runSuccessCallback = null;
|
||||
cancelCallback = null;
|
||||
runSuccess = false;
|
||||
@@ -133,7 +143,4 @@ export default defineComponent({
|
||||
font-size: 9pt;
|
||||
font-weight: 600;
|
||||
}
|
||||
.footer {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -171,11 +171,6 @@ export default defineComponent({
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
machineForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -166,11 +166,6 @@ export default defineComponent({
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
mongoForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -170,11 +170,6 @@ export default defineComponent({
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
redisForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="account-dialog">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
|
||||
<el-form :model="form" ref="accountForm" :rules="rules" label-width="85px">
|
||||
<el-form-item prop="username" label="用户名:" required>
|
||||
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名" auto-complete="off"></el-input>
|
||||
@@ -105,15 +105,7 @@ export default defineComponent({
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
setTimeout(() => {
|
||||
emit('update:account', null);
|
||||
}, 800);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
accountForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="account-dialog">
|
||||
<el-dialog
|
||||
:title="account == null ? '' : '分配“' + account.username + '”的角色'"
|
||||
v-model="visible"
|
||||
v-model="dialogVisible"
|
||||
:before-close="cancel"
|
||||
:show-close="false"
|
||||
>
|
||||
@@ -15,7 +15,7 @@
|
||||
<el-table :data="allRole" border ref="roleTable" @select="select" style="width: 100%">
|
||||
<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="code" label="角色code"></el-table-column>
|
||||
<el-table-column prop="remark" label="角色描述">
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark ? scope.row.remark : '暂无描述' }}
|
||||
@@ -59,7 +59,7 @@ export default defineComponent({
|
||||
setup(props: any, { emit }) {
|
||||
const roleTable: any = ref(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
dialogVisible: false,
|
||||
btnLoading: false,
|
||||
// 所有角色
|
||||
allRole: [] as any,
|
||||
@@ -73,8 +73,8 @@ export default defineComponent({
|
||||
total: 0,
|
||||
});
|
||||
|
||||
watch(props, (newValue, oldValue) => {
|
||||
state.visible = newValue.visible;
|
||||
watch(props, (newValue) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
if (newValue.account && newValue.account.id != 0) {
|
||||
accountApi.roleIds
|
||||
.request({
|
||||
|
||||
@@ -284,11 +284,6 @@ export default defineComponent({
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
setTimeout(() => {
|
||||
menuForm.value.resetFields();
|
||||
// 重置对象属性为null
|
||||
state.form = {} as any;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -109,9 +109,7 @@ export default defineComponent({
|
||||
const cancel = () => {
|
||||
// 更新父组件visible prop对应的值为false
|
||||
emit('update:visible', false);
|
||||
setTimeout(() => {
|
||||
emit('cancel');
|
||||
}, 700);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<template>
|
||||
<div class="role-dialog">
|
||||
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px">
|
||||
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
|
||||
<el-form :model="form" label-width="90px">
|
||||
<el-form-item prop="name" label="角色名称:" required>
|
||||
<el-input v-model="form.name" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="角色code:" required>
|
||||
<el-input :disabled="form.id != null" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色" auto-complete="off"></el-input>
|
||||
<el-input
|
||||
:disabled="form.id != null"
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user