feat: 新增机器授权凭证管理与其他优化

This commit is contained in:
meilin.huang
2023-03-06 16:59:57 +08:00
parent 7c086bbec8
commit 0695ad9a85
73 changed files with 1666 additions and 811 deletions

View File

@@ -9,17 +9,17 @@
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@element-plus/icons-vue": "^2.1.0",
"asciinema-player": "^3.1.0",
"axios": "^1.3.2",
"axios": "^1.3.4",
"countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.4.0",
"element-plus": "^2.2.32",
"jsencrypt": "^3.2.1",
"element-plus": "^2.2.33",
"jsencrypt": "^3.3.1",
"lodash": "^4.17.21",
"mitt": "^3.0.0",
"monaco-editor": "^0.35.0",
"monaco-editor": "^0.36.1",
"monaco-sql-languages": "^0.11.0",
"monaco-themes": "^0.4.2",
"nprogress": "^0.2.0",
@@ -43,13 +43,13 @@
"@vitejs/plugin-vue": "^2.3.3",
"@vue/compiler-sfc": "^3.0.11",
"dotenv": "^10.0.0",
"eslint": "^8.5.0",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.3.0",
"sass": "^1.58.0",
"sass-loader": "^13.2.0",
"typescript": "^4.7.4",
"vite": "^4.1.1",
"typescript": "^4.9.5",
"vite": "^4.1.4",
"vue-eslint-parser": "^8.0.1"
},
"browserslist": [

View File

@@ -19,24 +19,6 @@ class Api {
this.method = method;
}
/**
* 设置rl
* @param {String} uri 请求url
*/
setUrl(url: string) {
this.url = url;
return this;
}
/**
* url的请求方法
* @param {String} method 请求方法
*/
setMethod(method: string) {
this.method = method;
return this;
}
/**
* 获取权限的完整url
*/

View File

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

View File

@@ -6,14 +6,14 @@ export const imports = {
"Home": () => import('@/views/home/Home.vue'),
'Personal': () => import('@/views/personal/index.vue'),
// machine
"MachineList": () => import('@/views/ops/machine'),
"MachineList": () => import('@/views/ops/machine/MachineList.vue'),
"AuthCertList": () => import('@/views/ops/machine/authcert/AuthCertList.vue'),
// sys
"ResourceList": () => import('@/views/system/resource'),
"RoleList": () => import('@/views/system/role'),
"AccountList": () => import('@/views/system/account'),
"SyslogList": () => import('@/views/system/syslog/SyslogList.vue'),
"ConfigList": () => import('@/views/system/config/ConfigList.vue'),
// tag
"TagTreeList": () => import('@/views/ops/tag/TagTreeList.vue'),
"TeamList": () => import('@/views/ops/tag/TeamList.vue'),

View File

@@ -0,0 +1,61 @@
<template>
<div style="width: 100%">
<el-select @focus="getSshTunnelMachines" @change="change" style="width: 100%" v-model="sshTunnelMachineId"
@clear="clear" placeholder="请选择SSH隧道机器" clearable>
<el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id">
</el-option>
</el-select>
</div>
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { machineApi } from '../machine/api';
const props = defineProps({
modelValue: {
type: Number,
},
})
//定义事件
const emit = defineEmits(['update:modelValue'])
const state = reactive({
// 单选则为id多选为id数组
sshTunnelMachineId: null as any,
sshTunnelMachineList: [] as any,
});
const {
sshTunnelMachineId,
sshTunnelMachineList,
} = toRefs(state)
onMounted(async () => {
if (!props.modelValue || props.modelValue <= 0) {
state.sshTunnelMachineId = null;
} else {
state.sshTunnelMachineId = props.modelValue;
}
await getSshTunnelMachines();
});
const getSshTunnelMachines = async () => {
if (state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
const clear = () => {
state.sshTunnelMachineId = null;
change();
}
const change = () => {
emit('update:modelValue', state.sshTunnelMachineId);
};
</script>
<style lang="scss"></style>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags"
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags" placeholder="请选择关联标签"
:render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
:props="{
value: 'id',

View File

@@ -3,88 +3,85 @@
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
:destroy-on-close="true" width="38%">
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-tabs v-model="tabActiveName">
<el-tab-pane label="基础信息" name="basic">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="别名:" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型:" required>
<el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option key="item.id" label="mysql" value="mysql"> </el-option>
<el-option key="item.id" label="postgres" value="postgres"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="请输入端口"></el-input>
</el-col>
</el-form-item>
<el-form-item prop="username" label="用户名:" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
</el-link>
<el-form-item prop="name" label="别名:" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型:" required>
<el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option key="item.id" label="mysql" value="mysql"> </el-option>
<el-option key="item.id" label="postgres" value="postgres"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="请输入端口"></el-input>
</el-col>
</el-form-item>
<el-form-item prop="username" label="用户名:" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
</el-link>
</template>
</el-popover>
</template>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item prop="params" label="连接参数:">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<template #suffix>
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
:underline="false" type="primary" class="mr5">参数参考</el-link>
</template>
</el-input>
</el-form-item>
<el-form-item prop="database" label="数据库名:" required>
<el-col :span="19">
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
style="width: 100%">
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">
<el-divider direction="vertical" border-style="dashed" />
</el-col>
<el-col :span="4">
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
</el-col>
</el-form-item>
</el-input>
</el-form-item>
<el-form-item prop="database" label="数据库名:" required>
<el-col :span="19">
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
style="width: 100%">
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">
<el-divider direction="vertical" border-style="dashed" />
</el-col>
<el-col :span="4">
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
</el-col>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
</el-tab-pane>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="5" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="16" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="params" label="连接参数:">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<template #suffix>
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
:underline="false" type="primary" class="mr5">参数参考</el-link>
</template>
</el-input>
</el-form-item>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -100,11 +97,11 @@
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { dbApi } from './api';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
const props = defineProps({
visible: {
@@ -170,9 +167,9 @@ const dbForm: any = ref(null);
const state = reactive({
dialogVisible: false,
tabActiveName: 'basic',
allDatabases: [] as any,
databaseList: [] as any,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
@@ -185,13 +182,8 @@ const state = reactive({
password: null,
params: null,
database: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
sshTunnelMachineId: null as any,
},
// 原密码
pwd: '',
@@ -200,9 +192,9 @@ const state = reactive({
const {
dialogVisible,
tabActiveName,
allDatabases,
databaseList,
sshTunnelMachineList,
form,
pwd,
btnLoading,
@@ -213,15 +205,15 @@ watch(props, (newValue: any) => {
if (!state.dialogVisible) {
return;
}
state.tabActiveName = 'basic';
if (newValue.db) {
state.form = { ...newValue.db };
// 将数据库名使用空格切割,获取所有数据库列表
state.databaseList = newValue.db.database.split(' ');
} else {
state.form = { port: 3306, enableSshTunnel: -1 } as any;
state.form = { port: 3306 } as any;
state.databaseList = [];
}
getSshTunnelMachines();
});
/**
@@ -231,13 +223,6 @@ const changeDatabase = () => {
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
};
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
const getAllDatabase = async () => {
const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
@@ -257,6 +242,9 @@ const btnOk = async () => {
if (valid) {
const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
if (!state.form.sshTunnelMachineId) {
reqForm.sshTunnelMachineId = -1;
}
dbApi.saveDb.request(reqForm).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
@@ -287,6 +275,4 @@ const cancel = () => {
}, 500);
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -264,7 +264,7 @@
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.enableSshTunnel == 1 ? '是' : '否' }}
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
</el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
@@ -322,7 +322,6 @@ const state = reactive({
*/
query: {
tagPath: null,
projectId: null,
pageNum: 1,
pageSize: 10,
},

View File

@@ -1,76 +1,73 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true"
:before-close="cancel" width="38%">
:before-close="cancel" width="650px">
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="ip" label="ip:" required>
<el-col :span="18">
<el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off">
</el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item prop="username" label="用户名:" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="authMethod" label="认证方式:" required>
<el-select style="width: 100%" v-model="form.authMethod" placeholder="请选择认证方式">
<el-option key="1" label="Password" :value="1"> </el-option>
<el-option key="2" label="PublicKey" :value="2"> </el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
<el-input type="textarea" :rows="3" v-model="form.password" placeholder="请将私钥文件内容拷贝至此,修改操作可不填">
</el-input>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input type="textarea" v-model="form.remark"></el-input>
</el-form-item>
<el-tabs v-model="tabActiveName">
<el-tab-pane label="基础信息" name="basic">
<el-form-item prop="tagId" label="标签:" required :rules="{
required: true,
message: '请选择标签',
trigger: ['change', 'blur'],
}">
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="ip" label="ip:" required>
<el-col :span="18">
<el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off">
</el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item prop="enableRecorder" label="终端回放:">
<el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
</el-form-item>
<el-form-item prop="username" label="用户名:">
<el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password">
</el-input>
</el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item label="认证方式:">
<el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType"
placeholder="请选认证方式">
<el-option key="1" label="密码" :value="1"> </el-option>
<el-option key="2" label="授权凭证" :value="2"> </el-option>
</el-select>
</el-form-item>
<el-form-item v-if="state.authType == 1" prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码"
autocomplete="new-password">
</el-input>
</el-form-item>
<el-form-item v-if="state.authType == 2" prop="authCertId" label="授权凭证:" required>
<auth-cert-select ref="authCertSelectRef" v-model="form.authCertId" />
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input type="textarea" v-model="form.remark"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="enableRecorder" label="终端回放:">
<el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
</el-form-item>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<div>
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div>
@@ -83,17 +80,14 @@
import { toRefs, reactive, watch, ref } from 'vue';
import { machineApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import AuthCertSelect from './authcert/AuthCertSelect.vue'
const props = defineProps({
visible: {
type: Boolean,
},
projects: {
type: Array,
},
machine: {
type: [Boolean, Object],
},
@@ -106,13 +100,6 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const rules = {
tagId: [
{
required: true,
message: '请选择标签',
trigger: ['change', 'blur'],
},
],
name: [
{
required: true,
@@ -127,50 +114,62 @@ const rules = {
trigger: ['change', 'blur'],
},
],
username: [
authCertId: [
{
required: true,
message: '请输入用户名',
message: '请选择授权凭证',
trigger: ['change', 'blur'],
},
],
authMethod: [
username: [
{
required: true,
message: '请选择认证方式',
message: '请输入授权用户名',
trigger: ['change', 'blur'],
},
],
password: [
{
required: true,
message: '请输入授权密码',
trigger: ['change', 'blur'],
},
],
}
const machineForm: any = ref(null);
const authCertSelectRef: any = ref(null);
const state = reactive({
dialogVisible: false,
tabActiveName: 'basic',
sshTunnelMachineList: [] as any,
authCerts: [] as any,
authType: 1,
form: {
id: null,
tagId: null as any,
tagPath: '',
ip: null,
name: null,
authMethod: 1,
port: 22,
name: null,
authCertId: null as any,
username: '',
password: '',
tagId: null as any,
tagPath: null as any,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
sshTunnelMachineId: null as any,
enableRecorder: -1,
},
pwd: '',
testConnBtnLoading: false,
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
tabActiveName,
form,
pwd,
testConnBtnLoading,
btnLoading,
} = toRefs(state)
@@ -179,53 +178,65 @@ watch(props, async (newValue: any) => {
if (!state.dialogVisible) {
return;
}
state.tabActiveName = 'basic';
if (newValue.machine) {
state.form = { ...newValue.machine };
// 如果凭证类型为公共的,则表示使用授权凭证认证
const authCertId = (state.form as any).authCertId
if (authCertId > 0) {
state.authType = 2;
} else {
state.authType = 1;
}
} else {
state.form = { port: 22, authMethod: 1 } as any;
state.form = { port: 22 } as any;
state.authType = 1;
}
getSshTunnelMachines();
});
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
const changeAuthMethod = (val: any) => {
if (state.form.id) {
if (val == 2) {
state.form.authCertId = null;
} else {
state.form.password = '';
}
}
};
}
const getSshTunnelMachine = (machineId: any) => {
notBlank(machineId, '请选择或先创建一台隧道机器');
return state.sshTunnelMachineList.find((x: any) => x.id == machineId);
};
const getPwd = async () => {
state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id });
};
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
}
const testConn = async () => {
machineForm.value.validate(async (valid: boolean) => {
if (valid) {
const form: any = state.form;
if (form.enableSshTunnel == 1) {
const tunnelMachine: any = getSshTunnelMachine(form.sshTunnelMachineId);
if (tunnelMachine.ip == form.ip && tunnelMachine.port == form.port) {
ElMessage.error('隧道机器不能与本机器一致');
return;
}
const form = getReqForm();
if (!form) {
return;
}
const reqForm: any = { ...form };
if (reqForm.authMethod == 1) {
reqForm.password = await RsaEncrypt(state.form.password);
state.testConnBtnLoading = true;
try {
await machineApi.testConn.request(form);
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
ElMessage.error('请正确填写信息');
return false;
}
});
}
const btnOk = async () => {
machineForm.value.validate(async (valid: boolean) => {
if (valid) {
const form = getReqForm();
if (!form) {
return;
}
state.btnLoading = true;
try {
await machineApi.saveMachine.request(reqForm);
await machineApi.saveMachine.request(form);
ElMessage.success('保存成功');
emit('val-change', state.form);
emit('val-change', form);
cancel();
} finally {
state.btnLoading = false;
@@ -237,11 +248,22 @@ const btnOk = async () => {
});
};
const getReqForm = () => {
const reqForm: any = { ...state.form };
debugger
// 如果为密码认证则置空授权凭证id
if (state.authType == 1) {
reqForm.authCertId = -1;
}
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
reqForm.sshTunnelMachineId = -1
}
return reqForm
}
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -15,8 +15,8 @@
</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-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>
@@ -38,14 +38,19 @@
</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}`}}
{{ `${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"
@@ -54,7 +59,7 @@
@change="changeStatus(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></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">
@@ -66,13 +71,13 @@
</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-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-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>
@@ -90,8 +95,8 @@
</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-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">
@@ -128,14 +133,13 @@
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
<el-descriptions-item :span="1" label="认证方式">{{
infoDialog.data.authMethod == 1 ? 'Password' :
'PublicKey'
}}</el-descriptions-item>
<el-descriptions-item :span="1" label="认证方式">
{{ infoDialog.data.authCertId > 1 ? '授权凭证' : '密码' }}
</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.enableSshTunnel == 1 ? '是' : '否' }}
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
</el-descriptions-item>
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }}
</el-descriptions-item>

View File

@@ -26,19 +26,27 @@
<el-col :span="5">
<el-input v-model="param.model" placeholder="内容中用{{.model}}替换"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<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-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="4">
<el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="2">
<el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
</el-col>
@@ -172,6 +180,4 @@ const cancel = () => {
state.params = [];
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -10,6 +10,7 @@ export const machineApi = {
// 终止进程
killProcess: Api.create("/machines/{id}/process", 'delete'),
closeCli: Api.create("/machines/{id}/close-cli", 'delete'),
testConn: Api.create("/machines/test-conn", 'post'),
// 保存按钮
saveMachine: Api.create("/machines", 'post'),
// 调整状态
@@ -36,3 +37,10 @@ export const machineApi = {
terminal: Api.create("/api/machines/{id}/terminal", 'get'),
recDirNames: Api.create("/machines/rec/names", 'get')
}
export const authCertApi = {
baseList : Api.create("/sys/authcerts/base", 'get'),
list: Api.create("/sys/authcerts", 'get'),
save: Api.create("/sys/authcerts", 'post'),
delete: Api.create("/sys/authcerts/{id}", 'delete'),
}

View File

@@ -0,0 +1,126 @@
<template>
<div>
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px"
:destroy-on-close="true">
<el-form ref="acForm" :rules="rules" :model="form" label-width="90px">
<el-form-item prop="name" label="名称:" required>
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item prop="authMethod" label="认证方式:" required>
<el-select style="width: 100%" v-model="form.authMethod" placeholder="请选择认证方式">
<el-option key="1" label="密码" :value="1"> </el-option>
<el-option key="2" label="密钥" :value="2"> </el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
<el-input type="password" show-password clearable v-model.trim="form.password" placeholder="请输入密码"
autocomplete="new-password">
</el-input>
</el-form-item>
<el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
<el-input type="textarea" :rows="5" v-model="form.password" placeholder="请将私钥文件内容拷贝至此">
</el-input>
</el-form-item>
<el-form-item v-if="form.authMethod == 2" prop="passphrase" label="秘钥密码:">
<el-input type="password" v-model="form.passphrase">
</el-input>
</el-form-item>
<el-form-item label="备注:">
<el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { authCertApi } from '../api';
const props = defineProps({
visible: {
type: Boolean,
},
data: {
type: [Boolean, Object],
},
title: {
type: String,
},
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const acForm: any = ref(null);
const rules = {
name: [
{
required: true,
message: '授权凭证名称不能为空',
trigger: ['change', 'blur'],
},
]
}
const state = reactive({
dvisible: false,
params: [] as any,
form: {
id: null,
name: '',
authMethod: 1,
password: '',
passphrase: '',
remark: '',
},
btnLoading: false,
});
const {
dvisible,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dvisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
} else {
state.form = { authMethod: 1 } as any;
state.params = [];
}
});
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
// 若父组件有取消事件,则调用
emit('cancel');
};
const btnOk = async () => {
acForm.value.validate(async (valid: boolean) => {
if (valid) {
state.btnLoading = true;
try {
await authCertApi.save.request(state.form);
emit('val-change', state.form);
cancel();
} finally {
state.btnLoading = false;
}
}
});
};
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,159 @@
<template>
<div class="role-list">
<el-card>
<div>
<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>
<el-button :disabled="chooseId == null" @click="deleteAc(chooseData)" type="danger" icon="delete">删除
</el-button>
<div style="float: right">
<el-select v-model="query.type" placeholder="请选择标签" @clear="search" filterable clearable>
<el-option label="" value="item"> </el-option>
</el-select>
<el-input class="ml5" placeholder="请输入凭证名称" style="width: 150px" v-model="query.name" @clear="search"
plain clearable></el-input>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
</div>
<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" type="primary" 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>
<auth-cert-edit :title="editor.title" v-model:visible="editor.visible" :data="editor.authcert"
@val-change="editChange" />
</div>
</template>
<script lang="ts" setup>
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';
const state = reactive({
query: {
pageNum: 1,
pageSize: 10,
name: null,
type: null,
},
total: 0,
authcerts: [],
chooseId: null,
chooseData: null,
paramsDialog: {
visible: false,
config: null as any,
params: {},
paramsFormItem: [] as any,
},
editor: {
title: '授权凭证保存',
visible: false,
authcert: {},
},
});
const {
query,
total,
authcerts,
chooseId,
chooseData,
editor,
} = toRefs(state)
onMounted(() => {
search();
});
const search = async () => {
let res = await authCertApi.list.request(state.query);
state.authcerts = res.list;
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
const editChange = () => {
ElMessage.success('保存成功');
state.chooseId = null;
state.chooseData = null;
search();
};
const edit = (data: any) => {
if (data) {
state.editor.authcert = data;
} else {
state.editor.authcert = false;
}
state.editor.visible = true;
};
const deleteAc = async (data: any) => {
try {
await ElMessageBox.confirm(`确定删除该授权凭证?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await authCertApi.delete.request({ id: data.id });
ElMessage.success('删除成功');
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) { }
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,63 @@
<template>
<div style="width: 100%">
<el-select @change="changeValue" v-model="id" filterable placeholder="请选择授权凭证,可前往[机器管理->授权凭证]添加"
style="width: 100%">
<el-option v-for="ac in acs" :key="ac.id" :value="ac.id" :label="ac.name">
<el-tag v-if="ac.authMethod == 1" type="success" size="small">密码</el-tag>
<el-tag v-if="ac.authMethod == 2" type="primary" size="small">密钥</el-tag>
<el-divider direction="vertical" border-style="dashed" />
{{ ac.name }}
<el-divider direction="vertical" border-style="dashed" />
{{ ac.remark }}
</el-option>
</el-select>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onMounted } from 'vue';
import { authCertApi } from '../api';
//定义事件
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
modelValue: {
type: [Number],
required: true,
},
})
const state = reactive({
acs: [] as any,
id: null as any,
})
const {
acs,
id,
} = toRefs(state)
onMounted(async () => {
await getAcs();
if (props.modelValue) {
state.id = props.modelValue;
}
})
const changeValue = (val: any) => {
emit('update:modelValue', val);
emit('change', val);
}
const getAcs = async () => {
const acs = await authCertApi.baseList.request({ pageSize: 100, type: 2 })
state.acs = acs.list;
}
</script>
<style lang="scss"></style>

View File

@@ -1,34 +1,29 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
width="38%" :destroy-on-close="true">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="38%"
:destroy-on-close="true">
<el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-tabs v-model="tabActiveName">
<el-tab-pane label="基础信息" name="basic">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="uri" label="uri" required>
<el-input type="textarea" :rows="2" v-model.trim="form.uri"
placeholder="形如 mongodb://username:password@host1:port1" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="uri" label="uri" required>
<el-input type="textarea" :rows="2" v-model.trim="form.uri"
placeholder="形如 mongodb://username:password@host1:port1" auto-complete="off"></el-input>
</el-form-item>
</el-tab-pane>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -44,9 +39,9 @@
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { mongoApi } from './api';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import TagSelect from '../component/TagSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
const props = defineProps({
visible: {
@@ -90,13 +85,12 @@ const rules = {
const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
tabActiveName: 'basic',
form: {
id: null,
name: null,
uri: null,
enableSshTunnel: -1,
sshTunnelMachineId: null,
sshTunnelMachineId: null as any,
tagId: null as any,
tagPath: null as any,
},
@@ -105,7 +99,7 @@ const state = reactive({
const {
dialogVisible,
sshTunnelMachineList,
tabActiveName,
form,
btnLoading,
} = toRefs(state)
@@ -115,25 +109,21 @@ watch(props, async (newValue: any) => {
if (!state.dialogVisible) {
return;
}
state.tabActiveName = 'basic';
if (newValue.mongo) {
state.form = { ...newValue.mongo };
} else {
state.form = { db: 0 } as any;
}
getSshTunnelMachines();
});
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
const reqForm = { ...state.form };
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
reqForm.sshTunnelMachineId = -1
}
// reqForm.uri = await RsaEncrypt(reqForm.uri);
mongoApi.saveMongo.request(reqForm).then(() => {
ElMessage.success('保存成功');
@@ -157,6 +147,4 @@ const cancel = () => {
emit('cancel');
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -14,6 +14,7 @@
<template #default>
<el-form class="instances-pop-form" label-width="50px" :size="'small'">
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
<el-form-item label="模式:">{{ data.params.mode }}</el-form-item>
<el-form-item label="链接:">{{ data.params.host }}</el-form-item>
<el-form-item label="备注:">{{
data.params.remark

View File

@@ -3,59 +3,57 @@
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
:destroy-on-close="true" width="38%">
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入redis名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="mode" label="mode:" required>
<el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
<el-option label="standalone" value="standalone"> </el-option>
<el-option label="cluster" value="cluster"> </el-option>
<el-option label="sentinel" value="sentinel"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-input v-model.trim="form.host"
placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割"
auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码, 修改操作可不填"
autocomplete="new-password"><template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
</el-popover>
</template></el-input>
</el-form-item>
<el-form-item prop="db" label="库号:" required>
<el-select @change="changeDb" v-model="dbList" multiple allow-create filterable
placeholder="请选择可操作库号" style="width: 100%">
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db"
:label="db" :value="db" />
</el-select>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option v-for="item in sshTunnelMachineList as any" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
</el-form-item>
<el-tabs v-model="tabActiveName">
<el-tab-pane label="基础信息" name="basic">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model.trim="form.name" placeholder="请输入redis名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="mode" label="mode:" required>
<el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
<el-option label="standalone" value="standalone"> </el-option>
<el-option label="cluster" value="cluster"> </el-option>
<el-option label="sentinel" value="sentinel"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-input v-model.trim="form.host"
placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割"
auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input type="password" show-password v-model.trim="form.password"
placeholder="请输入密码, 修改操作可不填" autocomplete="new-password"><template
v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary"
class="mr5">原密码</el-link>
</template>
</el-popover>
</template></el-input>
</el-form-item>
<el-form-item prop="db" label="库号:" required>
<el-select @change="changeDb" v-model="dbList" multiple allow-create filterable
placeholder="请选择可操作库号" style="width: 100%">
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db"
:label="db" :value="db" />
</el-select>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -71,10 +69,10 @@
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { redisApi } from './api';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
const props = defineProps({
visible: {
@@ -91,20 +89,6 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'val-change', 'cancel'])
const rules = {
projectId: [
{
required: true,
message: '请选择项目',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
trigger: ['change', 'blur'],
},
],
host: [
{
required: true,
@@ -131,7 +115,7 @@ const rules = {
const redisForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [],
tabActiveName: 'basic',
form: {
id: null,
tagId: null as any,
@@ -141,13 +125,8 @@ const state = reactive({
host: '',
password: null,
db: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
sshTunnelMachineId: -1,
},
dbList: [0],
pwd: '',
@@ -157,7 +136,7 @@ const state = reactive({
const {
dialogVisible,
sshTunnelMachineList,
tabActiveName,
form,
dbList,
pwd,
@@ -169,14 +148,14 @@ watch(props, async (newValue: any) => {
if (!state.dialogVisible) {
return;
}
state.tabActiveName = 'basic';
if (newValue.redis) {
state.form = { ...newValue.redis };
convertDb(state.form.db);
} else {
state.form = { db: '0', enableSshTunnel: -1 } as any;
state.form = { db: '0' } as any;
state.dbList = [];
}
getSshTunnelMachines();
});
const convertDb = (db: string) => {
@@ -190,13 +169,6 @@ const changeDb = () => {
state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(',');
};
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
const getPwd = async () => {
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
};
@@ -209,6 +181,9 @@ const btnOk = async () => {
ElMessage.error('sentinel模式host需为: mastername=sentinelhost:sentinelport模式');
return;
}
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
reqForm.sshTunnelMachineId = -1
}
reqForm.password = await RsaEncrypt(reqForm.password);
redisApi.saveRedis.request(reqForm).then(() => {
ElMessage.success('保存成功');
@@ -232,6 +207,4 @@ const cancel = () => {
emit('cancel');
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -137,7 +137,7 @@
<el-descriptions-item :span="3" label="库">{{ detailDialog.data.db }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.enableSshTunnel == 1 ? '是' : '否' }}
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
</el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(detailDialog.data.createTime) }}

View File

@@ -63,7 +63,7 @@
<el-dialog width="500px" :title="showTagDialog.title" :before-close="closeTagDialog"
v-model="showTagDialog.visible">
<el-form label-width="70px">
<el-form-item prop="project" label="标签:">
<el-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"

View File

@@ -18,19 +18,27 @@
<el-col :span="5">
<el-input v-model="param.model" placeholder="model"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<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-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="4">
<el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<span :span="1">
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-col :span="2">
<el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
</el-col>
@@ -143,6 +151,4 @@ const btnOk = async () => {
});
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -19,7 +19,7 @@
<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.createTime) }}
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" min-width="60px" show-overflow-tooltip></el-table-column>

View File

@@ -19,16 +19,16 @@
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz"
integrity sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==
"@element-plus/icons-vue@^2.0.10":
version "2.0.10"
resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.0.10.tgz#60808d613c3dbdad025577022be8a972739ade21"
integrity sha512-ygEZ1mwPjcPo/OulhzLE7mtDrQBWI8vZzEWSNB2W/RNCRjoQGwbaK4N8lV4rid7Ts4qvySU3njMN7YCiSlSaTQ==
"@element-plus/icons-vue@^2.0.6":
version "2.0.9"
resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.0.9.tgz"
integrity sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ==
"@element-plus/icons-vue@^2.1.0":
version "2.1.0"
resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.1.0.tgz#7ad90d08a8c0d5fd3af31c4f73264ca89614397a"
integrity sha512-PSBn3elNoanENc1vnCfh+3WA9fimRC7n+fWkf3rE5jvv+aBohNHABC/KAR5KWPecxWxDTVT1ERpRbOMRcOV/vA==
"@esbuild/android-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
@@ -139,21 +139,26 @@
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
"@eslint/eslintrc@^1.0.5":
version "1.0.5"
resolved "https://registry.npmmirror.com/@eslint/eslintrc/download/@eslint/eslintrc-1.0.5.tgz"
integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==
"@eslint/eslintrc@^2.0.0":
version "2.0.0"
resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.0.0.tgz#943309d8697c52fc82c076e90c1c74fbbe69dbff"
integrity sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.2.0"
globals "^13.9.0"
ignore "^4.0.6"
espree "^9.4.0"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.0.4"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.35.0":
version "8.35.0"
resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.35.0.tgz#b7569632b0b788a0ca0e438235154e45d42813a7"
integrity sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==
"@floating-ui/core@^1.0.1":
version "1.0.1"
resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-1.0.1.tgz"
@@ -166,14 +171,19 @@
dependencies:
"@floating-ui/core" "^1.0.1"
"@humanwhocodes/config-array@^0.9.2":
version "0.9.2"
resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/download/@humanwhocodes/config-array-0.9.2.tgz"
integrity sha1-aL5VxzcCMAnfxf4kXVEYG7ZHaRQ=
"@humanwhocodes/config-array@^0.11.8":
version "0.11.8"
resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==
dependencies:
"@humanwhocodes/object-schema" "^1.2.1"
debug "^4.1.1"
minimatch "^3.0.4"
minimatch "^3.0.5"
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^1.2.1":
version "1.2.1"
@@ -193,7 +203,7 @@
resolved "https://registry.nlark.com/@nodelib/fs.stat/download/@nodelib/fs.stat-2.0.5.tgz?cache=0&sync_timestamp=1622792616417&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40nodelib%2Ffs.stat%2Fdownload%2F%40nodelib%2Ffs.stat-2.0.5.tgz"
integrity sha1-W9Jir5Tp0lvR5xsF3u1Eh2oiLos=
"@nodelib/fs.walk@^1.2.3":
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
version "1.2.8"
resolved "https://registry.nlark.com/@nodelib/fs.walk/download/@nodelib/fs.walk-1.2.8.tgz"
integrity sha1-6Vc36LtnRt3t9pxVaVNJTxlv5po=
@@ -508,16 +518,21 @@
dependencies:
vue-demi "*"
acorn-jsx@^5.3.1:
acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.nlark.com/acorn-jsx/download/acorn-jsx-5.3.2.tgz"
integrity sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.7.0:
version "8.7.0"
resolved "https://registry.npmmirror.com/acorn/download/acorn-8.7.0.tgz"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
acorn@^8.8.0:
version "8.8.2"
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.npmmirror.com/ajv/download/ajv-6.12.6.tgz?cache=0&sync_timestamp=1637522259668&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fajv%2Fdownload%2Fajv-6.12.6.tgz"
@@ -528,11 +543,6 @@ ajv@^6.10.0, ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.nlark.com/ansi-colors/download/ansi-colors-4.1.1.tgz"
integrity sha1-y7muJWv3UK8eqzRPIpqif+lLo0g=
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.nlark.com/ansi-regex/download/ansi-regex-5.0.1.tgz"
@@ -586,10 +596,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.2:
version "1.3.2"
resolved "https://registry.npmmirror.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3"
integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==
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==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
@@ -782,10 +792,10 @@ echarts@^5.4.0:
tslib "2.3.0"
zrender "5.4.0"
element-plus@^2.2.32:
version "2.2.32"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.32.tgz#e7e6757a66ed158363a86b5c9fc10dfbc0096783"
integrity sha512-DTJMhYOy6MApbmh6z/95hPTK5WrBiNHGzV4IN+uEkup1WoimQ+Qyt8RxKdTe/X1LWEJ8YgWv/Cl8P4ocrt5z5g==
element-plus@^2.2.33:
version "2.2.33"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.33.tgz#30fe0db427dba42eb60a0ad8177f2d5c90435a92"
integrity sha512-E/PmMnv4+4I9Ue0ZDfx2gGgGj4iBlTCWcES3o4jxfYjayFlcQO3UvElJzhaJZ8vDm9yfNN7t2w/xYOhsSYCNNg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.0.6"
@@ -803,13 +813,6 @@ element-plus@^2.2.32:
memoize-one "^6.0.0"
normalize-wheel-es "^1.2.0"
enquirer@^2.3.5:
version "2.3.6"
resolved "https://registry.npm.taobao.org/enquirer/download/enquirer-2.3.6.tgz"
integrity sha1-Kn/l3WNKHkElqXXsmU/1RW3Dc00=
dependencies:
ansi-colors "^4.1.1"
esbuild@^0.16.14:
version "0.16.17"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
@@ -874,10 +877,10 @@ eslint-scope@^6.0.0:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-scope@^7.1.0:
version "7.1.0"
resolved "https://registry.npmmirror.com/eslint-scope/download/eslint-scope-7.1.0.tgz?cache=0&sync_timestamp=1637466831846&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-scope%2Fdownload%2Feslint-scope-7.1.0.tgz"
integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==
eslint-scope@^7.1.1:
version "7.1.1"
resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
@@ -899,51 +902,58 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0:
resolved "https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-3.1.0.tgz?cache=0&sync_timestamp=1636378395014&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-3.1.0.tgz"
integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==
eslint@^8.5.0:
version "8.6.0"
resolved "https://registry.npmmirror.com/eslint/download/eslint-8.6.0.tgz"
integrity sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw==
eslint-visitor-keys@^3.3.0:
version "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@^8.35.0:
version "8.35.0"
resolved "https://registry.npmmirror.com/eslint/-/eslint-8.35.0.tgz#fffad7c7e326bae606f0e8f436a6158566d42323"
integrity sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==
dependencies:
"@eslint/eslintrc" "^1.0.5"
"@humanwhocodes/config-array" "^0.9.2"
"@eslint/eslintrc" "^2.0.0"
"@eslint/js" "8.35.0"
"@humanwhocodes/config-array" "^0.11.8"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
enquirer "^2.3.5"
escape-string-regexp "^4.0.0"
eslint-scope "^7.1.0"
eslint-scope "^7.1.1"
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.1.0"
espree "^9.3.0"
esquery "^1.4.0"
eslint-visitor-keys "^3.3.0"
espree "^9.4.0"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^6.0.1"
globals "^13.6.0"
ignore "^4.0.6"
find-up "^5.0.0"
glob-parent "^6.0.2"
globals "^13.19.0"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
js-sdsl "^4.1.4"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.0.4"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.1"
progress "^2.0.0"
regexpp "^3.2.0"
semver "^7.2.1"
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^9.0.0, espree@^9.2.0, espree@^9.3.0:
espree@^9.0.0:
version "9.3.0"
resolved "https://registry.npmmirror.com/espree/download/espree-9.3.0.tgz"
integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==
@@ -952,6 +962,15 @@ espree@^9.0.0, espree@^9.2.0, espree@^9.3.0:
acorn-jsx "^5.3.1"
eslint-visitor-keys "^3.1.0"
espree@^9.4.0:
version "9.4.1"
resolved "https://registry.npmmirror.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd"
integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
esquery@^1.4.0:
version "1.4.0"
resolved "https://registry.nlark.com/esquery/download/esquery-1.4.0.tgz"
@@ -959,6 +978,13 @@ esquery@^1.4.0:
dependencies:
estraverse "^5.1.0"
esquery@^1.4.2:
version "1.5.0"
resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
dependencies:
estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.npm.taobao.org/esrecurse/download/esrecurse-4.3.0.tgz"
@@ -1038,6 +1064,14 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.nlark.com/flat-cache/download/flat-cache-3.0.4.tgz"
@@ -1092,10 +1126,10 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.1:
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.npmmirror.com/glob-parent/download/glob-parent-6.0.2.tgz?cache=0&sync_timestamp=1632953697891&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fglob-parent%2Fdownload%2Fglob-parent-6.0.2.tgz"
integrity sha1-bSN9mQg5UMeSkPJMdkKj3poo+eM=
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
@@ -1111,10 +1145,10 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^13.6.0, globals@^13.9.0:
version "13.12.0"
resolved "https://registry.npmmirror.com/globals/download/globals-13.12.0.tgz"
integrity sha1-TXM3YDBCMKAILtluIeXFZfiYCJ4=
globals@^13.19.0:
version "13.20.0"
resolved "https://registry.npmmirror.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82"
integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==
dependencies:
type-fest "^0.20.2"
@@ -1137,6 +1171,11 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
grapheme-splitter@^1.0.4:
version "1.0.4"
resolved "https://registry.npmmirror.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
has-flag@^4.0.0:
version "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"
@@ -1149,16 +1188,16 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.npmmirror.com/ignore/download/ignore-4.0.6.tgz?cache=0&sync_timestamp=1635926740448&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fignore%2Fdownload%2Fignore-4.0.6.tgz"
integrity sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=
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"
integrity sha1-nsGly+jhRG7GDUQgBg1Dqm5zgvs=
ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
immutable@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/immutable/download/immutable-4.0.0.tgz?cache=0&sync_timestamp=1633650644342&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fimmutable%2Fdownload%2Fimmutable-4.0.0.tgz"
@@ -1221,11 +1260,21 @@ is-number@^7.0.0:
resolved "https://registry.nlark.com/is-number/download/is-number-7.0.0.tgz"
integrity sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=
is-path-inside@^3.0.3:
version "3.0.3"
resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
js-sdsl@^4.1.4:
version "4.3.0"
resolved "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"
integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.npmmirror.com/js-yaml/download/js-yaml-4.1.0.tgz"
@@ -1233,10 +1282,10 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsencrypt@^3.2.1:
version "3.2.1"
resolved "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.2.1.tgz"
integrity sha512-k1sD5QV0KPn+D8uG9AdGzTQuamt82QZ3A3l6f7TRwMU6Oi2Vg0BsL+wZIQBONcraO1pc78ExMdvmBBJ8WhNYUA==
jsencrypt@^3.3.1:
version "3.3.2"
resolved "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.3.2.tgz#b0f1a2278810c7ba1cb8957af11195354622df7c"
integrity sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==
json-schema-traverse@^0.4.1:
version "0.4.1"
@@ -1261,6 +1310,13 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz"
@@ -1332,15 +1388,22 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.0.5, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
mitt@^3.0.0:
version "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.35.0:
version "0.35.0"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.35.0.tgz#49c4220c815262a900dacf0ae8a59bef66efab8b"
integrity sha512-BJfkAZ0EJ7JgrgWzqjfBNP9hPSS8NlfECEDMEIIiozV2UaPq22yeuOjgbd3TwMh3anH0krWZirXZfn8KUSxiOA==
monaco-editor@^0.36.1:
version "0.36.1"
resolved "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.36.1.tgz#aad528c815605307473a1634612946921d8079b5"
integrity sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg==
monaco-sql-languages@^0.11.0:
version "0.11.0"
@@ -1430,6 +1493,20 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/parent-module/download/parent-module-1.0.1.tgz"
@@ -1437,6 +1514,11 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.nlark.com/path-is-absolute/download/path-is-absolute-1.0.1.tgz"
@@ -1495,11 +1577,6 @@ prettier@^2.3.0:
resolved "https://registry.npmmirror.com/prettier/download/prettier-2.5.1.tgz"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.nlark.com/progress/download/progress-2.0.3.tgz"
integrity sha1-foz42PW48jnBvGi+tOt4Vn1XLvg=
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -1617,7 +1694,7 @@ select@^1.1.2:
resolved "https://registry.nlark.com/select/download/select-1.1.2.tgz"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
semver@^7.2.1, semver@^7.3.5:
semver@^7.3.5:
version "7.3.5"
resolved "https://registry.nlark.com/semver/download/semver-7.3.5.tgz?cache=0&sync_timestamp=1618846864940&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsemver%2Fdownload%2Fsemver-7.3.5.tgz"
integrity sha1-C2Ich5NI2JmOSw5L6Us/EuYBjvc=
@@ -1751,10 +1828,10 @@ type-fest@^0.20.2:
resolved "https://registry.npmmirror.com/type-fest/download/type-fest-0.20.2.tgz"
integrity sha1-G/IH9LKPkVg2ZstfvTJ4hzAc1fQ=
typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.npmmirror.com/typescript/-/typescript-4.7.4.tgz"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
typescript@^4.9.5:
version "4.9.5"
resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
uri-js@^4.2.2:
version "4.4.1"
@@ -1763,15 +1840,10 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.nlark.com/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz"
integrity sha1-LeGWGMZtwkfc+2+ZM4A12CRaLO4=
vite@^4.1.1:
version "4.1.1"
resolved "https://registry.npmmirror.com/vite/-/vite-4.1.1.tgz#3b18b81a4e85ce3df5cbdbf4c687d93ebf402e6b"
integrity sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==
vite@^4.1.4:
version "4.1.4"
resolved "https://registry.npmmirror.com/vite/-/vite-4.1.4.tgz#170d93bcff97e0ebc09764c053eebe130bfe6ca0"
integrity sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==
dependencies:
esbuild "^0.16.14"
postcss "^8.4.21"
@@ -1862,6 +1934,11 @@ yallist@^4.0.0:
resolved "https://registry.nlark.com/yallist/download/yallist-4.0.0.tgz"
integrity sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zrender@5.4.0:
version "5.4.0"
resolved "https://registry.npmmirror.com/zrender/-/zrender-5.4.0.tgz#d4f76e527b2e3bbd7add2bdaf27a16af85785576"

View File

@@ -6,7 +6,7 @@ require (
github.com/gin-gonic/gin v1.9.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.7
github.com/mojocn/base64Captcha v1.3.5 //
@@ -15,11 +15,11 @@ require (
github.com/sirupsen/logrus v1.9.0
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.mongodb.org/mongo-driver v1.11.1 // mongo
golang.org/x/crypto v0.6.0 // ssh
golang.org/x/crypto v0.7.0 // ssh
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.4.5
gorm.io/gorm v1.24.3
gorm.io/gorm v1.24.6
)
require (
@@ -55,10 +55,10 @@ require (
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

View File

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

View File

@@ -23,6 +23,5 @@ type SelectDataDbVO struct {
Modifier *string `json:"modifier"`
ModifierId *int64 `json:"modifierId"`
EnableSshTunnel *int8 `json:"enableSshTunnel"`
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"`
SshTunnelMachineId int `json:"sshTunnelMachineId"`
}

View File

@@ -86,7 +86,7 @@ func (d *dbAppImpl) Save(dbEntity *entity.Db) {
}
// 查找是否存在该库
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, TagId: dbEntity.TagId}
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, Username: dbEntity.Username}
err := d.GetDbBy(oldDb)
if dbEntity.Id == 0 {
@@ -228,8 +228,7 @@ type DbInfo struct {
Username string
TagPath string
Database string
EnableSshTunnel int8 // 是否启用ssh隧道
SshTunnelMachineId uint64
SshTunnelMachineId int
}
// 获取记录日志的描述
@@ -306,7 +305,7 @@ var dbCache = cache.NewTimedCache(constant.DbConnExpireTime, 5*time.Second).
})
func init() {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId uint64) bool {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId int) bool {
// 遍历所有db连接实例若存在db实例使用该ssh隧道机器则返回true表示还在使用中...
items := dbCache.Items()
for _, v := range items {

View File

@@ -14,7 +14,7 @@ import (
func getMysqlDB(d *entity.Db, db string) (*sql.DB, error) {
// SSH Conect
if d.EnableSshTunnel == 1 && d.SshTunnelMachineId != 0 {
if d.SshTunnelMachineId > 0 {
sshTunnelMachine := machineapp.GetMachineApp().GetSshTunnelMachine(d.SshTunnelMachineId)
mysql.RegisterDialContext(d.Network, func(ctx context.Context, addr string) (net.Conn, error) {
return sshTunnelMachine.GetDialConn("tcp", addr)

View File

@@ -18,7 +18,7 @@ import (
func getPgsqlDB(d *entity.Db, db string) (*sql.DB, error) {
driverName := d.Type
// SSH Conect
if d.EnableSshTunnel == 1 && d.SshTunnelMachineId != 0 {
if d.SshTunnelMachineId > 0 {
// 如果使用了隧道,则使用`postgres:ssh:隧道机器id`注册名
driverName = fmt.Sprintf("postgres:ssh:%d", d.SshTunnelMachineId)
if !utils.ArrContains(sql.Drivers(), driverName) {
@@ -36,7 +36,7 @@ func getPgsqlDB(d *entity.Db, db string) (*sql.DB, error) {
// pgsql dialer
type PqSqlDialer struct {
sshTunnelMachineId uint64
sshTunnelMachineId int
}
func (d *PqSqlDialer) Open(name string) (driver.Conn, error) {

View File

@@ -9,27 +9,25 @@ import (
type Db struct {
model.Model
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
Params string `json:"params"`
Remark string `json:"remark"`
TagId uint64
TagPath string
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `orm:"column(name)" json:"name"`
Type string `orm:"column(type)" json:"type"` // 类型mysql oracle等
Host string `orm:"column(host)" json:"host"`
Port int `orm:"column(port)" json:"port"`
Network string `orm:"column(network)" json:"network"`
Username string `orm:"column(username)" json:"username"`
Password string `orm:"column(password)" json:"-"`
Database string `orm:"column(database)" json:"database"`
Params string `json:"params"`
Remark string `json:"remark"`
TagId uint64
TagPath string
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
}
// 获取数据库连接网络, 若没有使用ssh隧道则直接返回。否则返回拼接的网络需要注册至指定dial
func (d *Db) GetNetwork() string {
network := d.Network
if d.EnableSshTunnel == 0 || d.EnableSshTunnel == -1 {
if d.SshTunnelMachineId <= 0 {
if network == "" {
return "tcp"
} else {

View File

@@ -18,7 +18,6 @@ type DbQuery struct {
Remark string `json:"remark"`
TagId uint64
ProjectIds []uint64
TagIds []uint64
TagPathLike string
}

View File

@@ -0,0 +1,62 @@
package api
import (
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
)
type AuthCert struct {
AuthCertApp application.AuthCert
}
func (ac *AuthCert) BaseAuthCerts(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.AuthCert{
Name: g.Query("name"),
AuthMethod: int8(ginx.QueryInt(g, "authMethod", 0)),
}
condition.Id = uint64(ginx.QueryInt(g, "id", 0))
rc.ResData = ac.AuthCertApp.GetPageList(condition, ginx.GetPageParam(g), new([]vo.AuthCertBaseVO))
}
func (ac *AuthCert) AuthCerts(rc *req.Ctx) {
g := rc.GinCtx
condition := &entity.AuthCert{
Name: g.Query("name"),
AuthMethod: int8(ginx.QueryInt(g, "authMethod", 0)),
}
condition.Id = uint64(ginx.QueryInt(g, "id", 0))
res := new([]*entity.AuthCert)
pageRes := ac.AuthCertApp.GetPageList(condition, ginx.GetPageParam(g), res)
for _, r := range *res {
r.PwdDecrypt()
}
rc.ResData = pageRes
}
func (c *AuthCert) SaveAuthCert(rc *req.Ctx) {
g := rc.GinCtx
acForm := &form.AuthCertForm{}
ginx.BindJsonAndValid(g, acForm)
ac := new(entity.AuthCert)
utils.Copy(ac, acForm)
// 脱敏记录日志
acForm.Passphrase = "***"
acForm.Password = "***"
rc.ReqParam = acForm
ac.SetBaseInfo(rc.LoginAccount)
c.AuthCertApp.Save(ac)
}
func (c *AuthCert) Delete(rc *req.Ctx) {
c.AuthCertApp.DeleteById(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
}

View File

@@ -1,19 +1,21 @@
package form
type MachineForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
Ip string `json:"ip" binding:"required"` // IP地址
Username string `json:"username" binding:"required"` // 用户名
AuthMethod int8 `json:"authMethod" binding:"required"`
Password string `json:"password"`
Port int `json:"port" binding:"required"` // 端口号
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
Ip string `json:"ip" binding:"required"` // IP地址
Port int `json:"port" binding:"required"` // 端口号
// 资产授权凭证信息列表
AuthCertId int `json:"authCertId"`
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath" binding:"required"`
Username string `json:"username"`
Password string `json:"password"`
Remark string `json:"remark"`
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
type MachineRunForm struct {
@@ -49,3 +51,21 @@ type MachineFileUpdateForm struct {
Id uint64 `binding:"required"`
Path string `binding:"required"`
}
// 授权凭证
type AuthCertForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required"`
AuthMethod int8 `json:"authMethod" binding:"required"` // 1.密码 2.秘钥
Username string `json:"username"`
Password string `json:"password"` // 密码or私钥
Passphrase string `json:"passphrase"` // 私钥口令
Remark string `json:"remark"`
}
// 资产授权凭证信息
type AssetAuthCertForm struct {
AuthCertId uint64 `json:"authCertId"`
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath" binding:"required"`
}

View File

@@ -53,7 +53,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
list := res.List.(*[]*vo.MachineVO)
for _, mv := range *list {
mv.HasCli = machine.HasCli(*mv.Id)
mv.HasCli = machine.HasCli(mv.Id)
}
rc.ResData = res
}
@@ -63,6 +63,7 @@ func (m *Machine) MachineStats(rc *req.Ctx) {
rc.ResData = stats
}
// 保存机器信息
func (m *Machine) SaveMachine(rc *req.Ctx) {
g := rc.GinCtx
machineForm := new(form.MachineForm)
@@ -71,27 +72,22 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
me := new(entity.Machine)
utils.Copy(me, machineForm)
if me.AuthMethod == entity.MachineAuthMethodPassword {
// 密码解密,并使用解密后的赋值
originPwd, err := utils.DefaultRsaDecrypt(machineForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
me.Password = originPwd
}
// 密码脱敏记录日志
machineForm.Password = "****"
machineForm.Password = "******"
rc.ReqParam = machineForm
me.SetBaseInfo(rc.LoginAccount)
m.MachineApp.Save(me)
}
// 获取机器实例密码,由于数据库是加密存储,故提供该接口展示原文密码
func (m *Machine) GetMachinePwd(rc *req.Ctx) {
mid := GetMachineId(rc.GinCtx)
me := m.MachineApp.GetById(mid, "Password")
me.PwdDecrypt()
rc.ResData = me.Password
func (m *Machine) TestConn(rc *req.Ctx) {
g := rc.GinCtx
machineForm := new(form.MachineForm)
ginx.BindJsonAndValid(g, machineForm)
me := new(entity.Machine)
utils.Copy(me, machineForm)
m.MachineApp.TestConn(me)
}
func (m *Machine) ChangeStatus(rc *req.Ctx) {

View File

@@ -1,17 +1,25 @@
package vo
import "time"
import (
"time"
)
// 授权凭证基础信息
type AuthCertBaseVO struct {
Id int `json:"id"`
Name string `json:"name"`
AuthMethod int8 `json:"authMethod"`
}
type MachineVO struct {
Id *uint64 `json:"id"`
Name *string `json:"name"`
Username *string `json:"username"`
Ip *string `json:"ip"`
Port *int `json:"port"`
AuthMethod *int8 `json:"authMethod"`
Id uint64 `json:"id"`
Name string `json:"name"`
Ip string `json:"ip"`
Port int `json:"port"`
Username string `json:"username"`
AuthCertId int `json:"authCertId"`
Status *int8 `json:"status"`
EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`

View File

@@ -1,11 +1,20 @@
package application
import "mayfly-go/internal/machine/infrastructure/persistence"
import (
"mayfly-go/internal/machine/infrastructure/persistence"
)
var (
machineApp Machine = newMachineApp(persistence.GetMachineRepo())
machineFileApp MachineFile = newMachineFileApp(persistence.GetMachineFileRepo(), persistence.GetMachineRepo())
machineFileApp MachineFile = newMachineFileApp(persistence.GetMachineFileRepo(), persistence.GetMachineRepo())
machineScriptApp MachineScript = newMachineScriptApp(persistence.GetMachineScriptRepo(), persistence.GetMachineRepo())
authCertApp AuthCert = newAuthCertApp(persistence.GetAuthCertRepo())
machineApp Machine = newMachineApp(
persistence.GetMachineRepo(),
GetAuthCertApp(),
)
)
func GetMachineApp() Machine {
@@ -19,3 +28,7 @@ func GetMachineFileApp() MachineFile {
func GetMachineScriptApp() MachineScript {
return machineScriptApp
}
func GetAuthCertApp() AuthCert {
return authCertApp
}

View File

@@ -0,0 +1,64 @@
package application
import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type AuthCert interface {
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Save(ac *entity.AuthCert)
GetById(id uint64) *entity.AuthCert
GetByIds(ids ...uint64) []*entity.AuthCert
DeleteById(id uint64)
}
func newAuthCertApp(authCertRepo repository.AuthCert) AuthCert {
return &authCertAppImpl{
authCertRepo: authCertRepo,
}
}
type authCertAppImpl struct {
authCertRepo repository.AuthCert
}
func (a *authCertAppImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return a.authCertRepo.GetPageList(condition, pageParam, toEntity)
}
func (a *authCertAppImpl) Save(ac *entity.AuthCert) {
oldAc := &entity.AuthCert{Name: ac.Name}
err := a.authCertRepo.GetByCondition(oldAc, "Id", "Name")
ac.PwdEncrypt()
if ac.Id == 0 {
biz.IsTrue(err != nil, "该凭证名已存在")
a.authCertRepo.Insert(ac)
return
}
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldAc.Id == ac.Id, "该凭证名已存在")
}
a.authCertRepo.Update(ac)
}
func (a *authCertAppImpl) GetById(id uint64) *entity.AuthCert {
return a.authCertRepo.GetById(id)
}
func (a *authCertAppImpl) GetByIds(ids ...uint64) []*entity.AuthCert {
return a.authCertRepo.GetByIds(ids...)
}
func (a *authCertAppImpl) DeleteById(id uint64) {
a.authCertRepo.DeleteById(id)
}

View File

@@ -14,7 +14,10 @@ type Machine interface {
// 根据条件获取账号信息
GetMachine(condition *entity.Machine, cols ...string) error
Save(entity *entity.Machine)
Save(*entity.Machine)
// 测试机器连接
TestConn(me *entity.Machine)
// 调整机器状态
ChangeStatus(id uint64, status int8)
@@ -33,16 +36,19 @@ type Machine interface {
GetCli(id uint64) *machine.Cli
// 获取ssh隧道机器连接
GetSshTunnelMachine(id uint64) *machine.SshTunnelMachine
GetSshTunnelMachine(id int) *machine.SshTunnelMachine
}
func newMachineApp(machineRepo repository.Machine) Machine {
return &machineAppImpl{machineRepo: machineRepo}
func newMachineApp(machineRepo repository.Machine, authCertApp AuthCert) Machine {
return &machineAppImpl{
machineRepo: machineRepo,
authCertApp: authCertApp,
}
}
type machineAppImpl struct {
machineRepo repository.Machine
authCertApp AuthCert
}
// 分页获取机器信息列表
@@ -54,36 +60,35 @@ func (m *machineAppImpl) Count(condition *entity.MachineQuery) int64 {
return m.machineRepo.Count(condition)
}
// 根据条件获取机器信息
func (m *machineAppImpl) Save(me *entity.Machine) {
// ’修改机器信息且密码不为空‘ or ‘新增’需要测试是否可连接
if (me.Id != 0 && me.Password != "") || me.Id == 0 {
biz.ErrIsNilAppendErr(machine.TestConn(*me, func(u uint64) *entity.Machine {
me := m.GetById(u)
me.PwdDecrypt()
return me
}), "该机器无法连接: %s")
}
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
err := m.GetMachine(oldMachine)
if me.Id != 0 {
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldMachine.Id == me.Id, "该机器信息已存在")
}
// 关闭连接
machine.DeleteCli(me.Id)
me.PwdEncrypt()
m.machineRepo.UpdateById(me)
} else {
me.PwdEncrypt()
if me.Id == 0 {
biz.IsTrue(err != nil, "该机器信息已存在")
// 新增机器,默认启用状态
me.Status = entity.MachineStatusEnable
me.PwdEncrypt()
m.machineRepo.Create(me)
return
}
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldMachine.Id == me.Id, "该机器信息已存在")
}
// 关闭连接
machine.DeleteCli(me.Id)
m.machineRepo.UpdateById(me)
}
func (m *machineAppImpl) TestConn(me *entity.Machine) {
me.Id = 0
// 测试连接
biz.ErrIsNilAppendErr(machine.TestConn(*m.toMachineInfo(me), func(u uint64) *machine.Info {
return m.toMachineInfoById(u)
}), "该机器无法连接: %s")
}
func (m *machineAppImpl) ChangeStatus(id uint64, status int8) {
@@ -128,24 +133,55 @@ func (m *machineAppImpl) GetById(id uint64, cols ...string) *entity.Machine {
return m.machineRepo.GetById(id, cols...)
}
func (m *machineAppImpl) GetCli(id uint64) *machine.Cli {
cli, err := machine.GetCli(id, func(machineId uint64) *entity.Machine {
machine := m.GetById(machineId)
machine.PwdDecrypt()
biz.IsTrue(machine.Status == entity.MachineStatusEnable, "该机器已被停用")
return machine
func (m *machineAppImpl) GetCli(machineId uint64) *machine.Cli {
cli, err := machine.GetCli(machineId, func(mid uint64) *machine.Info {
return m.toMachineInfoById(mid)
})
biz.ErrIsNilAppendErr(err, "获取客户端错误: %s")
return cli
}
func (m *machineAppImpl) GetSshTunnelMachine(id uint64) *machine.SshTunnelMachine {
sshTunnel, err := machine.GetSshTunnelMachine(id, func(machineId uint64) *entity.Machine {
machine := m.GetById(machineId)
machine.PwdDecrypt()
biz.IsTrue(machine.Status == entity.MachineStatusEnable, "该机器已被停用")
return machine
func (m *machineAppImpl) GetSshTunnelMachine(machineId int) *machine.SshTunnelMachine {
sshTunnel, err := machine.GetSshTunnelMachine(machineId, func(mid uint64) *machine.Info {
return m.toMachineInfoById(mid)
})
biz.ErrIsNilAppendErr(err, "获取ssh隧道连接失败: %s")
return sshTunnel
}
// 生成机器信息根据授权凭证id填充用户密码等
func (m *machineAppImpl) toMachineInfoById(machineId uint64) *machine.Info {
me := m.GetById(machineId)
biz.IsTrue(me.Status == entity.MachineStatusEnable, "该机器已被停用")
return m.toMachineInfo(me)
}
func (m *machineAppImpl) toMachineInfo(me *entity.Machine) *machine.Info {
mi := new(machine.Info)
mi.Id = me.Id
mi.Name = me.Name
mi.Ip = me.Ip
mi.Port = me.Port
mi.Username = me.Username
mi.TagId = me.TagId
mi.TagPath = me.TagPath
mi.EnableRecorder = me.EnableRecorder
mi.SshTunnelMachineId = me.SshTunnelMachineId
if me.UseAuthCert() {
ac := m.authCertApp.GetById(uint64(me.AuthCertId))
biz.NotNil(ac, "授权凭证信息已不存在,请重新关联")
mi.AuthMethod = ac.AuthMethod
ac.PwdDecrypt()
mi.Password = ac.Password
mi.Passphrase = ac.Passphrase
} else {
mi.AuthMethod = entity.AuthCertAuthMethodPassword
if me.Id != 0 {
me.PwdDecrypt()
}
mi.Password = me.Password
}
return mi
}

View File

@@ -6,6 +6,7 @@ import (
"io/fs"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/internal/machine/infrastructure/machine"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"os"
@@ -29,7 +30,7 @@ type MachineFile interface {
Delete(id uint64)
// 获取文件关联的机器信息,主要用于记录日志使用
GetMachine(fileId uint64) *entity.Machine
GetMachine(fileId uint64) *machine.Info
/** sftp 相关操作 **/
@@ -191,7 +192,7 @@ func (m *machineFileAppImpl) getSftpCli(machineId uint64) *sftp.Client {
return GetMachineApp().GetCli(machineId).GetSftpCli()
}
func (m *machineFileAppImpl) GetMachine(fileId uint64) *entity.Machine {
func (m *machineFileAppImpl) GetMachine(fileId uint64) *machine.Info {
return GetMachineApp().GetCli(m.GetById(fileId).MachineId).GetMachine()
}

View File

@@ -0,0 +1,41 @@
package entity
import (
"mayfly-go/internal/common/utils"
"mayfly-go/pkg/model"
)
// 授权凭证
type AuthCert struct {
model.Model
Name string `json:"name"`
AuthMethod int8 `json:"authMethod"` // 1.密码 2.秘钥
Password string `json:"password"` // 密码or私钥
Passphrase string `json:"passphrase"` // 私钥口令
Remark string `json:"remark"`
}
func (a *AuthCert) TableName() string {
return "t_auth_cert"
}
const (
AuthCertAuthMethodPassword int8 = 1 // 密码
MachineAuthMethodPublicKey int8 = 2 // 密钥
AuthCertTypePrivate int8 = 1
AuthCertTypePublic int8 = 2
)
// 密码加密
func (ac *AuthCert) PwdEncrypt() {
ac.Password = utils.PwdAesEncrypt(ac.Password)
ac.Passphrase = utils.PwdAesEncrypt(ac.Passphrase)
}
// 密码解密
func (ac *AuthCert) PwdDecrypt() {
ac.Password = utils.PwdAesDecrypt(ac.Password)
ac.Passphrase = utils.PwdAesDecrypt(ac.Passphrase)
}

View File

@@ -1,7 +1,6 @@
package entity
import (
"fmt"
"mayfly-go/internal/common/utils"
"mayfly-go/pkg/model"
)
@@ -11,24 +10,21 @@ type Machine struct {
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Port int `json:"port"` // 端口号
Username string `json:"username"` // 用户名
AuthMethod int8 `json:"authMethod"` // 授权认证方式
Password string `json:"-"`
Port int `json:"port"` // 端口号
Password string `json:"password"` // 密码
AuthCertId int `json:"authCertId"` // 授权凭证id
TagId uint64
TagPath string
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
const (
MachineStatusEnable int8 = 1 // 启用状态
MachineStatusDisable int8 = -1 // 禁用状态
MachineAuthMethodPassword int8 = 1 // 密码登录
MachineAuthMethodPublicKey int8 = 2 // 公钥免密登录
MachineStatusEnable int8 = 1 // 启用状态
MachineStatusDisable int8 = -1 // 禁用状态
)
func (m *Machine) PwdEncrypt() {
@@ -41,7 +37,6 @@ func (m *Machine) PwdDecrypt() {
m.Password = utils.PwdAesDecrypt(m.Password)
}
// 获取记录日志的描述
func (m *Machine) GetLogDesc() string {
return fmt.Sprintf("Machine[id=%d, tag=%s, name=%s, ip=%s:%d]", m.Id, m.TagPath, m.Name, m.Ip, m.Port)
func (m *Machine) UseAuthCert() bool {
return m.AuthCertId > 0
}

View File

@@ -4,8 +4,6 @@ import "mayfly-go/pkg/model"
type MachineQuery struct {
model.Model
ProjectId uint64 `json:"projectId"`
ProjectName string `json:"projectName"`
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Username string `json:"username"` // 用户名
@@ -14,11 +12,9 @@ type MachineQuery struct {
Port int `json:"port"` // 端口号
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
ProjectIds []uint64
TagId uint64
TagPathLike string
TagIds []uint64

View File

@@ -0,0 +1,22 @@
package repository
import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/pkg/model"
)
type AuthCert interface {
GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
Insert(ac *entity.AuthCert)
Update(ac *entity.AuthCert)
GetById(id uint64) *entity.AuthCert
GetByIds(ids ...uint64) []*entity.AuthCert
GetByCondition(condition *entity.AuthCert, cols ...string) error
DeleteById(id uint64)
}

View File

@@ -16,15 +16,42 @@ import (
"golang.org/x/crypto/ssh"
)
// 机器信息
type Info struct {
Id uint64
Name string `json:"name"`
Ip string `json:"ip"` // IP地址
Port int `json:"port"` // 端口号
AuthMethod int8 `json:"authMethod"` // 授权认证方式
Username string `json:"username"` // 用户名
Password string `json:"-"`
Passphrase string `json:"-"` // 私钥口令
Status int8 `json:"status"` // 状态 1:启用2:停用
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
EnableRecorder int8 `json:"enableRecorder"` // 是否启用终端回放记录
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}
func (m *Info) UseSshTunnel() bool {
return m.SshTunnelMachineId > 0
}
// 获取记录日志的描述
func (m *Info) GetLogDesc() string {
return fmt.Sprintf("Machine[id=%d, tag=%s, name=%s, ip=%s:%d]", m.Id, m.TagPath, m.Name, m.Ip, m.Port)
}
// 客户端信息
type Cli struct {
machine *entity.Machine
machine *Info
client *ssh.Client // ssh客户端
sftpClient *sftp.Client // sftp客户端
enableSshTunnel int8
sshTunnelMachineId uint64
sshTunnelMachineId int
}
// 连接
@@ -54,7 +81,7 @@ func (c *Cli) Close() {
c.sftpClient.Close()
c.sftpClient = nil
}
if c.enableSshTunnel == 1 {
if c.sshTunnelMachineId > 0 {
CloseSshTunnelMachine(c.sshTunnelMachineId, c.machine.Id)
}
}
@@ -106,7 +133,7 @@ func (c *Cli) Run(shell string) (string, error) {
return string(buf), nil
}
func (c *Cli) GetMachine() *entity.Machine {
func (c *Cli) GetMachine() *Info {
return c.machine
}
@@ -118,7 +145,7 @@ var cliCache = cache.NewTimedCache(constant.MachineConnExpireTime, 5*time.Second
})
func init() {
AddCheckSshTunnelMachineUseFunc(func(machineId uint64) bool {
AddCheckSshTunnelMachineUseFunc(func(machineId int) bool {
// 遍历所有机器连接实例若存在机器连接实例使用该ssh隧道机器则返回true表示还在使用中...
items := cliCache.Items()
for _, v := range items {
@@ -144,7 +171,7 @@ func DeleteCli(id uint64) {
}
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, error) {
func GetCli(machineId uint64, getMachine func(uint64) *Info) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(_ interface{}) (interface{}, error) {
me := getMachine(machineId)
err := IfUseSshTunnelChangeIpPort(me, getMachine)
@@ -156,7 +183,6 @@ func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, er
CloseSshTunnelMachine(me.SshTunnelMachineId, me.Id)
return nil, err
}
c.enableSshTunnel = me.EnableSshTunnel
c.sshTunnelMachineId = me.SshTunnelMachineId
return c, nil
})
@@ -168,7 +194,7 @@ func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, er
}
// 测试连接使用传值的方式而非引用。因为如果使用了ssh隧道则ip和端口会变为本地映射地址与端口
func TestConn(me entity.Machine, getSshTunnelMachine func(uint64) *entity.Machine) error {
func TestConn(me Info, getSshTunnelMachine func(uint64) *Info) error {
originId := me.Id
if originId == 0 {
// 随机设置一个ip如果使用了隧道则用于临时保存隧道
@@ -177,7 +203,7 @@ func TestConn(me entity.Machine, getSshTunnelMachine func(uint64) *entity.Machin
err := IfUseSshTunnelChangeIpPort(&me, getSshTunnelMachine)
biz.ErrIsNilAppendErr(err, "ssh隧道连接失败: %s")
if me.EnableSshTunnel == 1 {
if me.UseSshTunnel() {
defer CloseSshTunnelMachine(me.SshTunnelMachineId, me.Id)
}
sshClient, err := GetSshClient(&me)
@@ -189,11 +215,11 @@ func TestConn(me entity.Machine, getSshTunnelMachine func(uint64) *entity.Machin
}
// 如果使用了ssh隧道则修改机器ip port为暴露的ip port
func IfUseSshTunnelChangeIpPort(me *entity.Machine, getMachine func(uint64) *entity.Machine) error {
if me.EnableSshTunnel != 1 {
func IfUseSshTunnelChangeIpPort(me *Info, getMachine func(uint64) *Info) error {
if !me.UseSshTunnel() {
return nil
}
sshTunnelMachine, err := GetSshTunnelMachine(me.SshTunnelMachineId, func(u uint64) *entity.Machine {
sshTunnelMachine, err := GetSshTunnelMachine(me.SshTunnelMachineId, func(u uint64) *Info {
return getMachine(u)
})
if err != nil {
@@ -209,26 +235,34 @@ func IfUseSshTunnelChangeIpPort(me *entity.Machine, getMachine func(uint64) *ent
return nil
}
func GetSshClient(m *entity.Machine) (*ssh.Client, error) {
config := ssh.ClientConfig{
func GetSshClient(m *Info) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: m.Username,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
if m.AuthMethod == entity.MachineAuthMethodPassword {
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
} else if m.AuthMethod == entity.MachineAuthMethodPublicKey {
if signer, err := ssh.ParsePrivateKey([]byte(m.Password)); err != nil {
return nil, err
var key ssh.Signer
var err error
if len(m.Passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(m.Password), []byte(m.Passphrase))
} else {
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
key, err = ssh.ParsePrivateKey([]byte(m.Password))
}
if err != nil {
return nil, err
}
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(key)}
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
sshClient, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
@@ -236,7 +270,7 @@ func GetSshClient(m *entity.Machine) (*ssh.Client, error) {
}
// 根据机器信息创建客户端对象
func newClient(machine *entity.Machine) (*Cli, error) {
func newClient(machine *Info) (*Cli, error) {
if machine == nil {
return nil, errors.New("机器不存在")
}

View File

@@ -3,7 +3,6 @@ package machine
import (
"fmt"
"io"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/pkg/global"
"mayfly-go/pkg/scheduler"
"mayfly-go/pkg/utils"
@@ -15,7 +14,7 @@ import (
)
var (
sshTunnelMachines map[uint64]*SshTunnelMachine = make(map[uint64]*SshTunnelMachine)
sshTunnelMachines map[int]*SshTunnelMachine = make(map[int]*SshTunnelMachine)
mutex sync.Mutex
@@ -27,7 +26,7 @@ var (
)
// 检查ssh隧道机器是否有被使用
type CheckSshTunnelMachineHasUseFunc func(uint64) bool
type CheckSshTunnelMachineHasUseFunc func(int) bool
func startCheckUse() {
global.Log.Info("开启定时检测ssh隧道机器是否还有被使用")
@@ -66,7 +65,7 @@ func AddCheckSshTunnelMachineUseFunc(checkFunc CheckSshTunnelMachineHasUseFunc)
// ssh隧道机器
type SshTunnelMachine struct {
machineId uint64 // 隧道机器id
machineId int // 隧道机器id
SshClient *ssh.Client
mutex sync.Mutex
tunnels map[uint64]*Tunnel // 机器id -> 隧道
@@ -137,7 +136,7 @@ func (stm *SshTunnelMachine) Close() {
}
// 获取ssh隧道机器方便统一管理充当ssh隧道的机器避免创建多个ssh client
func GetSshTunnelMachine(machineId uint64, getMachine func(uint64) *entity.Machine) (*SshTunnelMachine, error) {
func GetSshTunnelMachine(machineId int, getMachine func(uint64) *Info) (*SshTunnelMachine, error) {
sshTunnelMachine := sshTunnelMachines[machineId]
if sshTunnelMachine != nil {
return sshTunnelMachine, nil
@@ -146,7 +145,7 @@ func GetSshTunnelMachine(machineId uint64, getMachine func(uint64) *entity.Machi
mutex.Lock()
defer mutex.Unlock()
me := getMachine(machineId)
me := getMachine(uint64(machineId))
sshClient, err := GetSshClient(me)
if err != nil {
return nil, err
@@ -165,7 +164,7 @@ func GetSshTunnelMachine(machineId uint64, getMachine func(uint64) *entity.Machi
}
// 关闭ssh隧道机器的指定隧道
func CloseSshTunnelMachine(machineId uint64, tunnelId uint64) {
func CloseSshTunnelMachine(machineId int, tunnelId uint64) {
sshTunnelMachine := sshTunnelMachines[machineId]
if sshTunnelMachine == nil {
return
@@ -182,7 +181,7 @@ func CloseSshTunnelMachine(machineId uint64, tunnelId uint64) {
type Tunnel struct {
id uint64 // 唯一标识
machineId uint64 // 隧道机器id
machineId int // 隧道机器id
localHost string // 本地监听地址
localPort int // 本地端口
remoteHost string // 远程连接地址

View File

@@ -0,0 +1,49 @@
package persistence
import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
)
type authCertRepoImpl struct{}
func newAuthCertRepo() repository.AuthCert {
return new(authCertRepoImpl)
}
func (m *authCertRepoImpl) GetPageList(condition *entity.AuthCert, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, condition, toEntity)
}
func (m *authCertRepoImpl) Insert(ac *entity.AuthCert) {
biz.ErrIsNil(model.Insert(ac), "新增授权凭证失败")
}
func (m *authCertRepoImpl) Update(ac *entity.AuthCert) {
biz.ErrIsNil(model.UpdateById(ac), "更新授权凭证失败")
}
func (m *authCertRepoImpl) GetById(id uint64) *entity.AuthCert {
ac := new(entity.AuthCert)
err := model.GetById(ac, id)
if err != nil {
return nil
}
return ac
}
func (m *authCertRepoImpl) GetByIds(ids ...uint64) []*entity.AuthCert {
acs := new([]*entity.AuthCert)
model.GetByIdIn(new(entity.AuthCert), acs, ids)
return *acs
}
func (m *authCertRepoImpl) GetByCondition(condition *entity.AuthCert, cols ...string) error {
return model.GetBy(condition, cols...)
}
func (m *authCertRepoImpl) DeleteById(id uint64) {
model.DeleteById(new(entity.AuthCert), id)
}

View File

@@ -6,8 +6,6 @@ import (
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils"
"strings"
)
type machineRepoImpl struct{}
@@ -30,7 +28,8 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
values = append(values, "%"+condition.Name+"%")
}
if len(condition.TagIds) > 0 {
sql = fmt.Sprintf("%s AND m.tag_id IN (%s) ", sql, strings.Join(utils.NumberArr2StrArr(condition.TagIds), ","))
sql = fmt.Sprintf("%s AND m.tag_id IN ? ", sql)
values = append(values, condition.TagIds)
}
if condition.TagPathLike != "" {
sql = sql + " AND m.tag_path LIKE ?"

View File

@@ -6,6 +6,7 @@ var (
machineRepo repository.Machine = newMachineRepo()
machineFileRepo repository.MachineFile = newMachineFileRepo()
machineScriptRepo repository.MachineScript = newMachineScriptRepo()
authCertRepo = newAuthCertRepo()
)
func GetMachineRepo() repository.Machine {
@@ -19,3 +20,7 @@ func GetMachineFileRepo() repository.MachineFile {
func GetMachineScriptRepo() repository.MachineScript {
return machineScriptRepo
}
func GetAuthCertRepo() repository.AuthCert {
return authCertRepo
}

View File

@@ -0,0 +1,45 @@
package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
"mayfly-go/pkg/req"
"github.com/gin-gonic/gin"
)
func InitAuthCertRouter(router *gin.RouterGroup) {
r := &api.AuthCert{AuthCertApp: application.GetAuthCertApp()}
db := router.Group("sys/authcerts")
{
listAcP := req.NewPermission("authcert")
db.GET("", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(listAcP).
Handle(r.AuthCerts)
})
// 基础授权凭证信息,不包含密码等
db.GET("base", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(r.BaseAuthCerts)
})
saveAc := req.NewLogInfo("保存授权凭证").WithSave(true)
saveAcP := req.NewPermission("authcert:save")
db.POST("", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(saveAc).
WithRequiredPermission(saveAcP).
Handle(r.SaveAuthCert)
})
deleteAc := req.NewLogInfo("删除授权凭证").WithSave(true)
deleteAcP := req.NewPermission("authcert:del")
db.DELETE(":id", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(deleteAc).
WithRequiredPermission(deleteAcP).
Handle(r.Delete)
})
}
}

View File

@@ -21,10 +21,6 @@ func InitMachineRouter(router *gin.RouterGroup) {
req.NewCtxWithGin(c).Handle(m.Machines)
})
machines.GET(":machineId/pwd", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetMachinePwd)
})
machines.GET(":machineId/stats", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.MachineStats)
})
@@ -52,6 +48,11 @@ func InitMachineRouter(router *gin.RouterGroup) {
Handle(m.SaveMachine)
})
machines.POST("test-conn", func(c *gin.Context) {
req.NewCtxWithGin(c).
Handle(m.TestConn)
})
changeStatus := req.NewLogInfo("调整机器状态").WithSave(true)
machines.PUT(":machineId/:status", func(c *gin.Context) {
req.NewCtxWithGin(c).

View File

@@ -6,4 +6,5 @@ func Init(router *gin.RouterGroup) {
InitMachineRouter(router)
InitMachineFileRouter(router)
InitMachineScriptRouter(router)
InitAuthCertRouter(router)
}

View File

@@ -3,8 +3,7 @@ package form
type Mongo struct {
Id uint64
Uri string `binding:"required" json:"uri"`
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Name string `binding:"required" json:"name"`
TagId uint64 `binding:"required" json:"tagId"`
TagPath string `json:"tagPath"`

View File

@@ -108,7 +108,7 @@ var mongoCliCache = cache.NewTimedCache(constant.MongoConnExpireTime, 5*time.Sec
})
func init() {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId uint64) bool {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId int) bool {
// 遍历所有mongo连接实例若存在redis实例使用该ssh隧道机器则返回true表示还在使用中...
items := mongoCliCache.Items()
for _, v := range items {
@@ -144,7 +144,7 @@ type MongoInfo struct {
Id uint64
Name string
TagPath string
SshTunnelMachineId uint64 // ssh隧道机器id
SshTunnelMachineId int // ssh隧道机器id
}
func (m *MongoInfo) GetLogDesc() string {
@@ -177,7 +177,7 @@ func connect(me *entity.Mongo) (*MongoInstance, error) {
mongoOptions := options.Client().ApplyURI(me.Uri).
SetMaxPoolSize(1)
// 启用ssh隧道则连接隧道机器
if me.EnableSshTunnel == 1 {
if me.SshTunnelMachineId > 0 {
mongoOptions.SetDialer(&MongoSshDialer{machineId: me.SshTunnelMachineId})
}
@@ -206,7 +206,7 @@ func toMongiInfo(me *entity.Mongo) *MongoInfo {
}
type MongoSshDialer struct {
machineId uint64
machineId int
}
func (sd *MongoSshDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {

View File

@@ -7,8 +7,7 @@ type Mongo struct {
Name string `orm:"column(name)" json:"name"`
Uri string `orm:"column(uri)" json:"uri"`
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`
}

View File

@@ -7,7 +7,6 @@ type MongoQuery struct {
Name string
Uri string
EnableSshTunnel int8 // 是否启用ssh隧道
SshTunnelMachineId uint64 // ssh隧道机器id
TagId uint64 `json:"tagId"`
TagPath string `json:"tagPath"`

View File

@@ -7,8 +7,7 @@ type Redis struct {
Password string `json:"password"`
Mode string `json:"mode"`
Db string `json:"db"`
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
TagId uint64 `binding:"required" json:"tagId"`
TagPath string `json:"tagPath"`
Remark string `json:"remark"`

View File

@@ -8,8 +8,7 @@ type Redis struct {
Host *string `json:"host"`
Db string `json:"db"`
Mode *string `json:"mode"`
EnableSshTunnel *int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId *uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Remark *string `json:"remark"`
TagId *uint64 `json:"tagId"`
TagPath *string `json:"tagPath"`

View File

@@ -189,7 +189,7 @@ func getRedisCient(re *entity.Redis, db int) *RedisInstance {
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1,
}
if re.EnableSshTunnel == 1 {
if re.SshTunnelMachineId > 0 {
redisOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
}
ri.Cli = redis.NewClient(redisOptions)
@@ -204,7 +204,7 @@ func getRedisClusterClient(re *entity.Redis) *RedisInstance {
Password: re.Password,
DialTimeout: 8 * time.Second,
}
if re.EnableSshTunnel == 1 {
if re.SshTunnelMachineId > 0 {
redisClusterOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
}
ri.ClusterCli = redis.NewClusterClient(redisClusterOptions)
@@ -224,14 +224,14 @@ func getRedisSentinelCient(re *entity.Redis, db int) *RedisInstance {
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1,
}
if re.EnableSshTunnel == 1 {
if re.SshTunnelMachineId > 0 {
sentinelOptions.Dialer = getRedisDialer(re.SshTunnelMachineId)
}
ri.Cli = redis.NewFailoverClient(sentinelOptions)
return ri
}
func getRedisDialer(machineId uint64) func(ctx context.Context, network, addr string) (net.Conn, error) {
func getRedisDialer(machineId int) func(ctx context.Context, network, addr string) (net.Conn, error) {
sshTunnel := machineapp.GetMachineApp().GetSshTunnelMachine(machineId)
return func(_ context.Context, network, addr string) (net.Conn, error) {
if sshConn, err := sshTunnel.GetDialConn(network, addr); err == nil {
@@ -259,7 +259,7 @@ func CloseRedis(id uint64, db int) {
}
func init() {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId uint64) bool {
machine.AddCheckSshTunnelMachineUseFunc(func(machineId int) bool {
// 遍历所有redis连接实例若存在redis实例使用该ssh隧道机器则返回true表示还在使用中...
items := redisCache.Items()
for _, v := range items {
@@ -303,7 +303,7 @@ type RedisInfo struct {
Mode string
Name string
SshTunnelMachineId uint64
SshTunnelMachineId int
}
// 获取记录日志的描述

View File

@@ -10,8 +10,7 @@ type RedisQuery struct {
Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"`
Db string `orm:"column(database)" json:"db"`
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
TagId uint64

View File

@@ -13,8 +13,7 @@ type Redis struct {
Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"`
Db string `orm:"column(database)" json:"db"`
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
TagId uint64
TagPath string

View File

@@ -1,6 +1,8 @@
package application
import "mayfly-go/internal/sys/infrastructure/persistence"
import (
"mayfly-go/internal/sys/infrastructure/persistence"
)
var (
accountApp = newAccountApp(persistence.GetAccountRepo())

View File

@@ -42,14 +42,14 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) {
}
func (p *TagTree) SaveTagTree(rc *req.Ctx) {
projectTree := &entity.TagTree{}
ginx.BindJsonAndValid(rc.GinCtx, projectTree)
tagTree := &entity.TagTree{}
ginx.BindJsonAndValid(rc.GinCtx, tagTree)
loginAccount := rc.LoginAccount
projectTree.SetBaseInfo(loginAccount)
p.TagTreeApp.Save(projectTree)
tagTree.SetBaseInfo(loginAccount)
p.TagTreeApp.Save(tagTree)
rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, codePath: %s", projectTree.Id, projectTree.Name, projectTree.CodePath)
rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, codePath: %s", tagTree.Id, tagTree.Name, tagTree.CodePath)
}
func (p *TagTree) DelTagTree(rc *req.Ctx) {

View File

@@ -20,7 +20,7 @@ type TagTree interface {
GetById(id uint64) *entity.TagTree
Save(project *entity.TagTree)
Save(tt *entity.TagTree)
Delete(id uint64)

View File

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

View File

@@ -4,8 +4,8 @@ import "mayfly-go/internal/tag/domain/entity"
type TagTreeTeam interface {
// 获取团队项目信息列表
ListProject(condition *entity.TagTreeTeam, toEntity interface{}, orderBy ...string)
// 获取团队标签信息列表
ListTag(condition *entity.TagTreeTeam, toEntity interface{}, orderBy ...string)
Save(mp *entity.TagTreeTeam)

View File

@@ -64,12 +64,12 @@ func (a *tagTreeRepoImpl) GetBy(condition *entity.TagTree, cols ...string) error
return model.GetBy(condition, cols...)
}
func (p *tagTreeRepoImpl) Insert(project *entity.TagTree) {
biz.ErrIsNil(model.Insert(project), "新增标签失败")
func (p *tagTreeRepoImpl) Insert(tagTree *entity.TagTree) {
biz.ErrIsNil(model.Insert(tagTree), "新增标签失败")
}
func (p *tagTreeRepoImpl) UpdateById(project *entity.TagTree) {
biz.ErrIsNil(model.UpdateById(project), "更新标签失败")
func (p *tagTreeRepoImpl) UpdateById(tagTree *entity.TagTree) {
biz.ErrIsNil(model.UpdateById(tagTree), "更新标签失败")
}
func (p *tagTreeRepoImpl) Delete(id uint64) {

View File

@@ -13,7 +13,7 @@ func newTagTreeTeamRepo() repository.TagTreeTeam {
return new(tagTreeTeamRepoImpl)
}
func (p *tagTreeTeamRepoImpl) ListProject(condition *entity.TagTreeTeam, toEntity interface{}, orderBy ...string) {
func (p *tagTreeTeamRepoImpl) ListTag(condition *entity.TagTreeTeam, toEntity interface{}, orderBy ...string) {
model.ListByOrder(condition, toEntity, orderBy...)
}

View File

@@ -17,18 +17,18 @@ func (p *teamRepoImpl) GetPageList(condition *entity.Team, pageParam *model.Page
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
}
func (p *teamRepoImpl) Insert(projectTeam *entity.Team) {
biz.ErrIsNil(model.Insert(projectTeam), "新增团队失败")
func (p *teamRepoImpl) Insert(team *entity.Team) {
biz.ErrIsNil(model.Insert(team), "新增团队失败")
}
func (p *teamRepoImpl) UpdateById(projectTeam *entity.Team) {
biz.ErrIsNil(model.UpdateById(projectTeam), "更新团队失败")
func (p *teamRepoImpl) UpdateById(team *entity.Team) {
biz.ErrIsNil(model.UpdateById(team), "更新团队失败")
}
func (p *teamRepoImpl) Delete(id uint64) {
model.DeleteById(new(entity.Team), id)
}
func (p *teamRepoImpl) DeleteBy(projectTeam *entity.Team) {
model.DeleteByCondition(projectTeam)
func (p *teamRepoImpl) DeleteBy(team *entity.Team) {
model.DeleteByCondition(team)
}

View File

@@ -13,37 +13,37 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
TagTreeApp: application.GetTagTreeApp(),
}
project := router.Group("/tag-trees")
tagTree := router.Group("/tag-trees")
{
// 获取标签树列表
project.GET("", func(c *gin.Context) {
tagTree.GET("", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetTagTree)
})
// 根据条件获取标签
project.GET("query", func(c *gin.Context) {
tagTree.GET("query", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.ListByQuery)
})
// 获取登录账号拥有的标签信息
project.GET("account-has", func(c *gin.Context) {
tagTree.GET("account-has", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(m.GetAccountTags)
})
saveProjectTreeLog := req.NewLogInfo("标签树-保存信息").WithSave(true)
saveTagTreeLog := req.NewLogInfo("标签树-保存信息").WithSave(true)
savePP := req.NewPermission("tag:save")
// 保存项目树下的环境信息
project.POST("", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(saveProjectTreeLog).
tagTree.POST("", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(saveTagTreeLog).
WithRequiredPermission(savePP).
Handle(m.SaveTagTree)
})
delProjectLog := req.NewLogInfo("标签树-删除信息").WithSave(true)
delTagLog := req.NewLogInfo("标签树-删除信息").WithSave(true)
delPP := req.NewPermission("tag:del")
// 删除标签
project.DELETE(":id", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(delProjectLog).
tagTree.DELETE(":id", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(delTagLog).
WithRequiredPermission(delPP).
Handle(m.DelTagTree)
})

View File

@@ -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',
@@ -498,6 +512,10 @@ INSERT INTO `t_sys_resource` VALUES (99, 95, 2, 1, '删除团队', 'team:del', 2
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` VALUES (103, 2, 1, 1, '授权凭证', 'authcerts', 6, '{"component":"AuthCertList","icon":"Unlock","isKeepAlive":true,"routeName":"AuthCertList"}', 1, 'admin', 1, 'admin', '2023-02-23 11:36:26', '2023-02-23 14:40:23');
INSERT INTO `t_sys_resource` VALUES (104, 103, 2, 1, '基本权限', 'authcert', 1, 'null', 1, 'admin', 1, 'admin', '2023-02-23 11:37:24', '2023-02-23 11:37:24');
INSERT INTO `t_sys_resource` VALUES (105, 103, 2, 1, '保存权限', 'authcert:save', 2, 'null', 1, 'admin', 1, 'admin', '2023-02-23 11:37:54', '2023-02-23 11:37:54');
INSERT INTO `t_sys_resource` VALUES (106, 103, 2, 1, '删除权限', 'authcert:del', 3, 'null', 1, 'admin', 1, 'admin', '2023-02-23 11:38:09', '2023-02-23 11:38:09');
COMMIT;
-- ----------------------------
@@ -702,6 +720,10 @@ INSERT INTO `t_sys_role_resource` VALUES (522, 1, 99, 1, 'admin', '2022-10-26 20
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` VALUES (526, 1, 103, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (527, 1, 104, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (528, 1, 105, 1, 'admin', '2022-10-26 20:03:14');
INSERT INTO `t_sys_role_resource` VALUES (529, 1, 106, 1, 'admin', '2022-10-26 20:03:14');
COMMIT;
-- ----------------------------

View File

@@ -8,7 +8,7 @@ import (
"reflect"
)
func ErrIsNil(err error, msg string, params ...interface{}) {
func ErrIsNil(err error, msg string, params ...any) {
if err != nil {
global.Log.Error(msg + ": " + err.Error())
panic(NewBizErr(fmt.Sprintf(msg, params...)))
@@ -31,7 +31,7 @@ func IsNil(err error) {
}
}
func IsTrue(exp bool, msg string, params ...interface{}) {
func IsTrue(exp bool, msg string, params ...any) {
if !exp {
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
@@ -43,21 +43,21 @@ func IsTrueBy(exp bool, err BizError) {
}
}
func NotEmpty(str string, msg string, params ...interface{}) {
func NotEmpty(str string, msg string, params ...any) {
if str == "" {
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func NotNil(data interface{}, msg string) {
func NotNil(data interface{}, msg string, params ...any) {
if reflect.ValueOf(data).IsNil() {
panic(NewBizErr(msg))
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func NotBlank(data interface{}, msg string) {
func NotBlank(data interface{}, msg string, params ...any) {
if utils.IsBlank(data) {
panic(NewBizErr(msg))
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}

View File

@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
Version = "v1.4.0"
Version = "v1.4.1"
)
func GetAppInfo() string {