fix: fixed some minor issues

This commit is contained in:
meilin.huang
2024-11-28 20:11:16 +08:00
parent d07cd74a8c
commit ebc89e056f
15 changed files with 100 additions and 50 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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": [

View File

@@ -196,6 +196,9 @@ watch(
(newValue: any) => {
if (!monacoEditorIns.hasTextFocus()) {
state.languageMode = props.language;
if (newValue == null) {
newValue = '';
}
monacoEditorIns?.setValue(newValue);
}
}

View File

@@ -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',

View File

@@ -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',

View File

@@ -47,7 +47,7 @@ export default {
copy: '复制',
pleaseInput: '请输入{label}',
pleaseSelect: '请选择{label}',
formValidationError: '请正确填写表单信息',
formValidationError: '信息填写有误,请检查',
createTitle: '创建{name}',
editTitle: '编辑{name}',
detailTitle: '{name}详情',

View File

@@ -17,7 +17,6 @@ export default {
newTabOpen: '新tab打开',
redisSelectErr: '请先选择redis',
flushDbTips: '确定清空[{db}]库的所有key?',
keyNotEmpty: 'Key不能为空',
// info
redisInfoTitle: 'Redis服务器信息',

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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';

View File

@@ -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(
{

View File

@@ -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
})
}

View File

@@ -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 {

View File

@@ -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 {