mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-01 23:10:26 +08:00
fix: fixed some minor issues
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# 构建前端资源
|
||||
FROM m.daocloud.io/docker.io/node:18-bookworm-slim as fe-builder
|
||||
FROM m.daocloud.io/docker.io/node:18-bookworm-slim AS fe-builder
|
||||
|
||||
WORKDIR /mayfly
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN yarn config set registry 'https://registry.npmmirror.com' && \
|
||||
yarn build
|
||||
|
||||
# 构建后端资源
|
||||
FROM m.daocloud.io/docker.io/golang:1.23 as be-builder
|
||||
FROM m.daocloud.io/docker.io/golang:1.23 AS be-builder
|
||||
|
||||
ENV GOPROXY https://goproxy.cn
|
||||
WORKDIR /mayfly
|
||||
|
||||
@@ -56,6 +56,7 @@ function build() {
|
||||
if [ "${os}" == "windows" ];then
|
||||
execFileName="${execFileName}.exe"
|
||||
fi
|
||||
go mod tidy
|
||||
CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -ldflags=-w -o ${execFileName} main.go
|
||||
|
||||
if [ -d ${toFolder} ] ; then
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"@vueuse/core": "^11.3.0",
|
||||
"asciinema-player": "^3.8.1",
|
||||
"axios": "^1.6.2",
|
||||
"clipboard": "^2.0.11",
|
||||
@@ -28,7 +28,7 @@
|
||||
"monaco-sql-languages": "^0.12.2",
|
||||
"monaco-themes": "^0.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.2.6",
|
||||
"pinia": "^2.2.7",
|
||||
"qrcode.vue": "^3.5.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.3",
|
||||
@@ -38,7 +38,7 @@
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.4",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-router": "^4.5.0",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
"xterm-addon-search": "^0.13.0",
|
||||
@@ -52,16 +52,16 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"code-inspector-plugin": "^0.4.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.81.0",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.1",
|
||||
"vue-eslint-parser": "^9.4.3"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -196,6 +196,9 @@ watch(
|
||||
(newValue: any) => {
|
||||
if (!monacoEditorIns.hasTextFocus()) {
|
||||
state.languageMode = props.language;
|
||||
if (newValue == null) {
|
||||
newValue = '';
|
||||
}
|
||||
monacoEditorIns?.setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
copy: 'Copy',
|
||||
pleaseInput: 'Please enter {label}',
|
||||
pleaseSelect: 'Please select {label}',
|
||||
formValidationError: 'Please fill in the form information correctly',
|
||||
formValidationError: 'Please check the form',
|
||||
createTitle: 'Create {name}',
|
||||
editTitle: 'Edit {name}',
|
||||
detailTitle: '{name} Details',
|
||||
|
||||
@@ -19,7 +19,6 @@ export default {
|
||||
newTabOpen: 'New tab opens',
|
||||
redisSelectErr: 'Select redis first',
|
||||
flushDbTips: 'Make sure to clear all keys of the [{db}] library?',
|
||||
keyNotEmpty: 'The Key cannot be empty',
|
||||
|
||||
// info
|
||||
redisInfoTitle: 'Redis server information',
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
copy: '复制',
|
||||
pleaseInput: '请输入{label}',
|
||||
pleaseSelect: '请选择{label}',
|
||||
formValidationError: '请正确填写表单信息',
|
||||
formValidationError: '信息填写有误,请检查',
|
||||
createTitle: '创建{name}',
|
||||
editTitle: '编辑{name}',
|
||||
detailTitle: '{name}详情',
|
||||
|
||||
@@ -17,7 +17,6 @@ export default {
|
||||
newTabOpen: '新tab打开',
|
||||
redisSelectErr: '请先选择redis',
|
||||
flushDbTips: '确定清空[{db}]库的所有key?',
|
||||
keyNotEmpty: 'Key不能为空',
|
||||
|
||||
// info
|
||||
redisInfoTitle: 'Redis服务器信息',
|
||||
|
||||
@@ -303,17 +303,17 @@ const onRunSql = async (newTab = false) => {
|
||||
|
||||
const sqls = splitSql(sql);
|
||||
|
||||
// 简单截取前十个字符
|
||||
const sqlPrefix = sql.slice(0, 10).toLowerCase();
|
||||
const nonQuery =
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alter') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create');
|
||||
|
||||
if (sqls.length == 1) {
|
||||
const oneSql = sqls[0];
|
||||
// 简单截取前十个字符
|
||||
const sqlPrefix = oneSql.slice(0, 10).toLowerCase();
|
||||
const nonQuery =
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alter') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create');
|
||||
let execRemark;
|
||||
if (nonQuery) {
|
||||
const res: any = await ElMessageBox.prompt(t('db.enterExecRemarkTips'), 'Tip', {
|
||||
@@ -323,7 +323,7 @@ const onRunSql = async (newTab = false) => {
|
||||
});
|
||||
execRemark = res.value;
|
||||
}
|
||||
runSql(sql, execRemark, newTab);
|
||||
runSql(oneSql, execRemark, newTab);
|
||||
} else {
|
||||
let isFirst = true;
|
||||
for (let s of sqls) {
|
||||
|
||||
@@ -483,7 +483,6 @@ const setTableColumns = (columns: any) => {
|
||||
x.dataType = dbDialect.getDataType(x.columnType);
|
||||
x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
|
||||
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
|
||||
console.log(x);
|
||||
return {
|
||||
...x,
|
||||
key: columnName,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<tag-tree
|
||||
class="machine-terminal-tree"
|
||||
ref="tagTreeRef"
|
||||
:resource-type="TagResourceTypeEnum.Machine.value"
|
||||
:resource-type="TagResourceTypePath.MachineAuthCert"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
>
|
||||
@@ -168,7 +168,7 @@ import { useRouter } from 'vue-router';
|
||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
<div style="text-align: center; margin-top: 10px"></div>
|
||||
|
||||
<el-dialog :title="$t('redis.addKey')" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-form ref="keyForm" label-width="auto">
|
||||
<el-form ref="keyForm" label-width="auto" :rules="keyFormRules" :model="newKeyDialog.keyInfo">
|
||||
<el-form-item prop="key" label="Key" required>
|
||||
<el-input v-model.trim="newKeyDialog.keyInfo.key"></el-input>
|
||||
</el-form-item>
|
||||
@@ -187,9 +187,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { redisApi } from './api';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notBlank, notNull } from '@/common/assert';
|
||||
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref, watch, useTemplateRef } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { isTrue, notNull } from '@/common/assert';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { TagTreeNode, NodeType, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
@@ -202,13 +202,21 @@ import { RedisInst } from './redis';
|
||||
import { useAutoOpenResource } from '@/store/autoOpenResource';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nFormValidate, useI18nOperateSuccessMsg, useI18nPleaseInput } from '@/hooks/useI18n';
|
||||
|
||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const contextmenuRef = ref();
|
||||
const keyFormRules = {
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: useI18nPleaseInput('Key'),
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const cmCopyKey = new ContextmenuItem('copyValue', 'Copy')
|
||||
.withIcon('CopyDocument')
|
||||
@@ -301,8 +309,11 @@ const treeProps = {
|
||||
|
||||
const defaultCount = 250;
|
||||
|
||||
const contextmenuRef = ref();
|
||||
const keyTreeRef: any = ref(null);
|
||||
const tagTreeRef: any = ref(null);
|
||||
const keyFormRef = useTemplateRef('keyForm');
|
||||
|
||||
const redisInst: Ref<RedisInst> = ref(new RedisInst());
|
||||
|
||||
const state = reactive({
|
||||
@@ -573,9 +584,9 @@ const cancelNewKey = () => {
|
||||
};
|
||||
|
||||
const newKey = async () => {
|
||||
await useI18nFormValidate(keyFormRef);
|
||||
const keyInfo = state.newKeyDialog.keyInfo;
|
||||
const key = keyInfo.key;
|
||||
notBlank(key, t('redis.keyNotEmpty'));
|
||||
|
||||
showKeyDetail(
|
||||
{
|
||||
|
||||
@@ -370,12 +370,9 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
}
|
||||
|
||||
codePathLikes := accountTagPaths
|
||||
needFilterAccountTagPaths := accountTagPaths
|
||||
|
||||
needFilter := false
|
||||
needFilterAccountTagPaths := make(map[string][]string, 0)
|
||||
typePaths := query.TypePaths
|
||||
if len(typePaths) > 0 {
|
||||
needFilterAccountTagPaths = make([]string, 0)
|
||||
codePathLikes = []string{}
|
||||
|
||||
for _, typePath := range typePaths {
|
||||
@@ -391,14 +388,13 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
return cast.ToString(int8(tt)) + entity.CodePathResourceSeparator + "%"
|
||||
}), entity.CodePathSeparator) + entity.CodePathSeparator
|
||||
|
||||
// 根据用户拥有的标签路径,赋值要过滤匹配的标签路径条件
|
||||
// 根据用户拥有的标签路径,赋值要过滤匹配的标签类型路径
|
||||
for _, accountTag := range accountTagPaths {
|
||||
accountTagCodePath := entity.CodePath(accountTag)
|
||||
// 标签路径,不包含资源段,如tag1/tag2/1|xxx => tag1/tag2/
|
||||
tagPath := accountTagCodePath.GetTag()
|
||||
// 纯纯的标签类型(不包含资源段),则直接在该标签路径上补上对应的子资源类型匹配表达式
|
||||
if tagPath == accountTagCodePath {
|
||||
needFilterAccountTagPaths = append(needFilterAccountTagPaths, accountTag)
|
||||
// 查询标签类型为标签时,特殊处理
|
||||
if len(childOrderTypes) == 1 && childOrderTypes[0] == entity.TagTypeTag {
|
||||
codePathLikes = append(codePathLikes, accountTag)
|
||||
@@ -410,7 +406,7 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
continue
|
||||
}
|
||||
|
||||
// 将用户有权限操作的标签如 tag1/tag2/1|xxx 替换为tag1/tag2/1|%,并与需要查询的资源类型进行匹配
|
||||
// 将用户有权限操作的标签如 tag1/tag2/type|code 替换为tag1/tag2/type|%,并与需要查询的资源类型进行匹配
|
||||
accountTagCodePathSections := accountTagCodePath.GetPathSections()
|
||||
for _, section := range accountTagCodePathSections {
|
||||
if section.Type == entity.TagTypeTag {
|
||||
@@ -419,21 +415,33 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
section.Code = "%"
|
||||
}
|
||||
|
||||
// tag1/tag2/type1|%/type2|%
|
||||
codePathLike := string(tagPath) + childOrderTypesMatch
|
||||
if entity.CodePath(accountTagCodePathSections.ToCodePath()).CanAccess(codePathLike) {
|
||||
codePathLikes = append(codePathLikes, codePathLike)
|
||||
needFilterAccountTagPaths = append(needFilterAccountTagPaths, accountTag)
|
||||
accountMatchPath := accountTagCodePathSections.ToCodePath()
|
||||
// 用户有权限操作该标签则直接添加即可
|
||||
if entity.CodePath(accountMatchPath).CanAccess(codePathLike) {
|
||||
codePathLikes = append(codePathLikes, accountTag)
|
||||
continue
|
||||
}
|
||||
|
||||
// 如用户分配了: "default/type1|code1/type2|code2/type3|code3/", 需要查询的codePathLike为: default/type1|%/type2|%/,即用户分配的标签路径是查询的子节点。
|
||||
// 若需要获取所有子节点,则codePathLike 使用default/type1|code1/type2|code2/去查。否则需要单独再去查一遍
|
||||
if strings.HasPrefix(accountMatchPath, codePathLike) {
|
||||
actualMatchCodePath := accountTagCodePathSections[len(entity.CodePath(codePathLike).GetPathSections())-1].Path
|
||||
needFilterAccountTagPaths[actualMatchCodePath] = append(needFilterAccountTagPaths[actualMatchCodePath], accountTag)
|
||||
if query.GetAllChildren {
|
||||
codePathLikes = append(codePathLikes, actualMatchCodePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去重处理
|
||||
codePathLikes = collx.ArrayDeduplicate(codePathLikes)
|
||||
needFilter = true
|
||||
}
|
||||
|
||||
// 账号权限经过处理为空,则说明没有用户可以操作的标签,直接返回即可
|
||||
if needFilter && len(needFilterAccountTagPaths) == 0 {
|
||||
if len(codePathLikes) == 0 {
|
||||
return tagResources
|
||||
}
|
||||
|
||||
@@ -441,11 +449,28 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
tagResourceQuery.CodePathLikes = codePathLikes
|
||||
p.ListByQuery(tagResourceQuery, &tagResources)
|
||||
|
||||
if needFilter {
|
||||
// 不是获取所有子节点,则需要额外查询需要过滤的节点信息。如用户分配了default/2|db_local/5|db_local_root/22|cWMpm6137g/标签,但是typePath为default/2|%/5|%/
|
||||
// 由于不是获取所有子节点,则会被追加Type进行过滤,故获取不到default/2|db_local/5|db_local_root/的信息,需要额外查询
|
||||
if !query.GetAllChildren && len(needFilterAccountTagPaths) > 0 {
|
||||
var otherTags []*dto.SimpleTagTree
|
||||
p.ListByQuery(&entity.TagTreeQuery{
|
||||
CodePaths: collx.MapKeys(needFilterAccountTagPaths),
|
||||
}, &otherTags)
|
||||
tagResources = append(tagResources, otherTags...)
|
||||
// 清空,因为不是获取所有子节点,so 后续不需要进行过滤
|
||||
clear(needFilterAccountTagPaths)
|
||||
}
|
||||
|
||||
if len(needFilterAccountTagPaths) > 0 {
|
||||
tagResources = collx.ArrayFilter(tagResources, func(tr *dto.SimpleTagTree) bool {
|
||||
return slices.ContainsFunc(needFilterAccountTagPaths, func(accountTagPath string) bool {
|
||||
return entity.CodePath(accountTagPath).CanAccess(tr.CodePath)
|
||||
})
|
||||
for codePathLike, accountTags := range needFilterAccountTagPaths {
|
||||
if strings.HasPrefix(tr.CodePath, codePathLike) {
|
||||
return slices.ContainsFunc(accountTags, func(accountTag string) bool {
|
||||
return entity.CodePath(accountTag).CanAccess(tr.CodePath)
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ func TestTagPathMatch(t *testing.T) {
|
||||
codePathLike := "default/2|%/22|%/"
|
||||
accountCodePath := "default/2|db_local/5|db_local_root/"
|
||||
|
||||
// strings.HasPrefix(resourceTagPath, accountPath)
|
||||
// resourceTagPath -> default/2|%/5|%/
|
||||
// account -> default/2|%/5|%/22|%/ "default/2|db_local/5|db_local_root/22|cWMpm6137g/"
|
||||
|
||||
// resourceTagPath -> default/2|%/5|%/
|
||||
// account -> default/2|%/ "default/2|db_local/"
|
||||
|
||||
sections := entity.CodePath(accountCodePath).GetPathSections()
|
||||
for _, section := range sections {
|
||||
if section.Type == entity.TagTypeTag {
|
||||
|
||||
@@ -198,6 +198,12 @@ func (tps PathSections) ToCodePath() string {
|
||||
}), CodePathSeparator) + CodePathSeparator
|
||||
}
|
||||
|
||||
func (tps PathSections) GetSection(tagType TagType) []*PathSection {
|
||||
return collx.ArrayFilter(tps, func(tp *PathSection) bool {
|
||||
return tp.Type == tagType
|
||||
})
|
||||
}
|
||||
|
||||
// GetCodesByCodePaths 从codePaths中提取指定标签类型的所有tagCode并去重
|
||||
// 如:codePaths = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode
|
||||
func GetCodesByCodePaths(tagType TagType, codePaths ...string) []string {
|
||||
|
||||
Reference in New Issue
Block a user