refactor: 标签不可移动,资源选择优化等

This commit is contained in:
meilin.huang
2025-10-07 15:41:19 +08:00
parent c4d52ce47a
commit 4ac57cd140
23 changed files with 172 additions and 258 deletions

View File

@@ -11,23 +11,23 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@logicflow/core": "^2.1.1",
"@logicflow/extension": "^2.1.2",
"@logicflow/core": "^2.1.2",
"@logicflow/extension": "^2.1.3",
"@vueuse/core": "^13.9.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-search": "^0.15.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"asciinema-player": "^3.10.0",
"asciinema-player": "^3.11.0",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"element-plus": "^2.11.2",
"element-plus": "^2.11.4",
"js-base64": "^3.7.8",
"jsencrypt": "^3.5.4",
"monaco-editor": "^0.53.0",
"monaco-editor": "^0.54.0",
"monaco-sql-languages": "^0.15.1",
"monaco-themes": "^0.4.7",
"nprogress": "^0.2.0",
@@ -38,13 +38,13 @@
"sql-formatter": "^15.6.8",
"trzsz": "^1.1.5",
"uuid": "^13.0.0",
"vue": "^v3.6.0-alpha.2",
"vue": "^v3.5.22",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.13",
"@tailwindcss/vite": "^4.1.14",
"@types/crypto-js": "^4.2.2",
"@types/node": "^22.13.14",
"@types/nprogress": "^0.2.0",
@@ -56,11 +56,11 @@
"autoprefixer": "^10.4.21",
"code-inspector-plugin": "^1.0.4",
"eslint": "^9.29.0",
"eslint-plugin-vue": "^10.4.0",
"eslint-plugin-vue": "^10.5.0",
"postcss": "^8.5.6",
"prettier": "^3.6.1",
"sass": "^1.92.1",
"tailwindcss": "^4.1.13",
"sass": "^1.93.2",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.2",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-progress": "0.0.7",

View File

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

View File

@@ -34,7 +34,6 @@ const props = defineProps({
},
placement: {
type: String,
required: true,
default: 'top',
},
});

View File

@@ -7,6 +7,7 @@ export default {
tagTips1: '1. Used to group assets',
tagTips2: '2. Can be allocated in team management for resource isolation',
tagTips3: '3. Team members who own a parent tag have access to resources that manipulate their own or child tag associations',
tagTips4: '4. Right-click nodes to edit or add child tags',
machine: 'Machine',
db: 'Db',
code: 'Code',

View File

@@ -7,6 +7,7 @@ export default {
tagTips1: '1. 用于将资产进行归类',
tagTips2: '2. 可在团队管理中进行分配,用于资源隔离',
tagTips3: '3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源',
tagTips4: '4. 右击节点可进行编辑或添加子标签操作',
machine: '机器',
db: '数据库',
es: 'ES',

View File

@@ -1,7 +1,7 @@
<template>
<el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="id" label="DB" required>
<TagTreeResourceSelect
<ResourceSelect
v-bind="$attrs"
v-model="selectRedis"
@change="changeRedis"
@@ -9,7 +9,7 @@
:tag-path-node-type="NodeTypeTagPath"
:placeholder="$t('flow.selectRedisPlaceholder')"
>
</TagTreeResourceSelect>
</ResourceSelect>
</el-form-item>
<el-form-item prop="cmd" label="CMD" required>
@@ -21,12 +21,13 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import TagTreeResourceSelect from '@/views/ops/component/TagTreeResourceSelect.vue';
import ResourceSelect from '@/views/ops/resource/ResourceSelect.vue';
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
import { redisApi } from '@/views/ops/redis/api';
import { sleep } from '@/common/utils/loading';
import { useI18n } from 'vue-i18n';
import { Rules } from '@/common/rule';
import { RedisIcon } from '@/views/ops/redis/resource';
const { t } = useI18n();
@@ -52,7 +53,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
await sleep(100);
return redisInfos.map((x: any) => {
x.tagPath = parentNode.key;
return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x).withIcon(RedisIcon);
});
});
@@ -61,15 +62,18 @@ const NodeTypeRedis = new NodeType(1).withLoadNodesFunc(async (parentNode: TagTr
const redisInfo = parentNode.params;
let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
return new TagTreeNode(x, `db${x}`, 2 as any).withIsLeaf(true).withParams({
id: redisInfo.id,
db: x,
name: `db${x}`,
keys: 0,
tagPath: redisInfo.tagPath,
redisName: redisInfo.name,
code: redisInfo.code,
});
return new TagTreeNode(x, `db${x}`, 2 as any)
.withIsLeaf(true)
.withParams({
id: redisInfo.id,
db: x,
name: `db${x}`,
keys: 0,
tagPath: redisInfo.tagPath,
redisName: redisInfo.name,
code: redisInfo.code,
})
.withIcon({ name: 'Coin', color: '#67c23a' });
});
if (redisInfo.mode == 'cluster') {

View File

@@ -8,7 +8,7 @@
<el-form :model="form" ref="dbForm" :rules="rules" label-position="top" label-width="auto">
<el-tabs v-model="tabActiveName">
<el-tab-pane :label="$t('common.basic')" :name="basicTab">
<el-row gutter="10">
<el-row :gutter="10">
<el-col :span="12">
<el-form-item prop="taskName" :label="$t('db.taskName')" required>
<el-input v-model.trim="form.taskName" auto-complete="off" />
@@ -59,7 +59,7 @@
<monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
</el-form-item>
<el-row gutter="10">
<el-row :gutter="10">
<el-col :span="12">
<el-form-item prop="targetTableName" :label="$t('db.targetDbTable')" required>
<el-select v-model="form.targetTableName" filterable>
@@ -80,7 +80,7 @@
</el-col>
</el-row>
<el-row gutter="10">
<el-row :gutter="10">
<el-col :span="12">
<FormItemTooltip :label="$t('db.updateField')" prop="updField" :tooltip="$t('db.updateFieldTips')">
<el-input v-model.trim="form.updField" :placeholder="$t('db.updateFiledPlaceholder')" auto-complete="off" />
@@ -94,7 +94,7 @@
</el-col>
</el-row>
<el-row gutter="10">
<el-row :gutter="10">
<el-col :span="12">
<FormItemTooltip :label="$t('db.fieldValueSrc')" prop="updFieldSrc" :tooltip="$t('db.fieldValueSrcTips')">
<el-input v-model.trim="form.updFieldSrc" :placeholder="$t('db.fieldValueSrcPlaceholder')" auto-complete="off" />
@@ -320,7 +320,7 @@ watch(dialogVisible, async (newValue: boolean) => {
state.tabActiveName = 'basic';
const propsData = props.data as any;
if (!propsData?.id) {
let d = {} as FormData;
let d = { taskCron: '' } as FormData;
Object.assign(d, basicFormData);
state.form = d;
return;
@@ -416,6 +416,7 @@ const refreshPreviewInsertSql = () => {
const onSelectSrcDb = async (params: any) => {
// 初始化数据源
params.databases = params.dbs; // 数据源里需要这个值
console.log(params.dbs);
state.srcDbInst = await DbInst.getOrNewInst(params);
registerDbCompletionItemProvider(params.id, params.db, params.dbs, params.type);
};

View File

@@ -1,31 +1,23 @@
<template>
<TagTreeResourceSelect
v-bind="$attrs"
v-model="selectNode"
@change="changeNode"
:resource-type="TagResourceTypePath.Db"
:tag-path-node-type="NodeTypeTagPath"
>
<ResourceSelect v-bind="$attrs" v-model="selectNode" @change="changeNode" :resource-type="TagResourceTypePath.Db" :tag-path-node-type="NodeTypeDbInst">
<template #iconPrefix>
<SvgIcon v-if="dbType && getDbDialect(dbType)" :name="getDbDialect(dbType).getInfo().icon" :size="18" />
</template>
<template #prefix="{ data }">
<SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
<SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
</template>
</TagTreeResourceSelect>
</ResourceSelect>
</template>
<script setup lang="ts">
import { TagResourceTypePath } from '@/common/commonEnum';
import { computed } from 'vue';
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
import { dbApi } from '@/views/ops/db/api';
import { sleep } from '@/common/utils/loading';
import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
import { computed } from 'vue';
import { DbInst } from '../db';
import { getDbDialect, schemaDbTypes } from '@/views/ops/db/dialect';
import ResourceSelect from '@/views/ops/resource/ResourceSelect.vue';
import NodeDbInst from '@/views/ops/db/resource/NodeDbInst.vue';
import NodeDb from '@/views/ops/db/resource/NodeDb.vue';
import { DbIcon, SchemaIcon } from '@/views/ops/db/resource';
import { DbInst } from '@/views/ops/db/db';
const dbId = defineModel<number>('dbId');
const instName = defineModel<string>('instName');
@@ -35,20 +27,6 @@ const dbType = defineModel<string>('dbType');
const emits = defineEmits(['selectDb']);
/**
* 树节点类型
*/
class SqlExecNodeType {
static DbInst = 1;
static Db = 2;
static TableMenu = 3;
static SqlMenu = 4;
static Table = 5;
static Sql = 6;
static PgSchemaMenu = 7;
static PgSchema = 8;
}
const selectNode = computed({
get: () => {
return dbName.value ? `${tagPath.value} > ${instName.value} > ${dbName.value}` : '';
@@ -58,90 +36,94 @@ const selectNode = computed({
},
});
const DbIcon = {
name: 'Coin',
color: '#67c23a',
};
const NodeTypeDbInst = new NodeType(TagResourceTypeEnum.DbInstance.value).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const tagPath = parentNode.key;
// pgsql schema icon
const SchemaIcon = {
name: 'List',
color: '#67c23a',
};
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
const dbInfos = dbInfoRes.list;
if (!dbInfos) {
const dbInstancesRes = await dbApi.instances.request({ tagPath, pageSize: 100 });
const dbInstances = dbInstancesRes.list;
if (!dbInstances) {
return [];
}
// 防止过快加载会出现一闪而过,对眼睛不好
await sleep(100);
return dbInfos?.map((x: any) => {
x.tagPath = parentNode.key;
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
return dbInstances?.map((x: any) => {
x.tagPath = tagPath;
return TagTreeNode.new(parentNode, `${x.code}`, x.name, NodeTypeDbConf).withParams(x).withNodeComponent(NodeDbInst);
});
});
/** mysql类型的数据库没有schema层 */
const noSchemaType = (type: string) => {
return noSchemaTypes.includes(type);
};
const NodeTypeDbConf = new NodeType(TagResourceTypeEnum.Db.value).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params;
// 数据库实例节点类型
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const tagPath = params.tagPath;
const authCerts = {} as any;
for (let authCert of params.authCerts) {
authCerts[authCert.name] = authCert;
}
const dbInfoRes = await dbApi.dbs.request({
tagPath: `${tagPath}${TagResourceTypeEnum.DbInstance.value}|${params.code}`,
});
const dbInfos = dbInfoRes.list;
if (!dbInfos) {
return [];
}
return dbInfos?.map((x: any) => {
x.tagPath = tagPath;
x.username = authCerts[x.authCertName]?.username;
return TagTreeNode.new(parentNode, `${x.code}`, x.name, NodeTypeDbs).withParams(x).withIcon(DbIcon).withNodeComponent(NodeDb);
});
});
// 数据库列表名类型
const NodeTypeDbs = new NodeType(222).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params;
const dbs = (await DbInst.getDbNames(params))?.sort();
let fn: NodeType;
if (noSchemaType(params.type)) {
fn = MysqlNodeTypes;
} else {
fn = PgNodeTypes;
}
const hasSchema = schemaDbTypes.includes(params.type);
const nodeType = hasSchema ? NodeTypeDbSchema : NodeTypeNoSchemaDb;
return dbs.map((x: any) => {
let tagTreeNode = new TagTreeNode(`${parentNode.key}.${x}`, `${x}`, fn)
return TagTreeNode.new(parentNode, `${parentNode.key}.${x}`, x, nodeType)
.withParams({
tagPath: params.tagPath,
id: params.id,
code: params.code,
instanceId: params.instanceId,
name: params.name,
type: params.type,
host: `${params.host}:${params.port}`,
dbs: dbs,
db: x,
code: params.code,
})
.withIcon(DbIcon);
if (noSchemaType(params.type)) {
tagTreeNode.isLeaf = true;
}
return tagTreeNode;
.withIcon(DbIcon)
.withIsLeaf(!hasSchema);
});
});
// 数据库节点
const PgNodeTypes = new NodeType(SqlExecNodeType.Db).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
// pg类数据库会多一层schema
const NodeTypeDbSchema = new NodeType(2).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params;
params.parentKey = parentNode.key;
const { id, db } = params;
const schemaNames = await dbApi.pgSchemas.request({ id, db });
const dbs = schemaNames.map((x: any) => `${db}/${x}`);
return schemaNames.map((sn: any) => {
// 将db变更为 db/schema;
const nParams = { ...params };
nParams.schema = sn;
nParams.db = nParams.db + '/' + sn;
nParams.dbs = schemaNames;
let tagTreeNode = new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema).withParams(nParams).withIcon(SchemaIcon);
tagTreeNode.isLeaf = true;
return tagTreeNode;
nParams.dbs = dbs;
return TagTreeNode.new(parentNode, `${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema)
.withParams(nParams)
.withIcon(SchemaIcon)
.withIsLeaf(true);
});
});
const MysqlNodeTypes = new NodeType(SqlExecNodeType.Db);
// postgres schema模式
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema);
const NodeTypePostgresSchema = new NodeType(99);
const NodeTypeNoSchemaDb = new NodeType(99);
const changeNode = (nodeData: TagTreeNode) => {
const params = nodeData.params;

View File

@@ -16,18 +16,18 @@ const NodeDbInst = defineAsyncComponent(() => import('./NodeDbInst.vue'));
const NodeDb = defineAsyncComponent(() => import('./NodeDb.vue'));
const NodeDbTable = defineAsyncComponent(() => import('./NodeDbTable.vue'));
const DbIcon = {
export const DbIcon = {
name: ResourceTypeEnum.Db.extra.icon,
color: ResourceTypeEnum.Db.extra.iconColor,
};
// pgsql schema icon
const SchemaIcon = {
export const SchemaIcon = {
name: 'List',
color: '#67c23a',
};
const TableIcon = {
export const TableIcon = {
name: 'icon db/table',
color: '#409eff',
};

View File

@@ -33,13 +33,9 @@
<el-descriptions-item :span="3" :label="$t('tag.relateTag')"><ResourceTags :tags="detailDialog.data.tags" /></el-descriptions-item>
<el-descriptions-item :span="3" label="Host">{{ detailDialog.data.host }}</el-descriptions-item>
<el-descriptions-item :span="3" :label="$t('docker.addr')">{{ detailDialog.data.addr }}</el-descriptions-item>
<el-descriptions-item :span="3" label="DB">{{ detailDialog.data.db }}</el-descriptions-item>
<el-descriptions-item :span="3" :label="$t('common.remark')">{{ detailDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" :label="$t('machine.sshTunnel')">
{{ detailDialog.data.sshTunnelMachineId > 0 ? $t('common.yes') : $t('common.no') }}
</el-descriptions-item>
<el-descriptions-item :span="2" :label="$t('common.createTime')">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" :label="$t('common.creator')">{{ detailDialog.data.creator }}</el-descriptions-item>

View File

@@ -8,7 +8,7 @@
<el-row class="mb-2 ml-4">
<el-breadcrumb separator-icon="ArrowRight">
<el-breadcrumb-item v-for="path in filePathNav" :key="path">
<el-link @click="setFiles(path.path)" style="font-weight: bold">{{ path.name }}</el-link>
<el-link @click="setFiles(path.path)" class="!cursor-pointer !font-bold">{{ path.name }}</el-link>
</el-breadcrumb-item>
</el-breadcrumb>
</el-row>

View File

@@ -4,7 +4,7 @@ import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
import { redisApi } from '../api';
import { sleep } from '@/common/utils/loading';
const RedisIcon = {
export const RedisIcon = {
name: ResourceTypeEnum.Redis.extra.icon,
color: ResourceTypeEnum.Redis.extra.iconColor,
};

View File

@@ -2,7 +2,7 @@
<div
:id="props.node.key"
class="w-full node-container flex items-center cursor-pointer select-none"
:class="props.data.type.nodeDblclickFunc ? 'select-none' : ''"
:class="props.data.type?.nodeDblclickFunc ? 'select-none' : ''"
@mouseenter="showActions = true"
@mouseleave="showActions = false"
>
@@ -55,7 +55,7 @@ import { ContextmenuItem } from '@/components/contextmenu';
import { ResourceOpCtx, TagTreeNode } from '@/views/ops/component/tag';
import { ResourceOpCtxKey } from '@/views/ops/resource/resource';
const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey);
const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey, undefined);
const props = defineProps({
node: {

View File

@@ -18,28 +18,18 @@
<slot name="iconPrefix" :node="node" :data="data" />
</template>
<template #default="{ node, data }">
<span>
<span v-if="data.type.value == TagTreeNode.TagPath">
<tag-info :tag-path="data.label" />
</span>
<slot v-else :node="node" :data="data" name="prefix"></slot>
<span class="ml-0.5" :title="data.labelRemark">
<slot name="label" :data="data"> {{ data.label }}</slot>
</span>
<slot :node="node" :data="data" name="suffix"></slot>
</span>
<component v-if="data.nodeComponent" :is="data.nodeComponent" :node="node" :data="data" />
<BaseTreeNode v-else :node="node" :data="data" />
</template>
</el-tree-select>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, toRefs, watch } from 'vue';
import { NodeType, TagTreeNode } from './tag';
import TagInfo from './TagInfo.vue';
import { tagApi } from '../tag/api';
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
import { tagApi } from '@/views/ops/tag/api';
import BaseTreeNode from '@/views/ops/resource/BaseTreeNode.vue';
const props = defineProps({
resourceType: {

View File

@@ -16,8 +16,11 @@
<template #content>
{{ $t('tag.tagTips1') }}
<br />
{{ $t('tag.tagTips2') }} <br />
{{ $t('tag.tagTips2') }}
<br />
{{ $t('tag.tagTips3') }}
<br />
{{ $t('tag.tagTips4') }}
</template>
<SvgIcon class="ml-1" name="question-filled" />
</el-tooltip>
@@ -37,10 +40,6 @@
@node-contextmenu="onNodeContextmenu"
@node-click="onTreeNodeClick"
:default-expanded-keys="defaultExpandedKeys"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
@node-drop="onNodeDrop"
:expand-on-click-node="false"
:filter-node-method="filterNode"
>
@@ -279,7 +278,7 @@ watch(filterTag, (val) => {
watch(
() => state.currentTag,
(val: any) => {
if (val.type == TagResourceTypeEnum.Tag.value) {
if (val?.type == TagResourceTypeEnum.Tag.value) {
tagApi.countTagResource.request({ tagPath: val.codePath }).then((res: any) => {
state.resourceCount = res;
});
@@ -289,82 +288,6 @@ watch(
}
);
const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
// 不允许同层级移动
if (type != 'inner') {
return false;
}
const dropNodeData = dropNode.data;
const draggingNodeData = draggingNode.data;
const dropTagType = dropNodeData.type;
const draggingTagType = draggingNodeData.type;
// 目标节点只允许为标签类型
if (dropTagType != TagResourceTypeEnum.Tag.value) {
return false;
}
// 目标节点下没有子节点
if (!dropNodeData.children) {
// 都为标签类型允许移动
if (dropTagType == draggingTagType && dropTagType == TagResourceTypeEnum.Tag.value) {
return true;
}
// 目标节点为标签,允许移动
if (dropTagType == TagResourceTypeEnum.Tag.value) {
return true;
}
return false;
}
for (let child of dropNodeData.children) {
// 当前移动节点若在目标节点下有相同code则不允许移动
if (draggingNodeData.code == child.code) {
return false;
}
const childType = child.type;
// 移动节点非标签类型时(资源标签),并且子节点存在标签类型,则不允许移动,因为资源只允许放在叶子标签类型下
if (draggingTagType != TagResourceTypeEnum.Tag.value && childType == TagResourceTypeEnum.Tag.value) {
return false;
}
// 移动节点为标签类型时(资源标签),并且子节点存在资源类型,则不允许移动
if (draggingTagType == TagResourceTypeEnum.Tag.value && childType != TagResourceTypeEnum.Tag.value) {
return false;
}
}
return true;
};
const allowDrag = (node: any) => {
const tagType = node.data.type;
return (
tagType == TagResourceTypeEnum.Tag.value ||
tagType == TagResourceTypeEnum.DbInstance.value ||
tagType == TagResourceTypeEnum.Redis.value ||
tagType == TagResourceTypeEnum.Machine.value ||
tagType == TagResourceTypeEnum.Mongo.value
);
};
const onNodeDrop = async (draggingNode: any, dropNode: any) => {
const draggingData = draggingNode.data;
const dropData = dropNode.data;
try {
await tagApi.movingTag.request({
fromPath: draggingData.codePath,
toPath: dropData.codePath,
});
} finally {
search();
}
};
const onTabChange = () => {
setNowTabData();
};

View File

@@ -6,21 +6,21 @@ require (
gitee.com/chunanyong/dm v1.8.20
gitee.com/liuzongyang/libpq v1.10.11
github.com/antlr4-go/antlr/v4 v4.13.1
github.com/docker/docker v28.4.0+incompatible
github.com/docker/docker v28.5.0+incompatible
github.com/docker/go-connections v0.6.0
github.com/gin-gonic/gin v1.10.1
github.com/gin-gonic/gin v1.11.0
github.com/glebarez/sqlite v1.11.0
github.com/go-gormigrate/gormigrate/v2 v2.1.5
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.27.0
github.com/go-playground/validator/v10 v10.28.0
github.com/go-sql-driver/mysql v1.9.3
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250630080345-f9402614f6ba
github.com/microsoft/go-mssqldb v1.9.2
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250930013652-2d71241a3bb9
github.com/microsoft/go-mssqldb v1.9.3
github.com/mojocn/base64Captcha v1.3.8 //
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
@@ -49,10 +49,12 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
@@ -62,13 +64,14 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@@ -81,7 +84,7 @@ require (
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
@@ -92,9 +95,10 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.55.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@@ -103,24 +107,26 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
golang.org/x/arch v0.19.0 // indirect
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/image v0.29.0 // indirect
golang.org/x/net v0.43.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/arch v0.21.0 // indirect
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
golang.org/x/image v0.31.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
modernc.org/libc v1.66.4 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.1 // indirect
modernc.org/sqlite v1.39.0 // indirect
)
replace google.golang.org/genproto => google.golang.org/genproto v0.0.0-20250603155806-513f23925822

View File

@@ -370,9 +370,11 @@ func (app *dataSyncAppImpl) saveLog(log *entity.DataSyncLog) {
}
func (app *dataSyncAppImpl) InitCronJob() {
ctx := contextx.NewTraceId()
defer func() {
if err := recover(); err != nil {
logx.ErrorTrace("the data synchronization task failed to initialize", err)
logx.ErrorTraceContext(ctx, "the data synchronization task failed to initialize", err)
}
}()
@@ -380,11 +382,11 @@ func (app *dataSyncAppImpl) InitCronJob() {
_ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning})
if err := app.CursorByCond(&entity.DataSyncTaskQuery{Status: entity.DataSyncTaskStatusEnable}, func(dst *entity.DataSyncTask) error {
app.addCronJob(contextx.NewTraceId(), dst)
app.MarkStop(dst.Id)
app.addCronJob(ctx, dst)
return nil
}); err != nil {
logx.ErrorTrace("the db data sync task failed to initialize: %v", err)
logx.ErrorTraceContext(ctx, "the db data sync task failed to initialize: %v", err)
}
}
@@ -415,13 +417,13 @@ func (app *dataSyncAppImpl) addCronJob(ctx context.Context, taskEntity *entity.D
// 根据状态添加新的任务
if taskEntity.Status == entity.DataSyncTaskStatusEnable {
taskId := taskEntity.Id
logx.Infof("start add the data sync task job: %s, cron[%s]", taskEntity.TaskName, taskEntity.TaskCron)
logx.InfofContext(ctx, "start add the data sync task job: %s, cron[%s]", taskEntity.TaskName, taskEntity.TaskCron)
if err := scheduler.AddFunByKey(key, taskEntity.TaskCron, func() {
if err := app.Run(context.Background(), taskId); err != nil {
logx.Errorf("the data sync task failed to execute at a scheduled time: %s", err.Error())
logx.ErrorfContext(ctx, "the data sync task failed to execute at a scheduled time: %s", err.Error())
}
}); err != nil {
logx.ErrorTrace("add db data sync job failed", err)
logx.ErrorTraceContext(ctx, "add db data sync job failed", err)
}
}
}

View File

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

View File

@@ -87,6 +87,7 @@ func (a *Account) GetPermissions(rc *req.Ctx) {
var resources vo.AccountResourceVOList
// 获取账号菜单资源
biz.ErrIsNil(a.resourceApp.GetAccountResources(account.Id, &resources))
biz.IsTrue(len(resources) > 0, "no permission")
// 菜单树与权限code数组
var menus vo.AccountResourceVOList
var permissions []string

View File

@@ -121,8 +121,8 @@ func Errorf(format string, args ...any) {
Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
}
// 错误记录并将堆栈信息添加至msg里默认记录10个堆栈信息
func ErrorTrace(msg string, err any) {
// ErrorTraceContext 错误记录并将堆栈信息添加至msg里默认记录10个堆栈信息
func ErrorTraceContext(ctx context.Context, msg string, err any) {
errMsg := ""
switch t := err.(type) {
case error:
@@ -132,7 +132,12 @@ func ErrorTrace(msg string, err any) {
default:
errMsg = fmt.Sprintf("%v", t)
}
Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+"\n%s\n%s", errMsg, runtimex.StackStr(2, 20)))
Log(ctx, slog.LevelError, fmt.Sprintf(msg+"\n%s\n%s", errMsg, runtimex.StackStr(2, 20)))
}
// ErrorTrace 错误记录并将堆栈信息添加至msg里默认记录10个堆栈信息
func ErrorTrace(msg string, err any) {
ErrorTraceContext(context.Background(), msg, err)
}
func ErrorWithFields(ctx context.Context, msg string, mapFields map[string]any) {

View File

@@ -49,8 +49,9 @@ func Error(bizerr *errorx.BizError) *Result {
}
// 返回服务器错误Result
func ServerError() *Result {
return Error(errorx.ServerError)
func ServerError(msg string) *Result {
serverErr := errorx.NewBizCode(errorx.ServerError.Code(), msg)
return Error(serverErr)
}
func TokenError() *Result {

View File

@@ -1,7 +1,9 @@
package req
import (
"cmp"
"context"
"fmt"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
@@ -109,8 +111,8 @@ func (rc *Ctx) res() {
case *errorx.BizError:
rc.JSONRes(http.StatusOK, model.Error(t))
default:
logx.ErrorTrace("服务器错误", t)
rc.JSONRes(http.StatusOK, model.ServerError())
logx.ErrorTrace("server error", t)
rc.JSONRes(http.StatusOK, model.ServerError(fmt.Sprintf("server error [%d-%s]", errorx.ServerError.Code(), cmp.Or(contextx.GetTraceId(rc.MetaCtx), "none"))))
}
return
}

View File

@@ -53,7 +53,7 @@ func Ip2Region(ip string) string {
}
// 2、用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
searcher, err := xdb.NewWithVectorIndex(ip2RegionDbPath, vectorIndex)
searcher, err := xdb.NewWithVectorIndex(xdb.IPv4, ip2RegionDbPath, vectorIndex)
if err != nil {
logx.Errorf("failed to create searcher with vector index: %s\n", err)
return ""