Compare commits

..

29 Commits

Author SHA1 Message Date
meilin.huang
e2c929aae1 feat: 统一分页表格组件、修复系统配置无法配置单个属性 2023-06-28 21:35:03 +08:00
Coder慌
0d155d592b !53 feat:修改表结构:主键默认自增,自动生成索引名
Merge pull request !53 from zongyangleo/dev_0625
2023-06-26 01:26:51 +00:00
Coder慌
ae510ff1ff !52 update server/mayfly-go.sql.
Merge pull request !52 from noday/N/A
2023-06-26 01:24:48 +00:00
noday
5b0654ad2c update server/mayfly-go.sql.
sql values字段不匹配
2023-06-26 01:18:48 +00:00
刘宗洋
466f97ecbe feat:修改表结构:主键默认自增,自动生成索引名 2023-06-25 21:12:28 +08:00
meilin.huang
27a14c22d7 fix: sql遗漏调整 2023-06-25 19:10:37 +08:00
meilin.huang
4709edcd1c fix: 遗漏sql补充 2023-06-21 15:18:43 +08:00
meilin.huang
414de9f2eb fix: 数据库提示问题修复 2023-06-20 17:08:13 +08:00
Coder慌
a53e7e7dab !50 SQL字段补全的BUG修复
Merge pull request !50 from amell/sql_autocomplete
2023-06-17 11:42:39 +00:00
amell
7fa6628dc5 [bugfix] issue修复: https://gitee.com/objs/mayfly-go/issues/I7E9LF 2023-06-17 19:29:19 +08:00
Coder慌
62c25afea8 !49 对于update和delete的SQL操作,建议增加where条件检测,缺失where条件时不执行相应的SQL
Merge pull request !49 from amell/sql_where
2023-06-17 09:25:02 +00:00
amell
481b622e3b [fix] https://gitee.com/objs/mayfly-go/issues/I7E8ZF的修复 2023-06-17 16:57:06 +08:00
meilin.huang
64f8f9a200 fix: meta_sql文件中windows换行符不同问题 2023-06-17 16:04:21 +08:00
meilin.huang
0eca951465 Merge branch 'dev' 2023-06-17 15:15:56 +08:00
meilin.huang
ef4e34c584 feat: 新增双因素校验(OTP) 2023-06-17 15:15:03 +08:00
Coder慌
d91acbc7ee !45 修复同时执行多条SQL的bug
Merge pull request !45 from amell/mutil_sql
2023-06-17 04:47:48 +00:00
amell
b397d1022e [fix] issue: I7E6QQ的缺陷修复 2023-06-17 08:59:37 +08:00
meilin.huang
b42a98aff5 fix: sql脚本问题修复 2023-06-16 08:59:22 +08:00
meilin.huang
adc65439e4 feat: redis支持flushdb、菜单资源支持拖拽排序、禁用等 2023-06-15 19:18:29 +08:00
Coder慌
445cf3716b !44 feat: sql字段提示优化
Merge pull request !44 from zongyangleo/dev_20230613
2023-06-13 09:03:53 +00:00
刘宗洋
f4b59b8503 feat:单表查询支持无别名提示字段 2023-06-13 17:01:39 +08:00
刘宗洋
4f08975df2 fix:修复数据库schema提示 2023-06-13 15:57:08 +08:00
刘宗洋
570db453d7 feat:sql字段提示优化 2023-06-13 14:54:52 +08:00
meilin.huang
b93984bf6f refactor: json tag完善 2023-06-11 19:59:35 +08:00
meilin.huang
e483db1b97 fix: 哨兵节点密码调整 2023-06-06 20:51:54 +08:00
meilin.huang
17d96acceb refactor: interface{} -> any
feat: 新增外链菜单
2023-06-01 12:31:32 +08:00
meilin.huang
9900b236ef refactor: 组件升级、代码优化 2023-05-24 12:32:17 +08:00
Coder慌
4fa52412c1 !42 fix:修改表sql,如果是修改的主键,sql bug修复
Merge pull request !42 from zongyangleo/dev_20230417
2023-04-17 09:40:11 +00:00
刘宗洋
0076869deb fix:修改表:如果修改的字段是主键 2023-04-17 16:21:06 +08:00
176 changed files with 3804 additions and 2350 deletions

View File

@@ -115,7 +115,7 @@ function buildxDocker() {
echo_yellow "-------------------docker buildx构建镜像开始-------------------"
imageVersion=$1
cd ${server_folder}
imageName="mayflygo/mayfly-go:${imageVersion}"
imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
echo_green "docker多版本镜像构建完成->[${imageName}]"
echo_yellow "-------------------docker buildx构建镜像结束-------------------"

View File

@@ -10,27 +10,28 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"asciinema-player": "^3.2.0",
"axios": "^1.3.4",
"asciinema-player": "^3.3.0",
"axios": "^1.4.0",
"countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.4.0",
"element-plus": "^2.3.2",
"element-plus": "^2.3.7",
"jsencrypt": "^3.3.1",
"lodash": "^4.17.21",
"mitt": "^3.0.0",
"monaco-editor": "^0.37.1",
"monaco-editor": "^0.39.0",
"monaco-sql-languages": "^0.11.0",
"monaco-themes": "^0.4.2",
"monaco-themes": "^0.4.4",
"nprogress": "^0.2.0",
"pinia": "^2.0.33",
"pinia": "^2.1.4",
"qrcode.vue": "^3.4.0",
"screenfull": "^6.0.2",
"sortablejs": "^1.13.0",
"sql-formatter": "^12.1.2",
"vue": "^3.2.47",
"vue": "^3.3.4",
"vue-clipboard3": "^1.0.1",
"vue-router": "^4.1.6",
"xterm": "^5.1.0",
"vue-router": "^4.2.2",
"xterm": "^5.2.1",
"xterm-addon-fit": "^0.7.0"
},
"devDependencies": {
@@ -46,11 +47,11 @@
"eslint": "^8.35.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.3.0",
"sass": "^1.58.0",
"sass": "^1.62.0",
"sass-loader": "^13.2.0",
"typescript": "^5.0.2",
"vite": "^4.2.0",
"vue-eslint-parser": "^8.0.1"
"vite": "^4.3.9",
"vue-eslint-parser": "^9.1.1"
},
"browserslist": [
"> 1%",

View File

@@ -11,7 +11,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
version: 'v1.4.2'
version: 'v1.4.4'
}
export default config

View File

@@ -2,6 +2,7 @@ import Api from './Api'
export default {
login: Api.newPost("/sys/accounts/login"),
otpVerify: Api.newPost("/sys/accounts/otp-verify"),
changePwd: Api.newPost("/sys/accounts/change-pwd"),
getPublicKey: Api.newGet("/common/public-key"),
getConfigValue: Api.newGet("/sys/configs/value"),

View File

@@ -1,6 +1,7 @@
import openApi from './openApi';
// 登录是否使用验证码配置key
const AccountLoginSecurity = "AccountLoginSecurity"
const UseLoginCaptchaConfigKey = "UseLoginCaptcha"
const UseWartermarkConfigKey = "UseWartermark"
@@ -22,11 +23,24 @@ export async function getConfigValue(key: string) : Promise<string> {
* @returns 是否为ture1: true其他: false
*/
export async function getBoolConfigValue(key :string, defaultValue :boolean) : Promise<boolean> {
const value = await getConfigValue(key)
const value = await getConfigValue(key);
return convertBool(value, defaultValue);
}
/**
* 获取账号登录安全配置
*
* @returns
*/
export async function getAccountLoginSecurity() : Promise<any> {
const value = await getConfigValue(AccountLoginSecurity);
if (!value) {
return defaultValue;
return null;
}
return value == "1";
const jsonValue = JSON.parse(value);
jsonValue.useCaptcha = convertBool(jsonValue.useCaptcha, true);
jsonValue.useOtp = convertBool(jsonValue.useOtp, true);
return jsonValue;
}
/**
@@ -45,4 +59,11 @@ export async function useLoginCaptcha() : Promise<boolean> {
*/
export async function useWartermark() : Promise<boolean> {
return await getBoolConfigValue(UseWartermarkConfigKey, true)
}
function convertBool(value: string, defaultValue: boolean) {
if (!value) {
return defaultValue;
}
return value == "1" || value == "true";
}

View File

@@ -2,7 +2,7 @@
<div class="monaco-editor" style="border: 1px solid #ccc;">
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
<el-option v-for="mode in languages" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
</el-select>
</div>
</template>
@@ -54,7 +54,7 @@ const props = defineProps({
//定义事件
const emit = defineEmits(['update:modelValue'])
const languages = [
const languageArr = [
{
value: 'shell',
label: 'Shell',

View File

@@ -0,0 +1,339 @@
<template>
<div class="page-table">
<!--
实现通过我们配置好的 查询条件
首先去创建form表单根据我们配置的查询条件去做一个循环判断展示出不用类型所对应不同的输入框
比如text对应普通的输入框select对应下拉选择dateTime对应日期时间选择器
在使用时父组件会传来一个queryForm空的对象
循环出来的输入框会绑定表格配置中的prop字段绑定在queryForm对象中
-->
<el-card>
<div class="query" ref="queryRef">
<div>
<div v-if="props.query.length > 0" class="query-head">
<div style="display: flex; align-items: center;">
<el-form :model="props.queryForm" label-width="70px" style="display: flex;">
<el-form-item :label="item.label" style="margin-right: 20px; margin-bottom: 0px;"
v-for="item in props.query?.slice(0, defaultQueryCount)" :key="item.prop">
<!-- 这里只获取指定个数的筛选条件 -->
<el-input v-model="queryForm[item.prop]" :placeholder="'输入' + item.label + '关键字'"
clearable v-if="item.type == 'text'"></el-input>
<el-select-v2 v-model="queryForm[item.prop]" :options="item.options" clearable
:placeholder="'选择' + item.label + '关键字'" v-else-if="item.type == 'select'" />
<el-date-picker v-model="queryForm[item.prop]" clearable type="datetimerange"
format="YYYY-MM-DD hh:mm:ss" value-format="x" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" v-else-if="item.type == 'date'" />
<slot :name="item.slot"></slot>
</el-form-item>
</el-form>
<template v-if="props.query?.length > defaultQueryCount">
<el-button @click="isOpenMoreQuery = !isOpenMoreQuery" v-if="!isOpenMoreQuery"
icon="ArrowDownBold" circle></el-button>
<el-button @click="isOpenMoreQuery = !isOpenMoreQuery" v-else icon="ArrowUpBold"
circle></el-button>
</template>
<el-button @click="queryData()" type="primary" plain>查询</el-button>
<el-button @click="reset()">重置</el-button>
</div>
</div>
<!-- 这里做的是一个类似于折叠面板的功能 -->
<div class="query-content" :class="isOpenMoreQuery ? 'is-open' : ''">
<el-form :model="props.queryForm" label-width="70px" style="display: flex; flex-wrap: wrap;">
<el-form-item :label="item.label" style="margin-right: 20px; margin-bottom: 0px;"
v-for="item in props.query?.slice(defaultQueryCount)" :key="item.prop">
<!-- 这里获取除前两个以外所有的筛选条件 -->
<el-input v-model="queryForm[item.prop]" :placeholder="'输入' + item.label + '关键字'" clearable
v-if="item.type == 'text'"></el-input>
<el-select-v2 v-model="queryForm[item.prop]" :options="item.options" clearable
:placeholder="'选择' + item.label + '关键字'" v-else-if="item.type == 'select'" />
<el-date-picker v-model="queryForm[item.prop]" clearable type="datetimerange"
format="YYYY-MM-DD hh:mm:ss" value-format="x" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" v-else-if="item.type == 'date'" />
<slot :name="item.slot"></slot>
</el-form-item>
</el-form>
</div>
</div>
<div class="slot">
<!-- 查询栏右侧slot插槽用来添加表格其他操作比如新增数据删除数据等其他操作 -->
<slot name="queryRight"></slot>
<!--
动态表头显示根据表格每条配置项中的show字段来决定改列是否显示或者隐藏
columns 就是我们表格配置的数组对象
-->
<el-popover placement="bottom" title="表格配置" :width="200" trigger="click">
<div v-for="(item, index) in props.columns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
</div>
<template #reference>
<!-- 一个Element Plus中的图标 -->
<el-button icon="Operation"></el-button>
</template>
</el-popover>
</div>
</div>
<el-table v-bind="$attrs" max-height="700" @current-change="choose" :data="props.data" border
highlight-current-row show-overflow-tooltip>
<el-table-column v-if="props.showChooseColumn" label="选择" align="center" width="53px">
<template #default="scope">
<el-radio v-model="state.chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<template v-for="(item, index) in columns">
<el-table-column :key="index" v-if="item.show" :prop="item.prop" :label="item.label" :fixed="item.fixed"
:align="item.align" :show-overflow-tooltip="item.showOverflowTooltip || true"
:min-width="item.minWidth" :sortable="item.sortable || false" :type="item.type" :width="item.width">
<!-- 插槽预留功能 -->
<template #default="scope" v-if="item.slot">
<slot :name="item.slot" :data="scope.row"></slot>
</template>
<template #default="scope" v-else>
<span>{{ item.formatFunc ? item.formatFunc(scope.row[item.prop]) : scope.row[item.prop]
}}</span>
</template>
</el-table-column>
</template>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination @current-change="handlePageChange" @size-change="handleSizeChange" style="text-align: right"
layout="prev, pager, next, total, sizes, jumper" :total="props.total"
v-model:current-page="state.pageNum" v-model:page-size="state.pageSize"
:page-sizes="[10, 15, 20, 25]" />
</el-row>
</el-card>
</div>
</template>
<script lang='ts' setup>
import { toRefs, watch, reactive, onMounted } from 'vue';
import { TableColumn, TableQuery } from './index';
const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:chooseData', 'pageChange'])
const props = defineProps({
// 是否显示选择列
showChooseColumn: {
type: Boolean,
default: false,
},
// 选择列绑定的主键key字段名
chooseDataIdKey: {
type: String,
default: "id"
},
// 当前选择的数据
chooseData: {
type: Object
},
// 列信息
columns: {
type: Array<TableColumn>,
default: function () {
return [];
}
},
// 表格数据
data: {
type: Array,
},
total: {
type: [Number],
default: 0,
},
pageNum: {
type: Number,
default: 1,
},
pageSize: {
type: [Number],
default: 10,
},
// 绑定的查询表单
queryForm: {
type: Object,
default: function () {
return {};
}
},
// 查询条件配置
query: {
type: Array<TableQuery>,
default: function () {
return [];
}
}
})
const state = reactive({
pageSize: 10,
pageNum: 1,
chooseData: null as any,
chooseId: 0 as any,
isOpenMoreQuery: false,
defaultQueryCount: 2, // 默认显示的查询参数个数
queryForm: {} as any,
})
const {
isOpenMoreQuery,
defaultQueryCount,
queryForm,
} = toRefs(state)
watch(() => props.queryForm, (newValue: any) => {
state.queryForm = newValue
})
watch(() => props.chooseData, (newValue: any) => {
state.chooseData = newValue
if (newValue) {
state.chooseId = state.chooseData[props.chooseDataIdKey]
} else {
state.chooseId = 0;
}
})
watch(() => props.pageNum, (newValue: any) => {
state.pageNum = newValue
})
watch(() => props.pageSize, (newValue: any) => {
state.pageSize = newValue
})
watch(() => props.data, (newValue: any) => {
if (newValue.length > 0) {
props.columns.forEach(item => {
if (item.autoWidth && item.show) {
item.minWidth = TableColumn.flexColumnWidth(item.prop, item.label, props.data) as any
}
})
}
})
onMounted(() => {
state.pageNum = props.pageNum;
state.pageSize = props.pageSize;
state.queryForm = props.queryForm;
})
// 处理选中了列表中的某一条数据
const choose = (item: any) => {
if (!item || !props.showChooseColumn) {
return;
}
state.chooseData = item;
state.chooseId = item[props.chooseDataIdKey]
emit('update:chooseData', state.chooseData)
};
const handlePageChange = () => {
emit('update:pageNum', state.pageNum)
emit("pageChange")
}
const handleSizeChange = () => {
emit('update:pageSize', state.pageSize)
emit("pageChange")
}
const queryData = () => {
// 触发重新调用查询接口即可
emit("pageChange")
}
const reset = () => {
// 触发重新调用查询接口即可
state.queryForm = {};
emit('update:queryForm', state.queryForm)
emit("pageChange")
}
</script>
<style scoped lang="scss">
.page-table {
.query {
margin-bottom: 10px;
overflow: hidden;
.query-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.query-content {
width: 100%;
max-height: 0px;
transition: all 0.8s;
}
.is-open {
padding: 10px 0;
max-height: 200px;
}
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 10px;
.query-content {
display: flex;
align-items: flex-start;
.query-form {
.el-form-item {
margin: 0px;
margin-right: 20px;
}
}
}
.slot {
display: flex;
justify-content: flex-end;
padding-right: 20px;
}
}
.page {
margin-top: 10px;
}
}
::v-deep(.el-form-item__label) {
font-weight: bold;
}
.el-input {
width: 200px;
}
.el-select-v2 {
width: 200px;
}
::v-deep(.el-date-editor) {
width: 380px !important;
}
</style>

View File

@@ -0,0 +1,233 @@
import { dateFormat } from '@/common/utils/date';
export class TableColumn {
/**
* 属性字段
*/
prop: string;
/**
* 显示表头
*/
label: string;
/**
* 是否自动计算宽度
*/
autoWidth: boolean = true;
/**
* 最小宽度
*/
minWidth: number | string;
/**
* 插槽名
*/
slot: string;
showOverflowTooltip: boolean = true;
sortable: boolean = false;
type: string;
width: number | string;
fixed: any;
align: string = "center"
formatFunc: Function
show: boolean = true
constructor(prop: string, label: string) {
this.prop = prop;
this.label = label;
}
static new(prop: string, label: string): TableColumn {
return new TableColumn(prop, label)
}
setMinWidth(minWidth: number | string): TableColumn {
this.minWidth = minWidth
this.autoWidth = false;
return this;
}
setSlot(slot: string): TableColumn {
this.slot = slot
return this;
}
setFormatFunc(func: Function): TableColumn {
this.formatFunc = func;
return this;
}
/**
* 为时间字段,则使用默认时间格式函数
* @returns this
*/
isTime(): TableColumn {
this.setFormatFunc(dateFormat)
return this;
}
fixedRight(): TableColumn {
this.fixed = "right";
return this;
}
fixedLeft(): TableColumn {
this.fixed = "left";
return this;
}
/**
*
* @param str 字符串
* @param tableData 表数据
* @param label 表头label也参与宽度计算
* @returns 列宽度
*/
static flexColumnWidth = (str: any, label: string, tableData: any) => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
str = str + '';
let columnContent = '';
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return;
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return;
}
// 获取该列中最长的数据(内容)
let index = 0;
for (let i = 0; i < tableData.length; i++) {
if (!tableData[i][str]) {
continue;
}
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] + '';
const contentWidth: number = TableColumn.getContentWidth(columnContent);
// 获取label的宽度取较大的宽度
const columnWidth: number = TableColumn.getContentWidth(label) + 30;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
return flexWidth + 'px';
};
/**
* 获取内容所需要占用的宽度
*/
static getContentWidth = (content: any): number => {
if (!content) {
return 50;
}
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0;
for (const char of content) {
if (flexWidth > 500) {
break;
}
if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
// 小写字母、数字字符
flexWidth += 9.3;
continue;
}
if (char >= 'A' && char <= 'Z') {
flexWidth += 9;
continue;
}
if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符为字符分配16个单位宽度
flexWidth += 20;
} else {
// 其他种类字符
flexWidth += 8;
}
}
if (flexWidth > 400) {
// 设置最大宽度
flexWidth = 400;
}
return flexWidth;
};
}
export class TableQuery {
/**
* 属性字段
*/
prop: string;
/**
* 显示表头
*/
label: string;
/**
* 查询类型text、select、date
*/
type: string;
/**
* select可选值
*/
options: any;
/**
* 插槽名
*/
slot: string;
constructor(prop: string, label: string) {
this.prop = prop;
this.label = label;
}
static new(prop: string, label: string): TableQuery {
return new TableQuery(prop, label)
}
static text(prop: string, label: string): TableQuery {
const tq = new TableQuery(prop, label)
tq.type = 'text';
return tq;
}
static select(prop: string, label: string): TableQuery {
const tq = new TableQuery(prop, label)
tq.type = 'select';
return tq;
}
static date(prop: string, label: string): TableQuery {
const tq = new TableQuery(prop, label)
tq.type = 'date';
return tq;
}
static slot(prop: string, label: string, slotName: string): TableQuery {
const tq = new TableQuery(prop, label)
tq.slot = slotName;
return tq;
}
setOptions(options: any): TableQuery {
this.options = options;
return this
}
}

View File

@@ -7,7 +7,7 @@
if (el) columnsAsideOffsetTopRefs[k] = el;
}
" :class="{ 'layout-columns-active': state.liIndex === k }" :title="v.meta.title">
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.isIframe)">
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
<i :class="v.meta.icon"></i>
<div class="layout-columns-aside-li-box-title font12">
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}

View File

@@ -1,15 +1,15 @@
<template>
<el-main class="layout-main">
<el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
v-show="!state.currentRouteMeta.link && !state.currentRouteMeta.isIframe"
v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
<LayoutParentView />
<Footer v-if="themeConfig.isFooter" />
</el-scrollbar>
<Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
v-if="state.currentRouteMeta.link && !state.currentRouteMeta.isIframe" />
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2" />
<Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
v-if="state.currentRouteMeta.link && state.currentRouteMeta.isIframe && state.isShowLink"
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
@getCurrentRouteMeta="onGetCurrentRouteMeta" />
</el-main>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">mayfly</div>
<div>Made by mayfly with </div>
<div class="mt5">mayfly-go</div>
</div>
</div>
</template>

View File

@@ -7,7 +7,7 @@
<script setup lang="ts" name="layout">
import { onBeforeMount, onUnmounted } from 'vue';
import { getLocal, setLocal } from '@/common/utils/storage.ts';
import { getLocal, setLocal } from '@/common/utils/storage';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import Defaults from '@/views/layout/main/defaults.vue';

View File

@@ -87,7 +87,7 @@ const getRoutes = (routes: any) => {
// 当前菜单选中时
const onHandleSelect = (item: any) => {
let { path, redirect } = item;
if (item.meta.link && !item.meta.isIframe) window.open(item.meta.link);
if (item.meta.link && item.meta.linkType == 2) window.open(item.meta.link);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();

View File

@@ -38,7 +38,7 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import screenfull from 'screenfull';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import { getSession, setSession, removeSession } from '@/common/utils/storage.ts';
import { getSession, setSession, removeSession } from '@/common/utils/storage';
import mittBus from '@/common/utils/mitt';
import Sortable from 'sortablejs';
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
@@ -99,13 +99,14 @@ const addTagsView = (path: string, to: any = null) => {
if (!to) {
to = route;
}
path = decodeURI(path);
for (let tv of state.tagsViewList) {
if (tv.fullPath === path) {
return false;
}
}
const tagView = { ...to }
// 防止Converting circular structure to JSON错误
tagView.matched = null;

View File

@@ -4,15 +4,15 @@
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal"
@select="onHorizontalSelect">
<template v-for="val in menuLists">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon"/>
<span>{{ val.meta.title }}</span>
</template>
<SubItem :chil="val.children" />
</el-submenu>
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<SvgIcon :name="val.meta.icon"/>
{{ val.meta.title }}
</template>
@@ -58,15 +58,15 @@ const menuLists = computed(() => {
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft =
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft =
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft = els.offsetLeft;
});
};
// 设置页面当前路由高亮

View File

@@ -8,7 +8,7 @@
<sub-item :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<template v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
<template v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<SvgIcon :name="val.meta.icon"/>
<span>{{ val.meta.title }}</span>
</template>

View File

@@ -11,7 +11,7 @@
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<SvgIcon :name="val.meta.icon"/>
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<span>{{ val.meta.title }}</span>
</template>
<template #title v-else>

View File

@@ -11,7 +11,7 @@
autocomplete="off" @keyup.enter="login" show-password>
</el-input>
</el-form-item>
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
<el-form-item v-if="accountLoginSecurity.useCaptcha" prop="captcha">
<el-row :gutter="15">
<el-col :span="16">
<el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
@@ -25,6 +25,9 @@
</el-col>
</el-row>
</el-form-item>
<span v-if="showLoginFailTips" style="color: #f56c6c;font-size: 12px;">
提示登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
</span>
<el-form-item>
<el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
<span> </span>
@@ -34,8 +37,7 @@
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
:destroy-on-close="true">
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
label-width="65px">
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="65px">
<el-form-item prop="username" label="用户名" required>
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
</el-form-item>
@@ -56,6 +58,26 @@
</div>
</template>
</el-dialog>
<el-dialog title="OTP校验" v-model="otpDialog.visible" @close="loading.signIn = false" :close-on-click-modal="false"
width="350px" :destroy-on-close="true">
<el-form ref="otpFormRef" :model="otpDialog.form" :rules="otpDialog.rules" label-width="65px">
<el-form-item v-if="otpDialog.otpUrl" label="二维码">
<qrcode-vue :value="otpDialog.otpUrl" :size="200" level="H" />
</el-form-item>
<el-form-item prop="code" label="OTP" required>
<el-input style="width:220px" ref="otpCodeInputRef" v-model.trim="otpDialog.form.code" clearable @keyup.enter="otpVerify"
placeholder="请输入令牌APP中显示的授权码"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="otpVerify" type="primary" :loading="loading.otpConfirm"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -68,9 +90,10 @@ import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/comm
import { formatAxis } from '@/common/utils/format';
import openApi from '@/common/openApi';
import { RsaEncrypt } from '@/common/rsa';
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
import { getAccountLoginSecurity, useWartermark } from '@/common/sysconfig';
import { letterAvatar } from '@/common/utils/string';
import { useUserInfo } from '@/store/userInfo';
import QrcodeVue from 'qrcode.vue'
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
@@ -82,9 +105,17 @@ const route = useRoute();
const router = useRouter();
const loginFormRef: any = ref(null);
const changePwdFormRef: any = ref(null);
const otpFormRef: any = ref(null);
const otpCodeInputRef: any = ref(null);
const state = reactive({
isUseLoginCaptcha: false,
accountLoginSecurity: {
useCaptcha: true,
useOtp: false,
loginFailCount: 5,
loginFailMin: 10,
},
showLoginFailTips: false,
captchaImage: '',
loginForm: {
username: '',
@@ -110,23 +141,42 @@ const state = reactive({
],
},
},
otpDialog: {
visible: false,
otpUrl: "",
form: {
code: '',
otpToken: '',
},
rules: {
code: [
{ required: true, message: '请输入OTP授权码', trigger: 'blur' },
],
},
},
loading: {
signIn: false,
changePwd: false,
otpConfirm: false,
},
});
const {
isUseLoginCaptcha,
accountLoginSecurity,
showLoginFailTips,
captchaImage,
loginForm,
changePwdDialog,
otpDialog,
loading,
} = toRefs(state)
onMounted(async () => {
nextTick(async () => {
state.isUseLoginCaptcha = await useLoginCaptcha();
const res = await getAccountLoginSecurity();
if (res) {
state.accountLoginSecurity = res;
}
getCaptcha();
});
// 移除公钥, 方便后续重新获取
@@ -134,7 +184,7 @@ onMounted(async () => {
});
const getCaptcha = async () => {
if (!state.isUseLoginCaptcha) {
if (!state.accountLoginSecurity.useCaptcha) {
return;
}
let res: any = await openApi.captcha.request();
@@ -158,6 +208,22 @@ const login = () => {
});
};
const otpVerify = async () => {
otpFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
return false;
}
try {
state.loading.otpConfirm = true;
const accessToken = await openApi.otpVerify.request(state.otpDialog.form);
await signInSuccess(accessToken);
state.otpDialog.visible = false;
} finally {
state.loading.otpConfirm = false;
}
});
}
// 登录
const onSignIn = async () => {
state.loading.signIn = true;
@@ -167,8 +233,6 @@ const onSignIn = async () => {
const loginReq = { ...state.loginForm };
loginReq.password = await RsaEncrypt(originPwd);
loginRes = await openApi.login.request(loginReq);
// 存储 token 到浏览器缓存
setSession('token', loginRes.token);
} catch (e: any) {
state.loading.signIn = false;
state.loginForm.captcha = '';
@@ -180,9 +244,11 @@ const onSignIn = async () => {
state.changePwdDialog.visible = true;
} else {
getCaptcha();
state.showLoginFailTips = true;
}
return;
}
state.showLoginFailTips = false;
// 用户信息
const userInfos = {
name: loginRes.name,
@@ -190,7 +256,7 @@ const onSignIn = async () => {
// 头像
photo: letterAvatar(state.loginForm.username),
time: new Date().getTime(),
permissions: loginRes.permissions,
// permissions: loginRes.permissions,
lastLoginTime: loginRes.lastLoginTime,
lastLoginIp: loginRes.lastLoginIp,
};
@@ -199,12 +265,28 @@ const onSignIn = async () => {
setUserInfo2Session(userInfos);
// 1、请注意执行顺序(存储用户信息到vuex)
useUserInfo().setUserInfo(userInfos);
await initRouter();
signInSuccess();
const token = loginRes.token;
// 如果不需要otp校验则该token即为accessToken否则为otp校验token
if (loginRes.otp == -1) {
signInSuccess(token);
return;
}
state.otpDialog.form.otpToken = token;
state.otpDialog.otpUrl = loginRes.otpUrl
state.otpDialog.visible = true;
setTimeout(() => {
otpCodeInputRef.value.focus();
}, 400);
};
// 登录成功后的跳转
const signInSuccess = () => {
const signInSuccess = async (accessToken: string = "") => {
// 存储 token 到浏览器缓存
setSession('token', accessToken);
// 初始化路由
await initRouter();
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页

View File

@@ -48,14 +48,17 @@ const showTagInfo = async () => {
const tagPaths = [];
let nowTag = '';
for (let tagStr of tagStrs) {
if (nowTag) {
nowTag = `${nowTag}/${tagStr}`
} else {
nowTag = tagStr
if (!tagStr) {
continue;
}
tagPaths.push(nowTag)
if (nowTag) {
nowTag = `${nowTag}${tagStr}/`;
} else {
nowTag = tagStr + '/';
}
tagPaths.push(nowTag);
}
state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') })
state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
}
</script>

View File

@@ -82,11 +82,12 @@
v-for="item in tableData.indexs.colNames" :key="item.prop">
<template #default="scope">
<el-input v-if="item.prop === 'indexName'" size="small"
<el-input v-if="item.prop === 'indexName'" size="small" disabled
v-model="scope.row.indexName"></el-input>
<el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
@change="indexChanges(scope.row)"
style="width: 100%">
<el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
:label="cl.name" :value="cl.name">
@@ -94,7 +95,7 @@
</el-option>
</el-select>
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique">
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
</el-checkbox>
<el-select v-if="item.prop === 'indexType'" filterable size="small"
@@ -468,7 +469,7 @@ const genSql = () => {
let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
// 搜集修改和删除的索引添加到drop index xx
// 收集新增和修改的索引添加到ADD xx
// ALTER TABLE `test1`
// ALTER TABLE `test1`
// DROP INDEX `test1_name_uindex`,
// DROP INDEX `test1_column_name4_index`,
// ADD UNIQUE INDEX `test1_name_uindex`(`id`) USING BTREE COMMENT 'ASDASD',
@@ -546,6 +547,21 @@ const reset = () => {
},]
};
const indexChanges = (row: any) => {
let name = '';
if(row.columnNames && row.columnNames.length > 0){
for (const column of row.columnNames) {
name += column.replace('_','').toLowerCase() + '_'
}
name = name.substring(0, name.length-1)
} else {
return;
}
let prefix = row.unique? 'udx_' : 'idx_';
row.indexName = prefix + name;
}
const oldData = { indexs: [] as any[], fields: [] as any[] }
watch(() => props.data, (newValue: any) => {
const { row, indexs, columns } = newValue;
@@ -569,7 +585,7 @@ watch(() => props.data, (newValue: any) => {
length,
notNull: a.nullable !== 'YES',
pri: a.columnKey === 'PRI',
auto_increment: a.extra?.indexOf('auto_increment') > -1,
auto_increment: a.columnKey === 'PRI' /*a.extra?.indexOf('auto_increment') > -1*/,
remark: a.columnComment,
};
state.tableData.fields.res.push(data)

View File

@@ -1,85 +1,66 @@
<template>
<div class="db-list">
<el-card>
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="datas" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable
style="width: 200px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button type="success" icon="search" @click="search()" class="ml5"></el-button>
</div>
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
<el-table-column label="选择" width="60px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
<template #default="scope">
{{ `${scope.row.host}:${scope.row.port}` }}
</template>
</el-table-column>
<el-table-column prop="type" label="类型" min-width="90"></el-table-column>
<el-table-column prop="database" label="数据库" min-width="80">
<template #default="scope">
<el-popover placement="right" trigger="click" :width="300">
<template #reference>
<el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
</el-link>
</template>
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
size="small">
<template #prefix>
<el-icon class="el-input__icon">
<search-icon />
</el-icon>
</template>
</el-input>
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false"
@click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
</el-link>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
</template>
<el-table-column label="操作" min-width="160" fixed="right">
<template #default="scope">
<el-link plain size="small" :underline="false" @click="showInfo(scope.row)">
详情</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link class="ml5" type="primary" plain size="small" :underline="false"
@click="onShowSqlExec(scope.row)">
SQL执行记录</el-link>
<template #queryRight>
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
<el-button v-auth="permissions.saveDb" :disabled="!chooseData" @click="editDb(false)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="permissions.delDb" :disabled="!chooseData" @click="deleteDb(chooseData.id)" type="danger"
icon="delete">删除</el-button>
</template>
<template #tagPath="{ data }">
<tag-info :tag-path="data.tagPath" />
<span class="ml5">
{{ data.tagPath }}
</span>
</template>
<template #hostPort="{ data }">
{{ `${data.host}:${data.port}` }}
</template>
<template #database="{ data }">
<el-popover placement="right" trigger="click" :width="300">
<template #reference>
<el-link type="primary" :underline="false" plain @click="selectDb(data.dbs)">查看
</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索" size="small">
<template #prefix>
<el-icon class="el-input__icon">
<search-icon />
</el-icon>
</template>
</el-input>
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false" @click="showTableInfo(data, db)"
style="position: absolute; right: 4px">操作
</el-link>
</div>
</el-popover>
</template>
<template #action="{ data }">
<el-link plain size="small" :underline="false" @click="showInfo(data)">
详情</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link class="ml5" type="primary" plain size="small" :underline="false" @click="onShowSqlExec(data)">
SQL执行记录</el-link>
</template>
</page-table>
<el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
<el-row class="mb10">
@@ -147,8 +128,7 @@
<template #default="scope">
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
<el-link class="ml5"
v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
<el-link class="ml5" v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
@click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
</template>
@@ -218,8 +198,8 @@
</el-dialog>
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
size="small"> </el-input>
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small">
</el-input>
</el-dialog>
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
@@ -299,6 +279,8 @@ import { Search as SearchIcon } from '@element-plus/icons-vue'
import { tagApi } from '../tag/api';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
@@ -313,11 +295,10 @@ const state = reactive({
dbId: 0,
db: '',
tags: [],
chooseId: null as any,
/**
* 选中的数据
*/
chooseData: null,
chooseData: null as any,
/**
* 查询条件
*/
@@ -326,6 +307,19 @@ const state = reactive({
pageNum: 1,
pageSize: 10,
},
queryConfig: [
TableQuery.slot("tagPath", "标签", "tagPathSelect"),
],
columns: [
TableColumn.new("tagPath", "标签路径").setSlot("tagPath"),
TableColumn.new("name", "名称"),
TableColumn.new("host", "host:port").setSlot("hostPort"),
TableColumn.new("type", "类型"),
TableColumn.new("database", "数据库").setSlot("database").setMinWidth(60),
TableColumn.new("username", "用户名"),
TableColumn.new("remark", "备注"),
TableColumn.new("action", "操作").setSlot("action").setMinWidth(155).fixedRight(),
],
datas: [],
total: 0,
infoDialog: {
@@ -408,7 +402,7 @@ const {
dbId,
db,
tags,
chooseId,
chooseData,
query,
datas,
total,
@@ -452,14 +446,6 @@ const filterTableInfos = computed(() => {
});
});
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);
// 切割数据库
@@ -471,11 +457,6 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const showInfo = (info: any) => {
state.infoDialog.data = info;
state.infoDialog.visible = true;
@@ -498,7 +479,6 @@ const editDb = async (isAdd = false) => {
const valChange = () => {
state.chooseData = null;
state.chooseId = null;
search();
};
@@ -512,7 +492,6 @@ const deleteDb = async (id: number) => {
await dbApi.deleteDb.request({ id });
ElMessage.success('删除成功');
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) { }
};
@@ -651,7 +630,7 @@ const closeTableInfo = () => {
const showColumns = async (row: any) => {
state.chooseTableName = row.tableName;
state.columnDialog.columns = await dbApi.columnMetadata.request({
id: state.chooseId,
id: state.chooseData.id,
db: state.db,
tableName: row.tableName,
});
@@ -662,7 +641,7 @@ const showColumns = async (row: any) => {
const showTableIndex = async (row: any) => {
state.chooseTableName = row.tableName;
state.indexDialog.indexs = await dbApi.tableIndex.request({
id: state.chooseId,
id: state.chooseData.id,
db: state.db,
tableName: row.tableName,
});
@@ -673,11 +652,11 @@ const showTableIndex = async (row: any) => {
const showCreateDdl = async (row: any) => {
state.chooseTableName = row.tableName;
const res = await dbApi.tableDdl.request({
id: state.chooseId,
id: state.chooseData.id,
db: state.db,
tableName: row.tableName,
});
state.ddlDialog.ddl = res[0]['Create Table'];
state.ddlDialog.ddl = res;
state.ddlDialog.visible = true;
};
@@ -694,10 +673,10 @@ const dropTable = async (row: any) => {
});
SqlExecBox({
sql: `DROP TABLE ${tableName}`,
dbId: state.chooseId,
dbId: state.chooseData.id,
db: state.db,
runSuccessCallback: async () => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseData.id, db: state.db });
},
});
} catch (err) { }
@@ -733,12 +712,12 @@ const openEditTable = async (row: any) => {
if (row.tableName) {
state.tableCreateDialog.title = '修改表'
let indexs = await dbApi.tableIndex.request({
id: state.chooseId,
id: state.chooseData.id,
db: state.db,
tableName: row.tableName,
});
let columns = await dbApi.columnMetadata.request({
id: state.chooseId,
id: state.chooseData.id,
db: state.db,
tableName: row.tableName,
});
@@ -746,6 +725,4 @@ const openEditTable = async (row: any) => {
}
}
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -2,7 +2,8 @@
<div>
<el-row>
<el-col :span="4">
<el-button type="primary" icon="plus" @click="addQueryTab({ id: nowDbInst.id }, state.db)"
<el-button type="primary" icon="plus"
@click="addQueryTab({ id: nowDbInst.id, dbs: nowDbInst.databases }, state.db)"
size="small">新建查询</el-button>
</el-col>
<el-col :span="20" v-if="state.db">
@@ -23,7 +24,8 @@
</el-row>
<el-row type="flex">
<el-col :span="4" style="border-left: 1px solid #eee; margin-top: 10px">
<tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode" :load-contextmenu-items="getContextmenuItems" @current-contextmenu-click="onCurrentContextmenuClick"
<tag-tree ref="tagTreeRef" @node-click="nodeClick" :load="loadNode"
:load-contextmenu-items="getContextmenuItems" @current-contextmenu-click="onCurrentContextmenuClick"
:height="state.tagTreeHeight">
<template #prefix="{ data }">
<span v-if="data.type == NodeType.DbInst">
@@ -245,8 +247,8 @@ const nodeClick = async (data: any) => {
const nodeKey = data.key;
const dataType = data.type;
// 点击数据库,修改当前数据库信息
if (dataType === NodeType.Db || dataType === NodeType.SqlMenu || dataType === NodeType.TableMenu) {
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath }, params.db);
if (dataType === NodeType.Db || dataType === NodeType.SqlMenu || dataType === NodeType.TableMenu || dataType === NodeType.DbInst) {
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.database }, params.db);
return;
}
@@ -296,9 +298,9 @@ const getTables = async (params: any) => {
/**
* 加载用户保存的sql脚本
*
* @param inst
* @param schema
*
* @param inst
* @param schema
*/
const loadSqls = async (id: any, db: string, dbs: any) => {
const sqls = await dbApi.getSqlNames.request({ id: id, db: db, })
@@ -440,7 +442,7 @@ const reloadTables = (nodeKey: string) => {
const registerSqlCompletionItemProvider = () => {
// 参考 https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
self.completionItemProvider = self.completionItemProvider || monaco.languages.registerCompletionItemProvider('sql', {
triggerCharacters: ['.'],
triggerCharacters: ['.', ' '],
provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
let word = model.getWordUntilPosition(position);
const nowTab = state.tabs.get(state.activeName);
@@ -490,16 +492,47 @@ const registerSqlCompletionItemProvider = () => {
// // const nextToken = nextTokens[0].toLowerCase()
const tokens = textBeforePointer.trim().split(/\s+/)
const lastToken = tokens[tokens.length - 1].toLowerCase()
const secondToken = tokens.length > 2 && tokens[tokens.length - 2].toLowerCase() || ''
const dbs = nowTab.params && nowTab.params.dbs;
// const dbs = nowTab.params?.dbs?.split(' ') || [];
const dbs = nowTab.params && nowTab.params.dbs && nowTab.params.dbs.split(' ') || [];
// console.log("光标前文本:=>" + textBeforePointerMulti)
// console.log("最后输入的:=>" + lastToken)
if (lastToken.endsWith('.')) {
let suggestions: languages.CompletionItem[] = []
const tables = await dbInst.loadTables(db);
async function hintTableColumns(tableName: any, db: any) {
let dbHits = await dbInst.loadDbHints(db)
let columns = dbHits[tableName]
let suggestions: languages.CompletionItem[] = []
columns?.forEach((a: string, index: any) => {
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
const nameAndComment = a.split(" ")
const fieldName = nameAndComment[0]
suggestions.push({
label: {
label: a,
description: 'column'
},
kind: monaco.languages.CompletionItemKind.Property,
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
insertText: fieldName + ' ', // create_time
range,
sortText: 100 + index + '' // 使用表字段声明顺序排序,排序需为字符串类型
});
})
return suggestions
}
if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
let str = lastToken.substring(0, lastToken.lastIndexOf('.'))
// 库.表名联想
if (lastToken.trim().startsWith('.')) {
str = secondToken
}
// 库.表名联想
if (dbs && dbs.filter((a: any) => a === str)?.length > 0) {
let tables = await dbInst.loadTables(str)
let suggestions: languages.CompletionItem[] = []
@@ -522,36 +555,43 @@ const registerSqlCompletionItemProvider = () => {
// 表别名.表字段联想
let tableInfo = getTableByAlias(sql, db, str)
if (tableInfo.tableName) {
let table = tableInfo.tableName
let tableName = tableInfo.tableName
let db = tableInfo.dbName;
// 取出表名并提示
let dbHits = await dbInst.loadDbHints(db)
let columns = dbHits[table]
let suggestions: languages.CompletionItem[] = []
columns?.forEach((a: string, index: any) => {
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
const nameAndComment = a.split(" ")
const fieldName = nameAndComment[0]
suggestions.push({
label: {
label: a,
description: 'column'
},
kind: monaco.languages.CompletionItemKind.Property,
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
insertText: fieldName + ' ', // create_time
range,
sortText: 100 + index + '' // 使用表字段声明顺序排序,排序需为字符串类型
});
})
return { suggestions }
let suggestions = await hintTableColumns(tableName, db);
if (suggestions.length > 0) {
return { suggestions };
}
}
return { suggestions: [] }
} else {
// 如果sql里含有表名则提示表字段
let mat = textBeforePointerMulti.match(/[from|update]\n*\s+\n*(\w+)\n*\s+\n*/i)
if (mat && mat.length > 1) {
let tableName = mat[1]
// 取出表名并提示
let addSuggestions = await hintTableColumns(tableName, db);
if (addSuggestions.length > 0) {
suggestions = suggestions.concat(addSuggestions)
}
}
}
// 名联想
// 名联想
tables.forEach((tableMeta: any) => {
const { tableName, tableComment } = tableMeta;
suggestions.push({
label: {
label: tableName + ' - ' + tableComment,
description: 'table'
},
kind: monaco.languages.CompletionItemKind.File,
detail: tableComment,
insertText: tableName + ' ',
range
});
});
let suggestions: languages.CompletionItem[] = []
// mysql关键字
sqlLanguage.keywords.forEach((item: any) => {
suggestions.push({
@@ -602,7 +642,7 @@ const registerSqlCompletionItemProvider = () => {
})
// 库名提示
if (dbs) {
if (dbs && dbs.length > 0) {
dbs.forEach((a: any) => {
suggestions.push({
label: {
@@ -616,22 +656,6 @@ const registerSqlCompletionItemProvider = () => {
})
}
const tables = await dbInst.loadTables(db);
// 表名联想
tables.forEach((tableMeta: any) => {
const { tableName, tableComment } = tableMeta
suggestions.push({
label: {
label: tableName + ' - ' + tableComment,
description: 'table'
},
kind: monaco.languages.CompletionItemKind.File,
detail: tableComment,
insertText: tableName + ' ',
range
});
})
// 默认提示
return {
suggestions: suggestions

View File

@@ -3,7 +3,7 @@
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
:empty-text="emptyText" stripe border class="mt5">
:empty-text="emptyText" highlight-current-row stripe border class="mt5">
<el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
@@ -268,6 +268,10 @@ const submitUpdateFields = () => {
let primaryKeyName = a.primaryKeyName;
a.fields.forEach(f => {
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
// 如果修改的字段是主键
if(f.fieldName === primaryKeyName){
primaryKey = f.oldValue
}
divs.push(f.div)
})
sql = sql.substring(0, sql.length - 1)

View File

@@ -101,7 +101,7 @@
</el-dialog>
<el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
<el-form ref="dataForm" :model="addDataDialog.data" label-width="160px">
<el-form ref="dataForm" :model="addDataDialog.data" label-width="160px" size="small">
<el-form-item v-for="column in columns" class="w100" :prop="column.columnName" :label="column.columnName"
:required="column.nullable != 'YES' && column.columnKey != 'PRI'">
<el-input-number v-if="DbInst.isNumber(column.columnType)"

View File

@@ -30,6 +30,9 @@ export class DbInst {
*/
dbs: Map<string, Db> = new Map()
/** 数据库schema多个用空格隔开 */
databases: string
/**
* 默认查询分页数量
*/
@@ -124,7 +127,7 @@ export class DbInst {
/**
* 执行sql
*
*
* @param sql sql
* @param remark 执行备注
*/
@@ -186,7 +189,7 @@ export class DbInst {
return `DELETE FROM ${table} WHERE ${primaryKeyColumnName} IN (${ids})`;
}
/*
/*
* 弹框提示是否执行sql
*/
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
@@ -216,6 +219,7 @@ export class DbInst {
dbInst.id = inst.id;
dbInst.name = inst.name;
dbInst.type = inst.type;
dbInst.databases = inst.databases;
dbInstCache.set(dbInst.id, dbInst);
return dbInst;
@@ -283,14 +287,14 @@ export class DbInst {
/**
* 判断字段类型是否为数字类型
* @param columnType 字段类型
* @returns
* @returns
*/
static isNumber(columnType: string) {
return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
};
/**
*
*
* @param str 字符串
* @param tableData 表数据
* @param flag 标志
@@ -483,4 +487,4 @@ export type FieldsMeta = {
oldValue: string
// 新值
newValue: string
}
}

View File

@@ -1,127 +1,96 @@
<template>
<div>
<el-card>
<div>
<page-table :query="state.queryConfig" v-model:query-form="params" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="data.list" :columns="state.columns" :total="data.total"
v-model:page-size="params.pageSize" v-model:page-num="params.pageNum" @pageChange="search()">
<template #tagPathSelect>
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable
clearable style="width: 200px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</template>
<template #queryRight>
<el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加
</el-button>
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId"
@click="openFormDialog(currentData)" plain>编辑</el-button>
<el-button v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable
clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
plain clearable></el-input>
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search" plain
clearable></el-input>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
</div>
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!chooseData"
@click="openFormDialog(chooseData)" plain>编辑</el-button>
<el-button v-auth="'machine:del'" :disabled="!chooseData" @click="deleteMachine(chooseData.id)"
type="danger" icon="delete">删除</el-button>
</template>
<el-table :data="data.list" stripe style="width: 100%" @current-change="choose">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="currentId" :label="scope.row.id">
<i></i>
</el-radio>
<template #tagPath="{ data }">
<tag-info :tag-path="data.tagPath" />
<span class="ml5">
{{ data.tagPath }}
</span>
</template>
<template #ipPort="{ data }">
<el-link :disabled="data.status == -1" @click="showMachineStats(data)" type="primary" :underline="false">
{{ `${data.ip}:${data.port}` }}
</el-link>
</template>
<template #status="{ data }">
<el-switch v-auth:disabled="'machine:update'" :width="52" v-model="data.status" :active-value="1"
:inactive-value="-1" inline-prompt active-text="启用" inactive-text="停用"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
@change="changeStatus(data)"></el-switch>
</template>
<template #action="{ data }">
<span v-auth="'machine:terminal'">
<el-link :disabled="data.status == -1" type="primary" @click="showTerminal(data)" plain size="small"
:underline="false">终端</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<span v-auth="'machine:file'">
<el-link type="success" :disabled="data.status == -1" @click="showFileManage(data)" plain size="small"
:underline="false">文件</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-link :disabled="data.status == -1" type="warning" @click="serviceManager(data)" plain size="small"
:underline="false">脚本</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-dropdown>
<span class="el-dropdown-link-machine-list">
更多
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-link @click="showInfo(data)" plain :underline="false" size="small">详情
</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link @click="showProcess(data)" :disabled="data.status == -1" plain :underline="false"
size="small">进程</el-link>
</el-dropdown-item>
<el-dropdown-item v-if="data.enableRecorder == 1">
<el-link v-auth="'machine:update'" @click="showRec(data)" plain :underline="false"
size="small">终端回放</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link v-auth="'machine:close-cli'" :disabled="!data.hasCli || data.status == -1"
type="danger" @click="closeCli(data)" plain size="small" :underline="false">关闭连接
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="ip:port" min-width="150">
<template #default="scope">
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
:underline="false">
{{ `${scope.row.ip}:${scope.row.port}` }}
</el-link>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="100">
</el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template #default="scope">
<el-switch v-auth:disabled="'machine:update'" :width="52" v-model="scope.row.status"
:active-value="1" :inactive-value="-1" inline-prompt active-text="启用" inactive-text="停用"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
@change="changeStatus(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="235" fixed="right">
<template #default="scope">
<span v-auth="'machine:terminal'">
<el-link :disabled="scope.row.status == -1" type="primary" @click="showTerminal(scope.row)"
plain size="small" :underline="false">终端</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<span v-auth="'machine:file'">
<el-link type="success" :disabled="scope.row.status == -1" @click="showFileManage(scope.row)"
plain size="small" :underline="false">文件</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)" plain
size="small" :underline="false">脚本</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-dropdown>
<span class="el-dropdown-link-machine-list">
更多
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-link @click="showInfo(scope.row)" plain :underline="false" size="small">详情
</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain
:underline="false" size="small">进程</el-link>
</el-dropdown-item>
<el-dropdown-item v-if="scope.row.enableRecorder == 1">
<el-link v-auth="'machine:update'" @click="showRec(scope.row)" plain
:underline="false" size="small">终端回放</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link v-auth="'machine:close-cli'"
:disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
@click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" :total="data.total" layout="prev, pager, next, total, jumper"
v-model:current-page="params.pageNum" :page-size="params.pageSize"
@current-change="handlePageChange"></el-pagination>
</el-row>
</el-card>
</el-dropdown>
</template>
</page-table>
<el-dialog v-model="infoDialog.visible">
<el-descriptions title="详情" :column="3" border>
@@ -184,6 +153,8 @@ import { machineApi } from './api';
import { tagApi } from '../tag/api';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
// 组件
const MachineEdit = defineAsyncComponent(() => import('./MachineEdit.vue'));
@@ -203,6 +174,20 @@ const state = reactive({
name: null,
tagPath: null,
},
queryConfig: [
TableQuery.slot("tagPath", "标签", "tagPathSelect"),
TableQuery.text("ip", "IP"),
TableQuery.text("name", "名称"),
],
columns: [
TableColumn.new("tagPath", "标签路径").setSlot("tagPath"),
TableColumn.new("name", "名称"),
TableColumn.new("ipPort", "ip:port").setSlot("ipPort"),
TableColumn.new("username", "用户名"),
TableColumn.new("status", "状态").setSlot("status"),
TableColumn.new("remark", "备注"),
TableColumn.new("action", "操作").setSlot("action").setMinWidth(235).fixedRight(),
],
// 列表数据
data: {
list: [],
@@ -212,9 +197,8 @@ const state = reactive({
visible: false,
data: null as any,
},
// 当前选中数据id
currentId: 0,
currentData: null,
// 当前选中数据
chooseData: null as any,
serviceDialog: {
visible: false,
machineId: 0,
@@ -252,8 +236,7 @@ const {
params,
data,
infoDialog,
currentId,
currentData,
chooseData,
serviceDialog,
processDialog,
fileDialog,
@@ -266,14 +249,6 @@ onMounted(async () => {
search();
});
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
const showTerminal = (row: any) => {
const { href } = router.resolve({
path: `/machine/terminal`,
@@ -303,7 +278,7 @@ const getTags = async () => {
const openFormDialog = async (machine: any) => {
let dialogTitle;
if (machine) {
state.machineEditDialog.data = state.currentData as any;
state.machineEditDialog.data = state.chooseData as any;
dialogTitle = '编辑机器';
} else {
state.machineEditDialog.data = null;
@@ -323,8 +298,7 @@ const deleteMachine = async (id: number) => {
});
await machineApi.del.request({ id });
ElMessage.success('操作成功');
state.currentId = 0;
state.currentData = null;
state.chooseData = null;
search();
} catch (err) { }
};
@@ -339,6 +313,9 @@ const serviceManager = (row: any) => {
* 调整机器状态
*/
const changeStatus = async (row: any) => {
if (!row.id) {
return;
}
await machineApi.changeStatus.request({ id: row.id, status: row.status });
};
@@ -352,15 +329,14 @@ const showMachineStats = async (machine: any) => {
};
const submitSuccess = () => {
state.currentId = 0;
state.currentData = null;
state.chooseData = null;
search();
};
const showFileManage = (currentData: any) => {
const showFileManage = (chooseData: any) => {
state.fileDialog.visible = true;
state.fileDialog.machineId = currentData.id;
state.fileDialog.title = `${currentData.name} => ${currentData.ip}`;
state.fileDialog.machineId = chooseData.id;
state.fileDialog.title = `${chooseData.name} => ${chooseData.ip}`;
};
const search = async () => {
@@ -368,11 +344,6 @@ const search = async () => {
state.data = res;
};
const handlePageChange = (curPage: number) => {
state.params.pageNum = curPage;
search();
};
const showInfo = (info: any) => {
state.infoDialog.data = info;
state.infoDialog.visible = true;

View File

@@ -4,11 +4,11 @@
:show-close="true" :destroy-on-close="true" width="900px">
<el-form :model="form" ref="scriptForm" label-width="50px" size="small">
<el-form-item prop="method" label="名称">
<el-input v-model.trim="form.name" placeholder="请输入名称"></el-input>
<el-input v-model="form.name" placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item prop="description" label="描述">
<el-input v-model.trim="form.description" placeholder="请输入描述"></el-input>
<el-input v-model="form.description" placeholder="请输入描述"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型">
@@ -24,13 +24,13 @@
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
<el-row>
<el-col :span="5">
<el-input v-model="param.model" placeholder="内容中用{{.model}}替换"></el-input>
<el-input v-model.trim="param.model" placeholder="内容中用{{.model}}替换"></el-input>
</el-col>
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="4">
<el-input v-model="param.name" placeholder="字段名"></el-input>
<el-input v-model.trim="param.name" placeholder="字段名"></el-input>
</el-col>
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />

View File

@@ -1,56 +1,23 @@
<template>
<div class="role-list">
<el-card>
<div>
<div>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="authcerts" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<template #queryRight>
<el-button type="primary" icon="plus" @click="edit(false)">添加</el-button>
<el-button :disabled="chooseId == null" @click="edit(chooseData)" type="primary" icon="edit">编辑
<el-button :disabled="!chooseData" @click="edit(chooseData)" type="primary" icon="edit">编辑
</el-button>
<el-button :disabled="chooseId == null" @click="deleteAc(chooseData)" type="danger" icon="delete">删除
<el-button :disabled="!chooseData" @click="deleteAc(chooseData)" type="danger" icon="delete">删除
</el-button>
<div style="float: right">
<el-input class="ml5" placeholder="请输入凭证名称" style="width: 200px" v-model="query.name" @clear="search"
plain clearable></el-input>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
</div>
</template>
<el-table :data="authcerts" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="60px" show-overflow-tooltip></el-table-column>
<el-table-column prop="authMethod" label="认证方式" min-width="50px">
<template #default="scope">
<el-tag v-if="scope.row.authMethod == 1" type="success" size="small">密码</el-tag>
<el-tag v-if="scope.row.authMethod == 2" size="small">密钥</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="creator" label="创建人" min-width="60px"></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="100px">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" min-width="60px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope">
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #authMethod="{ data }">
<el-tag v-if="data.authMethod == 1" type="success" size="small">密码</el-tag>
<el-tag v-if="data.authMethod == 2" size="small">密钥</el-tag>
</template>
</page-table>
<auth-cert-edit :title="editor.title" v-model:visible="editor.visible" :data="editor.authcert"
@val-change="editChange" />
@@ -62,19 +29,30 @@ import { toRefs, reactive, onMounted } from 'vue';
import AuthCertEdit from './AuthCertEdit.vue';
import { authCertApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
query: {
pageNum: 1,
pageSize: 10,
name: null,
type: null,
},
queryConfig: [
TableQuery.text("name", "凭证名称"),
],
columns: [
TableColumn.new("name", "名称"),
TableColumn.new("authMethod", "认证方式").setSlot("authMethod"),
TableColumn.new("remark", "备注"),
TableColumn.new("creator", "创建人"),
TableColumn.new("createTime", "创建时间").isTime(),
TableColumn.new("creator", "修改者"),
TableColumn.new("createTime", "修改时间").isTime(),
],
total: 0,
authcerts: [],
chooseId: null,
chooseData: null,
chooseData: null as any,
paramsDialog: {
visible: false,
config: null as any,
@@ -92,7 +70,6 @@ const {
query,
total,
authcerts,
chooseId,
chooseData,
editor,
} = toRefs(state)
@@ -107,22 +84,8 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const editChange = () => {
ElMessage.success('保存成功');
state.chooseId = null;
state.chooseData = null;
search();
};
@@ -147,7 +110,6 @@ const deleteAc = async (data: any) => {
await authCertApi.delete.request({ id: data.id });
ElMessage.success('删除成功');
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) { }

View File

@@ -1,59 +1,35 @@
<template>
<div>
<el-card>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除
</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="list" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable
style="width: 200px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="list" style="width: 100%" @current-change="choose" stripe>
<el-table-column label="选择" width="60px">
<template #default="scope">
<el-radio v-model="currentId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" width></el-table-column>
<el-table-column prop="uri" label="连接uri" min-width="150" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.uri.split('@')[1] }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人"></el-table-column>
</template>
<el-table-column label="操作" width>
<template #default="scope">
<el-link type="primary" @click="showDatabases(scope.row.id)" plain size="small"
:underline="false">数据库</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #queryRight>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="!chooseData" @click="editMongo(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="!chooseData" @click="deleteMongo" plain>删除
</el-button>
</template>
<template #tagPath="{ data }">
<tag-info :tag-path="data.tagPath" />
<span class="ml5">
{{ data.tagPath }}
</span>
</template>
<template #action="{ data }">
<el-link type="primary" @click="showDatabases(data.id)" plain size="small" :underline="false">数据库</el-link>
</template>
</page-table>
<el-dialog width="800px" :title="databaseDialog.title" v-model="databaseDialog.visible">
<el-table :data="databaseDialog.data" size="small">
@@ -76,8 +52,7 @@
</el-table-column>
</el-table>
<el-dialog width="700px" :title="databaseDialog.statsDialog.title"
v-model="databaseDialog.statsDialog.visible">
<el-dialog width="700px" :title="databaseDialog.statsDialog.title" v-model="databaseDialog.statsDialog.visible">
<el-descriptions title="库状态信息" :column="3" border size="small">
<el-descriptions-item label="db" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.db }}
@@ -199,8 +174,9 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api';
import MongoEdit from './MongoEdit.vue';
import { formatByteSize } from '@/common/utils/format';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
tags: [],
@@ -210,13 +186,23 @@ const state = reactive({
},
list: [],
total: 0,
currentId: null,
currentData: null as any,
chooseData: null as any,
query: {
pageNum: 1,
pageSize: 10,
tagPath: null,
},
queryConfig: [
TableQuery.slot("tagPath", "标签", "tagPathSelect"),
],
columns: [
TableColumn.new("tagPath", "标签路径").setSlot("tagPath"),
TableColumn.new("name", "名称"),
TableColumn.new("uri", "连接uri"),
TableColumn.new("createTime", "创建时间").isTime(),
TableColumn.new("creator", "创建人"),
TableColumn.new("action", "操作").setSlot("action").setMinWidth(100).fixedRight(),
],
mongoEditDialog: {
visible: false,
data: null as any,
@@ -255,7 +241,7 @@ const {
tags,
list,
total,
currentId,
chooseData,
query,
mongoEditDialog,
databaseDialog,
@@ -267,19 +253,6 @@ onMounted(async () => {
search();
});
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
const showDatabases = async (id: number) => {
// state.query.tagPath = row.tagPath
state.dbOps.dbId = id
@@ -291,7 +264,7 @@ const showDatabases = async (id: number) => {
const showDatabaseStats = async (dbName: string) => {
state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
id: state.chooseData.id,
database: dbName,
command: {
dbStats: 1,
@@ -310,7 +283,7 @@ const showCollections = async (database: string) => {
};
const setCollections = async (database: string) => {
const res = await mongoApi.collections.request({ id: state.currentId, database });
const res = await mongoApi.collections.request({ id: state.chooseData.id, database });
const collections = [] as any;
for (let r of res) {
collections.push({ name: r });
@@ -323,7 +296,7 @@ const setCollections = async (database: string) => {
*/
const showCollectionStats = async (collection: string) => {
state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
id: state.chooseData.id,
database: state.collectionsDialog.database,
command: {
collStats: collection,
@@ -338,7 +311,7 @@ const showCollectionStats = async (collection: string) => {
*/
const onDeleteCollection = async (collection: string) => {
await mongoApi.runCommand.request({
id: state.currentId,
id: state.chooseData.id,
database: state.collectionsDialog.database,
command: {
drop: collection,
@@ -355,7 +328,7 @@ const showCreateCollectionDialog = () => {
const onCreateCollection = async () => {
const form = state.createCollectionDialog.form;
await mongoApi.runCommand.request({
id: state.currentId,
id: state.chooseData.id,
database: state.collectionsDialog.database,
command: {
create: form.name,
@@ -374,10 +347,9 @@ const deleteMongo = async () => {
cancelButtonText: '取消',
type: 'warning',
});
await mongoApi.deleteMongo.request({ id: state.currentId });
await mongoApi.deleteMongo.request({ id: state.chooseData.id });
ElMessage.success('删除成功');
state.currentData = null;
state.currentId = null;
state.chooseData = null;
search();
} catch (err) { }
};
@@ -397,20 +369,17 @@ const editMongo = async (isAdd = false) => {
state.mongoEditDialog.data = null;
state.mongoEditDialog.title = '新增mongo';
} else {
state.mongoEditDialog.data = state.currentData;
state.mongoEditDialog.data = state.chooseData;
state.mongoEditDialog.title = '修改mongo';
}
state.mongoEditDialog.visible = true;
};
const valChange = () => {
state.currentId = null;
state.currentData = null;
state.chooseData = null;
search();
};
</script>
<style>
</style>
<style></style>

View File

@@ -48,6 +48,7 @@
<el-button @click="scan()" icon="bottom" plain>scan</el-button>
<el-button @click="showNewKeyDialog" type="primary" icon="plus" plain
v-auth="'redis:data:save'"></el-button>
<el-button @click="flushDb" type="danger" plain v-auth="'redis:data:save'">flush</el-button>
</el-form-item>
<div style="float: right">
<span>keys: {{ state.dbsize }}</span>
@@ -338,11 +339,6 @@ const showKeyDetail = async (row: any) => {
state.keyDetailDialog.visible = true;
};
const closeKeyDetail = () => {
// state.keyDetailDialog.visible = false;
}
const showNewKeyDialog = () => {
notNull(state.scanParam.id, '请先选择redis');
notNull(state.scanParam.db, "请选择要操作的库")
@@ -350,6 +346,24 @@ const showNewKeyDialog = () => {
state.newKeyDialog.visible = true;
}
const flushDb = () => {
ElMessageBox.confirm(`确定清空[${state.scanParam.db}]库的所有key?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
redisApi.flushDb
.request({
id: state.scanParam.id,
db: state.scanParam.db,
})
.then(() => {
ElMessage.success('清除成功!');
searchKey();
});
}).catch(() => { });
}
const cancelNewKey = () => {
resetKeyDetailInfo();
state.newKeyDialog.visible = false;

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-table size="small" border :data="hashValues" min-height=300 stripe>
<el-table size="small" border :data="hashValues" height="450" min-height=300 stripe>
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
</el-table-column>
<el-table-column resizable sortable prop="field" label="field" show-overflow-tooltip min-width="100">

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-table size="small" border :data="values" min-height=300 stripe>
<el-table size="small" border :data="values" height="450" min-height=300 stripe>
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
</el-table-column>
<el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-table size="small" border :data="setDatas" min-height=300 stripe>
<el-table size="small" border :data="setDatas" height="450" min-height=300 stripe>
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
</el-table-column>
<el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-table size="small" border :data="values" min-height=300 stripe>
<el-table size="small" border :data="values" height="450" min-height=300 stripe>
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
</el-table-column>
<el-table-column resizable sortable prop="score" label="score" show-overflow-tooltip min-width="100">

View File

@@ -1,56 +1,41 @@
<template>
<div>
<el-card>
<el-button type="primary" icon="plus" @click="editRedis(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除
</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="redisTable" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable
style="width: 200px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="redisTable" @current-change="choose" stripe>
<el-table-column label="选择" width="60px">
<template #default="scope">
<el-radio v-model="currentId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
<template #default="scope">
<tag-info :tag-path="scope.row.tagPath" />
<span class="ml5">
{{ scope.row.tagPath }}
</span>
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="100"></el-table-column>
<el-table-column prop="host" label="host:port" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column prop="mode" label="mode" min-width="100"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip></el-table-column>
</template>
<el-table-column label="更多" min-width="155" fixed="right">
<template #default="scope">
<el-link @click="showDetail(scope.row)" :underline="false">详情</el-link>
<el-divider direction="vertical" border-style="dashed" />
<template #queryRight>
<el-button type="primary" icon="plus" @click="editRedis(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="!chooseData" @click="editRedis(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="!chooseData" @click="deleteRedis" plain>删除
</el-button>
</template>
<el-link v-if="scope.row.mode === 'standalone' || scope.row.mode === 'sentinel'" type="primary"
@click="showInfoDialog(scope.row)" :underline="false">单机信息</el-link>
<el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode === 'cluster'"
type="primary" :underline="false">集群信息</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #tagPath="{ data }">
<tag-info :tag-path="data.tagPath" />
<span class="ml5">
{{ data.tagPath }}
</span>
</template>
<template #more="{ data }">
<el-link @click="showDetail(data)" :underline="false">详情</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link v-if="data.mode === 'standalone' || data.mode === 'sentinel'" type="primary"
@click="showInfoDialog(data)" :underline="false">单机信息</el-link>
<el-link @click="onShowClusterInfo(data)" v-if="data.mode === 'cluster'" type="primary"
:underline="false">集群信息</el-link>
</template>
</page-table>
<info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
@@ -81,8 +66,8 @@
</el-tooltip>
</template>
<template #default="scope">
<el-tag @click="showInfoDialog({ id: clusterInfoDialog.redisId, ip: scope.row.ip })"
effect="plain" type="success" size="small" style="cursor: pointer">{{ scope.row.ip }}
<el-tag @click="showInfoDialog({ id: clusterInfoDialog.redisId, ip: scope.row.ip })" effect="plain"
type="success" size="small" style="cursor: pointer">{{ scope.row.ip }}
</el-tag>
</template>
</el-table-column>
@@ -90,8 +75,8 @@
<el-table-column prop="masterSlaveRelation" label="masterSlaveRelation" min-width="300">
<template #header>
masterSlaveRelation
<el-tooltip class="box-item" effect="dark"
content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'" placement="top">
<el-tooltip class="box-item" effect="dark" content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'"
placement="top">
<el-icon>
<question-filled />
</el-icon>
@@ -112,8 +97,7 @@
<template #header>
configEpoch
<el-tooltip class="box-item" effect="dark"
content="节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。"
placement="top">
content="节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。" placement="top">
<el-icon>
<question-filled />
</el-icon>
@@ -164,19 +148,30 @@ import { tagApi } from '../tag/api';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
import TagInfo from '../component/TagInfo.vue';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
tags: [],
redisTable: [],
total: 0,
currentId: null,
currentData: null,
chooseData: null as any,
query: {
tagPath: null,
pageNum: 1,
pageSize: 10,
clusterId: null,
},
queryConfig: [
TableQuery.slot("tagPath", "标签", "tagPathSelect"),
],
columns: [
TableColumn.new("tagPath", "标签路径").setSlot("tagPath"),
TableColumn.new("name", "名称"),
TableColumn.new("host", "host:port"),
TableColumn.new("mode", "mode"),
TableColumn.new("remark", "备注"),
TableColumn.new("more", "更多").setSlot("more").setMinWidth(155).fixedRight(),
],
detailDialog: {
visible: false,
data: null as any,
@@ -209,7 +204,7 @@ const {
tags,
redisTable,
total,
currentId,
chooseData,
query,
detailDialog,
clusterInfoDialog,
@@ -221,24 +216,12 @@ onMounted(async () => {
search();
});
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const showDetail = (detail: any) => {
state.detailDialog.data = detail;
state.detailDialog.visible = true;
}
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
const deleteRedis = async () => {
try {
await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
@@ -246,10 +229,9 @@ const deleteRedis = async () => {
cancelButtonText: '取消',
type: 'warning',
});
await redisApi.delRedis.request({ id: state.currentId });
await redisApi.delRedis.request({ id: state.chooseData.id });
ElMessage.success('删除成功');
state.currentData = null;
state.currentId = null;
state.chooseData = null;
search();
} catch (err) { }
};
@@ -288,19 +270,16 @@ const editRedis = async (isAdd = false) => {
state.redisEditDialog.data = null;
state.redisEditDialog.title = '新增redis';
} else {
state.redisEditDialog.data = state.currentData;
state.redisEditDialog.data = state.chooseData;
state.redisEditDialog.title = '修改redis';
}
state.redisEditDialog.visible = true;
};
const valChange = () => {
state.currentId = null;
state.currentData = null;
state.chooseData = null;
search();
};
</script>
<style>
</style>
<style></style>

View File

@@ -33,6 +33,7 @@ export const redisApi = {
del: Api.newDelete("/redis/{id}/{db}/scan/{cursor}/{count}"),
delKey: Api.newDelete("/redis/{id}/{db}/key"),
flushDb: Api.newDelete("/redis/{id}/{db}/flushdb"),
lrem: Api.newPost("/redis/{id}/{db}/lrem"),
getListValue: Api.newGet("/redis/{id}/{db}/list-value"),

View File

@@ -1,47 +1,32 @@
<template>
<div class="role-list">
<el-card>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:del'" :disabled="!chooseId" @click="deleteTeam(chooseData)" type="danger"
icon="delete">删除</el-button>
<div>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="data" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<div style="float: right">
<el-input placeholder="请输入团队名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search"
clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="data" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="团队名称"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="160px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建者"> </el-table-column>
<el-table-column label="操作" min-width="80px">
<template #default="scope">
<el-link @click.prevent="showMembers(scope.row)" :underline="false" type="primary">成员</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click.prevent="showTags(scope.row)" :underline="false" type="success">标签</el-link>
<el-divider v-auth="'team:save'" direction="vertical" border-style="dashed" />
<el-link v-auth="'team:save'" @click.prevent="showSaveTeamDialog(scope.row)" :underline="false" type="warning">编辑</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #queryRight>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:del'" :disabled="!chooseData" @click="deleteTeam(chooseData)" type="danger"
icon="delete">删除</el-button>
</template>
<template #tagPath="{ data }">
<tag-info :tag-path="data.tagPath" />
<span class="ml5">
{{ data.tagPath }}
</span>
</template>
<template #action="{ data }">
<el-link @click.prevent="showMembers(data)" :underline="false" type="primary">成员</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click.prevent="showTags(data)" :underline="false" type="success">标签</el-link>
<el-divider v-auth="'team:save'" direction="vertical" border-style="dashed" />
<el-link v-auth="'team:save'" @click.prevent="showSaveTeamDialog(data)" :underline="false"
type="warning">编辑</el-link>
</template>
</page-table>
<el-dialog width="400px" title="团队编辑" :before-close="cancelSaveTeam" v-model="addTeamDialog.visible">
<el-form ref="teamForm" :model="addTeamDialog.form" label-width="70px">
@@ -66,8 +51,8 @@
<el-form-item prop="tag" label="标签:">
<el-tree-select ref="tagTreeRef" style="width: 100%" v-model="showTagDialog.tagTreeTeams"
:data="showTagDialog.tags" :default-expanded-keys="showTagDialog.tagTreeTeams" multiple
:render-after-expand="true" show-checkbox check-strictly node-key="id"
:props="showTagDialog.props" @check="tagTreeNodeCheck">
:render-after-expand="true" show-checkbox check-strictly node-key="id" :props="showTagDialog.props"
@check="tagTreeNodeCheck">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
@@ -98,8 +83,8 @@
<el-button v-auth="'team:member:del'" @click="deleteMember" :disabled="showMemDialog.chooseId == null"
type="danger" icon="delete" size="small">移除</el-button>
<div style="float: right">
<el-input placeholder="请输入用户名" class="mr2" style="width: 150px"
v-model="showMemDialog.query.username" size="small" @clear="search" clearable></el-input>
<el-input placeholder="请输入用户名" class="mr2" style="width: 150px" v-model="showMemDialog.query.username"
size="small" @clear="search" clearable></el-input>
<el-button @click="setMemebers" type="success" icon="search" size="small"></el-button>
</div>
</div>
@@ -153,6 +138,8 @@ import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import { notBlank } from '@/common/assert';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
@@ -168,10 +155,19 @@ const state = reactive({
pageSize: 10,
name: null,
},
queryConfig: [
TableQuery.text("name", "团队名称"),
],
columns: [
TableColumn.new("name", "团队名称"),
TableColumn.new("remark", "备注"),
TableColumn.new("createTime", "创建时间").isTime(),
TableColumn.new("creator", "创建人"),
TableColumn.new("action", "操作").setSlot("action").setMinWidth(100).fixedRight(),
],
total: 0,
data: [],
chooseId: 0,
chooseData: null,
chooseData: null as any,
showMemDialog: {
visible: false,
chooseId: 0,
@@ -213,7 +209,6 @@ const {
addTeamDialog,
total,
data,
chooseId,
chooseData,
showMemDialog,
showTagDialog,
@@ -229,19 +224,6 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const showSaveTeamDialog = (data: any) => {
if (data) {
state.addTeamDialog.form.id = data.id;
@@ -278,6 +260,7 @@ const deleteTeam = (data: any) => {
}).then(async () => {
await tagApi.delTeam.request({ id: data.id });
ElMessage.success('删除成功!');
state.chooseData = null;
search();
});
};
@@ -332,7 +315,7 @@ const showAddMemberDialog = () => {
const addMember = async () => {
const memForm = state.showMemDialog.memForm;
memForm.teamId = state.chooseId;
memForm.teamId = state.chooseData.id;
notBlank(memForm.accountIds, '请先选择账号');
await tagApi.saveTeamMem.request(memForm);
@@ -404,6 +387,4 @@ const tagTreeNodeCheck = () => {
// console.log(state.showTagDialog.tagTreeTeams);
// }
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -1,78 +1,41 @@
<template>
<div class="role-list">
<el-card>
<el-button v-auth="'account:add'" type="primary" icon="plus" @click="editAccount(true)">添加</el-button>
<el-button v-auth="'account:add'" :disabled="chooseId == null" @click="editAccount(false)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="'account:saveRoles'" :disabled="chooseId == null" @click="showRoleEdit()" type="success"
icon="setting">角色分配</el-button>
<el-button v-auth="'account:del'" :disabled="chooseId == null" @click="deleteAccount()" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-input class="mr2" placeholder="请输入账号名" style="width: 200px" v-model="query.username"
@clear="search()" clearable></el-input>
<el-button @click="search()" type="success" icon="search"></el-button>
</div>
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip>
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="115"></el-table-column>
<el-table-column prop="username" label="用户名" min-width="115"></el-table-column>
<div>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="datas" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<el-table-column align="center" prop="status" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
<el-tag v-if="scope.row.status == -1" type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间" show-overflow-tooltip>
<template #default="scope">
{{ dateFormat(scope.row.lastLoginTime) }}
</template>
</el-table-column>
<template #queryRight>
<el-button v-auth="'account:add'" type="primary" icon="plus" @click="editAccount(true)">添加</el-button>
<el-button v-auth="'account:add'" :disabled="state.chooseData == null" @click="editAccount(false)"
type="primary" icon="edit">编辑</el-button>
<el-button v-auth="'account:saveRoles'" :disabled="state.chooseData == null" @click="showRoleEdit()"
type="success" icon="setting">角色分配</el-button>
<el-button v-auth="'account:del'" :disabled="state.chooseData == null" @click="deleteAccount()"
type="danger" icon="delete">删除</el-button>
</template>
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<!-- <el-table-column min-width="115" prop="modifier" label="更新账号"></el-table-column>
<el-table-column min-width="160" prop="updateTime" label="修改时间">
<template #default="scope">
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column> -->
<template #status="{ data }">
<el-tag v-if="data.status == 1" type="success">正常</el-tag>
<el-tag v-if="data.status == -1" type="danger">禁用</el-tag>
</template>
<!-- <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>
<template #showmore="{ data }">
<el-link @click.prevent="showRoles(data)" type="success">角色</el-link>
<el-link class="ml5" @click.prevent="showResources(scope.row)" type="info">菜单&权限</el-link>
</template>
</el-table-column>
<el-link class="ml5" @click.prevent="showResources(data)" type="info">菜单&权限</el-link>
</template>
<el-table-column label="操作" min-width="200px">
<template #default="scope">
<el-button v-auth="'account:changeStatus'" @click="changeStatus(scope.row)"
v-if="scope.row.status == 1" type="danger" icom="tickets" size="small" plain>禁用</el-button>
<el-button v-auth="'account:changeStatus'" v-if="scope.row.status == -1" type="success"
@click="changeStatus(scope.row)" size="small" plain>启用</el-button>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #action="{ data }">
<el-button v-auth="'account:changeStatus'" @click="changeStatus(data)" v-if="data.status == 1" type="danger"
size="small" plain>禁用</el-button>
<el-button v-auth="'account:changeStatus'" v-if="data.status == -1" type="success"
@click="changeStatus(data)" size="small" plain>启用</el-button>
<el-button v-auth="'account:add'" :disabled="!data.otpSecret || data.otpSecret == '-'"
@click="resetOtpSecret(data)" type="warning" size="small" plain>重置OTP</el-button>
</template>
</page-table>
<el-dialog width="500px" :title="showRoleDialog.title" v-model="showRoleDialog.visible">
<el-table border :data="showRoleDialog.accountRoles">
@@ -93,7 +56,7 @@
<span class="custom-tree-node">
<span v-if="data.type == enums.ResourceTypeEnum['MENU'].value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['PERMISSION'].value" style="color: #67c23a">{{
node.label
node.label
}}</span>
</span>
</template>
@@ -114,13 +77,14 @@ import enums from '../enums';
import { accountApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
chooseId: null,
/**
* 选中的数据
*/
chooseData: null,
chooseData: null as any,
/**
* 查询条件
*/
@@ -129,6 +93,21 @@ const state = reactive({
pageNum: 1,
pageSize: 10,
},
queryConfig: [
TableQuery.text("username", "用户名"),
],
columns: [
TableColumn.new("name", "姓名"),
TableColumn.new("username", "用户名"),
TableColumn.new("status", "状态").setSlot("status"),
TableColumn.new("lastLoginTime", "最后登录时间").isTime(),
TableColumn.new("showmore", "查看更多").setSlot("showmore").setMinWidth(150),
TableColumn.new("creator", "创建账号"),
TableColumn.new("createTime", "创建时间").isTime(),
TableColumn.new("modifier", "更新账号"),
TableColumn.new("updateTime", "更新时间").isTime(),
TableColumn.new("action", "操作").setSlot("action").fixedRight().setMinWidth(200),
],
datas: [],
total: 0,
showRoleDialog: {
@@ -157,7 +136,6 @@ const state = reactive({
});
const {
chooseId,
query,
datas,
total,
@@ -171,14 +149,6 @@ onMounted(() => {
search();
});
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const search = async () => {
let res: any = await accountApi.list.request(state.query);
state.datas = res.list;
@@ -215,13 +185,22 @@ const changeStatus = async (row: any) => {
search();
};
const resetOtpSecret = async (row: any) => {
let id = row.id;
await accountApi.resetOtpSecret.request({
id,
});
ElMessage.success('操作成功');
row.otpSecret = "-";
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const showRoleEdit = () => {
if (!state.chooseId) {
if (!state.chooseData) {
ElMessage.error('请选择账号');
}
state.roleDialog.visible = true;
@@ -255,14 +234,11 @@ const deleteAccount = async () => {
cancelButtonText: '取消',
type: 'warning',
});
await accountApi.del.request({ id: state.chooseId });
await accountApi.del.request({ id: state.chooseData.id });
ElMessage.success('删除成功');
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) { }
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -6,7 +6,7 @@
<div style="float: left">
<el-input placeholder="请输入角色名" style="width: 150px" v-model="query.name" @clear="clear()" clearable>
</el-input>
<el-button @click="search" type="success" icon="search"></el-button>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
</div>
<el-table :data="allRole" border ref="roleTable" @select="select" style="width: 100%">

View File

@@ -6,7 +6,8 @@ export const resourceApi = {
save: Api.newPost("/sys/resources"),
update: Api.newPut("/sys/resources/{id}"),
del: Api.newDelete("/sys/resources/{id}"),
changeStatus: Api.newPut("/sys/resources/{id}/{status}")
changeStatus: Api.newPut("/sys/resources/{id}/{status}"),
sort: Api.newPost("/sys/resources/sort")
}
export const roleApi = {
@@ -26,6 +27,7 @@ export const accountApi = {
update: Api.newPut("/sys/accounts/{id}"),
del: Api.newDelete("/sys/accounts/{id}"),
changeStatus: Api.newPut("/sys/accounts/change-status/{id}/{status}"),
resetOtpSecret: Api.newPut("/sys/accounts/{id}/reset-otp"),
roleIds: Api.newGet("/sys/accounts/{id}/roleIds"),
roles: Api.newGet("/sys/accounts/{id}/roles"),
resources: Api.newGet("/sys/accounts/{id}/resources"),

View File

@@ -1,57 +1,42 @@
<template>
<div class="role-list">
<el-card>
<el-button type="primary" icon="plus" @click="editConfig(false)">添加</el-button>
<el-button :disabled="chooseId == null" @click="editConfig(chooseData)" type="primary" icon="edit">编辑
</el-button>
<div>
<page-table :show-choose-column="true" v-model:choose-data="state.chooseData" :data="configs"
:columns="state.columns" :total="total" v-model:page-size="query.pageSize" v-model:page-num="query.pageNum"
@pageChange="search()">
<el-table :data="configs" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="配置项" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="key" label="配置key" min-width="100px"></el-table-column>
<el-table-column prop="value" label="配置值" show-overflow-tooltip></el-table-column>
<el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope">
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" min-width="60px" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope">
<el-link :disabled="scope.row.status == -1" type="warning"
@click="showSetConfigDialog(scope.row)" plain size="small" :underline="false">配置</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #queryRight>
<el-button v-auth="'config:save'" type="primary" icon="plus" @click="editConfig(false)">添加</el-button>
<el-button v-auth="'config:save'" :disabled="chooseData == null" @click="editConfig(chooseData)"
type="primary" icon="edit">编辑
</el-button>
</template>
<template #status="{ data }">
<el-tag v-if="data.status == 1" type="success">正常</el-tag>
<el-tag v-if="data.status == -1" type="danger">禁用</el-tag>
</template>
<template #action="{ data }">
<el-link :disabled="data.status == -1" type="warning" @click="showSetConfigDialog(data)" plain size="small"
:underline="false">配置</el-link>
</template>
</page-table>
<el-dialog :before-close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="500px">
<el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsForm" :model="paramsDialog.params"
label-width="90px">
<el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsFormRef" :model="paramsDialog.params"
label-width="130px">
<el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model"
:label="item.name" required>
<el-input v-if="!item.options" v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-select v-else v-model="paramsDialog.params[item.model]" :placeholder="item.placeholder"
filterable autocomplete="off" clearable style="width: 100%">
<el-input v-if="!item.options" v-model="paramsDialog.params[item.model]" :placeholder="item.placeholder"
autocomplete="off" clearable></el-input>
<el-select v-else v-model="paramsDialog.params[item.model]" :placeholder="item.placeholder" filterable
autocomplete="off" clearable style="width: 100%">
<el-option v-for="option in item.options.split(',')" :key="option" :label="option"
:value="option" />
</el-select>
</el-form-item>
</el-form>
<el-form v-else ref="paramsForm" label-width="90px">
<el-form v-else ref="paramsFormRef" label-width="90px">
<el-form-item label="配置值" required>
<el-input v-model="paramsDialog.params" :placeholder="paramsDialog.config.remark" autocomplete="off"
clearable></el-input>
@@ -60,7 +45,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="closeSetConfigDialog()"> </el-button>
<el-button type="primary" @click="setConfig()"> </el-button>
<el-button v-auth="'config:save'" type="primary" @click="setConfig()"> </el-button>
</span>
</template>
</el-dialog>
@@ -71,22 +56,32 @@
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted } from 'vue';
import ConfigEdit from './ConfigEdit.vue';
import { configApi } from '../api';
import { ElMessage } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn } from '@/components/pagetable';
const paramsFormRef: any = ref(null)
const state = reactive({
query: {
pageNum: 1,
pageSize: 10,
name: null,
},
columns: [
TableColumn.new("name", "配置项"),
TableColumn.new("key", "配置key"),
TableColumn.new("value", "配置值"),
TableColumn.new("remark", "备注"),
TableColumn.new("modifier", "更新账号"),
TableColumn.new("updateTime", "更新时间").isTime(),
TableColumn.new("action", "操作").setSlot("action").fixedRight().setMinWidth(60),
],
total: 0,
configs: [],
chooseId: null,
chooseData: null,
chooseData: null as any,
paramsDialog: {
visible: false,
config: null as any,
@@ -104,7 +99,6 @@ const {
query,
total,
configs,
chooseId,
chooseData,
paramsDialog,
configEdit,
@@ -120,11 +114,6 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const showSetConfigDialog = (row: any) => {
state.paramsDialog.config = row;
// 存在配置项则弹窗提示输入对应的配置项
@@ -155,13 +144,25 @@ const closeSetConfigDialog = () => {
const setConfig = async () => {
let paramsValue = state.paramsDialog.params;
if (state.paramsDialog.paramsFormItem.length > 0) {
// 如果配置项删除则需要将value中对应的字段移除
for (let paramKey in paramsValue) {
if (!hasParam(paramKey, state.paramsDialog.paramsFormItem)) {
delete paramsValue[paramKey];
await paramsFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
paramsValue = null as any;
return false;
}
}
paramsValue = JSON.stringify(paramsValue);
if (state.paramsDialog.paramsFormItem.length > 0) {
// 如果配置项删除则需要将value中对应的字段移除
for (let paramKey in paramsValue) {
if (!hasParam(paramKey, state.paramsDialog.paramsFormItem)) {
delete paramsValue[paramKey];
}
}
paramsValue = JSON.stringify(paramsValue);
}
});
}
// 说明校验失败
if (paramsValue == null) {
return;
}
await configApi.save.request({
id: state.paramsDialog.config.id,
@@ -172,6 +173,7 @@ const setConfig = async () => {
ElMessage.success('保存成功');
closeSetConfigDialog();
search();
};
const hasParam = (paramKey: string, paramItems: any) => {
@@ -183,17 +185,8 @@ const hasParam = (paramKey: string, paramItems: any) => {
return false;
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const configEditChange = () => {
ElMessage.success('保存成功');
state.chooseId = null;
state.chooseData = null;
search();
};
@@ -208,6 +201,4 @@ const editConfig = (data: any) => {
state.configEdit.visible = true;
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="system-menu-dialog-container layout-pd">
<el-dialog :title="title" :destroy-on-close="true" v-model="dialogVisible" width="800px">
<el-form :model="form" :inline="true" ref="menuForm" :rules="rules" label-width="95px">
<el-form :model="form" :inline="true" ref="menuForm" :rules="rules" label-width="100px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item class="w100" prop="type" label="类型" required>
@@ -19,31 +19,60 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item class="w100" prop="code" label="path|code">
<template #label>
path|code
<el-tooltip effect="dark" content="菜单类型则为访问路径(若菜单路径不以'/'开头则访问地址会自动拼接父菜单路径)、否则为资源唯一编码"
placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-input v-model.trim="form.code" placeholder="菜单不以'/'开头则自动拼接父菜单路径"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item class="w100" label="序号" prop="weight" required>
<el-input v-model.trim="form.weight" type="number" placeholder="请输入序号"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" label="图标">
<icon-selector v-model="form.meta.icon" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" prop="code" label="路由名">
<el-form-item class="w100">
<template #label>
路由名
<el-tooltip effect="dark" content="与vue的组件名一致才可使组件缓存生效如ResourceList" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-input v-model.trim="form.meta.routeName" placeholder="请输入路由名称"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" prop="code" label="组件路径">
<el-form-item class="w100" prop="code">
<template #label>
组件路径
<el-tooltip effect="dark" content="访问的组件路径,如:`system/resource/ResourceList`,默认在`views`目录下"
placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-input v-model.trim="form.meta.component" placeholder="请输入组件路径"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" prop="code" label="是否缓存">
<el-form-item class="w100" prop="isKeepAlive">
<template #label>
是否缓存
<el-tooltip effect="dark" content="选择是则会被`keep-alive`缓存(重新进入页面不会刷新页面及重新请求数据)需要路由名与vue的组件名一致" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-select v-model="form.meta.isKeepAlive" placeholder="请选择" class="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
@@ -51,7 +80,15 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" prop="code" label="是否隐藏">
<el-form-item class="w100">
<template #label>
是否隐藏
<el-tooltip effect="dark" content="选择隐藏则路由将不会出现在菜单栏中,但仍然可以访问。禁用则不可访问与操作" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-select v-model="form.meta.isHide" placeholder="请选择" class="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
@@ -67,17 +104,26 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" v-if="form.type === menuTypeValue">
<el-form-item class="w100" prop="code" label="是否iframe">
<el-select class="w100" @change="changeIsIframe" v-model="form.meta.isIframe" placeholder="请选择">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
<el-form-item class="w100" prop="linkType">
<template #label>
外链
<el-tooltip effect="dark" content="内嵌: 以iframe展示、外链: 新标签打开" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-select class="w100" @change="changeIsIframe" v-model="form.meta.linkType" placeholder="请选择">
<el-option :key="0" label="否" :value="0"> </el-option>
<el-option :key="1" label="内嵌" :value="1"> </el-option>
<el-option :key="2" label="外链" :value="2"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20"
v-if="form.type === menuTypeValue && form.meta.isIframe">
<el-form-item prop="code" label="iframe地址" class="w100">
<el-input v-model.trim="form.meta.link" placeholder="请输入iframe urlhttp://xxx.com"></el-input>
v-if="form.type === menuTypeValue && form.meta.linkType > 0">
<el-form-item prop="code" label="链接地址" class="w100">
<el-input v-model.trim="form.meta.link" placeholder="外链/内嵌的链接地址http://xxx.com"></el-input>
</el-form-item>
</el-col>
</el-row>
@@ -131,7 +177,7 @@ const defaultMeta = {
isKeepAlive: true,
isHide: false,
isAffix: false,
isIframe: false,
linkType: 0,
link: '',
};
@@ -143,13 +189,6 @@ const rules = {
trigger: ['change', 'blur'],
},
],
weight: [
{
required: true,
message: '请输入序号',
trigger: ['change', 'blur'],
},
],
}
const trueFalseOption = [
@@ -171,7 +210,6 @@ const state = reactive({
pid: null,
code: null,
type: null,
weight: 0,
meta: {
routeName: '',
icon: '',
@@ -180,7 +218,7 @@ const state = reactive({
isKeepAlive: true,
isHide: false,
isAffix: false,
isIframe: false,
linkType: 0,
link: '',
},
},
@@ -210,7 +248,7 @@ watch(props, (newValue: any) => {
state.form.meta.isKeepAlive = meta.isKeepAlive ? true : false;
state.form.meta.isHide = meta.isHide ? true : false;
state.form.meta.isAffix = meta.isAffix ? true : false;
state.form.meta.isIframe = meta.isIframe ? true : false;
state.form.meta.linkType = meta.linkType;
});
// 改变iframe字段如果为是则设置默认的组件
@@ -230,7 +268,6 @@ const btnOk = () => {
} else {
submitForm.meta = null as any;
}
submitForm.weight = parseInt(submitForm.weight as any);
menuForm.value.validate((valid: any) => {
if (valid) {
resourceApi.save.request(submitForm).then(() => {
@@ -263,8 +300,8 @@ const parseMenuMeta = (meta: any) => {
if (meta.isAffix) {
metaForm.isAffix = true;
}
if (meta.isIframe) {
metaForm.isIframe = true;
if (meta.linkType) {
metaForm.linkType = meta.linkType;
}
if (meta.link) {
metaForm.link = meta.link;

View File

@@ -3,19 +3,20 @@
<div class="toolbar">
<div>
<span style="font-size: 14px">
<SvgIcon name="info-filled" />红色字体表示禁用状态
<SvgIcon name="info-filled" />红色橙色字体表示禁用状态
</span>
</div>
<el-button v-auth="'resource:add'" type="primary" icon="plus" @click="addResource(false)">添加</el-button>
</div>
<el-tree class="none-select" :indent="38" node-key="id" :props="props" :data="data"
@node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false">
<el-tree class="none-select" :indent="38" node-key="id" :props="props" :data="data" @node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse" :default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false"
draggable :allow-drop="allowDrop" @node-drop="handleDrop">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px" v-if="data.type === menuTypeValue">
<span style="color: #3c8dbc"></span>
{{ data.name }}
<span v-if="data.status == 1">{{ data.name }}</span>
<span v-if="data.status == -1" style="color: #e6a23c;">{{ data.name }}</span>
<span style="color: #3c8dbc"></span>
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
</span>
@@ -31,21 +32,17 @@
<el-link v-auth="'resource:update'" @click.prevent="editResource(data)" class="ml5" type="primary"
icon="edit" :underline="false" />
<el-link v-auth="'resource:add'" @click.prevent="addResource(data)"
v-if="data.type === menuTypeValue" icon="circle-plus" :underline="false"
type="success" class="ml5" />
<el-link v-auth="'resource:add'" @click.prevent="addResource(data)" v-if="data.type === menuTypeValue"
icon="circle-plus" :underline="false" type="success" class="ml5" />
<el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, -1)"
v-if="data.status === 1 && data.type === permissionTypeValue"
icon="circle-close" :underline="false" type="warning" class="ml5" />
v-if="data.status === 1" icon="circle-close" :underline="false" type="warning" class="ml5" />
<el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, 1)"
v-if="data.status === -1 && data.type === permissionTypeValue"
type="success" icon="circle-check" :underline="false" plain class="ml5" />
v-if="data.status === -1" type="success" icon="circle-check" :underline="false" plain class="ml5" />
<el-link v-auth="'resource:delete'" v-if="data.children == null && data.name !== '首页'"
@click.prevent="deleteMenu(data)" type="danger" icon="delete" :underline="false" plain
class="ml5" />
<el-link v-auth="'resource:delete'" @click.prevent="deleteMenu(data)" type="danger" icon="delete"
:underline="false" plain class="ml5" />
</span>
</template>
</el-tree>
@@ -61,7 +58,9 @@
</el-descriptions-item>
<el-descriptions-item label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
<el-descriptions-item label="code[菜单path]">{{ infoDialog.data.code }}</el-descriptions-item>
<el-descriptions-item label="序号">{{ infoDialog.data.weight }}</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="图标">
<SvgIcon :name="infoDialog.data.meta.icon" />
</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="路由名">
{{ infoDialog.data.meta.routeName }}
</el-descriptions-item>
@@ -77,11 +76,11 @@
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="tag不可删除">
{{ infoDialog.data.meta.isAffix ? '' : '' }}
</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="是否iframe">
{{ infoDialog.data.meta.isIframe ? '' : '' }}
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="外链">
{{ infoDialog.data.meta.linkType ? '' : '' }}
</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue && infoDialog.data.meta.isIframe"
label="iframe url">
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue && infoDialog.data.meta.linkType > 0"
label="外链">
{{ infoDialog.data.meta.link }}
</el-descriptions-item>
@@ -117,7 +116,7 @@ const state = reactive({
type: null,
title: '',
visible: false,
data: { pid: 0, type: 1, weight: 1 },
data: { pid: 0, type: 1 },
// 资源类型选择是否选
typeDisabled: true,
},
@@ -134,7 +133,6 @@ const state = reactive({
modifier: '',
createTime: '',
updateTime: '',
weight: null,
code: '',
},
},
@@ -182,7 +180,7 @@ const deleteMenu = (data: any) => {
const addResource = (data: any) => {
let dialog = state.dialogForm;
dialog.data = { pid: 0, type: 1, weight: 1 };
dialog.data = { pid: 0, type: 1 };
// 添加顶级菜单情况
if (!data) {
dialog.typeDisabled = true;
@@ -213,7 +211,6 @@ const addResource = (data: any) => {
} else {
dialog.data.type = menuTypeValue;
}
dialog.data.weight = data.children.length + 1;
}
dialog.visible = true;
};
@@ -242,7 +239,7 @@ const changeStatus = async (data: any, status: any) => {
id: data.id,
status: status,
});
data.status = status;
search();
ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
};
@@ -271,6 +268,51 @@ const handleNodeCollapse = (data: any, node: any) => {
}
};
const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
// 如果是插入至目标节点
if (type === 'inner') {
// 只有目标节点下没有子节点才允许移动
if (!dropNode.data.children || dropNode.data.children == 0) {
// 只有权限节点可移动至菜单节点下 或者移动菜单
return (draggingNode.data.type == permissionTypeValue && dropNode.data.type == menuTypeValue) ||
(draggingNode.data.type == menuTypeValue && dropNode.data.type == menuTypeValue)
}
return false;
}
return draggingNode.data.type === dropNode.data.type;
}
const handleDrop = async (
draggingNode: any,
dropNode: any,
dropType: any,
) => {
const draggingData = draggingNode.data;
const dropData = dropNode.data;
if (draggingData.pid !== dropData.pid) {
draggingData.pid = dropData.pid;
}
if (dropType === 'inner') {
draggingData.weight = 1;
draggingData.pid = dropData.id;
}
if (dropType === 'before') {
draggingData.weight = dropData.weight - 1;
}
if (dropType === 'after') {
draggingData.weight = dropData.weight + 1;
}
await resourceApi.sort.request([
{
id: draggingData.id,
name: draggingData.name,
pid: draggingData.pid,
weight: draggingData.weight
},
])
}
const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) {

View File

@@ -1,52 +1,28 @@
<template>
<div class="role-list">
<el-card>
<el-button v-auth="'role:add'" type="primary" icon="plus" @click="editRole(false)">添加</el-button>
<el-button v-auth="'role:update'" :disabled="chooseId == null" @click="editRole(chooseData)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="'role:saveResources'" :disabled="chooseId == null" @click="editResource(chooseData)"
type="success" icon="setting">分配菜单&权限</el-button>
<el-button v-auth="'role:del'" :disabled="chooseId == null" @click="deleteRole(chooseData)" type="danger"
icon="delete">删除</el-button>
<div>
<page-table :query="state.queryConfig" v-model:query-form="query" :show-choose-column="true"
v-model:choose-data="state.chooseData" :data="roles" :columns="state.columns" :total="total"
v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<div style="float: right">
<el-input placeholder="请输入角色名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search"
clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="roles" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="角色名称"></el-table-column>
<el-table-column prop="code" label="角色code"></el-table-column>
<el-table-column prop="remark" label="描述" min-width="160px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="updateTime" label="修改时间">
<template #default="scope">
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="查看更多" min-width="80px">
<template #default="scope">
<el-link @click.prevent="showResources(scope.row)" type="info">菜单&权限</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #queryRight>
<el-button v-auth="'role:add'" type="primary" icon="plus" @click="editRole(false)">添加</el-button>
<el-button v-auth="'role:update'" :disabled="chooseData == null" @click="editRole(chooseData)"
type="primary" icon="edit">编辑</el-button>
<el-button v-auth="'role:saveResources'" :disabled="chooseData == null" @click="editResource(chooseData)"
type="success" icon="setting">分配菜单&权限</el-button>
<el-button v-auth="'role:del'" :disabled="chooseData == null" @click="deleteRole(chooseData)" type="danger"
icon="delete">删除</el-button>
</template>
<template #status="{ data }">
<el-tag v-if="data.status == 1" type="success" size="small">正常</el-tag>
<el-tag v-if="data.status == -1" type="danger" size="small">禁用</el-tag>
</template>
<template #showmore="{ data }">
<el-link @click.prevent="showResources(data)" type="info">菜单&权限</el-link>
</template>
</page-table>
<role-edit :title="roleEditDialog.title" v-model:visible="roleEditDialog.visible" :data="roleEditDialog.role"
@val-change="roleEditChange" />
@@ -65,7 +41,8 @@ import ResourceEdit from './ResourceEdit.vue';
import ShowResource from './ShowResource.vue';
import { roleApi, resourceApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
query: {
@@ -73,10 +50,23 @@ const state = reactive({
pageSize: 10,
name: null,
},
queryConfig: [
TableQuery.text("name", "角色名"),
],
columns: [
TableColumn.new("name", "角色名称"),
TableColumn.new("code", "角色code"),
TableColumn.new("remark", "备注"),
TableColumn.new("status", "状态").setSlot("status"),
TableColumn.new("creator", "创建账号"),
TableColumn.new("createTime", "创建时间").isTime(),
TableColumn.new("modifier", "更新账号"),
TableColumn.new("updateTime", "更新时间").isTime(),
TableColumn.new("showmore", "查看更多").setSlot("showmore").setMinWidth(150).fixedRight(),
],
total: 0,
roles: [],
chooseId: null,
chooseData: null,
chooseData: null as any,
resourceDialog: {
visible: false,
role: {},
@@ -99,7 +89,6 @@ const {
query,
total,
roles,
chooseId,
chooseData,
resourceDialog,
roleEditDialog,
@@ -116,22 +105,8 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const roleEditChange = () => {
ElMessage.success('修改成功!');
state.chooseId = null;
state.chooseData = null;
search();
};
@@ -230,6 +205,4 @@ const cancelEditResources = () => {
}, 10);
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -10,8 +10,8 @@
node.label
}}</span>
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="InfoFilled" type="info"
:underline="false" />
<!-- <el-link @click.prevent="info(data)" style="margin-left: 25px" icon="InfoFilled" type="info"
:underline="false" /> -->
</span>
</template>
</el-tree>

View File

@@ -1,49 +1,29 @@
<template>
<div class="role-list">
<el-card>
<div style="float: right">
<el-select remote :remote-method="getAccount" v-model="query.creatorId" filterable
placeholder="请输入并选择账号" clearable class="mr5">
<div>
<page-table :query="state.queryConfig" v-model:query-form="query" :data="logs" :columns="state.columns"
:total="total" v-model:page-size="query.pageSize" v-model:page-num="query.pageNum" @pageChange="search()">
<template #selectAccount>
<el-select remote :remote-method="getAccount" v-model="query.creatorId" filterable placeholder="请输入并选择账号"
clearable class="mr5" style="width: 200px">
<el-option v-for="item in accounts" :key="item.id" :label="item.username" :value="item.id">
</el-option>
</el-select>
<el-select v-model="query.type" filterable placeholder="请选择操作结果" clearable class="mr5">
<el-option label="成功" :value="1"> </el-option>
<el-option label="失败" :value="2"> </el-option>
</el-select>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="logs" style="width: 100%">
<el-table-column prop="creator" label="操作人" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="操作时间" min-width="160">
<template #default="scope">
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="type" label="结果" min-width="65">
<template #default="scope">
<el-tag v-if="scope.row.type == 1" type="success" size="small">成功</el-tag>
<el-tag v-if="scope.row.type == 2" type="danger" size="small">失败</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="160" show-overflow-tooltip></el-table-column>
</template>
<el-table-column prop="reqParam" label="操作信息" min-width="300" show-overflow-tooltip></el-table-column>
<el-table-column prop="resp" label="响应信息" min-width="200" 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="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<template #type="{ data }">
<el-tag v-if="data.type == 1" type="success" size="small">成功</el-tag>
<el-tag v-if="data.type == 2" type="danger" size="small">失败</el-tag>
</template>
</page-table>
</div>
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { logApi, accountApi } from '../api';
import { dateFormat } from '@/common/utils/date';
import PageTable from '@/components/pagetable/PageTable.vue'
import { TableColumn, TableQuery } from '@/components/pagetable';
const state = reactive({
query: {
@@ -51,8 +31,22 @@ const state = reactive({
creatorId: null,
pageNum: 1,
pageSize: 10,
name: null,
},
queryConfig: [
TableQuery.slot("creatorId", "操作人", "selectAccount"),
TableQuery.select("type", "操作结果").setOptions([
{ label: "成功", value: 1 },
{ label: "失败", value: 2 },
]),
],
columns: [
TableColumn.new("creator", "操作人"),
TableColumn.new("createTime", "操作时间").isTime(),
TableColumn.new("type", "结果").setSlot("type"),
TableColumn.new("description", "描述"),
TableColumn.new("reqParam", "操作信息"),
TableColumn.new("resp", "响应信息"),
],
total: 0,
logs: [],
accounts: [] as any,
@@ -75,11 +69,6 @@ const search = async () => {
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {
state.accounts = res.list;
@@ -87,6 +76,4 @@ const getAccount = (username: any) => {
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -7,12 +7,17 @@
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.19.1.tgz"
integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==
"@babel/runtime@^7.15.4":
version "7.19.0"
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.19.0.tgz"
integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
"@babel/parser@^7.20.15", "@babel/parser@^7.21.3":
version "7.21.8"
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
"@babel/runtime@^7.21.0":
version "7.21.5"
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
dependencies:
regenerator-runtime "^0.13.4"
regenerator-runtime "^0.13.11"
"@ctrl/tinycolor@^3.4.1":
version "3.4.1"
@@ -190,6 +195,11 @@
resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/download/@humanwhocodes/object-schema-1.2.1.tgz"
integrity sha1-tSBSnsIdjllFoYUd/Rwy6U45/0U=
"@jridgewell/sourcemap-codec@^1.4.13":
version "1.4.15"
resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.nlark.com/@nodelib/fs.scandir/download/@nodelib/fs.scandir-2.1.5.tgz?cache=0&sync_timestamp=1622792738877&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40nodelib%2Ffs.scandir%2Fdownload%2F%40nodelib%2Ffs.scandir-2.1.5.tgz"
@@ -348,15 +358,15 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-core@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
"@vue/compiler-core@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128"
integrity sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.47"
"@babel/parser" "^7.21.3"
"@vue/shared" "3.3.4"
estree-walker "^2.0.2"
source-map "^0.6.1"
source-map-js "^1.0.2"
"@vue/compiler-dom@3.2.39":
version "3.2.39"
@@ -366,29 +376,29 @@
"@vue/compiler-core" "3.2.39"
"@vue/shared" "3.2.39"
"@vue/compiler-dom@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
"@vue/compiler-dom@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151"
integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==
dependencies:
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/compiler-core" "3.3.4"
"@vue/shared" "3.3.4"
"@vue/compiler-sfc@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
"@vue/compiler-sfc@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df"
integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-ssr" "3.2.47"
"@vue/reactivity-transform" "3.2.47"
"@vue/shared" "3.2.47"
"@babel/parser" "^7.20.15"
"@vue/compiler-core" "3.3.4"
"@vue/compiler-dom" "3.3.4"
"@vue/compiler-ssr" "3.3.4"
"@vue/reactivity-transform" "3.3.4"
"@vue/shared" "3.3.4"
estree-walker "^2.0.2"
magic-string "^0.25.7"
magic-string "^0.30.0"
postcss "^8.1.10"
source-map "^0.6.1"
source-map-js "^1.0.2"
"@vue/compiler-sfc@^3.0.11":
version "3.2.39"
@@ -414,18 +424,13 @@
"@vue/compiler-dom" "3.2.39"
"@vue/shared" "3.2.39"
"@vue/compiler-ssr@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
"@vue/compiler-ssr@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777"
integrity sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==
dependencies:
"@vue/compiler-dom" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/devtools-api@^6.4.5":
version "6.4.5"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz"
integrity sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==
"@vue/compiler-dom" "3.3.4"
"@vue/shared" "3.3.4"
"@vue/devtools-api@^6.5.0":
version "6.5.0"
@@ -443,58 +448,58 @@
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity-transform@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
"@vue/reactivity-transform@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929"
integrity sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
"@babel/parser" "^7.20.15"
"@vue/compiler-core" "3.3.4"
"@vue/shared" "3.3.4"
estree-walker "^2.0.2"
magic-string "^0.25.7"
magic-string "^0.30.0"
"@vue/reactivity@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
"@vue/reactivity@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.4.tgz#a27a29c6cd17faba5a0e99fbb86ee951653e2253"
integrity sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==
dependencies:
"@vue/shared" "3.2.47"
"@vue/shared" "3.3.4"
"@vue/runtime-core@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
"@vue/runtime-core@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz#4bb33872bbb583721b340f3088888394195967d1"
integrity sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==
dependencies:
"@vue/reactivity" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/reactivity" "3.3.4"
"@vue/shared" "3.3.4"
"@vue/runtime-dom@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
"@vue/runtime-dom@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz#992f2579d0ed6ce961f47bbe9bfe4b6791251566"
integrity sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==
dependencies:
"@vue/runtime-core" "3.2.47"
"@vue/shared" "3.2.47"
csstype "^2.6.8"
"@vue/runtime-core" "3.3.4"
"@vue/shared" "3.3.4"
csstype "^3.1.1"
"@vue/server-renderer@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
"@vue/server-renderer@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz#ea46594b795d1536f29bc592dd0f6655f7ea4c4c"
integrity sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==
dependencies:
"@vue/compiler-ssr" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/compiler-ssr" "3.3.4"
"@vue/shared" "3.3.4"
"@vue/shared@3.2.39":
version "3.2.39"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.39.tgz"
integrity sha512-D3dl2ZB9qE6mTuWPk9RlhDeP1dgNRUKC3NJxji74A4yL8M2MwlhLKUC/49WHjrNzSPug58fWx/yFbaTzGAQSBw==
"@vue/shared@3.2.47":
version "3.2.47"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
"@vue/shared@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780"
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
"@vueuse/core@^9.1.0":
version "9.2.0"
@@ -578,12 +583,12 @@ array-union@^2.1.0:
resolved "https://registry.npm.taobao.org/array-union/download/array-union-2.1.0.tgz?cache=0&sync_timestamp=1614624262896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-union%2Fdownload%2Farray-union-2.1.0.tgz"
integrity sha1-t5hCCtvrHego2ErNii4j0+/oXo0=
asciinema-player@^3.2.0:
version "3.2.0"
resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.2.0.tgz#9db608a2d81ff4ac235d50a88b498258bf7087fb"
integrity sha512-fV3Coe/UGieUy+5KCrh1qZf1lFWkuj12a6KcKrr84b61UFfUOpAZfyem4UnpczuP6cjrV3w5t0u8Wd1kTz/e7g==
asciinema-player@^3.3.0:
version "3.3.0"
resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.3.0.tgz#45616fa8dc3950c2be12b51d0a365ea2b9a845c2"
integrity sha512-4uyCGe83+5gZ06jgIGyV4vl0TS3egBgW0NXyCpDuDDvmzDGHEG8OICChrTecmTvajgLyq8YQet9nI7SYkVt8vQ==
dependencies:
"@babel/runtime" "^7.15.4"
"@babel/runtime" "^7.21.0"
solid-js "^1.3.0"
async-validator@^4.2.5:
@@ -596,10 +601,10 @@ asynckit@^0.4.0:
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.3.4:
version "1.3.4"
resolved "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024"
integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==
axios@^1.4.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
@@ -715,16 +720,16 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
csstype@^2.6.8:
version "2.6.21"
resolved "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz"
integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
csstype@^3.1.0:
version "3.1.1"
resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.1.tgz"
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
csstype@^3.1.1:
version "3.1.2"
resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
dayjs@^1.11.3:
version "1.11.3"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.3.tgz"
@@ -737,6 +742,13 @@ debug@^4.1.1, debug@^4.3.1, debug@^4.3.2:
dependencies:
ms "2.1.2"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.nlark.com/deep-is/download/deep-is-0.1.4.tgz"
@@ -792,10 +804,10 @@ echarts@^5.4.0:
tslib "2.3.0"
zrender "5.4.0"
element-plus@^2.3.2:
version "2.3.2"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.2.tgz#93abe96bcd5d00e96ef281e8d148391d698fb6e3"
integrity sha512-NIK0QckKKagdlMm3eLoVoD4Lkfj5ECzpZAxwrRLdm1Yi5qiTU9Guor33+IbIa52z4gHMuhMxgSVS2xAGmjLHsw==
element-plus@^2.3.7:
version "2.3.7"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.7.tgz#544a127f0e65f51715e3b24ec3ebf545c46859cd"
integrity sha512-h6TxclbaLUJxg/Bv5j/ZKsK+K5yadQliw5+R30HWyE69pXlqXTX24oYx+yw3pA4Dy+lqEDi5501FQ0CORk3OSA==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"
@@ -907,6 +919,11 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint-visitor-keys@^3.4.0:
version "3.4.0"
resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc"
integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==
eslint@^8.35.0:
version "8.35.0"
resolved "https://registry.npmmirror.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323"
@@ -962,6 +979,15 @@ espree@^9.0.0:
acorn-jsx "^5.3.1"
eslint-visitor-keys "^3.1.0"
espree@^9.3.1:
version "9.5.1"
resolved "https://registry.npmmirror.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4"
integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.0"
espree@^9.4.0:
version "9.4.1"
resolved "https://registry.npmmirror.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd"
@@ -1038,10 +1064,10 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.nlark.com/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-plist@^0.1.2:
version "0.1.2"
resolved "https://registry.npmmirror.com/fast-plist/-/fast-plist-0.1.2.tgz"
integrity sha512-2HxzrqJhmMoxVzARjYFvkzkL2dCBB8sogU5sD8gqcZWv5UCivK9/cXM9KIPDRwU+eD3mbRDN/GhW8bO/4dtMfg==
fast-plist@^0.1.3:
version "0.1.3"
resolved "https://registry.npmmirror.com/fast-plist/-/fast-plist-0.1.3.tgz#328cd9335e93a2479ac90814a1302437574ea925"
integrity sha512-d9cEfo/WcOezgPLAC/8t8wGb6YOD6JTCPMw2QcG2nAdFmyY+9rTUizCTaGjIZAloWENTEUMAPpkUAIJJJ0i96A==
fastq@^1.6.0:
version "1.13.0"
@@ -1109,11 +1135,6 @@ fsevents@~2.3.2:
resolved "https://registry.npmmirror.com/fsevents/download/fsevents-2.3.2.tgz"
integrity sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.nlark.com/function-bind/download/function-bind-1.1.1.tgz"
integrity sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/functional-red-black-tree/download/functional-red-black-tree-1.0.1.tgz?cache=0&sync_timestamp=1577806294691&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffunctional-red-black-tree%2Fdownload%2Ffunctional-red-black-tree-1.0.1.tgz"
@@ -1181,13 +1202,6 @@ has-flag@^4.0.0:
resolved "https://registry.nlark.com/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1626715907927&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz"
integrity sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=
has@^1.0.3:
version "1.0.3"
resolved "https://registry.nlark.com/has/download/has-1.0.3.tgz"
integrity sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=
dependencies:
function-bind "^1.1.1"
ignore@^5.1.4, ignore@^5.1.8:
version "5.1.9"
resolved "https://registry.npmmirror.com/ignore/download/ignore-5.1.9.tgz?cache=0&sync_timestamp=1635926740448&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fignore%2Fdownload%2Fignore-5.1.9.tgz"
@@ -1236,13 +1250,6 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz"
@@ -1351,6 +1358,13 @@ magic-string@^0.25.7:
dependencies:
sourcemap-codec "^1.4.8"
magic-string@^0.30.0:
version "0.30.0"
resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529"
integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.13"
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.npmmirror.com/memoize-one/download/memoize-one-6.0.0.tgz?cache=0&sync_timestamp=1634697208428&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmemoize-one%2Fdownload%2Fmemoize-one-6.0.0.tgz"
@@ -1400,10 +1414,10 @@ mitt@^3.0.0:
resolved "https://registry.npmmirror.com/mitt/download/mitt-3.0.0.tgz"
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
monaco-editor@^0.37.1:
version "0.37.1"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.37.1.tgz#d6f5ffb593e019e74e19bf8a2bdef5a691876f4e"
integrity sha512-jLXEEYSbqMkT/FuJLBZAVWGuhIb4JNwHE9kPTorAVmsdZ4UzHAfgWxLsVtD7pLRFaOwYPhNG9nUCpmFL1t/dIg==
monaco-editor@^0.39.0:
version "0.39.0"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.39.0.tgz#3cf8e3718d6aac347d374516a6837d1c13d967d2"
integrity sha512-zhbZ2Nx93tLR8aJmL2zI1mhJpsl87HMebNBM6R8z4pLfs8pj604pIVIVwyF1TivcfNtIPpMXL+nb3DsBmE/x6Q==
monaco-sql-languages@^0.11.0:
version "0.11.0"
@@ -1412,12 +1426,12 @@ monaco-sql-languages@^0.11.0:
dependencies:
dt-sql-parser "^4.0.0-beta.3.2"
monaco-themes@^0.4.2:
version "0.4.2"
resolved "https://registry.npmmirror.com/monaco-themes/-/monaco-themes-0.4.2.tgz"
integrity sha512-T3kp6SC5MPJvwYGXZENCd0UOIKVgUVV5SjsiXLBhgEZBnScY+6gEbwNRK1oYmfwbf+dGVqF1bSLN5YcrFu3HmA==
monaco-themes@^0.4.4:
version "0.4.4"
resolved "https://registry.npmmirror.com/monaco-themes/-/monaco-themes-0.4.4.tgz#28ab13e538c4867a9bc89dc67f15dfaf3fc69de1"
integrity sha512-Hbb9pvRrpSi0rZezcB/IOdQnpx10o55Lx4zFdRAAVpFMa1HP7FgaqEZdKffb4ovd90fETCixeFO9JPYFMAq+TQ==
dependencies:
fast-plist "^0.1.2"
fast-plist "^0.1.3"
moo@^0.5.0:
version "0.5.2"
@@ -1434,10 +1448,10 @@ nanoid@^3.1.30:
resolved "https://registry.npmmirror.com/nanoid/download/nanoid-3.1.30.tgz"
integrity sha1-Y/k8xUjSoRPcXfvGO/oJ4rm2Q2I=
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
natural-compare@^1.4.0:
version "1.4.0"
@@ -1529,11 +1543,6 @@ path-key@^3.1.0:
resolved "https://registry.nlark.com/path-key/download/path-key-3.1.1.tgz"
integrity sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U=
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.nlark.com/path-type/download/path-type-4.0.0.tgz"
@@ -1549,13 +1558,13 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
resolved "https://registry.nlark.com/picomatch/download/picomatch-2.3.0.tgz?cache=0&sync_timestamp=1621648246651&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpicomatch%2Fdownload%2Fpicomatch-2.3.0.tgz"
integrity sha1-8fBh3o9qS/AiiS4tEoI0+5gwKXI=
pinia@^2.0.33:
version "2.0.33"
resolved "https://registry.npmmirror.com/pinia/-/pinia-2.0.33.tgz#b70065be697874d5824e9792f59bd5d87ddb5e7d"
integrity sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg==
pinia@^2.1.3:
version "2.1.3"
resolved "https://registry.npmmirror.com/pinia/-/pinia-2.1.3.tgz#50c70c7b4c94c109fade0ed4122231cbba72f8c5"
integrity sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==
dependencies:
"@vue/devtools-api" "^6.5.0"
vue-demi "*"
vue-demi ">=0.14.5"
postcss@^8.1.10:
version "8.4.5"
@@ -1566,12 +1575,12 @@ postcss@^8.1.10:
picocolors "^1.0.0"
source-map-js "^1.0.1"
postcss@^8.4.21:
version "8.4.21"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
postcss@^8.4.23:
version "8.4.23"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
dependencies:
nanoid "^3.3.4"
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
@@ -1595,6 +1604,11 @@ punycode@^2.1.0:
resolved "https://registry.nlark.com/punycode/download/punycode-2.1.1.tgz"
integrity sha1-tYsBCsQMIsVldhbI0sLALHv0eew=
qrcode.vue@^3.4.0:
version "3.4.0"
resolved "https://registry.npmmirror.com/qrcode.vue/-/qrcode.vue-3.4.0.tgz#4513ff1a4734cb7184086c2fd439f0d462c6d281"
integrity sha512-4XeImbv10Fin16Fl2DArCMhGyAdvIg2jb7vDT+hZiIAMg/6H6mz9nUZr/dR8jBcun5VzNzkiwKhiqOGbloinwA==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.nlark.com/queue-microtask/download/queue-microtask-1.2.3.tgz"
@@ -1620,10 +1634,10 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regexpp@^3.1.0, regexpp@^3.2.0:
version "3.2.0"
@@ -1635,15 +1649,6 @@ resolve-from@^4.0.0:
resolved "https://registry.nlark.com/resolve-from/download/resolve-from-4.0.0.tgz"
integrity sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=
resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -1661,10 +1666,10 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rollup@^3.18.0:
version "3.19.1"
resolved "https://registry.npmmirror.com/rollup/-/rollup-3.19.1.tgz#2b3a31ac1ff9f3afab2e523fa687fef5b0ee20fc"
integrity sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==
rollup@^3.21.0:
version "3.21.3"
resolved "https://registry.npmmirror.com/rollup/-/rollup-3.21.3.tgz#b3f1920a9d35a9de70f120a1d085753e41997941"
integrity sha512-VnPfEG51nIv2xPLnZaekkuN06q9ZbnyDcLkaBdJa/W7UddyhOfMP2yOPziYQfeY7k++fZM8FdQIummFN5y14kA==
optionalDependencies:
fsevents "~2.3.2"
@@ -1683,10 +1688,10 @@ sass-loader@^13.2.0:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.58.0:
version "1.58.0"
resolved "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc"
integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==
sass@^1.62.0:
version "1.62.0"
resolved "https://registry.npmmirror.com/sass/-/sass-1.62.0.tgz#3686b2195b93295d20765135e562366b33ece37d"
integrity sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@@ -1709,6 +1714,13 @@ semver@^7.3.5:
dependencies:
lru-cache "^6.0.0"
semver@^7.3.6:
version "7.5.0"
resolved "https://registry.npmmirror.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0"
integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
dependencies:
lru-cache "^6.0.0"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.nlark.com/shebang-command/download/shebang-command-2.0.0.tgz"
@@ -1785,11 +1797,6 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.npm.taobao.org/text-table/download/text-table-0.2.0.tgz"
@@ -1848,15 +1855,14 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
vite@^4.2.0:
version "4.2.0"
resolved "https://registry.npmmirror.com/vite/-/vite-4.2.0.tgz#d4e6eafbc034f3faf0ab376bd5b76ac15775eb99"
integrity sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==
vite@^4.3.9:
version "4.3.9"
resolved "https://registry.npmmirror.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d"
integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==
dependencies:
esbuild "^0.17.5"
postcss "^8.4.21"
resolve "^1.22.1"
rollup "^3.18.0"
postcss "^8.4.23"
rollup "^3.21.0"
optionalDependencies:
fsevents "~2.3.2"
@@ -1872,6 +1878,11 @@ vue-demi@*:
resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz"
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
vue-demi@>=0.14.5:
version "0.14.5"
resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz#676d0463d1a1266d5ab5cba932e043d8f5f2fbd9"
integrity sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==
vue-eslint-parser@^8.0.1:
version "8.0.1"
resolved "https://registry.npmmirror.com/vue-eslint-parser/download/vue-eslint-parser-8.0.1.tgz"
@@ -1885,23 +1896,36 @@ vue-eslint-parser@^8.0.1:
lodash "^4.17.21"
semver "^7.3.5"
vue-router@^4.1.6:
version "4.1.6"
resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz"
integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==
vue-eslint-parser@^9.1.1:
version "9.1.1"
resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.1.1.tgz#3f4859be7e9bb7edaa1dc7edb05abffee72bf3dd"
integrity sha512-C2aI/r85Q6tYcz4dpgvrs4wH/MqVrRAVIdpYedrxnATDHHkb+TroeRcDpKWGZCx/OcECMWfz7tVwQ8e+Opy6rA==
dependencies:
"@vue/devtools-api" "^6.4.5"
debug "^4.3.4"
eslint-scope "^7.1.1"
eslint-visitor-keys "^3.3.0"
espree "^9.3.1"
esquery "^1.4.0"
lodash "^4.17.21"
semver "^7.3.6"
vue@^3.2.47:
version "3.2.47"
resolved "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
vue-router@^4.2.2:
version "4.2.2"
resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.2.tgz#b0097b66d89ca81c0986be03da244c7b32a4fd81"
integrity sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==
dependencies:
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-sfc" "3.2.47"
"@vue/runtime-dom" "3.2.47"
"@vue/server-renderer" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/devtools-api" "^6.5.0"
vue@^3.3.4:
version "3.3.4"
resolved "https://registry.npmmirror.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"
integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==
dependencies:
"@vue/compiler-dom" "3.3.4"
"@vue/compiler-sfc" "3.3.4"
"@vue/runtime-dom" "3.3.4"
"@vue/server-renderer" "3.3.4"
"@vue/shared" "3.3.4"
which@^2.0.1:
version "2.0.2"
@@ -1925,10 +1949,10 @@ xterm-addon-fit@^0.7.0:
resolved "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz#b8ade6d96e63b47443862088f6670b49fb752c6a"
integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==
xterm@^5.1.0:
version "5.1.0"
resolved "https://registry.npmmirror.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc"
integrity sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==
xterm@^5.2.1:
version "5.2.1"
resolved "https://registry.npmmirror.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8"
integrity sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA==
yallist@^4.0.0:
version "4.0.0"

View File

@@ -28,7 +28,7 @@ mysql:
# redis:
# host: localhost
# port: 6379
# passsord:
# password: 111049
# db: 0
log:
# 日志等级, trace, debug, info, warn, error, fatal

View File

@@ -22,7 +22,7 @@ DROP TABLE IF EXISTS `t_db`;
CREATE TABLE `t_db` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`host` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`host` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`port` int(8) NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
@@ -30,7 +30,6 @@ CREATE TABLE `t_db` (
`database` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
`params` varchar(125) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',
`network` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注,描述等',
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
@@ -105,6 +104,23 @@ CREATE TABLE `t_db_sql_exec` (
BEGIN;
COMMIT;
DROP TABLE IF EXISTS `t_auth_cert`;
CREATE TABLE `t_auth_cert` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`auth_method` tinyint NOT NULL COMMENT '1.密码 2.秘钥',
`password` varchar(4200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码or私钥',
`passphrase` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '私钥口令',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`creator_id` bigint NOT NULL,
`update_time` datetime NOT NULL,
`modifier` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`modifier_id` bigint NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='授权凭证';
-- ----------------------------
-- Table structure for t_machine
-- ----------------------------
@@ -116,8 +132,8 @@ CREATE TABLE `t_machine` (
`port` int(12) NOT NULL,
`username` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`auth_method` tinyint(2) DEFAULT NULL COMMENT '1.密码登录2.publickey登录',
`password` varchar(3200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`auth_cert_id` bigint(20) DEFAULT NULL COMMENT '授权凭证id',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`enable_recorder` tinyint(2) DEFAULT NULL COMMENT '是否启用终端回放记录',
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
@@ -226,7 +242,6 @@ CREATE TABLE `t_mongo` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '名称',
`uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '连接uri',
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
`tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
@@ -256,7 +271,6 @@ CREATE TABLE `t_redis` (
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`db` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '库号: 多个库用,分割',
`mode` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL,
`tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
@@ -352,6 +366,8 @@ CREATE TABLE `t_sys_config` (
BEGIN;
INSERT INTO `t_sys_config` VALUES (1, '是否启用登录验证码', 'UseLoginCaptcha', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 22:27:17', 1, 'admin', '2022-08-26 10:26:56', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (2, '是否启用水印', 'UseWartermark', NULL, '1', '1: 启用、0: 不启用', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (3, '数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
INSERT INTO `t_sys_config` VALUES (4, '数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
COMMIT;
-- ----------------------------
@@ -424,78 +440,85 @@ CREATE TABLE `t_sys_resource` (
-- Records of t_sys_resource
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_resource` VALUES (1, 0, 1, 1, '首页', '/home', 1, '{\"component\":\"Home\",\"icon\":\"HomeFilled\",\"isAffix\":true,\"isKeepAlive\":true,\"routeName\":\"Home\"}', 1, 'admin', 1, 'admin', '2021-05-25 16:44:41', '2021-05-27 09:12:56');
INSERT INTO `t_sys_resource` VALUES (2, 0, 1, 1, '机器管理', '/machine', 4, '{\"icon\":\"Monitor\",\"isKeepAlive\":true,\"redirect\":\"machine/list\",\"routeName\":\"Machine\"}', 1, 'admin', 1, 'admin', '2021-05-25 16:48:16', '2022-10-06 14:58:49');
INSERT INTO `t_sys_resource` VALUES (3, 2, 1, 1, '机器列表', 'machines', 2, '{\"component\":\"MachineList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"MachineList\"}', 2, 'admin', 1, 'admin', '2021-05-25 16:50:04', '2021-06-30 16:20:08');
INSERT INTO `t_sys_resource` VALUES (4, 0, 1, 1, '系统管理', '/sys', 8, '{\"icon\":\"Setting\",\"isKeepAlive\":true,\"redirect\":\"/sys/resources\",\"routeName\":\"sys\"}', 1, 'admin', 1, 'admin', '2021-05-26 15:20:20', '2022-10-06 14:59:53');
INSERT INTO `t_sys_resource` VALUES (5, 4, 1, 1, '资源管理', 'resources', 3, '{\"component\":\"ResourceList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"ResourceList\"}', 1, 'admin', 1, 'admin', '2021-05-26 15:23:07', '2021-06-08 11:27:55');
INSERT INTO `t_sys_resource` VALUES (11, 4, 1, 1, '角色管理', 'roles', 2, '{\"component\":\"RoleList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RoleList\"}', 1, 'admin', 1, 'admin', '2021-05-27 11:15:35', '2021-06-03 09:59:41');
INSERT INTO `t_sys_resource` VALUES (12, 3, 2, 1, '机器终端按钮', 'machine:terminal', 4, '', 1, 'admin', 1, 'admin', '2021-05-28 14:06:02', '2021-05-31 17:47:59');
INSERT INTO `t_sys_resource` VALUES (14, 4, 1, 1, '账号管理', 'accounts', 1, '{\"component\":\"AccountList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"AccountList\"}', 1, 'admin', 1, 'admin', '2021-05-28 14:56:25', '2021-06-03 09:39:22');
INSERT INTO `t_sys_resource` VALUES (15, 3, 2, 1, '文件管理按钮', 'machine:file', 5, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:44:37', '2021-05-31 17:48:07');
INSERT INTO `t_sys_resource` VALUES (16, 3, 2, 1, '机器添加按钮', 'machine:add', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:11', '2021-05-31 19:34:15');
INSERT INTO `t_sys_resource` VALUES (17, 3, 2, 1, '机器编辑按钮', 'machine:update', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:23', '2021-05-31 19:34:18');
INSERT INTO `t_sys_resource` VALUES (18, 3, 2, 1, '机器删除按钮', 'machine:del', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:46:36', '2021-05-31 19:34:17');
INSERT INTO `t_sys_resource` VALUES (19, 14, 2, 1, '角色分配按钮', 'account:saveRoles', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:50:51', '2021-05-31 19:19:30');
INSERT INTO `t_sys_resource` VALUES (20, 11, 2, 1, '分配菜单&权限按钮', 'role:saveResources', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 17:51:41', '2021-05-31 19:33:37');
INSERT INTO `t_sys_resource` VALUES (21, 14, 2, 1, '账号删除按钮', 'account:del', 2, 'null', 1, 'admin', 1, 'admin', '2021-05-31 18:02:01', '2021-06-10 17:12:17');
INSERT INTO `t_sys_resource` VALUES (22, 11, 2, 1, '角色删除按钮', 'role:del', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:29', '2021-05-31 19:33:38');
INSERT INTO `t_sys_resource` VALUES (23, 11, 2, 1, '角色新增按钮', 'role:add', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:44', '2021-05-31 19:33:39');
INSERT INTO `t_sys_resource` VALUES (24, 11, 2, 1, '角色编辑按钮', 'role:update', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:02:57', '2021-05-31 19:33:40');
INSERT INTO `t_sys_resource` VALUES (25, 5, 2, 1, '资源新增按钮', 'resource:add', 1, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:03:33', '2021-05-31 19:31:47');
INSERT INTO `t_sys_resource` VALUES (26, 5, 2, 1, '资源删除按钮', 'resource:delete', 2, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:03:47', '2021-05-31 19:29:40');
INSERT INTO `t_sys_resource` VALUES (27, 5, 2, 1, '资源编辑按钮', 'resource:update', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:04:03', '2021-05-31 19:29:40');
INSERT INTO `t_sys_resource` VALUES (28, 5, 2, 1, '资源禁用启用按钮', 'resource:changeStatus', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 18:04:33', '2021-05-31 18:04:33');
INSERT INTO `t_sys_resource` VALUES (29, 14, 2, 1, '账号添加按钮', 'account:add', 3, NULL, 1, 'admin', 1, 'admin', '2021-05-31 19:23:42', '2021-05-31 19:23:42');
INSERT INTO `t_sys_resource` VALUES (30, 14, 2, 1, '账号编辑修改按钮', 'account:update', 4, NULL, 1, 'admin', 1, 'admin', '2021-05-31 19:23:58', '2021-05-31 19:23:58');
INSERT INTO `t_sys_resource` VALUES (31, 14, 2, 1, '账号管理基本权限', 'account', 0, 'null', 1, 'admin', 1, 'admin', '2021-05-31 21:25:06', '2021-06-22 11:20:34');
INSERT INTO `t_sys_resource` VALUES (32, 5, 2, 1, '资源管理基本权限', 'resource', 0, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:25:25', '2021-05-31 21:25:25');
INSERT INTO `t_sys_resource` VALUES (33, 11, 2, 1, '角色管理基本权限', 'role', 0, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:25:40', '2021-05-31 21:25:40');
INSERT INTO `t_sys_resource` VALUES (34, 14, 2, 1, '账号启用禁用按钮', 'account:changeStatus', 5, NULL, 1, 'admin', 1, 'admin', '2021-05-31 21:29:48', '2021-05-31 21:29:48');
INSERT INTO `t_sys_resource` VALUES (36, 0, 1, 1, 'DBMS', '/dbms', 5, '{\"icon\":\"Grid\",\"isKeepAlive\":true,\"routeName\":\"DBMS\"}', 1, 'admin', 1, 'admin', '2021-06-01 14:01:33', '2022-10-06 15:00:40');
INSERT INTO `t_sys_resource` VALUES (37, 3, 2, 1, '添加文件配置', 'machine:addFile', 6, 'null', 1, 'admin', 1, 'admin', '2021-06-01 19:54:23', '2021-06-01 19:54:23');
INSERT INTO `t_sys_resource` VALUES (38, 36, 1, 1, '数据操作', 'sql-exec', 1, '{\"component\":\"SqlExec\",\"icon\":\"Search\",\"isKeepAlive\":true,\"routeName\":\"SqlExec\"}', 1, 'admin', 1, 'admin', '2021-06-03 09:09:29', '2021-11-08 09:59:26');
INSERT INTO `t_sys_resource` VALUES (39, 0, 1, 1, '个人中心', '/personal', 2, '{\"component\":\"Personal\",\"icon\":\"UserFilled\",\"isHide\":true,\"isKeepAlive\":true,\"routeName\":\"Personal\"}', 1, 'admin', 1, 'admin', '2021-06-03 14:25:35', '2021-09-10 09:18:46');
INSERT INTO `t_sys_resource` VALUES (40, 3, 2, 1, '文件管理-新增按钮', 'machine:file:add', 7, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:06:26', '2021-06-08 11:12:28');
INSERT INTO `t_sys_resource` VALUES (41, 3, 2, 1, '文件管理-删除按钮', 'machine:file:del', 8, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:06:49', '2021-06-08 11:06:49');
INSERT INTO `t_sys_resource` VALUES (42, 3, 2, 1, '文件管理-写入or下载文件权限', 'machine:file:write', 9, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:07:27', '2021-06-08 11:07:27');
INSERT INTO `t_sys_resource` VALUES (43, 3, 2, 1, '文件管理-文件上传按钮', 'machine:file:upload', 10, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:07:42', '2021-06-08 11:07:42');
INSERT INTO `t_sys_resource` VALUES (44, 3, 2, 1, '文件管理-删除文件按钮', 'machine:file:rm', 11, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:08:12', '2021-06-08 11:08:12');
INSERT INTO `t_sys_resource` VALUES (45, 3, 2, 1, '脚本管理-保存脚本按钮', 'machine:script:save', 12, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:01', '2021-06-08 11:09:01');
INSERT INTO `t_sys_resource` VALUES (46, 3, 2, 1, '脚本管理-删除按钮', 'machine:script:del', 13, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:27', '2021-06-08 11:09:27');
INSERT INTO `t_sys_resource` VALUES (47, 3, 2, 1, '脚本管理-执行按钮', 'machine:script:run', 14, 'null', 1, 'admin', 1, 'admin', '2021-06-08 11:09:50', '2021-06-08 11:09:50');
INSERT INTO `t_sys_resource` VALUES (49, 36, 1, 1, '数据库管理', 'dbs', 2, '{\"component\":\"DbList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"DbList\"}', 1, 'admin', 1, 'admin', '2021-07-07 15:13:55', '2021-07-07 15:13:55');
INSERT INTO `t_sys_resource` VALUES (54, 49, 2, 1, '数据库保存', 'db:save', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-08 17:30:36', '2021-07-08 17:31:05');
INSERT INTO `t_sys_resource` VALUES (55, 49, 2, 1, '数据库删除', 'db:del', 2, 'null', 1, 'admin', 1, 'admin', '2021-07-08 17:30:48', '2021-07-08 17:30:48');
INSERT INTO `t_sys_resource` VALUES (57, 3, 2, 1, '基本权限', 'machine', 0, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:48:02', '2021-07-09 10:48:02');
INSERT INTO `t_sys_resource` VALUES (58, 49, 2, 1, '基本权限', 'db', 0, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:48:22', '2021-07-09 10:48:22');
INSERT INTO `t_sys_resource` VALUES (59, 38, 2, 1, '基本权限', 'db:exec', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-09 10:50:13', '2021-07-09 10:50:13');
INSERT INTO `t_sys_resource` VALUES (60, 0, 1, 1, 'Redis', '/redis', 6, '{\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RDS\"}', 1, 'admin', 1, 'admin', '2021-07-19 20:15:41', '2022-10-06 15:01:29');
INSERT INTO `t_sys_resource` VALUES (61, 60, 1, 1, '数据操作', 'data-operation', 1, '{\"component\":\"DataOperation\",\"icon\":\"Search\",\"isKeepAlive\":true,\"routeName\":\"DataOperation\"}', 1, 'admin', 1, 'admin', '2021-07-19 20:17:29', '2021-07-20 10:45:28');
INSERT INTO `t_sys_resource` VALUES (62, 61, 2, 1, '基本权限', 'redis:data', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-19 20:18:54', '2021-07-19 20:18:54');
INSERT INTO `t_sys_resource` VALUES (63, 60, 1, 1, 'redis管理', 'manage', 2, '{\"component\":\"RedisList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"RedisList\"}', 1, 'admin', 1, 'admin', '2021-07-20 10:48:04', '2021-07-20 10:48:04');
INSERT INTO `t_sys_resource` VALUES (64, 63, 2, 1, '基本权限', 'redis:manage', 1, 'null', 1, 'admin', 1, 'admin', '2021-07-20 10:48:26', '2021-07-20 10:48:26');
INSERT INTO `t_sys_resource` VALUES (71, 61, 2, 1, '数据保存', 'redis:data:save', 6, 'null', 1, 'admin', 1, 'admin', '2021-08-17 11:20:37', '2021-08-17 11:20:37');
INSERT INTO `t_sys_resource` VALUES (72, 3, 2, 1, '终止进程', 'machine:killprocess', 6, 'null', 1, 'admin', 1, 'admin', '2021-08-17 11:20:37', '2021-08-17 11:20:37');
INSERT INTO `t_sys_resource` VALUES (79, 0, 1, 1, 'Mongo', '/mongo', 7, '{\"icon\":\"Document\",\"isKeepAlive\":true,\"routeName\":\"Mongo\"}', 1, 'admin', 1, 'admin', '2022-05-13 14:00:41', '2022-10-06 15:01:34');
INSERT INTO `t_sys_resource` VALUES (80, 79, 1, 1, '数据操作', 'mongo-data-operation', 1, '{\"component\":\"MongoDataOp\",\"icon\":\"Document\",\"isKeepAlive\":true,\"routeName\":\"MongoDataOp\"}', 1, 'admin', 1, 'admin', '2022-05-13 14:03:58', '2022-05-14 20:16:07');
INSERT INTO `t_sys_resource` VALUES (81, 80, 2, 1, '基本权限', 'mongo:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-05-13 14:04:16', '2022-05-13 14:04:16');
INSERT INTO `t_sys_resource` VALUES (82, 79, 1, 1, 'Mongo管理', 'mongo-manage', 2, '{\"component\":\"MongoList\",\"icon\":\"Menu\",\"isKeepAlive\":true,\"routeName\":\"MongoList\"}', 1, 'admin', 1, 'admin', '2022-05-16 18:13:06', '2022-05-16 18:13:06');
INSERT INTO `t_sys_resource` VALUES (83, 82, 2, 1, '基本权限', 'mongo:manage:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-05-16 18:13:25', '2022-05-16 18:13:25');
INSERT INTO `t_sys_resource` VALUES (84, 4, 1, 1, '操作日志', 'syslogs', 4, '{\"component\":\"SyslogList\",\"icon\":\"Tickets\",\"routeName\":\"SyslogList\"}', 1, 'admin', 1, 'admin', '2022-07-13 19:57:07', '2022-07-13 22:58:19');
INSERT INTO `t_sys_resource` VALUES (85, 84, 2, 1, '操作日志基本权限', 'syslog', 1, 'null', 1, 'admin', 1, 'admin', '2022-07-13 19:57:55', '2022-07-13 19:57:55');
INSERT INTO `t_sys_resource` VALUES (87, 4, 1, 1, '系统配置', 'configs', 5, '{\"component\":\"ConfigList\",\"icon\":\"Setting\",\"isKeepAlive\":true,\"routeName\":\"ConfigList\"}', 1, 'admin', 1, 'admin', '2022-08-25 22:18:55', '2022-08-25 22:19:18');
INSERT INTO `t_sys_resource` VALUES (88, 87, 2, 1, '基本权限', 'config:base', 1, 'null', 1, 'admin', 1, 'admin', '2022-08-25 22:19:35', '2022-08-25 22:19:35');
INSERT INTO `t_sys_resource` VALUES (93, 0, 1, 1, '标签管理', '/tag', 3, '{\"icon\":\"CollectionTag\",\"isKeepAlive\":true,\"routeName\":\"Tag\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:18:40', '2022-10-24 15:24:29');
INSERT INTO `t_sys_resource` VALUES (94, 93, 1, 1, '标签', 'tag-trees', 1, '{\"component\":\"TagTreeList\",\"icon\":\"CollectionTag\",\"isKeepAlive\":true,\"routeName\":\"TagTreeList\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:19:40', '2022-10-24 15:28:07');
INSERT INTO `t_sys_resource` VALUES (95, 93, 1, 1, '团队管理', 'teams', 2, '{\"component\":\"TeamList\",\"icon\":\"UserFilled\",\"isKeepAlive\":true,\"routeName\":\"TeamList\"}', 1, 'admin', 1, 'admin', '2022-10-24 15:20:09', '2022-10-24 15:24:01');
INSERT INTO `t_sys_resource` VALUES (96, 94, 2, 1, '保存标签', 'tag:save', 1, 'null', 1, 'admin', 1, 'admin', '2022-10-24 15:20:40', '2022-10-26 13:58:36');
INSERT INTO `t_sys_resource` VALUES (97, 95, 2, 1, '保存团队', 'team:save', 1, 'null', 1, 'admin', 1, 'admin', '2022-10-24 15:20:57', '2022-10-26 13:58:56');
INSERT INTO `t_sys_resource` VALUES (98, 94, 2, 1, '删除标签', 'tag:del', 2, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:58:47', '2022-10-26 13:58:47');
INSERT INTO `t_sys_resource` VALUES (99, 95, 2, 1, '删除团队', 'team:del', 2, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:06', '2022-10-26 13:59:06');
INSERT INTO `t_sys_resource` VALUES (100, 95, 2, 1, '新增团队成员', 'team:member:save', 3, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:27', '2022-10-26 13:59:27');
INSERT INTO `t_sys_resource` VALUES (101, 95, 2, 1, '移除团队成员', 'team:member:del', 4, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:43', '2022-10-26 13:59:43');
INSERT INTO `t_sys_resource` VALUES (102, 95, 2, 1, '保存团队标签', 'team:tag:save', 5, 'null', 1, 'admin', 1, 'admin', '2022-10-26 13:59:57', '2022-10-26 13:59:57');
INSERT INTO `t_sys_resource` (id,pid,`type`,status,name,code,weight,meta,creator_id,creator,modifier_id,modifier,create_time,update_time) VALUES
(1,0,1,1,'首页','/home',1,'{"component":"home/Home","icon":"HomeFilled","isAffix":true,"isKeepAlive":true,"routeName":"Home"}',1,'admin',1,'admin','2021-05-25 16:44:41','2023-03-14 14:27:07'),
(2,0,1,1,'机器管理','/machine',4,'{"icon":"Monitor","isKeepAlive":true,"redirect":"machine/list","routeName":"Machine"}',1,'admin',1,'admin','2021-05-25 16:48:16','2022-10-06 14:58:49'),
(3,2,1,1,'机器列表','machines',2,'{"component":"ops/machine/MachineList","icon":"Monitor","isKeepAlive":true,"routeName":"MachineList"}',2,'admin',1,'admin','2021-05-25 16:50:04','2023-03-15 17:14:44'),
(4,0,1,1,'系统管理','/sys',8,'{"icon":"Setting","isKeepAlive":true,"redirect":"/sys/resources","routeName":"sys"}',1,'admin',1,'admin','2021-05-26 15:20:20','2022-10-06 14:59:53'),
(5,4,1,1,'资源管理','resources',3,'{"component":"system/resource/ResourceList","icon":"Menu","isKeepAlive":true,"routeName":"ResourceList"}',1,'admin',1,'admin','2021-05-26 15:23:07','2023-03-14 15:44:34'),
(11,4,1,1,'角色管理','roles',2,'{"component":"system/role/RoleList","icon":"Menu","isKeepAlive":true,"routeName":"RoleList"}',1,'admin',1,'admin','2021-05-27 11:15:35','2023-03-14 15:44:22'),
(12,3,2,1,'机器终端按钮','machine:terminal',4,'',1,'admin',1,'admin','2021-05-28 14:06:02','2021-05-31 17:47:59'),
(14,4,1,1,'账号管理','accounts',1,'{"component":"system/account/AccountList","icon":"Menu","isKeepAlive":true,"routeName":"AccountList"}',1,'admin',1,'admin','2021-05-28 14:56:25','2023-03-14 15:44:10'),
(15,3,2,1,'文件管理按钮','machine:file',5,NULL,1,'admin',1,'admin','2021-05-31 17:44:37','2021-05-31 17:48:07'),
(16,3,2,1,'机器添加按钮','machine:add',1,NULL,1,'admin',1,'admin','2021-05-31 17:46:11','2021-05-31 19:34:15'),
(17,3,2,1,'机器编辑按钮','machine:update',2,NULL,1,'admin',1,'admin','2021-05-31 17:46:23','2021-05-31 19:34:18'),
(18,3,2,1,'机器删除按钮','machine:del',3,NULL,1,'admin',1,'admin','2021-05-31 17:46:36','2021-05-31 19:34:17'),
(19,14,2,1,'角色分配按钮','account:saveRoles',1,NULL,1,'admin',1,'admin','2021-05-31 17:50:51','2021-05-31 19:19:30'),
(20,11,2,1,'分配菜单&权限按钮','role:saveResources',1,NULL,1,'admin',1,'admin','2021-05-31 17:51:41','2021-05-31 19:33:37'),
(21,14,2,1,'账号删除按钮','account:del',2,'null',1,'admin',1,'admin','2021-05-31 18:02:01','2021-06-10 17:12:17'),
(22,11,2,1,'角色删除按钮','role:del',2,NULL,1,'admin',1,'admin','2021-05-31 18:02:29','2021-05-31 19:33:38'),
(23,11,2,1,'角色新增按钮','role:add',3,NULL,1,'admin',1,'admin','2021-05-31 18:02:44','2021-05-31 19:33:39'),
(24,11,2,1,'角色编辑按钮','role:update',4,NULL,1,'admin',1,'admin','2021-05-31 18:02:57','2021-05-31 19:33:40'),
(25,5,2,1,'资源新增按钮','resource:add',1,NULL,1,'admin',1,'admin','2021-05-31 18:03:33','2021-05-31 19:31:47'),
(26,5,2,1,'资源删除按钮','resource:delete',2,NULL,1,'admin',1,'admin','2021-05-31 18:03:47','2021-05-31 19:29:40'),
(27,5,2,1,'资源编辑按钮','resource:update',3,NULL,1,'admin',1,'admin','2021-05-31 18:04:03','2021-05-31 19:29:40'),
(28,5,2,1,'资源禁用启用按钮','resource:changeStatus',4,NULL,1,'admin',1,'admin','2021-05-31 18:04:33','2021-05-31 18:04:33'),
(29,14,2,1,'账号添加按钮','account:add',3,NULL,1,'admin',1,'admin','2021-05-31 19:23:42','2021-05-31 19:23:42'),
(30,14,2,1,'账号编辑修改按钮','account:update',4,NULL,1,'admin',1,'admin','2021-05-31 19:23:58','2021-05-31 19:23:58'),
(31,14,2,1,'账号管理基本权限','account',0,'null',1,'admin',1,'admin','2021-05-31 21:25:06','2021-06-22 11:20:34'),
(32,5,2,1,'资源管理基本权限','resource',0,NULL,1,'admin',1,'admin','2021-05-31 21:25:25','2021-05-31 21:25:25'),
(33,11,2,1,'角色管理基本权限','role',0,NULL,1,'admin',1,'admin','2021-05-31 21:25:40','2021-05-31 21:25:40'),
(34,14,2,1,'账号启用禁用按钮','account:changeStatus',5,NULL,1,'admin',1,'admin','2021-05-31 21:29:48','2021-05-31 21:29:48'),
(36,0,1,1,'DBMS','/dbms',5,'{"icon":"Coin","isKeepAlive":true,"routeName":"DBMS"}',1,'admin',1,'admin','2021-06-01 14:01:33','2023-03-15 17:31:08'),
(37,3,2,1,'添加文件配置','machine:addFile',6,'null',1,'admin',1,'admin','2021-06-01 19:54:23','2021-06-01 19:54:23'),
(38,36,1,1,'数据操作','sql-exec',1,'{"component":"ops/db/SqlExec","icon":"Coin","isKeepAlive":true,"routeName":"SqlExec"}',1,'admin',1,'admin','2021-06-03 09:09:29','2023-03-15 17:31:21'),
(39,0,1,1,'个人中心','/personal',2,'{"component":"personal/index","icon":"UserFilled","isHide":true,"isKeepAlive":true,"routeName":"Personal"}',1,'admin',1,'admin','2021-06-03 14:25:35','2023-03-14 14:28:36'),
(40,3,2,1,'文件管理-新增按钮','machine:file:add',7,'null',1,'admin',1,'admin','2021-06-08 11:06:26','2021-06-08 11:12:28'),
(41,3,2,1,'文件管理-删除按钮','machine:file:del',8,'null',1,'admin',1,'admin','2021-06-08 11:06:49','2021-06-08 11:06:49'),
(42,3,2,1,'文件管理-写入or下载文件权限','machine:file:write',9,'null',1,'admin',1,'admin','2021-06-08 11:07:27','2021-06-08 11:07:27'),
(43,3,2,1,'文件管理-文件上传按钮','machine:file:upload',10,'null',1,'admin',1,'admin','2021-06-08 11:07:42','2021-06-08 11:07:42'),
(44,3,2,1,'文件管理-删除文件按钮','machine:file:rm',11,'null',1,'admin',1,'admin','2021-06-08 11:08:12','2021-06-08 11:08:12'),
(45,3,2,1,'脚本管理-保存脚本按钮','machine:script:save',12,'null',1,'admin',1,'admin','2021-06-08 11:09:01','2021-06-08 11:09:01'),
(46,3,2,1,'脚本管理-删除按钮','machine:script:del',13,'null',1,'admin',1,'admin','2021-06-08 11:09:27','2021-06-08 11:09:27'),
(47,3,2,1,'脚本管理-执行按钮','machine:script:run',14,'null',1,'admin',1,'admin','2021-06-08 11:09:50','2021-06-08 11:09:50'),
(49,36,1,1,'数据库管理','dbs',2,'{"component":"ops/db/DbList","icon":"Coin","isKeepAlive":true,"routeName":"DbList"}',1,'admin',1,'admin','2021-07-07 15:13:55','2023-03-15 17:31:28'),
(54,49,2,1,'数据库保存','db:save',1,'null',1,'admin',1,'admin','2021-07-08 17:30:36','2021-07-08 17:31:05'),
(55,49,2,1,'数据库删除','db:del',2,'null',1,'admin',1,'admin','2021-07-08 17:30:48','2021-07-08 17:30:48'),
(57,3,2,1,'基本权限','machine',0,'null',1,'admin',1,'admin','2021-07-09 10:48:02','2021-07-09 10:48:02'),
(58,49,2,1,'基本权限','db',0,'null',1,'admin',1,'admin','2021-07-09 10:48:22','2021-07-09 10:48:22'),
(59,38,2,1,'基本权限','db:exec',1,'null',1,'admin',1,'admin','2021-07-09 10:50:13','2021-07-09 10:50:13'),
(60,0,1,1,'Redis','/redis',6,'{"icon":"iconfont icon-redis","isKeepAlive":true,"routeName":"RDS"}',1,'admin',1,'admin','2021-07-19 20:15:41','2023-03-15 16:44:59'),
(61,60,1,1,'数据操作','data-operation',1,'{"component":"ops/redis/DataOperation","icon":"iconfont icon-redis","isKeepAlive":true,"routeName":"DataOperation"}',1,'admin',1,'admin','2021-07-19 20:17:29','2023-03-15 16:37:50'),
(62,61,2,1,'基本权限','redis:data',1,'null',1,'admin',1,'admin','2021-07-19 20:18:54','2021-07-19 20:18:54'),
(63,60,1,1,'redis管理','manage',2,'{"component":"ops/redis/RedisList","icon":"iconfont icon-redis","isKeepAlive":true,"routeName":"RedisList"}',1,'admin',1,'admin','2021-07-20 10:48:04','2023-03-15 16:38:00'),
(64,63,2,1,'基本权限','redis:manage',1,'null',1,'admin',1,'admin','2021-07-20 10:48:26','2021-07-20 10:48:26'),
(71,61,2,1,'数据保存','redis:data:save',6,'null',1,'admin',1,'admin','2021-08-17 11:20:37','2021-08-17 11:20:37'),
(72,3,2,1,'终止进程','machine:killprocess',6,'null',1,'admin',1,'admin','2021-08-17 11:20:37','2021-08-17 11:20:37'),
(79,0,1,1,'Mongo','/mongo',7,'{"icon":"iconfont icon-mongo","isKeepAlive":true,"routeName":"Mongo"}',1,'admin',1,'admin','2022-05-13 14:00:41','2023-03-16 14:23:22'),
(80,79,1,1,'数据操作','mongo-data-operation',1,'{"component":"ops/mongo/MongoDataOp","icon":"iconfont icon-mongo","isKeepAlive":true,"routeName":"MongoDataOp"}',1,'admin',1,'admin','2022-05-13 14:03:58','2023-03-15 17:15:02'),
(81,80,2,1,'基本权限','mongo:base',1,'null',1,'admin',1,'admin','2022-05-13 14:04:16','2022-05-13 14:04:16'),
(82,79,1,1,'Mongo管理','mongo-manage',2,'{"component":"ops/mongo/MongoList","icon":"iconfont icon-mongo","isKeepAlive":true,"routeName":"MongoList"}',1,'admin',1,'admin','2022-05-16 18:13:06','2023-03-15 17:26:55'),
(83,82,2,1,'基本权限','mongo:manage:base',1,'null',1,'admin',1,'admin','2022-05-16 18:13:25','2022-05-16 18:13:25'),
(84,4,1,1,'操作日志','syslogs',4,'{"component":"system/syslog/SyslogList","icon":"Tickets","routeName":"SyslogList"}',1,'admin',1,'admin','2022-07-13 19:57:07','2023-03-14 15:44:45'),
(85,84,2,1,'操作日志基本权限','syslog',1,'null',1,'admin',1,'admin','2022-07-13 19:57:55','2022-07-13 19:57:55'),
(87,4,1,1,'系统配置','configs',5,'{"component":"system/config/ConfigList","icon":"Setting","isKeepAlive":true,"routeName":"ConfigList"}',1,'admin',1,'admin','2022-08-25 22:18:55','2023-03-15 11:06:07'),
(88,87,2,1,'基本权限','config:base',1,'null',1,'admin',1,'admin','2022-08-25 22:19:35','2022-08-25 22:19:35'),
(93,0,1,1,'标签管理','/tag',3,'{"icon":"CollectionTag","isKeepAlive":true,"routeName":"Tag"}',1,'admin',1,'admin','2022-10-24 15:18:40','2022-10-24 15:24:29'),
(94,93,1,1,'标签树','tag-trees',1,'{"component":"ops/tag/TagTreeList","icon":"CollectionTag","isKeepAlive":true,"routeName":"TagTreeList"}',1,'admin',1,'admin','2022-10-24 15:19:40','2023-03-14 14:30:51'),
(95,93,1,1,'团队管理','teams',2,'{"component":"ops/tag/TeamList","icon":"UserFilled","isKeepAlive":true,"routeName":"TeamList"}',1,'admin',1,'admin','2022-10-24 15:20:09','2023-03-14 14:31:03'),
(96,94,2,1,'保存标签','tag:save',1,'null',1,'admin',1,'admin','2022-10-24 15:20:40','2022-10-26 13:58:36'),
(97,95,2,1,'保存团队','team:save',1,'null',1,'admin',1,'admin','2022-10-24 15:20:57','2022-10-26 13:58:56'),
(98,94,2,1,'删除标签','tag:del',2,'null',1,'admin',1,'admin','2022-10-26 13:58:47','2022-10-26 13:58:47'),
(99,95,2,1,'删除团队','team:del',2,'null',1,'admin',1,'admin','2022-10-26 13:59:06','2022-10-26 13:59:06'),
(100,95,2,1,'新增团队成员','team:member:save',3,'null',1,'admin',1,'admin','2022-10-26 13:59:27','2022-10-26 13:59:27'),
(101,95,2,1,'移除团队成员','team:member:del',4,'null',1,'admin',1,'admin','2022-10-26 13:59:43','2022-10-26 13:59:43'),
(102,95,2,1,'保存团队标签','team:tag:save',5,'null',1,'admin',1,'admin','2022-10-26 13:59:57','2022-10-26 13:59:57'),
(103,2,1,1,'授权凭证','authcerts',6,'{"component":"ops/machine/authcert/AuthCertList","icon":"Unlock","isKeepAlive":true,"routeName":"AuthCertList"}',1,'admin',1,'admin','2023-02-23 11:36:26','2023-03-14 14:33:28'),
(104,103,2,1,'基本权限','authcert',1,'null',1,'admin',1,'admin','2023-02-23 11:37:24','2023-02-23 11:37:24'),
(105,103,2,1,'保存权限','authcert:save',2,'null',1,'admin',1,'admin','2023-02-23 11:37:54','2023-02-23 11:37:54'),
(106,103,2,1,'删除权限','authcert:del',3,'null',1,'admin',1,'admin','2023-02-23 11:38:09','2023-02-23 11:38:09'),
(108,61,2,1,'数据删除','redis:data:del',3,'null',1,'admin',1,'admin','2023-03-14 17:20:00','2023-03-14 17:20:00'),
(109,3,2,1,'关闭连接','machine:close-cli',6,'null',1,'admin',1,'admin','2023-03-16 16:11:04','2023-03-16 16:11:04');
COMMIT;
-- ----------------------------
@@ -546,160 +569,170 @@ CREATE TABLE `t_sys_role_resource` (
-- Records of t_sys_role_resource
-- ----------------------------
BEGIN;
INSERT INTO `t_sys_role_resource` VALUES (1, 1, 1, 1, 'admin', '2021-05-27 15:07:39');
INSERT INTO `t_sys_role_resource` VALUES (323, 1, 2, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (326, 1, 4, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (327, 1, 5, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (328, 1, 11, 1, 'admin', '2021-05-28 09:04:50');
INSERT INTO `t_sys_role_resource` VALUES (335, 1, 14, 1, 'admin', '2021-05-28 17:42:21');
INSERT INTO `t_sys_role_resource` VALUES (336, 1, 3, 1, 'admin', '2021-05-28 17:42:43');
INSERT INTO `t_sys_role_resource` VALUES (337, 1, 12, 1, 'admin', '2021-05-28 17:42:43');
INSERT INTO `t_sys_role_resource` VALUES (338, 6, 2, 1, 'admin', '2021-05-28 19:19:38');
INSERT INTO `t_sys_role_resource` VALUES (339, 6, 3, 1, 'admin', '2021-05-28 19:19:38');
INSERT INTO `t_sys_role_resource` VALUES (342, 6, 1, 1, 'admin', '2021-05-29 01:31:22');
INSERT INTO `t_sys_role_resource` VALUES (343, 5, 1, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (344, 5, 4, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (345, 5, 14, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (346, 5, 5, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (347, 5, 11, 1, 'admin', '2021-05-31 14:05:23');
INSERT INTO `t_sys_role_resource` VALUES (348, 5, 3, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (349, 5, 12, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (350, 5, 2, 1, 'admin', '2021-05-31 16:33:14');
INSERT INTO `t_sys_role_resource` VALUES (353, 1, 15, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (354, 1, 16, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (355, 1, 17, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (356, 1, 18, 1, 'admin', '2021-05-31 17:48:33');
INSERT INTO `t_sys_role_resource` VALUES (358, 1, 20, 1, 'admin', '2021-05-31 17:52:08');
INSERT INTO `t_sys_role_resource` VALUES (360, 1, 22, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (361, 1, 23, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (362, 1, 24, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (363, 1, 25, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (364, 1, 26, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (365, 1, 27, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (366, 1, 28, 1, 'admin', '2021-05-31 18:05:04');
INSERT INTO `t_sys_role_resource` VALUES (369, 1, 31, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (370, 1, 32, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (371, 1, 33, 1, 'admin', '2021-05-31 21:25:56');
INSERT INTO `t_sys_role_resource` VALUES (374, 1, 36, 1, 'admin', '2021-06-01 14:01:57');
INSERT INTO `t_sys_role_resource` VALUES (375, 1, 19, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (376, 1, 21, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (377, 1, 29, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (378, 1, 30, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (379, 1, 34, 1, 'admin', '2021-06-01 17:34:03');
INSERT INTO `t_sys_role_resource` VALUES (380, 1, 37, 1, 'admin', '2021-06-03 09:09:42');
INSERT INTO `t_sys_role_resource` VALUES (381, 1, 38, 1, 'admin', '2021-06-03 09:09:42');
INSERT INTO `t_sys_role_resource` VALUES (383, 1, 40, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (384, 1, 41, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (385, 1, 42, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (386, 1, 43, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (387, 1, 44, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (388, 1, 45, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (389, 1, 46, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (390, 1, 47, 1, 'admin', '2021-06-08 11:21:52');
INSERT INTO `t_sys_role_resource` VALUES (391, 6, 39, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (392, 6, 15, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (395, 6, 31, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (396, 6, 33, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (397, 6, 32, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (398, 6, 4, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (399, 6, 14, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (400, 6, 11, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (401, 6, 5, 1, 'admin', '2021-06-08 15:10:58');
INSERT INTO `t_sys_role_resource` VALUES (403, 7, 1, 1, 'admin', '2021-07-06 15:07:09');
INSERT INTO `t_sys_role_resource` VALUES (405, 1, 49, 1, 'admin', '2021-07-07 15:14:17');
INSERT INTO `t_sys_role_resource` VALUES (410, 1, 54, 1, 'admin', '2021-07-08 17:32:19');
INSERT INTO `t_sys_role_resource` VALUES (411, 1, 55, 1, 'admin', '2021-07-08 17:32:19');
INSERT INTO `t_sys_role_resource` VALUES (413, 1, 57, 1, 'admin', '2021-07-09 10:48:50');
INSERT INTO `t_sys_role_resource` VALUES (414, 1, 58, 1, 'admin', '2021-07-09 10:48:50');
INSERT INTO `t_sys_role_resource` VALUES (415, 8, 1, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (416, 8, 39, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (418, 8, 57, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (419, 8, 12, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (420, 8, 15, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (421, 8, 38, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (422, 8, 58, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (423, 8, 2, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (425, 8, 3, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (426, 8, 36, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (427, 8, 49, 1, 'admin', '2021-07-09 10:49:46');
INSERT INTO `t_sys_role_resource` VALUES (428, 1, 59, 1, 'admin', '2021-07-09 10:50:20');
INSERT INTO `t_sys_role_resource` VALUES (429, 8, 59, 1, 'admin', '2021-07-09 10:50:32');
INSERT INTO `t_sys_role_resource` VALUES (431, 6, 57, 1, 'admin', '2021-07-12 16:44:12');
INSERT INTO `t_sys_role_resource` VALUES (433, 1, 60, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (434, 1, 61, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (435, 1, 62, 1, 'admin', '2021-07-19 20:19:29');
INSERT INTO `t_sys_role_resource` VALUES (436, 1, 63, 1, 'admin', '2021-07-20 10:48:39');
INSERT INTO `t_sys_role_resource` VALUES (437, 1, 64, 1, 'admin', '2021-07-20 10:48:39');
INSERT INTO `t_sys_role_resource` VALUES (444, 7, 39, 1, 'admin', '2021-09-09 10:10:30');
INSERT INTO `t_sys_role_resource` VALUES (450, 6, 16, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (451, 6, 17, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (452, 6, 18, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (453, 6, 37, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (454, 6, 40, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (455, 6, 41, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (456, 6, 42, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (457, 6, 43, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (458, 6, 44, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (459, 6, 45, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (460, 6, 46, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (461, 6, 47, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (462, 6, 36, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (463, 6, 38, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (464, 6, 59, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (465, 6, 49, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (466, 6, 58, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (467, 6, 54, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (468, 6, 55, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (469, 6, 60, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (470, 6, 61, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (471, 6, 62, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (472, 6, 63, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (473, 6, 64, 1, 'admin', '2021-09-09 15:52:38');
INSERT INTO `t_sys_role_resource` VALUES (479, 6, 19, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (480, 6, 21, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (481, 6, 29, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (482, 6, 30, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (483, 6, 34, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (484, 6, 20, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (485, 6, 22, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (486, 6, 23, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (487, 6, 24, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (488, 6, 25, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (489, 6, 26, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (490, 6, 27, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (491, 6, 28, 1, 'admin', '2021-09-09 15:53:56');
INSERT INTO `t_sys_role_resource` VALUES (492, 8, 42, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (493, 8, 43, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (494, 8, 47, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (495, 8, 60, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (496, 8, 61, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (497, 8, 62, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (498, 8, 63, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (499, 8, 64, 1, 'admin', '2021-11-05 15:59:16');
INSERT INTO `t_sys_role_resource` VALUES (500, 1, 72, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (501, 1, 71, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (502, 1, 79, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (503, 1, 80, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (504, 1, 81, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (505, 1, 82, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (506, 1, 83, 1, 'admin', '2022-07-14 11:03:09');
INSERT INTO `t_sys_role_resource` VALUES (507, 1, 84, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (508, 1, 85, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (510, 1, 87, 1, 'admin', '2022-07-14 11:10:11');
INSERT INTO `t_sys_role_resource` VALUES (511, 1, 88, 1, 'admin', '2022-10-08 10:54:06');
INSERT INTO `t_sys_role_resource` VALUES (512, 8, 80, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (513, 8, 81, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (515, 8, 79, 1, 'admin', '2022-10-08 10:54:34');
INSERT INTO `t_sys_role_resource` VALUES (516, 1, 93, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (517, 1, 94, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (518, 1, 96, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (519, 1, 98, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (520, 1, 95, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (521, 1, 97, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (522, 1, 99, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (523, 1, 100, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (524, 1, 101, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (525, 1, 102, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` (id,role_id,resource_id,creator_id,creator,create_time) VALUES
(1,1,1,1,'admin','2021-05-27 15:07:39'),
(323,1,2,1,'admin','2021-05-28 09:04:50'),
(326,1,4,1,'admin','2021-05-28 09:04:50'),
(327,1,5,1,'admin','2021-05-28 09:04:50'),
(328,1,11,1,'admin','2021-05-28 09:04:50'),
(335,1,14,1,'admin','2021-05-28 17:42:21'),
(336,1,3,1,'admin','2021-05-28 17:42:43'),
(337,1,12,1,'admin','2021-05-28 17:42:43'),
(338,6,2,1,'admin','2021-05-28 19:19:38'),
(339,6,3,1,'admin','2021-05-28 19:19:38'),
(342,6,1,1,'admin','2021-05-29 01:31:22'),
(343,5,1,1,'admin','2021-05-31 14:05:23'),
(344,5,4,1,'admin','2021-05-31 14:05:23'),
(345,5,14,1,'admin','2021-05-31 14:05:23'),
(346,5,5,1,'admin','2021-05-31 14:05:23'),
(347,5,11,1,'admin','2021-05-31 14:05:23'),
(348,5,3,1,'admin','2021-05-31 16:33:14'),
(349,5,12,1,'admin','2021-05-31 16:33:14'),
(350,5,2,1,'admin','2021-05-31 16:33:14'),
(353,1,15,1,'admin','2021-05-31 17:48:33'),
(354,1,16,1,'admin','2021-05-31 17:48:33'),
(355,1,17,1,'admin','2021-05-31 17:48:33'),
(356,1,18,1,'admin','2021-05-31 17:48:33'),
(358,1,20,1,'admin','2021-05-31 17:52:08'),
(360,1,22,1,'admin','2021-05-31 18:05:04'),
(361,1,23,1,'admin','2021-05-31 18:05:04'),
(362,1,24,1,'admin','2021-05-31 18:05:04'),
(363,1,25,1,'admin','2021-05-31 18:05:04'),
(364,1,26,1,'admin','2021-05-31 18:05:04'),
(365,1,27,1,'admin','2021-05-31 18:05:04'),
(366,1,28,1,'admin','2021-05-31 18:05:04'),
(369,1,31,1,'admin','2021-05-31 21:25:56'),
(370,1,32,1,'admin','2021-05-31 21:25:56'),
(371,1,33,1,'admin','2021-05-31 21:25:56'),
(374,1,36,1,'admin','2021-06-01 14:01:57'),
(375,1,19,1,'admin','2021-06-01 17:34:03'),
(376,1,21,1,'admin','2021-06-01 17:34:03'),
(377,1,29,1,'admin','2021-06-01 17:34:03'),
(378,1,30,1,'admin','2021-06-01 17:34:03'),
(379,1,34,1,'admin','2021-06-01 17:34:03'),
(380,1,37,1,'admin','2021-06-03 09:09:42'),
(381,1,38,1,'admin','2021-06-03 09:09:42'),
(383,1,40,1,'admin','2021-06-08 11:21:52'),
(384,1,41,1,'admin','2021-06-08 11:21:52'),
(385,1,42,1,'admin','2021-06-08 11:21:52'),
(386,1,43,1,'admin','2021-06-08 11:21:52'),
(387,1,44,1,'admin','2021-06-08 11:21:52'),
(388,1,45,1,'admin','2021-06-08 11:21:52'),
(389,1,46,1,'admin','2021-06-08 11:21:52'),
(390,1,47,1,'admin','2021-06-08 11:21:52'),
(391,6,39,1,'admin','2021-06-08 15:10:58'),
(392,6,15,1,'admin','2021-06-08 15:10:58'),
(395,6,31,1,'admin','2021-06-08 15:10:58'),
(396,6,33,1,'admin','2021-06-08 15:10:58'),
(397,6,32,1,'admin','2021-06-08 15:10:58'),
(398,6,4,1,'admin','2021-06-08 15:10:58'),
(399,6,14,1,'admin','2021-06-08 15:10:58'),
(400,6,11,1,'admin','2021-06-08 15:10:58'),
(401,6,5,1,'admin','2021-06-08 15:10:58'),
(403,7,1,1,'admin','2021-07-06 15:07:09'),
(405,1,49,1,'admin','2021-07-07 15:14:17'),
(410,1,54,1,'admin','2021-07-08 17:32:19'),
(411,1,55,1,'admin','2021-07-08 17:32:19'),
(413,1,57,1,'admin','2021-07-09 10:48:50'),
(414,1,58,1,'admin','2021-07-09 10:48:50'),
(418,8,57,1,'admin','2021-07-09 10:49:46'),
(419,8,12,1,'admin','2021-07-09 10:49:46'),
(420,8,15,1,'admin','2021-07-09 10:49:46'),
(421,8,38,1,'admin','2021-07-09 10:49:46'),
(423,8,2,1,'admin','2021-07-09 10:49:46'),
(425,8,3,1,'admin','2021-07-09 10:49:46'),
(426,8,36,1,'admin','2021-07-09 10:49:46'),
(428,1,59,1,'admin','2021-07-09 10:50:20'),
(429,8,59,1,'admin','2021-07-09 10:50:32'),
(431,6,57,1,'admin','2021-07-12 16:44:12'),
(433,1,60,1,'admin','2021-07-19 20:19:29'),
(434,1,61,1,'admin','2021-07-19 20:19:29'),
(435,1,62,1,'admin','2021-07-19 20:19:29'),
(436,1,63,1,'admin','2021-07-20 10:48:39'),
(437,1,64,1,'admin','2021-07-20 10:48:39'),
(444,7,39,1,'admin','2021-09-09 10:10:30'),
(450,6,16,1,'admin','2021-09-09 15:52:38'),
(451,6,17,1,'admin','2021-09-09 15:52:38'),
(452,6,18,1,'admin','2021-09-09 15:52:38'),
(453,6,37,1,'admin','2021-09-09 15:52:38'),
(454,6,40,1,'admin','2021-09-09 15:52:38'),
(455,6,41,1,'admin','2021-09-09 15:52:38'),
(456,6,42,1,'admin','2021-09-09 15:52:38'),
(457,6,43,1,'admin','2021-09-09 15:52:38'),
(458,6,44,1,'admin','2021-09-09 15:52:38'),
(459,6,45,1,'admin','2021-09-09 15:52:38'),
(460,6,46,1,'admin','2021-09-09 15:52:38'),
(461,6,47,1,'admin','2021-09-09 15:52:38'),
(462,6,36,1,'admin','2021-09-09 15:52:38'),
(463,6,38,1,'admin','2021-09-09 15:52:38'),
(464,6,59,1,'admin','2021-09-09 15:52:38'),
(465,6,49,1,'admin','2021-09-09 15:52:38'),
(466,6,58,1,'admin','2021-09-09 15:52:38'),
(467,6,54,1,'admin','2021-09-09 15:52:38'),
(468,6,55,1,'admin','2021-09-09 15:52:38'),
(469,6,60,1,'admin','2021-09-09 15:52:38'),
(470,6,61,1,'admin','2021-09-09 15:52:38'),
(471,6,62,1,'admin','2021-09-09 15:52:38'),
(472,6,63,1,'admin','2021-09-09 15:52:38'),
(473,6,64,1,'admin','2021-09-09 15:52:38'),
(479,6,19,1,'admin','2021-09-09 15:53:56'),
(480,6,21,1,'admin','2021-09-09 15:53:56'),
(481,6,29,1,'admin','2021-09-09 15:53:56'),
(482,6,30,1,'admin','2021-09-09 15:53:56'),
(483,6,34,1,'admin','2021-09-09 15:53:56'),
(484,6,20,1,'admin','2021-09-09 15:53:56'),
(485,6,22,1,'admin','2021-09-09 15:53:56'),
(486,6,23,1,'admin','2021-09-09 15:53:56'),
(487,6,24,1,'admin','2021-09-09 15:53:56'),
(488,6,25,1,'admin','2021-09-09 15:53:56'),
(489,6,26,1,'admin','2021-09-09 15:53:56'),
(490,6,27,1,'admin','2021-09-09 15:53:56'),
(491,6,28,1,'admin','2021-09-09 15:53:56'),
(492,8,42,1,'admin','2021-11-05 15:59:16'),
(493,8,43,1,'admin','2021-11-05 15:59:16'),
(494,8,47,1,'admin','2021-11-05 15:59:16'),
(495,8,60,1,'admin','2021-11-05 15:59:16'),
(496,8,61,1,'admin','2021-11-05 15:59:16'),
(497,8,62,1,'admin','2021-11-05 15:59:16'),
(500,1,72,1,'admin','2022-07-14 11:03:09'),
(501,1,71,1,'admin','2022-07-14 11:03:09'),
(502,1,79,1,'admin','2022-07-14 11:03:09'),
(503,1,80,1,'admin','2022-07-14 11:03:09'),
(504,1,81,1,'admin','2022-07-14 11:03:09'),
(505,1,82,1,'admin','2022-07-14 11:03:09'),
(506,1,83,1,'admin','2022-07-14 11:03:09'),
(507,1,84,1,'admin','2022-07-14 11:10:11'),
(508,1,85,1,'admin','2022-07-14 11:10:11'),
(510,1,87,1,'admin','2022-07-14 11:10:11'),
(511,1,88,1,'admin','2022-10-08 10:54:06'),
(512,8,80,1,'admin','2022-10-08 10:54:34'),
(513,8,81,1,'admin','2022-10-08 10:54:34'),
(515,8,79,1,'admin','2022-10-08 10:54:34'),
(516,1,93,1,'admin','2022-10-26 20:03:14'),
(517,1,94,1,'admin','2022-10-26 20:03:14'),
(518,1,96,1,'admin','2022-10-26 20:03:14'),
(519,1,98,1,'admin','2022-10-26 20:03:14'),
(520,1,95,1,'admin','2022-10-26 20:03:14'),
(521,1,97,1,'admin','2022-10-26 20:03:14'),
(522,1,99,1,'admin','2022-10-26 20:03:14'),
(523,1,100,1,'admin','2022-10-26 20:03:14'),
(524,1,101,1,'admin','2022-10-26 20:03:14'),
(525,1,102,1,'admin','2022-10-26 20:03:14'),
(527,1,106,1,'admin','2023-02-23 14:30:54'),
(528,1,103,1,'admin','2023-02-23 14:30:54'),
(529,1,105,1,'admin','2023-02-23 14:31:00'),
(530,1,104,1,'admin','2023-02-24 13:40:26'),
(532,1,108,1,'admin','2023-03-14 17:28:06'),
(533,6,79,1,'admin','2023-03-14 17:28:50'),
(534,6,80,1,'admin','2023-03-14 17:28:50'),
(535,6,81,1,'admin','2023-03-14 17:28:50'),
(536,6,82,1,'admin','2023-03-14 17:28:50'),
(537,6,83,1,'admin','2023-03-14 17:28:50'),
(538,6,84,1,'admin','2023-03-14 17:29:00'),
(539,6,85,1,'admin','2023-03-14 17:29:00'),
(540,6,87,1,'admin','2023-03-14 17:29:00'),
(541,6,88,1,'admin','2023-03-14 17:29:00'),
(544,1,109,1,'admin','2023-03-16 16:11:25');
COMMIT;
-- ----------------------------

View File

@@ -3,61 +3,64 @@ module mayfly-go
go 1.20
require (
github.com/gin-gonic/gin v1.9.0
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.7
github.com/mojocn/base64Captcha v1.3.5 //
github.com/pkg/sftp v1.13.5
github.com/redis/go-redis/v9 v9.0.2
github.com/pquerna/otp v1.4.0
github.com/redis/go-redis/v9 v9.0.5
github.com/robfig/cron/v3 v3.0.1 //
github.com/sirupsen/logrus v1.9.0
github.com/sirupsen/logrus v1.9.3
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.11.4 // mongo
golang.org/x/crypto v0.8.0 // ssh
golang.org/x/crypto v0.10.0 // ssh
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.5.0
gorm.io/gorm v1.25.0
gorm.io/gorm v1.25.1
)
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)

View File

@@ -36,7 +36,7 @@ func (i *Index) Count(rc *req.Ctx) {
dbNum = i.DbApp.Count(&dbentity.DbQuery{TagIds: tagIds})
redisNum = i.RedisApp.Count(&redisentity.RedisQuery{TagIds: tagIds})
}
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"mongoNum": mongoNum,
"machineNum": machienNum,
"dbNum": dbNum,

View File

@@ -142,7 +142,8 @@ func (d *Db) ExecSql(rc *req.Ctx) {
LoginAccount: rc.LoginAccount,
}
sqls := strings.Split(sql, ";\n")
sqls, err := sqlparser.SplitStatementToPieces(sql)
biz.ErrIsNil(err, "SQL解析错误,请检查您的执行SQL")
isMulti := len(sqls) > 1
var execResAll *application.DbSqlExecRes
for _, s := range sqls {
@@ -164,7 +165,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
}
}
colAndRes := make(map[string]interface{})
colAndRes := make(map[string]any)
colAndRes["colNames"] = execResAll.ColNames
colAndRes["res"] = execResAll.Res
rc.ResData = colAndRes
@@ -266,7 +267,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
if needStruct {
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", table))
writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS `%s`;\n", table))
writer.WriteString(dbmeta.GetCreateTableDdl(table)[0]["Create Table"].(string) + ";\n")
writer.WriteString(dbmeta.GetCreateTableDdl(table) + ";\n")
}
if !needData {
@@ -339,7 +340,7 @@ func (d *Db) HintTables(rc *req.Ctx) {
tables := dm.GetTables()
tableNames := make([]string, 0)
for _, v := range tables {
tableNames = append(tableNames, v["tableName"].(string))
tableNames = append(tableNames, v.TableName)
}
// key = 表名value = 列名数组
res := make(map[string][]string)
@@ -353,15 +354,15 @@ func (d *Db) HintTables(rc *req.Ctx) {
// 获取所有表下的所有列信息
columnMds := dm.GetColumns(tableNames...)
for _, v := range columnMds {
tName := v["tableName"].(string)
tName := v.TableName
if res[tName] == nil {
res[tName] = make([]string, 0)
}
columnName := fmt.Sprintf("%s [%s]", v["columnName"], v["columnType"])
comment := v["columnComment"]
columnName := fmt.Sprintf("%s [%s]", v.ColumnName, v.ColumnType)
comment := v.ColumnComment
// 如果字段备注不为空,则加上备注信息
if comment != nil && comment != "" {
if comment != "" {
columnName = fmt.Sprintf("%s[%s]", columnName, comment)
}

View File

@@ -1,7 +1,7 @@
package form
type DbForm struct {
Id uint64
Id uint64 `json:"id"`
Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"`
@@ -17,10 +17,10 @@ type DbForm struct {
}
type DbSqlSaveForm struct {
Name string `binding:"required"`
Sql string `binding:"required"`
Type int `binding:"required"`
Db string `binding:"required"`
Name string `json:"name" binding:"required"`
Sql string `json:"sql" binding:"required"`
Type int `json:"type" binding:"required"`
Db string `json:"db" binding:"required"`
}
// 数据库SQL执行表单

View File

@@ -21,7 +21,7 @@ import (
type Db interface {
// 分页获取
GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.DbQuery) int64
@@ -58,7 +58,7 @@ type dbAppImpl struct {
}
// 分页获取数据库信息列表
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *dbAppImpl) GetPageList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return d.dbRepo.GetDbList(condition, pageParam, toEntity, orderBy...)
}
@@ -108,19 +108,19 @@ func (d *dbAppImpl) Save(dbEntity *entity.Db) {
dbId := dbEntity.Id
old := d.GetById(dbId)
var oldDbs []interface{}
var oldDbs []any
for _, v := range strings.Split(old.Database, " ") {
// 关闭数据库连接
CloseDb(dbEntity.Id, v)
oldDbs = append(oldDbs, v)
}
var newDbs []interface{}
var newDbs []any
for _, v := range strings.Split(dbEntity.Database, " ") {
newDbs = append(newDbs, v)
}
// 比较新旧数据库列表,需要将移除的数据库相关联的信息删除
_, delDb, _ := utils.ArrayCompare(newDbs, oldDbs, func(i1, i2 interface{}) bool {
_, delDb, _ := utils.ArrayCompare(newDbs, oldDbs, func(i1, i2 any) bool {
return i1.(string) == i2.(string)
})
for _, v := range delDb {
@@ -249,22 +249,15 @@ type DbInstance struct {
// 执行查询语句
// 依次返回 列名数组结果map错误
func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]interface{}, error) {
func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]any, error) {
return SelectDataByDb(d.db, execSql)
}
// 将查询结果映射至struct可具体参考sqlx库
func (d *DbInstance) SelectData2Struct(execSql string, dest interface{}) error {
func (d *DbInstance) SelectData2Struct(execSql string, dest any) error {
return Select2StructByDb(d.db, execSql, dest)
}
// 执行内部查询语句,不返回列名以及不限制行数
// 依次返回 结果map错误
func (d *DbInstance) innerSelect(execSql string) ([]map[string]interface{}, error) {
_, res, err := SelectDataByDb(d.db, execSql)
return res, err
}
// 执行 update, insert, delete建表等sql
// 返回影响条数和错误
func (d *DbInstance) Exec(sql string) (int64, error) {
@@ -302,7 +295,7 @@ func (d *DbInstance) Close() {
// 客户端连接缓存,指定时间内没有访问则会被关闭, key为数据库实例id:数据库
var dbCache = cache.NewTimedCache(constant.DbConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
OnEvicted(func(key any, value any) {
global.Log.Info(fmt.Sprintf("删除db连接缓存 id = %s", key))
value.(*DbInstance).Close()
})
@@ -360,7 +353,7 @@ func GetDbConn(d *entity.Db, db string) (*sql.DB, error) {
return DB, nil
}
func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interface{}, error) {
func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]any, error) {
rows, err := db.Query(selectSql)
if err != nil {
return nil, nil, err
@@ -374,7 +367,7 @@ func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interf
}()
colTypes, _ := rows.ColumnTypes()
// 这里表示一行填充数据
scans := make([]interface{}, len(colTypes))
scans := make([]any, len(colTypes))
// 这里表示一行所有列的值,用[]byte表示
vals := make([][]byte, len(colTypes))
// 这里scans引用vals把数据填充到[]byte里
@@ -382,7 +375,7 @@ func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interf
scans[k] = &vals[k]
}
result := make([]map[string]interface{}, 0)
result := make([]map[string]any, 0)
// 列名用于前端表头名称按照数据库与查询字段顺序显示
colNames := make([]string, 0)
// 是否第一次遍历,列名数组只需第一次遍历时加入
@@ -394,7 +387,7 @@ func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interf
return nil, nil, err
}
// 每行数据
rowData := make(map[string]interface{})
rowData := make(map[string]any)
// 把vals中的数据复制到row中
for i, v := range vals {
colType := colTypes[i]
@@ -413,7 +406,7 @@ func SelectDataByDb(db *sql.DB, selectSql string) ([]string, []map[string]interf
}
// 将查询的值转为对应列类型的实际值,不全部转为字符串
func valueConvert(data []byte, colType *sql.ColumnType) interface{} {
func valueConvert(data []byte, colType *sql.ColumnType) any {
if data == nil {
return nil
}
@@ -462,7 +455,7 @@ func valueConvert(data []byte, colType *sql.ColumnType) interface{} {
}
// 查询数据结果映射至struct。可参考sqlx库
func Select2StructByDb(db *sql.DB, selectSql string, dest interface{}) error {
func Select2StructByDb(db *sql.DB, selectSql string, dest any) error {
rows, err := db.Query(selectSql)
if err != nil {
return err

View File

@@ -26,7 +26,7 @@ type DbSqlExecReq struct {
type DbSqlExecRes struct {
ColNames []string
Res []map[string]interface{}
Res []map[string]any
}
// 合并执行结果主要用于执行多条sql使用
@@ -52,7 +52,7 @@ type DbSqlExec interface {
DeleteBy(condition *entity.DbSqlExec)
// 分页获取
GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
}
func newDbSqlExecApp(dbExecSqlRepo repository.DbSqlExec) DbSqlExec {
@@ -151,7 +151,7 @@ func (d *dbSqlExecAppImpl) DeleteBy(condition *entity.DbSqlExec) {
d.dbSqlExecRepo.DeleteBy(condition)
}
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}
@@ -193,6 +193,7 @@ func doUpdate(update *sqlparser.Update, execSqlReq *DbSqlExecReq, dbSqlExec *ent
// 可能使用别名,故空格切割
tableName := strings.Split(tableStr, " ")[0]
where := sqlparser.String(update.Where)
biz.IsTrue(len(where) > 0, "SQL[%s]未执行. 请完善 where 条件后再执行", execSqlReq.Sql)
updateExprs := update.Exprs
updateColumns := make([]string, 0)
@@ -227,6 +228,7 @@ func doDelete(delete *sqlparser.Delete, execSqlReq *DbSqlExecReq, dbSqlExec *ent
// 可能使用别名,故空格切割
table := strings.Split(tableStr, " ")[0]
where := sqlparser.String(delete.Where)
biz.IsTrue(len(where) > 0, "SQL[%s]未执行. 请完善 where 条件后再执行", execSqlReq.Sql)
// 查询删除数据
selectSql := fmt.Sprintf("SELECT * FROM %s %s LIMIT 200", tableStr, where)
@@ -256,8 +258,8 @@ func doExec(sql string, dbInstance *DbInstance) (*DbSqlExecRes, error) {
if err != nil {
execRes = err.Error()
}
res := make([]map[string]interface{}, 0)
resData := make(map[string]interface{})
res := make([]map[string]any, 0)
resData := make(map[string]any)
resData["rowsAffected"] = rowsAffected
resData["sql"] = sql
resData["result"] = execRes

View File

@@ -1,32 +1,104 @@
package application
import (
"embed"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/utils"
"strings"
)
// 表信息
type Table struct {
TableName string `json:"tableName"` // 表名
TableComment string `json:"tableComment"` // 表备注
CreateTime string `json:"createTime"` // 创建时间
TableRows int `json:"tableRows"`
DataLength int64 `json:"dataLength"`
IndexLength int64 `json:"indexLength"`
}
// 表的列信息
type Column struct {
TableName string `json:"tableName"` // 表名
ColumnName string `json:"columnName"` // 列名
ColumnType string `json:"columnType"` // 列类型
ColumnComment string `json:"columnComment"` // 列备注
ColumnKey string `json:"columnKey"`
ColumnDefault string `json:"columnDefault"`
Nullable string `json:"nullable"` // 是否可为null
Extra string `json:"extra"` // 其他信息
}
// 表索引信息
type Index struct {
IndexName string `json:"indexName"` // 索引名
ColumnName string `json:"columnName"` // 列名
IndexType string `json:"indexType"` // 索引类型
IndexComment string `json:"indexComment"` // 备注
SeqInIndex int `json:"seqInIndex"`
NonUnique int `json:"nonUnique"`
}
// -----------------------------------元数据接口定义------------------------------------------
// 数据库元信息接口(表、列、获取表数据等元信息)
// 所有数据查出来直接用map接收注意不同数据库实现该接口返回的map中的key需要统一.
// 即: 使用别名统一即可。如table_name AS tableName
type DbMetadata interface {
// 获取表基础元信息
// 表名: tableName, 备注: tableComment
GetTables() []map[string]interface{}
GetTables() []Table
// 获取指定表名的所有列元信息
// 表名: tableName, 列名: columnName, 列类型: columnType, 备注: columnComment, 是否可为null: nullable, 其他信息: extra
GetColumns(tableNames ...string) []map[string]interface{}
GetColumns(tableNames ...string) []Column
// 获取表主键字段名,没有主键标识则默认第一个字段
GetPrimaryKey(tablename string) string
// 获取表信息比GetTables获取更详细的表信息
GetTableInfos() []map[string]interface{}
GetTableInfos() []Table
// 获取表索引信息
GetTableIndex(tableName string) []map[string]interface{}
GetTableIndex(tableName string) []Index
// 获取建表ddl
GetCreateTableDdl(tableName string) []map[string]interface{}
GetCreateTableDdl(tableName string) string
// 获取指定表的数据-分页查询
// @return columns: 列字段名result: 结果集error: 错误
GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error)
GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]any, error)
}
// ------------------------- 元数据sql操作 -------------------------
//
//go:embed metasql/*
var metasql embed.FS
// sql缓存 key: sql备注的key 如MYSQL_TABLE_MA value: sql内容
var sqlCache = make(map[string]string, 20)
// 获取本地文件的sql内容并进行解析获取对应key的sql内容
func GetLocalSql(file, key string) string {
sql := sqlCache[key]
if sql != "" {
return sql
}
bytes, err := metasql.ReadFile(file)
biz.ErrIsNilAppendErr(err, "获取sql meta文件内容失败: %s")
allSql := string(bytes)
sqls := strings.Split(allSql, "---------------------------------------")
var resSql string
for _, sql := range sqls {
sql = utils.StrTrimSpaceAndBr(sql)
// 获取sql第一行的sql备注信息如--MYSQL_TABLE_MA 表信息元数据
info := strings.SplitN(sql, "\n", 2)
// 原始sql即去除第一行的key与备注信息
rowSql := info[1]
// 获取sql keyMYSQL_TABLE_MA
sqlKey := strings.Split(strings.Split(info[0], " ")[0], "--")[1]
if key == sqlKey {
resSql = rowSql
}
sqlCache[sqlKey] = rowSql
}
return resSql
}

View File

@@ -0,0 +1,69 @@
--MYSQL_TABLE_MA
SELECT
table_name tableName,
table_comment tableComment
from
information_schema.tables
WHERE
table_schema = (
SELECT
database ()
)
---------------------------------------
--MYSQL_TABLE_INFO 表详细信息
SELECT
table_name tableName,
table_comment tableComment,
table_rows tableRows,
data_length dataLength,
index_length indexLength,
create_time createTime
FROM
information_schema.tables
WHERE
table_schema = (
SELECT
database ()
)
---------------------------------------
--MYSQL_INDEX_INFO 索引信息
SELECT
index_name indexName,
column_name columnName,
index_type indexType,
non_unique nonUnique,
SEQ_IN_INDEX seqInIndex,
INDEX_COMMENT indexComment
FROM
information_schema.STATISTICS
WHERE
table_schema = (
SELECT
database ()
)
AND table_name = '%s'
ORDER BY
index_name asc,
SEQ_IN_INDEX asc
---------------------------------------
--MYSQL_COLUMN_MA 列信息元数据
SELECT
table_name tableName,
column_name columnName,
column_type columnType,
column_default columnDefault,
column_comment columnComment,
column_key columnKey,
extra extra,
is_nullable nullable
from
information_schema.columns
WHERE
table_schema = (
SELECT
database ()
)
AND table_name in (%s)
ORDER BY
tableName,
ordinal_position

View File

@@ -0,0 +1,169 @@
--PGSQL_TABLE_MA
SELECT
obj_description (c.oid) AS "tableComment",
c.relname AS "tableName"
FROM
pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE
n.nspname = (
select
current_schema ()
)
AND c.reltype > 0
---------------------------------------
--PGSQL_TABLE_INFO 表详细信息
SELECT
obj_description (c.oid) AS "tableComment",
c.relname AS "tableName",
pg_table_size ('"' || n.nspname || '"."' || c.relname || '"') as "dataLength",
pg_indexes_size ('"' || n.nspname || '"."' || c.relname || '"') as "indexLength",
c.reltuples as "tableRows"
FROM
pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE
n.nspname = (
select
current_schema ()
)
AND c.reltype > 0
---------------------------------------
--PGSQL_INDEX_INFO 表索引信息
SELECT
indexname AS "indexName",
indexdef AS "indexComment"
FROM
pg_indexes
WHERE
schemaname = (
select
current_schema ()
)
AND tablename = '%s'
---------------------------------------
--PGSQL_COLUMN_MA 表列信息
SELECT
C.relname AS "tableName",
A.attname AS "columnName",
tc.is_nullable AS "nullable",
concat_ws ( '', t.typname, SUBSTRING ( format_type ( a.atttypid, a.atttypmod ) FROM '\(.*\)' ) ) AS "columnType",
(CASE WHEN ( SELECT COUNT(*) FROM pg_constraint WHERE conrelid = a.attrelid AND conkey[1]= attnum AND contype = 'p' ) > 0 THEN 'PRI' ELSE '' END ) AS "columnKey",
d.description AS "columnComment"
FROM
pg_attribute a LEFT JOIN pg_description d ON d.objoid = a.attrelid
AND d.objsubid = A.attnum
LEFT JOIN pg_class c ON A.attrelid = c.oid
LEFT JOIN pg_namespace pn ON c.relnamespace = pn.oid
LEFT JOIN pg_type t ON a.atttypid = t.oid
JOIN information_schema.columns tc ON tc.column_name = a.attname AND tc.table_name = C.relname AND tc.table_schema = pn.nspname
WHERE
A.attnum >= 0
AND pn.nspname = (select current_schema())
AND C.relname in (%s)
ORDER BY
C.relname DESC,
A.attnum ASC
---------------------------------------
--PGSQL_TABLE_DDL_FUNC 表ddl函数
CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character varying)
RETURNS character varying AS
$BODY$
declare
tableScript character varying default '';
begin
-- columns
tableScript:=tableScript || ' CREATE TABLE '|| tablename|| ' ( '|| chr(13)||chr(10) || array_to_string(
array(
select ' ' || concat_ws(' ',fieldName, fieldType, fieldLen, indexType, isNullStr, fieldComment ) as column_line
from (
select a.attname as fieldName,format_type(a.atttypid,a.atttypmod) as fieldType,(case when atttypmod-4>0 then
atttypmod-4 else 0 end) as fieldLen,
(case when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum and
contype='p')>0 then 'PRI'
when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum and contype='u')>0
then 'UNI'
when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum and contype='f')>0
then 'FRI'
else '' end) as indexType,
(case when a.attnotnull=true then 'not null' else 'null' end) as isNullStr,
' comment ' || col_description(a.attrelid,a.attnum) as fieldComment
from pg_attribute a where attstattarget=-1 and attrelid = (select c.oid from pg_class c,pg_namespace n where
c.relnamespace=n.oid and n.nspname =namespace and relname =tablename)
) as string_columns
),','||chr(13)||chr(10)) || ',';
-- 约束
tableScript:= tableScript || chr(13)||chr(10) || array_to_string(
array(
select concat(' CONSTRAINT ',conname ,c ,u,p,f) from (
select conname,
case when contype='c' then ' CHECK('|| ( select findattname(namespace,tablename,'c') ) ||')' end as c ,
case when contype='u' then ' UNIQUE('|| ( select findattname(namespace,tablename,'u') ) ||')' end as u ,
case when contype='p' then ' PRIMARY KEY ('|| ( select findattname(namespace,tablename,'p') ) ||')' end as p ,
case when contype='f' then ' FOREIGN KEY('|| ( select findattname(namespace,tablename,'u') ) ||') REFERENCES '||
(select p.relname from pg_class p where p.oid=c.confrelid ) || '('|| ( select
findattname(namespace,tablename,'u') ) ||')' end as f
from pg_constraint c
where contype in('u','c','f','p') and conrelid=(
select oid from pg_class where relname=tablename and relnamespace =(
select oid from pg_namespace where nspname = namespace
)
)
) as t
) ,',' || chr(13)||chr(10) ) || chr(13)||chr(10) ||' ); ';
-- indexs
-- CREATE UNIQUE INDEX pg_language_oid_index ON pg_language USING btree (oid); -- table pg_language
--
/** **/
--- 获取非约束索引 column
-- CREATE UNIQUE INDEX pg_language_oid_index ON pg_language USING btree (oid); -- table pg_language
tableScript:= tableScript || chr(13)||chr(10) || chr(13)||chr(10) || array_to_string(
array(
select 'CREATE INDEX ' || indexrelname || ' ON ' || tablename || ' USING btree '|| '(' || attname || ');' from (
SELECT
i.relname AS indexrelname , x.indkey,
( select array_to_string (
array(
select a.attname from pg_attribute a where attrelid=c.oid and a.attnum in ( select unnest(x.indkey) )
)
,',' ) )as attname
FROM pg_class c
JOIN pg_index x ON c.oid = x.indrelid
JOIN pg_class i ON i.oid = x.indexrelid
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname=tablename and i.relname not in
( select constraint_name from information_schema.key_column_usage where table_name=tablename )
)as t
) ,','|| chr(13)||chr(10));
-- COMMENT COMMENT ON COLUMN sys_activity.id IS '主键';
tableScript:= tableScript || chr(13)||chr(10) || chr(13)||chr(10) || array_to_string(
array(
SELECT 'COMMENT ON COLUMN' || tablename || '.' || a.attname ||' IS '|| ''''|| d.description ||''''
FROM pg_class c
JOIN pg_description d ON c.oid=d.objoid
JOIN pg_attribute a ON c.oid = a.attrelid
WHERE c.relname=tablename
AND a.attnum = d.objsubid),','|| chr(13)||chr(10)) ;
return tableScript;
end
$BODY$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION findattname(namespace character varying, tablename character varying, ctype character
varying)
RETURNS character varying as $BODY$
declare
tt oid ;
aname character varying default '';
begin
tt := oid from pg_class where relname= tablename and relnamespace =(select oid from pg_namespace where
nspname=namespace) ;
aname:= array_to_string(
array(
select a.attname from pg_attribute a
where a.attrelid=tt and a.attnum in (
select unnest(conkey) from pg_constraint c where contype=ctype
and conrelid=tt and array_to_string(conkey,',') is not null
)
),',');
return aname;
end
$BODY$ LANGUAGE plpgsql

View File

@@ -7,6 +7,7 @@ import (
"mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/utils"
"net"
"github.com/go-sql-driver/mysql"
@@ -30,25 +31,11 @@ func getMysqlDB(d *entity.Db, db string) (*sql.DB, error) {
// ---------------------------------- mysql元数据 -----------------------------------
const (
// mysql 表信息元数据
MYSQL_TABLE_MA = `SELECT table_name tableName, table_comment tableComment from information_schema.tables WHERE table_schema = (SELECT database())`
// mysql 表信息
MYSQL_TABLE_INFO = `SELECT table_name tableName, table_comment tableComment, table_rows tableRows,
data_length dataLength, index_length indexLength, create_time createTime
FROM information_schema.tables
WHERE table_schema = (SELECT database())`
// mysql 索引信息
MYSQL_INDEX_INFO = `SELECT index_name indexName, column_name columnName, index_type indexType, non_unique nonUnique,
SEQ_IN_INDEX seqInIndex, INDEX_COMMENT indexComment
FROM information_schema.STATISTICS
WHERE table_schema = (SELECT database()) AND table_name = '%s' ORDER BY index_name asc , SEQ_IN_INDEX asc`
// mysql 列信息元数据
MYSQL_COLUMN_MA = `SELECT table_name tableName, column_name columnName, column_type columnType, column_default columnDefault,
column_comment columnComment, column_key columnKey, extra, is_nullable nullable from information_schema.columns
WHERE table_schema = (SELECT database()) AND table_name in (%s) ORDER BY tableName, ordinal_position`
MYSQL_META_FILE = "metasql/mysql_meta.sql"
MYSQL_TABLE_MA_KEY = "MYSQL_TABLE_MA"
MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO"
MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO"
MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA"
)
type MysqlMetadata struct {
@@ -56,14 +43,22 @@ type MysqlMetadata struct {
}
// 获取表基础元信息, 如表名等
func (mm *MysqlMetadata) GetTables() []map[string]interface{} {
res, err := mm.di.innerSelect(MYSQL_TABLE_MA)
func (mm *MysqlMetadata) GetTables() []Table {
_, res, err := mm.di.SelectData(GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_MA_KEY))
biz.ErrIsNilAppendErr(err, "获取表基本信息失败: %s")
return res
tables := make([]Table, 0)
for _, re := range res {
tables = append(tables, Table{
TableName: re["tableName"].(string),
TableComment: utils.Any2String(re["tableComment"]),
})
}
return tables
}
// 获取列元信息, 如列名等
func (mm *MysqlMetadata) GetColumns(tableNames ...string) []map[string]interface{} {
func (mm *MysqlMetadata) GetColumns(tableNames ...string) []Column {
tableName := ""
for i := 0; i < len(tableNames); i++ {
if i != 0 {
@@ -71,9 +66,22 @@ func (mm *MysqlMetadata) GetColumns(tableNames ...string) []map[string]interface
}
tableName = tableName + "'" + tableNames[i] + "'"
}
result, err := mm.di.innerSelect(fmt.Sprintf(MYSQL_COLUMN_MA, tableName))
_, res, err := mm.di.SelectData(fmt.Sprintf(GetLocalSql(MYSQL_META_FILE, MYSQL_COLUMN_MA_KEY), tableName))
biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s")
return result
columns := make([]Column, 0)
for _, re := range res {
columns = append(columns, Column{
TableName: re["tableName"].(string),
ColumnName: re["columnName"].(string),
ColumnType: utils.Any2String(re["columnType"]),
ColumnComment: utils.Any2String(re["columnComment"]),
Nullable: utils.Any2String(re["nullable"]),
ColumnKey: utils.Any2String(re["columnKey"]),
ColumnDefault: utils.Any2String(re["columnDefault"]),
})
}
return columns
}
// 获取表主键字段名,不存在主键标识则默认第一个字段
@@ -81,36 +89,59 @@ func (mm *MysqlMetadata) GetPrimaryKey(tablename string) string {
columns := mm.GetColumns(tablename)
biz.IsTrue(len(columns) > 0, "[%s] 表不存在", tablename)
for _, v := range columns {
if v["columnKey"].(string) == "PRI" {
return v["columnName"].(string)
if v.ColumnKey == "PRI" {
return v.ColumnName
}
}
return columns[0]["columnName"].(string)
return columns[0].ColumnName
}
// 获取表信息比GetTableMetedatas获取更详细的表信息
func (mm *MysqlMetadata) GetTableInfos() []map[string]interface{} {
res, err := mm.di.innerSelect(MYSQL_TABLE_INFO)
func (mm *MysqlMetadata) GetTableInfos() []Table {
_, res, err := mm.di.SelectData(GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY))
biz.ErrIsNilAppendErr(err, "获取表信息失败: %s")
return res
tables := make([]Table, 0)
for _, re := range res {
tables = append(tables, Table{
TableName: re["tableName"].(string),
TableComment: utils.Any2String(re["tableComment"]),
CreateTime: utils.Any2String(re["createTime"]),
TableRows: utils.Any2Int(re["tableRows"]),
DataLength: utils.Any2Int64(re["dataLength"]),
IndexLength: utils.Any2Int64(re["indexLength"]),
})
}
return tables
}
// 获取表索引信息
func (mm *MysqlMetadata) GetTableIndex(tableName string) []map[string]interface{} {
res, err := mm.di.innerSelect(fmt.Sprintf(MYSQL_INDEX_INFO, tableName))
func (mm *MysqlMetadata) GetTableIndex(tableName string) []Index {
_, res, err := mm.di.SelectData(fmt.Sprintf(GetLocalSql(MYSQL_META_FILE, MYSQL_INDEX_INFO_KEY), tableName))
biz.ErrIsNilAppendErr(err, "获取表索引信息失败: %s")
indexs := make([]Index, 0)
for _, re := range res {
indexs = append(indexs, Index{
IndexName: re["indexName"].(string),
ColumnName: utils.Any2String(re["columnName"]),
IndexType: utils.Any2String(re["indexType"]),
IndexComment: utils.Any2String(re["indexComment"]),
NonUnique: utils.Any2Int(re["nonUnique"]),
SeqInIndex: utils.Any2Int(re["seqInIndex"]),
})
}
// 把查询结果以索引名分组,索引字段以逗号连接
result := make([]map[string]interface{}, 0)
result := make([]Index, 0)
key := ""
for _, v := range res {
for _, v := range indexs {
// 当前的索引名
in := v["indexName"].(string)
in := v.IndexName
if key == in {
// 索引字段已根据名称和顺序排序,故取最后一个即可
i := len(result) - 1
// 同索引字段以逗号连接
result[i]["columnName"] = result[i]["columnName"].(string) + "," + v["columnName"].(string)
result[i].ColumnName = result[i].ColumnName + "," + v.ColumnName
} else {
key = in
result = append(result, v)
@@ -120,11 +151,11 @@ func (mm *MysqlMetadata) GetTableIndex(tableName string) []map[string]interface{
}
// 获取建表ddl
func (mm *MysqlMetadata) GetCreateTableDdl(tableName string) []map[string]interface{} {
res, _ := mm.di.innerSelect(fmt.Sprintf("show create table %s ", tableName))
return res
func (mm *MysqlMetadata) GetCreateTableDdl(tableName string) string {
_, res, _ := mm.di.SelectData(fmt.Sprintf("show create table %s ", tableName))
return res[0]["Create Table"].(string)
}
func (mm *MysqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error) {
func (mm *MysqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]any, error) {
return mm.di.SelectData(fmt.Sprintf("SELECT * FROM %s LIMIT %d, %d", tableName, (pageNum-1)*pageSize, pageSize))
}

View File

@@ -57,38 +57,12 @@ func (pd *PqSqlDialer) DialTimeout(network, address string, timeout time.Duratio
// ---------------------------------- pgsql元数据 -----------------------------------
const (
// postgres 表信息元数据
PGSQL_TABLE_MA = `SELECT obj_description(c.oid) AS "tableComment", c.relname AS "tableName" FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = (select current_schema()) AND c.reltype > 0`
PGSQL_TABLE_INFO = `SELECT obj_description(c.oid) AS "tableComment", c.relname AS "tableName" FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = (select current_schema()) AND c.reltype > 0`
PGSQL_INDEX_INFO = `SELECT indexname AS "indexName", indexdef AS "indexComment"
FROM pg_indexes WHERE schemaname = (select current_schema()) AND tablename = '%s'`
PGSQL_COLUMN_MA = `SELECT
C.relname AS "tableName",
A.attname AS "columnName",
tc.is_nullable AS "nullable",
concat_ws ( '', t.typname, SUBSTRING ( format_type ( a.atttypid, a.atttypmod ) FROM '\(.*\)' ) ) AS "columnType",
(CASE WHEN ( SELECT COUNT(*) FROM pg_constraint WHERE conrelid = a.attrelid AND conkey[1]= attnum AND contype = 'p' ) > 0 THEN 'PRI' ELSE '' END ) AS "columnKey",
d.description AS "columnComment"
FROM
pg_attribute a LEFT JOIN pg_description d ON d.objoid = a.attrelid
AND d.objsubid = A.attnum
LEFT JOIN pg_class c ON A.attrelid = c.oid
LEFT JOIN pg_namespace pn ON c.relnamespace = pn.oid
LEFT JOIN pg_type t ON a.atttypid = t.oid
JOIN information_schema.columns tc ON tc.column_name = a.attname AND tc.table_name = C.relname AND tc.table_schema = pn.nspname
WHERE
A.attnum >= 0
AND pn.nspname = (select current_schema())
AND C.relname in (%s)
ORDER BY
C.relname DESC,
A.attnum ASC
`
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
PGSQL_TABLE_MA_KEY = "PGSQL_TABLE_MA"
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC"
)
type PgsqlMetadata struct {
@@ -96,14 +70,22 @@ type PgsqlMetadata struct {
}
// 获取表基础元信息, 如表名等
func (pm *PgsqlMetadata) GetTables() []map[string]interface{} {
res, err := pm.di.innerSelect(PGSQL_TABLE_MA)
func (pm *PgsqlMetadata) GetTables() []Table {
_, res, err := pm.di.SelectData(GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_MA_KEY))
biz.ErrIsNilAppendErr(err, "获取表基本信息失败: %s")
return res
tables := make([]Table, 0)
for _, re := range res {
tables = append(tables, Table{
TableName: re["tableName"].(string),
TableComment: utils.Any2String(re["tableComment"]),
})
}
return tables
}
// 获取列元信息, 如列名等
func (pm *PgsqlMetadata) GetColumns(tableNames ...string) []map[string]interface{} {
func (pm *PgsqlMetadata) GetColumns(tableNames ...string) []Column {
tableName := ""
for i := 0; i < len(tableNames); i++ {
if i != 0 {
@@ -111,43 +93,88 @@ func (pm *PgsqlMetadata) GetColumns(tableNames ...string) []map[string]interface
}
tableName = tableName + "'" + tableNames[i] + "'"
}
result, err := pm.di.innerSelect(fmt.Sprintf(PGSQL_COLUMN_MA, tableName))
_, res, err := pm.di.SelectData(fmt.Sprintf(GetLocalSql(PGSQL_META_FILE, PGSQL_COLUMN_MA_KEY), tableName))
biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s")
return result
columns := make([]Column, 0)
for _, re := range res {
columns = append(columns, Column{
TableName: re["tableName"].(string),
ColumnName: re["columnName"].(string),
ColumnType: utils.Any2String(re["columnType"]),
ColumnComment: utils.Any2String(re["columnComment"]),
Nullable: utils.Any2String(re["nullable"]),
ColumnKey: utils.Any2String(re["columnKey"]),
ColumnDefault: utils.Any2String(re["columnDefault"]),
})
}
return columns
}
func (pm *PgsqlMetadata) GetPrimaryKey(tablename string) string {
columns := pm.GetColumns(tablename)
biz.IsTrue(len(columns) > 0, "[%s] 表不存在", tablename)
for _, v := range columns {
if v["columnKey"].(string) == "PRI" {
return v["columnName"].(string)
if v.ColumnKey == "PRI" {
return v.ColumnName
}
}
return columns[0]["columnName"].(string)
return columns[0].ColumnName
}
// 获取表信息比GetTables获取更详细的表信息
func (pm *PgsqlMetadata) GetTableInfos() []map[string]interface{} {
res, err := pm.di.innerSelect(PGSQL_TABLE_INFO)
func (pm *PgsqlMetadata) GetTableInfos() []Table {
_, res, err := pm.di.SelectData(GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_INFO_KEY))
biz.ErrIsNilAppendErr(err, "获取表信息失败: %s")
return res
tables := make([]Table, 0)
for _, re := range res {
tables = append(tables, Table{
TableName: re["tableName"].(string),
TableComment: utils.Any2String(re["tableComment"]),
CreateTime: utils.Any2String(re["createTime"]),
TableRows: utils.Any2Int(re["tableRows"]),
DataLength: utils.Any2Int64(re["dataLength"]),
IndexLength: utils.Any2Int64(re["indexLength"]),
})
}
return tables
}
// 获取表索引信息
func (pm *PgsqlMetadata) GetTableIndex(tableName string) []map[string]interface{} {
res, err := pm.di.innerSelect(fmt.Sprintf(PGSQL_INDEX_INFO, tableName))
func (pm *PgsqlMetadata) GetTableIndex(tableName string) []Index {
_, res, err := pm.di.SelectData(fmt.Sprintf(GetLocalSql(PGSQL_META_FILE, PGSQL_INDEX_INFO_KEY), tableName))
biz.ErrIsNilAppendErr(err, "获取表索引信息失败: %s")
return res
indexs := make([]Index, 0)
for _, re := range res {
indexs = append(indexs, Index{
IndexName: re["indexName"].(string),
ColumnName: utils.Any2String(re["columnName"]),
IndexType: utils.Any2String(re["indexType"]),
IndexComment: utils.Any2String(re["indexComment"]),
NonUnique: utils.Any2Int(re["nonUnique"]),
SeqInIndex: utils.Any2Int(re["seqInIndex"]),
})
}
return indexs
}
// 获取建表ddl
func (pm *PgsqlMetadata) GetCreateTableDdl(tableName string) []map[string]interface{} {
biz.IsTrue(tableName == "", "暂不支持获取pgsql建表DDL")
return nil
func (pm *PgsqlMetadata) GetCreateTableDdl(tableName string) string {
_, err := pm.di.Exec(GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_DDL_KEY))
biz.ErrIsNilAppendErr(err, "创建ddl函数失败: %s")
_, schemaRes, _ := pm.di.SelectData("select current_schema() as schema")
schemaName := schemaRes[0]["schema"].(string)
ddlSql := fmt.Sprintf("select showcreatetable('%s','%s') as sql", schemaName, tableName)
_, res, err := pm.di.SelectData(ddlSql)
biz.ErrIsNilAppendErr(err, "获取表ddl失败: %s")
return res[0]["sql"].(string)
}
func (pm *PgsqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]interface{}, error) {
func (pm *PgsqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int) ([]string, []map[string]any, error) {
return pm.di.SelectData(fmt.Sprintf("SELECT * FROM %s OFFSET %d LIMIT %d", tableName, (pageNum-1)*pageSize, pageSize))
}

View File

@@ -11,7 +11,7 @@ import (
)
// 将结果scan至结构体copy至 sqlx库: https://github.com/jmoiron/sqlx
func scanAll(rows *sql.Rows, dest interface{}, structOnly bool) error {
func scanAll(rows *sql.Rows, dest any, structOnly bool) error {
var v, vp reflect.Value
value := reflect.ValueOf(dest)
@@ -50,7 +50,7 @@ func scanAll(rows *sql.Rows, dest interface{}, structOnly bool) error {
}
if !scannable {
var values []interface{}
var values []any
var m *Mapper = mapper()
fields := m.TraversalsByName(base, columns)
@@ -58,7 +58,7 @@ func scanAll(rows *sql.Rows, dest interface{}, structOnly bool) error {
if f, err := missingFields(fields); err != nil {
return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
}
values = make([]interface{}, len(columns))
values = make([]any, len(columns))
for rows.Next() {
// create a new struct type (which returns PtrTo) and indirect it
@@ -178,7 +178,7 @@ func missingFields(transversals [][]int) (field int, err error) {
// when iterating over many rows. Empty traversals will get an interface pointer.
// Because of the necessity of requesting ptrs or values, it's considered a bit too
// specialized for inclusion in reflectx itself.
func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}, ptrs bool) error {
func fieldsByTraversal(v reflect.Value, traversals [][]int, values []any, ptrs bool) error {
v = reflect.Indirect(v)
if v.Kind() != reflect.Struct {
return errors.New("argument not a struct")
@@ -186,7 +186,7 @@ func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}
for i, traversal := range traversals {
if len(traversal) == 0 {
values[i] = new(interface{})
values[i] = new(any)
continue
}
f := FieldByIndexes(v, traversal)

View File

@@ -7,7 +7,7 @@ import (
type Db interface {
// 分页获取机器信息列表
GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.DbQuery) int64

View File

@@ -11,5 +11,5 @@ type DbSqlExec interface {
DeleteBy(condition *entity.DbSqlExec)
// 分页获取
GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.DbSqlExec, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
}

View File

@@ -17,10 +17,10 @@ func newDbRepo() repository.Db {
}
// 分页获取数据库信息列表
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_db d WHERE 1 = 1 "
values := make([]interface{}, 0)
values := make([]any, 0)
if condition.Host != "" {
sql = sql + " AND d.host LIKE ?"
values = append(values, "%"+condition.Host+"%")
@@ -41,7 +41,7 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
}
func (d *dbRepoImpl) Count(condition *entity.DbQuery) int64 {
where := make(map[string]interface{})
where := make(map[string]any)
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}

View File

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

View File

@@ -19,37 +19,37 @@ type MachineForm struct {
}
type MachineRunForm struct {
MachineId int64 `binding:"required"`
Cmd string `binding:"required"`
MachineId int64 `json:"machineId" binding:"required"`
Cmd string `json:"cmd" binding:"required"`
}
type MachineFileForm struct {
Id uint64
Name string `binding:"required"`
MachineId uint64 `binding:"required"`
Type int `binding:"required"`
Path string `binding:"required"`
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Path string `json:"path" binding:"required"`
}
type MachineScriptForm struct {
Id uint64
Name string `binding:"required"`
MachineId uint64 `binding:"required"`
Type int `binding:"required"`
Description string `binding:"required"`
Params string
Script string `binding:"required"`
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
MachineId uint64 `json:"machineId" binding:"required"`
Type int `json:"type" binding:"required"`
Description string `json:"description" binding:"required"`
Params string `json:"params"`
Script string `json:"script" binding:"required"`
}
type MachineCreateFileForm struct {
Path string `binding:"required"`
Type string `binding:"required"`
Path string `json:"path" binding:"required"`
Type string `json:"type" binding:"required"`
}
type MachineFileUpdateForm struct {
Content string `binding:"required"`
Id uint64 `binding:"required"`
Path string `binding:"required"`
Content string `json:"content" binding:"required"`
Id uint64 `json:"id" binding:"required"`
Path string `json:"path" binding:"required"`
}
// 授权凭证

View File

@@ -8,7 +8,7 @@ import (
)
type AuthCert interface {
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Save(ac *entity.AuthCert)
@@ -29,7 +29,7 @@ type authCertAppImpl struct {
authCertRepo repository.AuthCert
}
func (a *authCertAppImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (a *authCertAppImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return a.authCertRepo.GetPageList(condition, pageParam, toEntity)
}

View File

@@ -30,7 +30,7 @@ type Machine interface {
GetById(id uint64, cols ...string) *entity.Machine
// 分页获取机器信息列表
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 获取机器连接
GetCli(id uint64) *machine.Cli
@@ -52,7 +52,7 @@ type machineAppImpl struct {
}
// 分页获取机器信息列表
func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return m.machineRepo.GetMachineList(condition, pageParam, toEntity, orderBy...)
}

View File

@@ -17,7 +17,7 @@ import (
type MachineFile interface {
// 分页获取机器文件信息列表
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 根据条件获取
GetMachineFile(condition *entity.MachineFile, cols ...string) error
@@ -67,7 +67,7 @@ type machineFileAppImpl struct {
}
// 分页获取机器脚本信息列表
func (m *machineFileAppImpl) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *machineFileAppImpl) GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return m.machineFileRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}

View File

@@ -9,7 +9,7 @@ import (
type MachineScript interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 根据条件获取
GetMachineScript(condition *entity.MachineScript, cols ...string) error
@@ -40,7 +40,7 @@ const Common_Script_Machine_Id = 9999999
// machineScriptRepo: persistence.MachineScriptDao}
// 分页获取机器脚本信息列表
func (m *machineScriptAppImpl) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *machineScriptAppImpl) GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return m.machineScriptRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
}

View File

@@ -6,7 +6,7 @@ import (
)
type AuthCert interface {
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Insert(ac *entity.AuthCert)

View File

@@ -7,7 +7,7 @@ import (
type Machine interface {
// 分页获取机器信息列表
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.MachineQuery) int64

View File

@@ -7,7 +7,7 @@ import (
type MachineFile interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MachineFile, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 根据条件获取
GetMachineFile(condition *entity.MachineFile, cols ...string) error

View File

@@ -7,7 +7,7 @@ import (
type MachineScript interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MachineScript, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 根据条件获取
GetMachineScript(condition *entity.MachineScript, cols ...string) error

View File

@@ -6,7 +6,7 @@ import (
)
type MachineTaskConfig interface {
GetPageList(condition *entity.MachineTaskConfig, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MachineTaskConfig, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
// 根据条件获取
GetBy(condition *entity.MachineTaskConfig, cols ...string) error

View File

@@ -140,7 +140,7 @@ func (c *Cli) GetMachine() *Info {
// 机器客户端连接缓存,指定时间内没有访问则会被关闭
var cliCache = cache.NewTimedCache(constant.MachineConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(_, value interface{}) {
OnEvicted(func(_, value any) {
value.(*Cli).Close()
})
@@ -172,7 +172,7 @@ func DeleteCli(id uint64) {
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *Info) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(_ interface{}) (interface{}, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(_ any) (any, error) {
me := getMachine(machineId)
err := IfUseSshTunnelChangeIpPort(me, getMachine)
if err != nil {

View File

@@ -68,7 +68,7 @@ func TestTemplateRev(t *testing.T) {
//next := temp[ei:]
//key := temp[index+1 : ei-1]
//value := SubString(str, index, UnicodeIndex(str, next))
res := make(map[string]interface{})
res := make(map[string]any)
utils.ReverStrTemplate(temp, str, res)
fmt.Println(res)
}

View File

@@ -57,7 +57,7 @@ func (rec *Recorder) WriteHeader(height, width int) {
}
func (rec *Recorder) WriteData(rectype RecType, data string) {
recData := make([]interface{}, 3)
recData := make([]any, 3)
recData[0] = float64(time.Since(rec.StartTime).Microseconds()) / float64(1000000)
recData[1] = rectype
recData[2] = data

View File

@@ -13,7 +13,7 @@ func newAuthCertRepo() repository.AuthCert {
return new(authCertRepoImpl)
}
func (m *authCertRepoImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *authCertRepoImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, condition, toEntity)
}

View File

@@ -15,10 +15,10 @@ func newMachineRepo() repository.Machine {
}
// 分页获取机器信息列表
func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
sql := "SELECT m.* FROM t_machine m WHERE 1 = 1 "
values := make([]interface{}, 0)
values := make([]any, 0)
if condition.Ip != "" {
sql = sql + " AND m.ip LIKE ?"
values = append(values, "%"+condition.Ip+"%")
@@ -40,7 +40,7 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
}
func (m *machineRepoImpl) Count(condition *entity.MachineQuery) int64 {
where := make(map[string]interface{})
where := make(map[string]any)
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package form
type Mongo struct {
Id uint64
Id uint64 `json:"id"`
Uri string `binding:"required" json:"uri"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `binding:"required" json:"name"`
@@ -10,30 +10,30 @@ type Mongo struct {
}
type MongoCommand struct {
Database string `binding:"required" json:"database"`
Collection string `binding:"required" json:"collection"`
Filter map[string]interface{} `json:"filter"`
Database string `binding:"required" json:"database"`
Collection string `binding:"required" json:"collection"`
Filter map[string]any `json:"filter"`
}
type MongoRunCommand struct {
Database string `binding:"required" json:"database"`
Command map[string]interface{} `json:"command"`
Database string `binding:"required" json:"database"`
Command map[string]any `json:"command"`
}
type MongoFindCommand struct {
MongoCommand
Sort map[string]interface{} `json:"sort"`
Skip int64
Limit int64
Sort map[string]any `json:"sort"`
Skip int64 `json:"skip"`
Limit int64 `json:"limit"`
}
type MongoUpdateByIdCommand struct {
MongoCommand
DocId interface{} `binding:"required" json:"docId"`
Update map[string]interface{} `json:"update"`
DocId any `binding:"required" json:"docId"`
Update map[string]any `json:"update"`
}
type MongoInsertCommand struct {
MongoCommand
Doc map[string]interface{} `json:"doc"`
Doc map[string]any `json:"doc"`
}

View File

@@ -23,7 +23,7 @@ import (
type Mongo interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.MongoQuery) int64
@@ -54,7 +54,7 @@ type mongoAppImpl struct {
}
// 分页获取数据库信息列表
func (d *mongoAppImpl) GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *mongoAppImpl) GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return d.mongoRepo.GetList(condition, pageParam, toEntity, orderBy...)
}
@@ -102,7 +102,7 @@ func (d *mongoAppImpl) GetMongoCli(id uint64) *mongo.Client {
// mongo客户端连接缓存指定时间内没有访问则会被关闭
var mongoCliCache = cache.NewTimedCache(constant.MongoConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
OnEvicted(func(key any, value any) {
global.Log.Info("删除mongo连接缓存: id = ", key)
value.(*MongoInstance).Close()
})
@@ -122,7 +122,7 @@ func init() {
// 获取mongo的连接实例
func GetMongoInstance(mongoId uint64, getMongoEntity func(uint64) *entity.Mongo) (*MongoInstance, error) {
mi, err := mongoCliCache.ComputeIfAbsent(mongoId, func(_ interface{}) (interface{}, error) {
mi, err := mongoCliCache.ComputeIfAbsent(mongoId, func(_ any) (any, error) {
c, err := connect(getMongoEntity(mongoId))
if err != nil {
return nil, err

View File

@@ -7,7 +7,7 @@ import (
type Mongo interface {
// 分页获取列表
GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.MongoQuery) int64

View File

@@ -17,14 +17,14 @@ func newMongoRepo() repository.Mongo {
}
// 分页获取数据库信息列表
func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_mongo d WHERE 1=1 "
if len(condition.TagIds) > 0 {
sql = sql + " AND d.tag_id IN " + fmt.Sprintf("(%s)", strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
}
values := make([]interface{}, 0)
values := make([]any, 0)
if condition.TagPathLike != "" {
values = append(values, condition.TagPathLike+"%")
sql = sql + " AND d.tag_path LIKE ?"
@@ -34,7 +34,7 @@ func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.P
}
func (d *mongoRepoImpl) Count(condition *entity.MongoQuery) int64 {
where := make(map[string]interface{})
where := make(map[string]any)
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}

View File

@@ -1,9 +1,9 @@
package form
type Redis struct {
Id uint64
Id uint64 `json:"id"`
Name string `json:"name"`
Host string `binding:"required" json:"host"`
Host string `json:"host" binding:"required"`
Password string `json:"password"`
Mode string `json:"mode"`
Db string `json:"db"`
@@ -25,34 +25,34 @@ type Expire struct {
type KeyInfo struct {
Key string `binding:"required" json:"key"`
Timed int64
Timed int64 `json:"timed"`
}
type StringValue struct {
KeyInfo
Value interface{} `binding:"required" json:"value"`
Value any `binding:"required" json:"value"`
}
type HashValue struct {
KeyInfo
Value []map[string]interface{} `binding:"required" json:"value"`
Value []map[string]any `binding:"required" json:"value"`
}
type SetValue struct {
KeyInfo
Value []interface{} `binding:"required" json:"value"`
Value []any `binding:"required" json:"value"`
}
type ListValue struct {
KeyInfo
Value []interface{} `binding:"required" json:"value"`
Value []any `binding:"required" json:"value"`
}
// list lset命令参数入参
type ListSetValue struct {
Key string `binding:"required" json:"key"`
Index int64
Value interface{} `binding:"required" json:"value"`
Index int64 `json:"index"`
Value any `binding:"required" json:"value"`
}
type RedisScanForm struct {

View File

@@ -2,6 +2,7 @@ package api
import (
"context"
"fmt"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
@@ -23,7 +24,7 @@ func (r *Redis) Hscan(rc *req.Ctx) {
keySize, err := cmdable.HLen(contextTodo, key).Result()
biz.ErrIsNilAppendErr(err, "hlen err: %s")
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"keys": keys,
"cursor": nextCursor,
"keySize": keySize,
@@ -34,6 +35,7 @@ func (r *Redis) Hdel(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
field := rc.GinCtx.Query("field")
rc.ReqParam = fmt.Sprintf("key=%s, field=%s", key, field)
delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hdel err: %s")
rc.ResData = delRes
@@ -52,6 +54,7 @@ func (r *Redis) Hset(rc *req.Ctx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
rc.ReqParam = hashValue
hv := hashValue.Value[0]
res, err := r.getRedisIns(rc).GetCmdable().HSet(context.TODO(), hashValue.Key, hv["field"].(string), hv["value"]).Result()

View File

@@ -148,7 +148,7 @@ func (r *Redis) Scan(rc *req.Ctx) {
func (r *Redis) TtlKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
ttl, err := ri.GetCmdable().TTL(context.Background(), key).Result()
biz.ErrIsNil(err, "ttl失败: %s")
biz.ErrIsNilAppendErr(err, "ttl失败: %s")
if ttl == -1 {
rc.ResData = -1
@@ -187,3 +187,10 @@ func (r *Redis) PersistKey(rc *req.Ctx) {
rc.ReqParam = fmt.Sprintf("%s -> 移除key[%s]的过期时间", ri.Info.GetLogDesc(), key)
ri.GetCmdable().Persist(context.Background(), key)
}
// 清空库
func (r *Redis) FlushDb(rc *req.Ctx) {
ri := r.getRedisIns(rc)
rc.ReqParam = fmt.Sprintf("%s -> flushdb", ri.Info.GetLogDesc())
ri.GetCmdable().FlushDB(context.Background())
}

View File

@@ -22,7 +22,7 @@ func (r *Redis) GetListValue(rc *req.Ctx) {
res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"len": len,
"list": res,
}

View File

@@ -185,7 +185,7 @@ func (r *Redis) ClusterInfo(rc *req.Ctx) {
}
nodesRes = append(nodesRes, node)
}
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"clusterInfo": info,
"clusterNodes": nodesRes,
}

View File

@@ -49,7 +49,7 @@ func (r *Redis) Sscan(rc *req.Ctx) {
cmd := r.getRedisIns(rc).GetCmdable()
keys, cursor, err := cmd.SScan(context.TODO(), scan.Key, scan.Cursor, scan.Match, scan.Count).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"keys": keys,
"cursor": cursor,
}

View File

@@ -28,7 +28,7 @@ func (r *Redis) ZScan(rc *req.Ctx) {
keys, cursor, err := ri.GetCmdable().ZScan(context.TODO(), key, cursor, match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"keys": keys,
"cursor": cursor,
}

View File

@@ -23,7 +23,7 @@ import (
type Redis interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult
Count(condition *entity.RedisQuery) int64
@@ -55,7 +55,7 @@ type redisAppImpl struct {
}
// 分页获取机器脚本信息列表
func (r *redisAppImpl) GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (r *redisAppImpl) GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
return r.redisRepo.GetRedisList(condition, pageParam, toEntity, orderBy...)
}
@@ -219,13 +219,14 @@ func getRedisSentinelCient(re *entity.Redis, db int) *RedisInstance {
// sentinel模式host为 masterName=host:port,host:port
masterNameAndHosts := strings.Split(re.Host, "=")
sentinelOptions := &redis.FailoverOptions{
MasterName: masterNameAndHosts[0],
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
Password: re.Password, // no password set
DB: db, // use default DB
DialTimeout: 8 * time.Second,
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1,
MasterName: masterNameAndHosts[0],
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
Password: re.Password, // no password set
SentinelPassword: re.Password, // 哨兵节点密码需与redis节点密码一致
DB: db, // use default DB
DialTimeout: 8 * time.Second,
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1,
}
if re.SshTunnelMachineId > 0 {
sentinelOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
@@ -251,7 +252,7 @@ func getRedisDialer(machineId int) func(ctx context.Context, network, addr strin
// redis客户端连接缓存指定时间内没有访问则会被关闭
var redisCache = cache.NewTimedCache(constant.RedisConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
OnEvicted(func(key any, value any) {
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %s", key))
value.(*RedisInstance).Close()
})

View File

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

View File

@@ -17,9 +17,9 @@ func newRedisRepo() repository.Redis {
}
// 分页获取机器信息列表
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult {
sql := "SELECT d.* FROM t_redis d WHERE 1=1 "
values := make([]interface{}, 0)
values := make([]any, 0)
if condition.Host != "" {
sql = sql + " AND d.host LIKE ?"
values = append(values, "%"+condition.Host+"%")
@@ -36,7 +36,7 @@ func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *mo
}
func (r *redisRepoImpl) Count(condition *entity.RedisQuery) int64 {
where := make(map[string]interface{})
where := make(map[string]any)
if len(condition.TagIds) > 0 {
where["tag_id"] = condition.TagIds
}

View File

@@ -91,6 +91,14 @@ func InitRedisRouter(router *gin.RouterGroup) {
Handle(rs.PersistKey)
})
flushDbL := req.NewLogInfo("redis-flushdb").WithSave(true)
redis.DELETE(":id/:db/flushdb", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(flushDbL).
WithRequiredPermission(saveDataP).
Handle(rs.FlushDb)
})
// 获取string类型值
redis.GET(":id/:db/string-value", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.GetStringValue)

View File

@@ -1,15 +1,18 @@
package api
import (
"encoding/json"
"fmt"
"mayfly-go/internal/sys/api/form"
"mayfly-go/internal/sys/api/vo"
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/captcha"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/otp"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"regexp"
@@ -18,6 +21,12 @@ import (
"time"
)
const (
OtpStatusNone = -1 // 未启用otp校验
OtpStatusReg = 1 // 用户otp secret已注册
OtpStatusNoReg = 2 // 用户otp secret未注册
)
type Account struct {
AccountApp application.Account
ResourceApp application.Resource
@@ -33,39 +42,142 @@ func (a *Account) Login(rc *req.Ctx) {
loginForm := &form.LoginForm{}
ginx.BindJsonAndValid(rc.GinCtx, loginForm)
accountLoginSecurity := a.ConfigApp.GetConfig(entity.ConfigKeyAccountLoginSecurity).ToAccountLoginSecurity()
// 判断是否有开启登录验证码校验
if a.ConfigApp.GetConfig(entity.ConfigKeyUseLoginCaptcha).BoolValue(true) {
if accountLoginSecurity.UseCaptcha {
// 校验验证码
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
}
username := loginForm.Username
clientIp := rc.GinCtx.ClientIP()
rc.ReqParam = loginForm.Username
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", loginForm.Username, clientIp)
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", username, clientIp)
originPwd, err := utils.DefaultRsaDecrypt(loginForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
account := &entity.Account{Username: loginForm.Username}
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp")
biz.ErrIsNil(err, "用户名或密码错误")
biz.IsTrue(utils.CheckPwdHash(originPwd, account.Password), "用户名或密码错误")
account := &entity.Account{Username: username}
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
nowFailCount := cache.GetInt(failCountKey)
loginFailCount := accountLoginSecurity.LoginFailCount
loginFailMin := accountLoginSecurity.LoginFailMin
biz.IsTrue(nowFailCount < loginFailCount, "登录失败超过%d次, 请%d分钟后再试", loginFailCount, loginFailMin)
if err != nil || !utils.CheckPwdHash(originPwd, account.Password) {
nowFailCount++
cache.SetStr(failCountKey, strconv.Itoa(nowFailCount), time.Minute*time.Duration(loginFailMin))
panic(biz.NewBizErr(fmt.Sprintf("用户名或密码错误【当前登录失败%d次】", nowFailCount)))
}
biz.IsTrue(account.IsEnable(), "该账号不可用")
// 校验密码强度是否符合
// 校验密码强度(新用户第一次登录密码与账号名一致)
biz.IsTrueBy(CheckPasswordLever(originPwd), biz.NewBizErrCode(401, "您的密码安全等级较低,请修改后重新登录"))
// 保存登录消息
go a.saveLogin(account, clientIp)
rc.ResData = map[string]interface{}{
"token": req.CreateToken(account.Id, account.Username),
res := map[string]any{
"name": account.Name,
"username": account.Username,
"username": username,
"lastLoginTime": account.LastLoginTime,
"lastLoginIp": account.LastLoginIp,
}
// 默认为不校验otp
otpStatus := OtpStatusNone
var token string
// 访问系统使用的token
accessToken := req.CreateToken(account.Id, username)
// 若系统配置中设置开启otp双因素校验则进行otp校验
if accountLoginSecurity.UseOtp {
account.OtpSecretDecrypt()
otpSecret := account.OtpSecret
// 修改状态为已注册
otpStatus = OtpStatusReg
// 该token用于otp双因素校验
token = utils.RandString(32)
// 未注册otp secret或重置了秘钥
if otpSecret == "" || otpSecret == "-" {
otpStatus = OtpStatusNoReg
key, err := otp.NewTOTP(otp.GenerateOpts{
AccountName: username,
Issuer: accountLoginSecurity.OtpIssuer,
})
biz.ErrIsNilAppendErr(err, "otp生成失败: %s")
res["otpUrl"] = key.URL()
otpSecret = key.Secret()
}
// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
otpInfo := &OtpVerifyInfo{
AccountId: account.Id,
Username: username,
OptStatus: otpStatus,
OtpSecret: otpSecret,
AccessToken: accessToken,
}
cache.SetStr(fmt.Sprintf("otp:token:%s", token), utils.ToJsonStr(otpInfo), time.Minute*time.Duration(3))
} else {
// 不进行otp二次校验则直接返回accessToken
token = accessToken
// 保存登录消息
go a.saveLogin(account, clientIp)
}
// 赋值otp状态
res["otp"] = otpStatus
res["token"] = token
rc.ResData = res
}
type OtpVerifyInfo struct {
AccountId uint64
Username string
OptStatus int
AccessToken string
OtpSecret string
}
// OTP双因素校验
func (a *Account) OtpVerify(rc *req.Ctx) {
otpVerify := new(form.OtpVerfiy)
ginx.BindJsonAndValid(rc.GinCtx, otpVerify)
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
otpInfoJson := cache.GetStr(tokenKey)
biz.NotEmpty(otpInfoJson, "otpToken错误或失效, 请重新登陆获取")
otpInfo := new(OtpVerifyInfo)
json.Unmarshal([]byte(otpInfoJson), otpInfo)
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
failCount := cache.GetInt(failCountKey)
biz.IsTrue(failCount < 5, "双因素校验失败超过5次, 请10分钟后再试")
otpStatus := otpInfo.OptStatus
accessToken := otpInfo.AccessToken
accountId := otpInfo.AccountId
otpSecret := otpInfo.OtpSecret
if !otp.Validate(otpVerify.Code, otpSecret) {
cache.SetStr(failCountKey, strconv.Itoa(failCount+1), time.Minute*time.Duration(10))
panic(biz.NewBizErr("双因素认证授权码不正确"))
}
// 如果是未注册状态则更新account表的otpSecret信息
if otpStatus == OtpStatusNoReg {
update := &entity.Account{OtpSecret: otpSecret}
update.Id = accountId
update.OtpSecretEncrypt()
a.AccountApp.Update(update)
}
la := &entity.Account{Username: otpInfo.Username}
la.Id = accountId
go a.saveLogin(la, rc.GinCtx.ClientIP())
cache.Del(tokenKey)
rc.ResData = accessToken
}
// 获取当前登录用户的菜单与权限码
func (a *Account) GetPermissions(rc *req.Ctx) {
account := rc.LoginAccount
@@ -84,7 +196,7 @@ func (a *Account) GetPermissions(rc *req.Ctx) {
}
// 保存该账号的权限codes
req.SavePermissionCodes(account.Id, permissions)
rc.ResData = map[string]interface{}{
rc.ResData = map[string]any{
"menus": menus.ToTrees(0),
"permissions": permissions,
}
@@ -268,20 +380,20 @@ func (a *Account) SaveRoles(rc *req.Ctx) {
// 将,拼接的字符串进行切割
idsStr := strings.Split(form.RoleIds, ",")
var newIds []interface{}
var newIds []any
for _, v := range idsStr {
id, _ := strconv.Atoi(v)
newIds = append(newIds, uint64(id))
}
// 将[]uint64转为[]interface{}
// 将[]uint64转为[]any
oIds := a.RoleApp.GetAccountRoleIds(uint64(form.Id))
var oldIds []interface{}
var oldIds []any
for _, v := range oIds {
oldIds = append(oldIds, v)
}
addIds, delIds, _ := utils.ArrayCompare(newIds, oldIds, func(i1, i2 interface{}) bool {
addIds, delIds, _ := utils.ArrayCompare(newIds, oldIds, func(i1, i2 any) bool {
return i1.(uint64) == i2.(uint64)
})
@@ -296,3 +408,12 @@ func (a *Account) SaveRoles(rc *req.Ctx) {
a.RoleApp.DeleteAccountRole(aid, v.(uint64))
}
}
// 重置otp秘钥
func (a *Account) ResetOtpSecret(rc *req.Ctx) {
account := &entity.Account{OtpSecret: "-"}
accountId := uint64(ginx.PathParamInt(rc.GinCtx, "id"))
account.Id = accountId
rc.ReqParam = fmt.Sprintf("accountId = %d", accountId)
a.AccountApp.Update(account)
}

View File

@@ -7,5 +7,5 @@ import (
func GenerateCaptcha(rc *req.Ctx) {
id, image := captcha.Generate()
rc.ResData = map[string]interface{}{"base64Captcha": image, "cid": id}
rc.ResData = map[string]any{"base64Captcha": image, "cid": id}
}

View File

@@ -1,7 +1,7 @@
package form
type AccountCreateForm struct {
Id uint64
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
Username string `json:"username" binding:"required,min=4,max=16"`
Password string `json:"password"`

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