fix: 资源关联多标签删除、数据库实例删除等问题修复与数据库等名称过滤优化

This commit is contained in:
meilin.huang
2024-06-07 12:31:40 +08:00
parent 73884bb693
commit f43851698e
14 changed files with 82 additions and 127 deletions

View File

@@ -22,7 +22,7 @@
### 介绍
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库mysql postgres oracle sqlserver 达梦 高斯 sqlite数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)、数据库mysql postgres oracle sqlserver 达梦 高斯 sqlite数据操作 数据同步 数据迁移、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
### 开发语言与主要框架

View File

@@ -17,7 +17,7 @@
"cropperjs": "^1.6.1",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"element-plus": "^2.7.3",
"element-plus": "^2.7.4",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
@@ -57,7 +57,7 @@
"prettier": "^3.2.5",
"sass": "^1.77.1",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite": "^5.2.12",
"vue-eslint-parser": "^9.4.2"
},
"browserslist": [

View File

@@ -97,43 +97,6 @@ export function getTextWidth(str: string) {
return width;
}
/**
* 获取内容所需要占用的宽度
*/
export function getContentWidth(content: any): number {
if (!content) {
return 50;
}
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0;
for (const char of content) {
if (flexWidth > 500) {
break;
}
if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
// 小写字母、数字字符
flexWidth += 9.3;
continue;
}
if (char >= 'A' && char <= 'Z') {
flexWidth += 9;
continue;
}
if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符为字符分配16个单位宽度
flexWidth += 20;
} else {
// 其他种类字符
flexWidth += 8;
}
}
// if (flexWidth > 450) {
// // 设置最大宽度
// flexWidth = 450;
// }
return flexWidth;
}
/**
*
* @returns uuid
@@ -179,3 +142,38 @@ export async function copyToClipboard(txt: string, selector: string = '#copyValu
clipboard.destroy();
});
}
export function fuzzyMatchField(keyword: string, fields: any[], ...valueExtractFuncs: Function[]) {
keyword = keyword?.toLowerCase();
return fields.filter((field) => {
for (let valueExtractFunc of valueExtractFuncs) {
const value = valueExtractFunc(field)?.toLowerCase();
if (isPrefixSubsequence(keyword, value)) {
return true;
}
}
return false;
});
}
/**
* 匹配是否为前缀子序列 targetTemplate=username prefix=uname -> trueprefix=uname2 -> false
* @param prefix 字符串前缀(不连续也可以,但不改变字符的相对顺序)
* @param targetTemplate 目标模板
* @returns 是否匹配
*/
export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
let i = 0; // 指向prefix的索引
let j = 0; // 指向targetTemplate的索引
while (i < prefix.length && j < targetTemplate.length) {
if (prefix[i] === targetTemplate[j]) {
// 字符匹配,两个指针都向前移动
i++;
}
j++; // 目标字符串指针始终向前移动
}
// 如果prefix的所有字符都被找到返回true
return i === prefix.length;
}

View File

@@ -53,6 +53,7 @@ import { NodeType, TagTreeNode } from './tag';
import TagInfo from './TagInfo.vue';
import { Contextmenu } from '@/components/contextmenu';
import { tagApi } from '../tag/api';
import { isPrefixSubsequence } from '@/common/utils/string';
const props = defineProps({
resourceType: {
@@ -105,8 +106,7 @@ watch(filterText, (val) => {
});
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.includes(value);
return !value || isPrefixSubsequence(value, data.label);
};
/**

View File

@@ -50,6 +50,7 @@ import { ref, reactive, onMounted } from 'vue';
import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumValue from '@/common/Enum';
import { isPrefixSubsequence } from '@/common/utils/string';
const props = defineProps({
height: {
@@ -102,10 +103,7 @@ const search = async () => {
};
const filterNode = (value: string, data: any) => {
if (!value) {
return true;
}
return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
return !value || isPrefixSubsequence(value, data.codePath) || isPrefixSubsequence(value, data.name);
};
const onFilterValChanged = (val: string) => {

View File

@@ -259,7 +259,7 @@ import DbTableData from './DbTableData.vue';
import { DbDialect } from '@/views/ops/db/dialect';
import SvgIcon from '@/components/svgIcon/index.vue';
import { useEventListener, useStorage } from '@vueuse/core';
import { copyToClipboard } from '@/common/utils/string';
import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
import DbTableDataForm from './DbTableDataForm.vue';
const props = defineProps({
@@ -476,10 +476,7 @@ const getColumnTips = (queryString: string, callback: any) => {
let res = [];
if (columnNameSearch) {
columnNameSearch = columnNameSearch.toLowerCase();
res = columns.filter((data: any) => {
return data.columnName.toLowerCase().includes(columnNameSearch);
});
res = fuzzyMatchField(columnNameSearch, columns, (x: any) => x.columnName);
}
completeCond = condition.value;
@@ -534,10 +531,12 @@ const filterColumns = (searchKey: string) => {
if (!searchKey) {
return columns;
}
searchKey = searchKey.toLowerCase();
return columns.filter((data: any) => {
return data.columnName.toLowerCase().includes(searchKey) || data.columnComment.toLowerCase().includes(searchKey);
});
return fuzzyMatchField(
searchKey,
columns,
(x: any) => x.columnName,
(x: any) => x.columnComment
);
};
/**

View File

@@ -131,6 +131,7 @@ import { compatibleMysql, editDbTypes, getDbDialect } from '../../dialect/index'
import { DbInst } from '../../db';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { format as sqlFormatter } from 'sql-formatter';
import { fuzzyMatchField } from '@/common/utils/string';
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
@@ -219,17 +220,11 @@ const filterTableInfos = computed(() => {
if (!tableNameSearch && !tableCommentSearch) {
return tables;
}
return tables.filter((data: any) => {
let tnMatch = true;
let tcMatch = true;
if (tableNameSearch) {
tnMatch = data.tableName.toLowerCase().includes(tableNameSearch.toLowerCase());
}
if (tableCommentSearch) {
tcMatch = data.tableComment.includes(tableCommentSearch);
}
return tnMatch && tcMatch;
});
if (tableNameSearch) {
return fuzzyMatchField(tableNameSearch, tables, (table: any) => table.tableName);
}
return fuzzyMatchField(tableCommentSearch, tables, (table: any) => table.tableComment);
});
const getTables = async () => {

View File

@@ -293,6 +293,7 @@ import { getToken } from '@/common/utils/storage';
import { convertToBytes, formatByteSize } from '@/common/utils/format';
import { getMachineConfig } from '@/common/sysconfig';
import { MachineProtocolEnum } from '../enums';
import { fuzzyMatchField } from '@/common/utils/string';
const props = defineProps({
machineId: { type: Number },
@@ -371,33 +372,7 @@ onMounted(async () => {
state.machineConfig = await getMachineConfig();
});
// watch(
// () => props.machineId,
// () => {
// if (props.protocol != MachineProtocolEnum.Ssh.value) {
// userMap.clear();
// groupMap.clear();
// return;
// }
// const machineId = props.machineId;
// machineApi.users.request({ machineId }).then((res: any) => {
// for (let user of res) {
// userMap.set(user.uid, user);
// }
// });
// machineApi.groups.request({ machineId }).then((res: any) => {
// for (let group of res) {
// groupMap.set(group.gid, group);
// }
// });
// }
// );
const filterFiles = computed(() =>
state.files.filter((data: any) => !state.fileNameFilter || data.name.toLowerCase().includes(state.fileNameFilter.toLowerCase()))
);
const filterFiles = computed(() => fuzzyMatchField(state.fileNameFilter, state.files, (file: any) => file.name));
const filePathNav = computed(() => {
let basePath = state.basePath;

View File

@@ -156,6 +156,7 @@ import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import EnumValue from '@/common/Enum';
import TagCodePath from '../component/TagCodePath.vue';
import { isPrefixSubsequence } from '@/common/utils/string';
const MachineList = defineAsyncComponent(() => import('../machine/MachineList.vue'));
const InstanceList = defineAsyncComponent(() => import('../db/InstanceList.vue'));
@@ -371,8 +372,7 @@ const setNowTabData = () => {
};
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.codePath.toLowerCase().includes(value) || data.name.includes(value);
return !value || isPrefixSubsequence(value, data.codePath) || isPrefixSubsequence(value, data.name);
};
const search = async () => {

View File

@@ -123,6 +123,7 @@ import { formatDate } from '@/common/utils/format';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
import { Splitpanes, Pane } from 'splitpanes';
import { isPrefixSubsequence } from '@/common/utils/string';
const menuTypeValue = ResourceTypeEnum.Menu.value;
const permissionTypeValue = ResourceTypeEnum.Permission.value;
@@ -209,10 +210,7 @@ watch(filterResource, (val) => {
});
const filterNode = (value: string, data: any) => {
if (!value) {
return true;
}
return data.name.includes(value);
return !value || isPrefixSubsequence(value, data.name);
};
const search = async () => {

View File

@@ -32,8 +32,8 @@ require (
github.com/stretchr/testify v1.9.0
github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver v1.15.0 // mongo
golang.org/x/crypto v0.23.0 // ssh
golang.org/x/oauth2 v0.20.0
golang.org/x/crypto v0.24.0 // ssh
golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.7.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
@@ -94,8 +94,8 @@ require (
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
google.golang.org/grpc v1.52.3 // indirect
google.golang.org/protobuf v1.34.1 // indirect

View File

@@ -2,7 +2,6 @@ package application
import (
"context"
"errors"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/application/dto"
"mayfly-go/internal/db/dbm"
@@ -13,14 +12,11 @@ import (
tagdto "mayfly-go/internal/tag/application/dto"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
"gorm.io/gorm"
)
type Instance interface {
@@ -171,7 +167,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
}
func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error {
instance, err := app.GetById(instanceId, "name")
instance, err := app.GetById(instanceId)
if err != nil {
return errorx.NewBiz("获取数据库实例错误数据库实例ID为: %d", instance.Id)
}
@@ -180,26 +176,16 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
DbInstanceId: instanceId,
}
err = app.restoreApp.restoreRepo.GetByCond(restore)
switch {
case err == nil:
biz.ErrNotNil(err, "不能删除数据库实例【%s】请先删除关联的数据库恢复任务。", instance.Name)
case errors.Is(err, gorm.ErrRecordNotFound):
break
default:
biz.ErrIsNil(err, "删除数据库实例失败: %v", err)
if err != nil {
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库恢复任务。", instance.Name)
}
backup := &entity.DbBackup{
DbInstanceId: instanceId,
}
err = app.backupApp.backupRepo.GetByCond(backup)
switch {
case err == nil:
biz.ErrNotNil(err, "不能删除数据库实例【%s】请先删除关联的数据库备份任务。", instance.Name)
case errors.Is(err, gorm.ErrRecordNotFound):
break
default:
biz.ErrIsNil(err, "删除数据库实例失败: %v", err)
if err != nil {
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库备份任务。", instance.Name)
}
dbs, _ := app.dbApp.ListByCond(&entity.Db{

View File

@@ -264,6 +264,9 @@ func getInterfaceInfo(iInfo string, stats *Stats) (err error) {
}
func getCPU(cpuInfo string, stats *Stats) (err error) {
if !strings.Contains(cpuInfo, ":") {
return
}
// %Cpu(s): 6.1 us, 3.0 sy, 0.0 ni, 90.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
value := strings.Split(cpuInfo, ":")[1]
values := strings.Split(value, ",")

View File

@@ -312,6 +312,7 @@ func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *dto.DelRes
}
delTagType := param.ChildType
var childrenTagIds []uint64
for _, resourceTag := range resourceTags {
// 获取所有关联的子标签
childrenTag, _ := p.ListByCond(model.NewCond().RLike("code_path", resourceTag.CodePath).Eq("type", delTagType))
@@ -319,14 +320,16 @@ func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *dto.DelRes
continue
}
childrenTagIds := collx.ArrayMap(childrenTag, func(item *entity.TagTree) uint64 {
childrenTagIds = append(childrenTagIds, collx.ArrayMap(childrenTag, func(item *entity.TagTree) uint64 {
return item.Id
})
// 删除code_path下的所有子标签
return p.deleteByIds(ctx, childrenTagIds)
})...)
}
return nil
if len(childrenTagIds) == 0 {
return nil
}
// 删除code_path下的所有子标签
return p.deleteByIds(ctx, collx.ArrayDeduplicate(childrenTagIds))
}
func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity any) {