fix: i18n & other optimizations

This commit is contained in:
meilin.huang
2024-11-23 17:23:18 +08:00
parent bffa9c2676
commit cda2963e1c
40 changed files with 400 additions and 324 deletions

View File

@@ -27,7 +27,7 @@ export function useI18nPleaseSelect(labelI18nKey: string) {
* @returns
*/
export async function useI18nDeleteConfirm(name: string = '') {
return useI18nConfirm('common.deleteConfirm', { name });
return useI18nConfirm('common.deleteConfirm2', { name });
}
/**

View File

@@ -51,7 +51,8 @@ export default {
createTitle: 'Create {name}',
editTitle: 'Edit {name}',
detailTitle: '{name} Details',
deleteConfirm: 'This operation will delete [{name}]. Do you want to continue?',
deleteConfirm: 'Sure to delete?',
deleteConfirm2: 'This operation will delete [{name}]. Do you want to continue?',
saveSuccess: 'save successfully',
deleteSuccess: 'delete successfully',
operateSuccess: 'operate successfully',

View File

@@ -73,6 +73,21 @@ export default {
newTabRunSql: 'NewTab Run SQL',
formatSql: 'Format SQL',
alias: 'Alias',
tableName: 'Name',
addColumn: 'Add Column',
addDefaultColumn: 'Add Default Column',
addIndex: 'Add Index',
length: 'Length',
numScale: 'Scale',
defaultValue: 'Default Value',
notNull: 'Not Null',
primaryKey: 'Pri',
autoIncrement: 'Auto Incr',
unique: 'Unique',
uniqueIndex: 'Unique Index',
normalIndex: 'Normal Index',
execTime: 'execution time',
oneClickCopy: 'One click copy',
asc: 'Asc',

View File

@@ -51,7 +51,8 @@ export default {
createTitle: '创建{name}',
editTitle: '编辑{name}',
detailTitle: '{name}详情',
deleteConfirm: '此操作将删除【{name}】, 是否继续',
deleteConfirm: '确定删除',
deleteConfirm2: '此操作将删除【{name}】, 是否继续?',
saveSuccess: '保存成功',
deleteSuccess: '删除成功',
operateSuccess: '操作成功',

View File

@@ -72,6 +72,21 @@ export default {
newTabRunSql: '新标签执行SQL',
formatSql: '格式化SQL',
alias: '别名',
tableName: '表名',
addColumn: '添加列',
addDefaultColumn: '添加默认列',
addIndex: '添加索引',
length: '长度',
numScale: '精度',
defaultValue: '默认值',
notNull: '非空',
primaryKey: '主键',
autoIncrement: '自增',
unique: '唯一',
uniqueIndex: '唯一索引',
normalIndex: '普通索引',
execTime: '执行时间',
oneClickCopy: '一键复制',
asc: '升序',

View File

@@ -112,7 +112,7 @@ const perms = {
const searchItems = [
SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'),
getTagPathSearchItem(TagResourceTypeEnum.Db.value),
getTagPathSearchItem(TagResourceTypeEnum.DbAuthCert.value),
];
const columns = ref([

View File

@@ -155,7 +155,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch, Ref } from 'vue';
import { ElInput, ElMessage } from 'element-plus';
import { copyToClipboard } from '@/common/utils/string';
import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
@@ -239,9 +239,11 @@ const cmHeaderFixed = new ContextmenuItem('fixed', 'db.fixed')
})
.withHideFunc((data: any) => data.fixed);
const cmHeaderCancenFixed = new ContextmenuItem('cancelFixed', 'db.cancelFiexd')
const cmHeaderCancelFixed = new ContextmenuItem('cancelFixed', 'db.cancelFiexd')
.withIcon('Minus')
.withOnClick((data: any) => (data.fixed = false))
.withOnClick((data: any) => {
data.fixed = false;
})
.withHideFunc((data: any) => !data.fixed);
/** 表数据 contextmenu items **/
@@ -253,7 +255,7 @@ const cmDataCopyCell = new ContextmenuItem('copyValue', 'common.copy')
})
.withHideFunc(() => {
// 选中多条则隐藏该复制按钮
return selectionRowsMap.size > 1;
return selectionRowsMap.value.size > 1;
});
const cmDataDel = new ContextmenuItem('deleteData', 'common.delete')
@@ -307,7 +309,7 @@ class UpdatedRow {
/**
* 修改到的列信息, columnName -> tablecelldata
*/
columnsMap: Map<string, TableCellData> = new Map();
columnsMap = new Map<string, TableCellData>();
}
class TableCellData {
@@ -319,16 +321,16 @@ class TableCellData {
let dbDialect: DbDialect = null as any;
let nowSortColumn = null as any;
let nowSortColumn = ref(null) as any;
// 当前正在更新的单元格
let nowUpdateCell: NowUpdateCell = null as any;
let nowUpdateCell: Ref<NowUpdateCell> = ref(null) as any;
// 选中的数据, key->rowIndex value->primaryKeyValue
const selectionRowsMap: Map<number, any> = new Map();
const selectionRowsMap = ref(new Map<number, any>());
// 更新单元格 key-> rowIndex value -> 更新行
const cellUpdateMap: Map<number, UpdatedRow> = new Map();
const cellUpdateMap = ref(new Map<number, UpdatedRow>());
// 数据加载时间计时器
const { pause, resume } = useIntervalFn(() => {
@@ -467,8 +469,8 @@ const formatDataValues = (datas: any) => {
const setTableData = (datas: any) => {
tableRef.value?.scrollTo({ scrollLeft: 0, scrollTop: 0 });
selectionRowsMap.clear();
cellUpdateMap.clear();
selectionRowsMap.value.clear();
cellUpdateMap.value.clear();
formatDataValues(datas);
state.datas = datas;
setTableColumns(props.columns);
@@ -520,7 +522,7 @@ const cancelLoading = async () => {
* @param colIndex ci
*/
const canEdit = (rowIndex: number, colIndex: number) => {
return state.table && nowUpdateCell?.rowIndex == rowIndex && nowUpdateCell?.colIndex == colIndex;
return state.table && nowUpdateCell.value?.rowIndex == rowIndex && nowUpdateCell.value?.colIndex == colIndex;
};
/**
@@ -529,7 +531,7 @@ const canEdit = (rowIndex: number, colIndex: number) => {
* @param columnName cn
*/
const isUpdated = (rowIndex: number, columnName: string) => {
return cellUpdateMap.get(rowIndex)?.columnsMap.get(columnName);
return cellUpdateMap.value.get(rowIndex)?.columnsMap.get(columnName);
};
/**
@@ -537,7 +539,7 @@ const isUpdated = (rowIndex: number, columnName: string) => {
* @param rowIndex
*/
const isSelection = (rowIndex: number): boolean => {
return selectionRowsMap.get(rowIndex);
return selectionRowsMap.value.get(rowIndex);
};
/**
@@ -549,16 +551,14 @@ const isSelection = (rowIndex: number): boolean => {
const selectionRow = (rowIndex: number, rowData: any, isMultiple = false) => {
if (isMultiple) {
// 如果重复点击,则取消改选中数据
if (selectionRowsMap.get(rowIndex)) {
selectionRowsMap.delete(rowIndex);
triggerRefresh();
if (selectionRowsMap.value.get(rowIndex)) {
selectionRowsMap.value.delete(rowIndex);
return;
}
} else {
selectionRowsMap.clear();
selectionRowsMap.value.clear();
}
selectionRowsMap.set(rowIndex, rowData);
triggerRefresh();
selectionRowsMap.value.set(rowIndex, rowData);
};
/**
@@ -584,7 +584,7 @@ const headerContextmenuClick = (event: any, data: any) => {
const { clientX, clientY } = event;
state.contextmenu.dropdown.x = clientX;
state.contextmenu.dropdown.y = clientY;
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancenFixed];
state.contextmenu.items = [cmHeaderAsc, cmHeaderDesc, cmHeaderFixed, cmHeaderCancelFixed];
contextmenuRef.value.openContextmenu(data);
};
@@ -606,7 +606,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
* 表排序字段变更
*/
const onTableSortChange = async (sort: any) => {
nowSortColumn = sort;
nowSortColumn.value = sort;
cancelUpdateFields();
emits('sortChange', sort);
};
@@ -615,7 +615,7 @@ const onTableSortChange = async (sort: any) => {
* 执行删除数据事件
*/
const onDeleteData = async () => {
const deleteDatas = Array.from(selectionRowsMap.values());
const deleteDatas = Array.from(selectionRowsMap.value.values());
const db = state.db;
const dbInst = getNowDbInst();
dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
@@ -624,7 +624,7 @@ const onDeleteData = async () => {
};
const onEditRowData = () => {
const selectionDatas = Array.from(selectionRowsMap.values());
const selectionDatas = Array.from(selectionRowsMap.value.values());
if (selectionDatas.length > 1) {
ElMessage.warning(t('db.onlySelectOneData'));
return;
@@ -636,14 +636,14 @@ const onEditRowData = () => {
};
const onGenerateInsertSql = async () => {
const selectionDatas = Array.from(selectionRowsMap.values());
const selectionDatas = Array.from(selectionRowsMap.value.values());
state.genTxtDialog.txt = await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas);
state.genTxtDialog.title = 'SQL';
state.genTxtDialog.visible = true;
};
const onGenerateJson = async () => {
const selectionDatas = Array.from(selectionRowsMap.values());
const selectionDatas = Array.from(selectionRowsMap.value.values());
// 按列字段重新排序对象key
const jsonObj = [];
for (let selectionData of selectionDatas) {
@@ -689,8 +689,7 @@ const onEnterEditMode = (rowData: any, column: any, rowIndex = 0, columnIndex =
return;
}
triggerRefresh();
nowUpdateCell = {
nowUpdateCell.value = {
rowIndex: rowIndex,
colIndex: columnIndex,
oldValue: rowData[column.dataKey],
@@ -702,21 +701,20 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
if (!nowUpdateCell) {
return;
}
const oldValue = nowUpdateCell.oldValue;
const oldValue = nowUpdateCell.value.oldValue;
const newValue = rowData[column.dataKey];
// 未改变单元格值
if (oldValue == newValue) {
nowUpdateCell = null as any;
triggerRefresh();
nowUpdateCell.value = null as any;
return;
}
let updatedRow = cellUpdateMap.get(rowIndex);
let updatedRow = cellUpdateMap.value.get(rowIndex);
if (!updatedRow) {
updatedRow = new UpdatedRow();
updatedRow.rowData = rowData;
cellUpdateMap.set(rowIndex, updatedRow);
cellUpdateMap.value.set(rowIndex, updatedRow);
}
const columnName = column.dataKey;
@@ -724,7 +722,7 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
if (cellData) {
// 多次修改情况,可能又修改回原值,则移除该修改单元格
if (cellData.oldValue == newValue) {
cellUpdateMap.delete(rowIndex);
cellUpdateMap.value.delete(rowIndex);
}
} else {
cellData = new TableCellData();
@@ -732,21 +730,20 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
updatedRow.columnsMap.set(columnName, cellData);
}
nowUpdateCell = null as any;
triggerRefresh();
nowUpdateCell.value = null as any;
changeUpdatedField();
};
const submitUpdateFields = async () => {
const dbInst = getNowDbInst();
if (cellUpdateMap.size == 0) {
if (cellUpdateMap.value.size == 0) {
return;
}
const db = state.db;
let res = '';
for (let updateRow of cellUpdateMap.values()) {
for (let updateRow of cellUpdateMap.value.values()) {
const rowData = { ...updateRow.rowData };
let updateColumnValue: any = {};
@@ -763,14 +760,13 @@ const submitUpdateFields = async () => {
}
dbInst.promptExeSql(db, res, null, () => {
triggerRefresh();
cellUpdateMap.clear();
cellUpdateMap.value.clear();
changeUpdatedField();
});
};
const cancelUpdateFields = () => {
const updateRows = cellUpdateMap.values();
const updateRows = cellUpdateMap.value.values();
// 恢复原值
for (let updateRow of updateRows) {
const rowData = updateRow.rowData;
@@ -778,12 +774,12 @@ const cancelUpdateFields = () => {
rowData[k] = v.oldValue;
});
}
cellUpdateMap.clear();
cellUpdateMap.value.clear();
changeUpdatedField();
};
const changeUpdatedField = () => {
emits('changeUpdatedField', cellUpdateMap);
emits('changeUpdatedField', cellUpdateMap.value);
};
const rowClass = (row: any) => {
@@ -819,18 +815,6 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
}
};
/**
* 触发响应式实时刷新,否则需要滑动或移动才能使样式实时生效
*/
const triggerRefresh = () => {
// 改变columns等属性值才能触发slot中的if条件等, 暂不知为啥
if (state.columns[0].opTimes) {
state.columns[0].opTimes = state.columns[0].opTimes + 1;
} else {
state.columns[0].opTimes = 1;
}
};
const scrollLeftValue = ref(0);
const onTableScroll = (param: any) => {
scrollLeftValue.value = param.scrollLeft;

View File

@@ -7,21 +7,27 @@
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item prop="tableName" label="表名">
<el-form-item prop="tableName" :label="$t('db.tableName')">
<el-input style="width: 80%" v-model="tableData.tableName" size="small"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="tableComment" label="备注">
<el-form-item prop="tableComment" :label="$t('db.comment')">
<el-input style="width: 80%" v-model="tableData.tableComment" size="small"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-tabs v-model="activeName">
<el-tab-pane label="字段" name="1">
<el-tab-pane :label="$t('db.column')" name="1">
<el-table ref="tableRef" :data="tableData.fields.res" :max-height="tableData.height">
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop" :width="item.width">
<el-table-column
:prop="item.prop"
:label="$t(item.label)"
v-for="item in tableData.fields.colNames"
:key="item.prop"
:width="item.width"
>
<template #default="scope">
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name" />
@@ -35,7 +41,7 @@
<span v-if="pgsqlType.dataType === pgsqlType.udtName"
>{{ pgsqlType.dataType }}{{ pgsqlType.desc && '' + pgsqlType.desc }}</span
>
<span v-else>{{ pgsqlType.dataType }}别名{{ pgsqlType.udtName }} {{ pgsqlType.desc }}</span>
<span v-else>{{ pgsqlType.dataType }}{{ $t('db.alias') }}: {{ pgsqlType.udtName }} {{ pgsqlType.desc }}</span>
</el-option>
</el-select>
@@ -58,20 +64,20 @@
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteRow(scope.$index)">
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.delete')" @confirm="deleteRow(scope.$index)">
<template #reference>
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
<el-link type="danger" plain size="small" :underline="false">{{ $t('common.delete') }}</el-link>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px">
<el-button @click="addDefaultRows()" link type="warning" icon="plus">添加默认列</el-button>
<el-button @click="addRow()" link type="primary" icon="plus">添加列</el-button>
<el-button @click="addDefaultRows()" link type="warning" icon="plus">{{ $t('db.addDefaultColumn') }}</el-button>
<el-button @click="addRow()" link type="primary" icon="plus">{{ $t('db.addColumn') }}</el-button>
</el-row>
</el-tab-pane>
<el-tab-pane label="索引" name="2">
<el-tab-pane :label="$t('db.index')" name="2">
<el-table :data="tableData.indexs.res" :max-height="tableData.height">
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.indexs.colNames" :key="item.prop">
<template #default="scope">
@@ -84,7 +90,6 @@
collapse-tags
collapse-tags-tooltip
filterable
placeholder="请选择字段"
@change="indexChanges(scope.row)"
style="width: 100%"
>
@@ -100,9 +105,9 @@
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteIndex(scope.$index)">
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.deleteConfirm')" @confirm="deleteIndex(scope.$index)">
<template #reference>
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
<el-link type="danger" plain size="small" :underline="false">{{ $t('common.delete') }}</el-link>
</template>
</el-popconfirm>
</template>
@@ -110,14 +115,14 @@
</el-table>
<el-row style="margin-top: 20px">
<el-button @click="addIndex()" link type="primary" icon="plus">添加索引</el-button>
<el-button @click="addIndex()" link type="primary" icon="plus">{{ $t('db.addIndex') }}</el-button>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="cancel()">取消</el-button>
<el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
<el-button :loading="btnloading" @click="submit()" type="primary">{{ $t('common.save') }}</el-button>
</template>
</el-drawer>
</template>
@@ -129,6 +134,9 @@ import SqlExecBox from '../sqleditor/SqlExecBox';
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
import { DbInst } from '../../db';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
visible: {
@@ -177,52 +185,52 @@ const state = reactive({
colNames: [
{
prop: 'name',
label: '字段名称',
label: 'db.columnName',
width: 200,
},
{
prop: 'type',
label: '字段类型',
label: 'common.type',
width: 120,
},
{
prop: 'length',
label: '长度',
label: 'db.length',
width: 120,
},
{
prop: 'numScale',
label: '小数精度',
label: 'db.numScale',
width: 120,
},
{
prop: 'value',
label: '默认值',
label: 'db.defaultValue',
width: 120,
},
{
prop: 'notNull',
label: '非空',
label: 'db.notNull',
width: 60,
},
{
prop: 'pri',
label: '主键',
label: 'db.primaryKey',
width: 60,
},
{
prop: 'auto_increment',
label: '自增',
label: 'db.autoIncrement',
width: 60,
},
{
prop: 'remark',
label: '备注',
label: 'db.comment',
},
{
prop: 'action',
label: '操作',
label: 'common.operation',
width: 70,
},
] as ColName[],
@@ -233,27 +241,27 @@ const state = reactive({
colNames: [
{
prop: 'indexName',
label: '索引名',
label: 'db.indexName',
},
{
prop: 'columnNames',
label: '列名',
label: 'db.columnName',
},
{
prop: 'unique',
label: '唯一',
label: 'db.unique',
},
{
prop: 'indexType',
label: '类型',
label: 'common.type',
},
{
prop: 'indexComment',
label: '备注',
label: 'db.comment',
},
{
prop: 'action',
label: '操作',
label: 'common.operation',
},
],
columns: [{ name: '', remark: '' }],
@@ -336,7 +344,7 @@ const deleteIndex = (index: any) => {
const submit = async () => {
let sql = genSql();
if (!sql) {
ElMessage.warning('没有更改');
ElMessage.warning(t('db.noChange'));
return;
}
SqlExecBox({
@@ -472,10 +480,10 @@ const indexChanges = (row: any) => {
}
let suffix = row.unique ? 'udx' : 'idx';
let commentSuffix = row.unique ? '唯一索引' : '普通索引';
let commentSuffix = row.unique ? t('db.uniqueIndex') : t('db.normalIndex');
// 以表名为前缀
row.indexName = `${tableData.value.tableName}_${name}_${suffix}`.replaceAll(' ', '');
row.indexComment = `${tableData.value.tableName}(${name.replaceAll('_', ',')})${commentSuffix}`;
row.indexComment = `${tableData.value.tableName} ${t('db.table')} (${name.replaceAll('_', ',')})${commentSuffix}`;
};
const disableEditIncr = () => {

View File

@@ -54,7 +54,7 @@
</div>
<el-divider content-position="left">{{ $t('common.other') }}</el-divider>
<el-form-item prop="enableRecorder" :label="$t('machine.sshTunnel')">
<el-form-item prop="enableRecorder" :label="$t('machine.terminalPlayback')">
<el-checkbox v-model="form.enableRecorder" :true-value="1" :false-value="-1"></el-checkbox>
</el-form-item>

View File

@@ -185,22 +185,19 @@
</el-table-column>
<el-table-column prop="mode" :label="$t('machine.attribute')" width="110"> </el-table-column>
<el-table-column
v-if="$props.protocol == MachineProtocolEnum.Ssh.value"
prop="username"
:label="$t('machine.user')"
min-width="70"
show-overflow-tooltip
>
<el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" :label="$t('machine.user')" min-width="70" show-overflow-tooltip>
<template #default="scope">
{{ userMap.get(scope.row.uid)?.uname || scope.row.uid }}
</template>
</el-table-column>
<el-table-column
v-if="$props.protocol == MachineProtocolEnum.Ssh.value"
prop="groupname"
:label="$t('machine.group')"
min-width="70"
show-overflow-tooltip
>
<el-table-column v-if="$props.protocol == MachineProtocolEnum.Ssh.value" :label="$t('machine.group')" min-width="70" show-overflow-tooltip>
<template #default="scope">
{{ groupMap.get(scope.row.gid)?.gname || scope.row.gid }}
</template>
</el-table-column>
<el-table-column prop="modTime" :label="$t('machine.modificationTime')" width="160" sortable> </el-table-column>
<el-table-column :width="130">
@@ -303,7 +300,7 @@
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, toRefs } from 'vue';
import { ElInput, ElMessage, ElMessageBox } from 'element-plus';
import { ElInput, ElMessage } from 'element-plus';
import { machineApi } from '../api';
import { joinClientParams } from '@/common/request';
@@ -334,8 +331,8 @@ const folderUploadRef: any = ref();
const folderType = 'd';
const userMap = new Map<number, any>();
const groupMap = new Map<number, any>();
const userMap = ref(new Map<number, any>());
const groupMap = ref(new Map<number, any>());
// 路径分隔符
const pathSep = '/';
@@ -382,13 +379,13 @@ onMounted(async () => {
if (props.protocol == MachineProtocolEnum.Ssh.value) {
machineApi.users.request({ id: machineId }).then((res: any) => {
for (let user of res) {
userMap.set(user.uid, user);
userMap.value.set(user.uid, user);
}
});
machineApi.groups.request({ id: machineId }).then((res: any) => {
for (let group of res) {
groupMap.set(group.gid, group);
groupMap.value.set(group.gid, group);
}
});
}
@@ -565,11 +562,6 @@ const lsFile = async (path: string) => {
path,
});
for (const file of res) {
if (props.protocol == MachineProtocolEnum.Ssh.value) {
file.username = userMap.get(file.uid)?.uname || file.uid;
file.groupname = groupMap.get(file.gid)?.gname || file.gid;
}
const type = file.type;
if (type == folderType) {
file.isFolder = true;

View File

@@ -121,10 +121,10 @@
<el-dialog width="400px" :title="$t('mongo.createDbAndColl')" v-model="createDbDialog.visible" :destroy-on-close="true">
<el-form :model="createDbDialog.form" label-width="auto">
<el-form-item prop="dbName" :title="$t('mongo.dbName')" required>
<el-form-item prop="dbName" :label="$t('mongo.dbName')" required>
<el-input v-model="createDbDialog.form.dbName" clearable></el-input>
</el-form-item>
<el-form-item prop="collectionName" :title="$t('mongo.collName')" required>
<el-form-item prop="collectionName" :label="$t('mongo.collName')" required>
<el-input v-model="createDbDialog.form.collectionName" clearable></el-input>
</el-form-item>
</el-form>

View File

@@ -16,10 +16,10 @@ const (
// RedisConnExpireTime = 2 * time.Minute
// MongoConnExpireTime = 2 * time.Minute
ResourceTypeMachine int8 = 1
ResourceTypeDb int8 = 2
ResourceTypeRedis int8 = 3
ResourceTypeMongo int8 = 4
ResourceTypeMachine int8 = 1
ResourceTypeDbInstance int8 = 2
ResourceTypeRedis int8 = 3
ResourceTypeMongo int8 = 4
// imsg起始编号
ImsgNumSys = 10000

View File

@@ -16,7 +16,7 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
tagCodePaths := m.TagTreeApp.GetAccountTagCodePaths(accountId, tagentity.TagTypeDbName, "")
tagCodePaths := m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{Types: collx.AsArray(tagentity.TagTypeDb)}).GetCodePaths()
rc.ResData = collx.M{
"dbNum": len(tagCodePaths),

View File

@@ -48,12 +48,15 @@ func (d *Db) Dbs(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
// 不存在可访问标签id即没有可操作数据
codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, int8(tagentity.TagTypeDbName), queryCond.TagPath)
if len(codes) == 0 {
tags := d.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
Types: collx.AsArray(tagentity.TagTypeDb),
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
queryCond.Codes = codes
queryCond.Codes = tags.GetCodes()
var dbvos []*vo.DbListVO
res, err := d.DbApp.GetPageList(queryCond, page, &dbvos)

View File

@@ -31,14 +31,18 @@ type Instance struct {
func (d *Instance) Instances(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
tagCodePaths := d.TagApp.GetAccountTagCodePaths(rc.GetLoginAccount().Id, tagentity.TagTypeDbAuthCert, queryCond.TagPath)
tags := d.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
Types: collx.AsArray(tagentity.TagTypeDbAuthCert),
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
// 不存在可操作的数据库,即没有可操作数据
if len(tagCodePaths) == 0 {
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
dbInstCodes := tagentity.GetCodeByPath(tagentity.TagTypeDb, tagCodePaths...)
tagCodePaths := tags.GetCodePaths()
dbInstCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeDbInstance, tagCodePaths...)
queryCond.Codes = dbInstCodes
var instvos []*vo.InstanceListVO
@@ -46,12 +50,12 @@ func (d *Instance) Instances(rc *req.Ctx) {
biz.ErrIsNil(err)
// 填充授权凭证信息
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodeByPath(tagentity.TagTypeDbAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeDbAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
return vos
})...)
// 填充标签信息
d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDb), collx.ArrayMap(instvos, func(insvo *vo.InstanceListVO) tagentity.ITagResource {
d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDbInstance), collx.ArrayMap(instvos, func(insvo *vo.InstanceListVO) tagentity.ITagResource {
return insvo
})...)

View File

@@ -94,7 +94,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
return d.tagApp.RelateTagsByCodeAndType(ctx, &tagdto.RelateTagsByCodeAndType{
Tags: []*tagdto.ResourceTag{{
Code: dbEntity.Code,
Type: tagentity.TagTypeDbName,
Type: tagentity.TagTypeDb,
Name: dbEntity.Name,
}},
ParentTagCode: authCert.Name,
@@ -136,12 +136,12 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
return d.UpdateById(ctx, dbEntity)
}, func(ctx context.Context) error {
if old.Name != dbEntity.Name {
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeDbName, old.Code, dbEntity.Name); err != nil {
if err := d.tagApp.UpdateTagName(ctx, tagentity.TagTypeDb, old.Code, dbEntity.Name); err != nil {
return err
}
}
if authCert.Name != old.AuthCertName {
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDbName, old.Code, tagentity.TagTypeDbAuthCert, authCert.Name)
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDb, old.Code, tagentity.TagTypeDbAuthCert, authCert.Name)
}
return nil
})
@@ -170,7 +170,7 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
}, func(ctx context.Context) error {
return d.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
ResourceCode: db.Code,
ResourceType: tagentity.TagTypeDbName,
ResourceType: tagentity.TagTypeDb,
})
})
}
@@ -191,7 +191,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
if err != nil {
return nil, err
}
di.CodePath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDbName), db.Code)
di.CodePath = d.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeDb), db.Code)
di.Id = db.Id
checkDb := di.GetDatabase()

View File

@@ -84,7 +84,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
instanceEntity := instance.DbInstance
// 默认tcp连接
instanceEntity.Network = instanceEntity.GetNetwork()
resourceType := consts.ResourceTypeDb
resourceType := consts.ResourceTypeDbInstance
authCerts := instance.AuthCerts
tagCodePaths := instance.TagCodePaths
@@ -145,7 +145,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
})
}, func(ctx context.Context) error {
if instanceEntity.Name != oldInstance.Name {
if err := app.tagApp.UpdateTagName(ctx, tagentity.TagTypeDb, oldInstance.Code, instanceEntity.Name); err != nil {
if err := app.tagApp.UpdateTagName(ctx, tagentity.TagTypeDbInstance, oldInstance.Code, instanceEntity.Name); err != nil {
return err
}
}
@@ -172,12 +172,12 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
// 删除该实例关联的授权凭证信息
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
ResourceType: tagentity.TagType(consts.ResourceTypeDbInstance),
})
}, func(ctx context.Context) error {
return app.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
ResourceType: tagentity.TagType(consts.ResourceTypeDbInstance),
})
}, func(ctx context.Context) error {
// 删除所有库配置
@@ -276,7 +276,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
authCertName2DbTags[db.AuthCertName] = append(authCertName2DbTags[db.AuthCertName], &tagdto.ResourceTag{
Code: db.Code,
Name: db.Name,
Type: tagentity.TagTypeDbName,
Type: tagentity.TagTypeDb,
})
}
@@ -287,7 +287,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
return &tagdto.ResourceTag{
Code: me.Code,
Type: tagentity.TagTypeDb,
Type: tagentity.TagTypeDbInstance,
Name: me.Name,
Children: authCertTags,
}

View File

@@ -16,8 +16,8 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
tagCodePaths := m.TagTreeApp.GetAccountTagCodePaths(accountId, tagentity.TagTypeMachineAuthCert, "")
machineCodes := tagentity.GetCodeByPath(tagentity.TagTypeMachine, tagCodePaths...)
tagCodePaths := m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{Types: collx.AsArray(tagentity.TagTypeMachineAuthCert)}).GetCodePaths()
machineCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeMachine, tagCodePaths...)
rc.ResData = collx.M{
"machineNum": len(machineCodes),

View File

@@ -43,14 +43,18 @@ type Machine struct {
func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
tagCodePaths := m.TagApp.GetAccountTagCodePaths(rc.GetLoginAccount().Id, tagentity.TagTypeMachineAuthCert, condition.TagPath)
tags := m.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
Types: collx.AsArray(tagentity.TagTypeMachineAuthCert),
CodePathLikes: collx.AsArray(condition.TagPath),
})
// 不存在可操作的机器-授权凭证标签,即没有可操作数据
if len(tagCodePaths) == 0 {
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
machineCodes := tagentity.GetCodeByPath(tagentity.TagTypeMachine, tagCodePaths...)
tagCodePaths := tags.GetCodePaths()
machineCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeMachine, tagCodePaths...)
condition.Codes = collx.ArrayDeduplicate(machineCodes)
var machinevos []*vo.MachineVO
@@ -67,7 +71,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
})...)
// 填充授权凭证信息
m.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodeByPath(tagentity.TagTypeMachineAuthCert, tagCodePaths...), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
m.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeMachineAuthCert, tagCodePaths...), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
return mvo
})...)

View File

@@ -151,7 +151,7 @@ func (m *machineCronJobAppImpl) RunCronJob(key string) {
relateCodePaths := m.tagTreeRelateApp.GetTagPathsByRelate(tagentity.TagRelateTypeMachineCronJob, cronJob.Id)
var machineTags []tagentity.TagTree
m.tagTreeApp.ListByQuery(&tagentity.TagTreeQuery{CodePathLikes: relateCodePaths, Type: tagentity.TagTypeMachine}, &machineTags)
m.tagTreeApp.ListByQuery(&tagentity.TagTreeQuery{CodePathLikes: relateCodePaths, Types: []tagentity.TagType{tagentity.TagTypeMachine}}, &machineTags)
machines, _ := m.machineApp.ListByCond(model.NewCond().In("code", collx.ArrayMap(machineTags, func(tag tagentity.TagTree) string {
return tag.Code
})), "id")

View File

@@ -31,5 +31,7 @@ var En = map[i18n.MsgId]string{
ErrFileUploadFail: "File upload failure",
MsgUploadFileSuccess: "File uploaded successfully",
TerminalCmdDisable: "This command has been disabled...",
LogMachineSecurityCmdSave: "Machine - Security - Save command configuration",
LogMachineSecurityCmdDelete: "Machine - Security - Delete command configuration",
TerminalCmdDisable: "This command has been disabled...",
}

View File

@@ -39,5 +39,9 @@ const (
ErrFileUploadFail
MsgUploadFileSuccess
// security
LogMachineSecurityCmdSave
LogMachineSecurityCmdDelete
TerminalCmdDisable
)

View File

@@ -31,5 +31,7 @@ var Zh_CN = map[i18n.MsgId]string{
ErrFileUploadFail: "文件上传失败",
MsgUploadFileSuccess: "文件上传成功",
TerminalCmdDisable: "该命令已被禁用...",
LogMachineSecurityCmdSave: "机器-安全-保存命令配置",
LogMachineSecurityCmdDelete: "机器-安全-删除命令配置",
TerminalCmdDisable: "该命令已被禁用...",
}

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -18,9 +19,9 @@ func InitMachineCmdConfRouter(router *gin.RouterGroup) {
reqs := [...]*req.Conf{
req.NewGet("", mcc.MachineCmdConfs),
req.NewPost("", mcc.Save).Log(req.NewLogSave("机器命令配置-保存")).RequiredPermissionCode("cmdconf:save"),
req.NewPost("", mcc.Save).Log(req.NewLogSaveI(imsg.LogMachineSecurityCmdSave)).RequiredPermissionCode("cmdconf:save"),
req.NewDelete(":id", mcc.Delete).Log(req.NewLogSave("机器命令配置-删除")).RequiredPermissionCode("cmdconf:del"),
req.NewDelete(":id", mcc.Delete).Log(req.NewLogSaveI(imsg.LogMachineSecurityCmdDelete)).RequiredPermissionCode("cmdconf:del"),
}
req.BatchSetGroup(mccs, reqs[:])

View File

@@ -1,8 +1,8 @@
package api
import (
"mayfly-go/internal/common/consts"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
@@ -13,7 +13,9 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
mongoNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, ""))
mongoNum := len(m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{Types: []tagentity.TagType{
tagentity.TagTypeMongo,
}}))
rc.ResData = collx.M{
"mongoNum": mongoNum,

View File

@@ -33,12 +33,15 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.MongoQuery](rc, new(entity.MongoQuery))
// 不存在可访问标签id即没有可操作数据
codes := m.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeMongo, queryCond.TagPath)
if len(codes) == 0 {
tags := m.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
Types: []tagentity.TagType{tagentity.TagTypeMongo},
CodePathLikes: []string{queryCond.TagPath},
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
queryCond.Codes = codes
queryCond.Codes = tags.GetCodes()
var mongovos []*vo.Mongo
res, err := m.MongoApp.GetPageList(queryCond, page, &mongovos)

View File

@@ -1,8 +1,8 @@
package api
import (
"mayfly-go/internal/common/consts"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
@@ -13,7 +13,9 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
redisNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, ""))
redisNum := len(m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{
Types: collx.AsArray(tagentity.TagTypeRedis),
}))
rc.ResData = collx.M{
"redisNum": redisNum,

View File

@@ -33,12 +33,15 @@ func (r *Redis) RedisList(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.RedisQuery](rc, new(entity.RedisQuery))
// 不存在可访问标签id即没有可操作数据
codes := r.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeRedis, queryCond.TagPath)
if len(codes) == 0 {
tags := r.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
Types: collx.AsArray(tagentity.TagTypeRedis),
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
queryCond.Codes = codes
queryCond.Codes = tags.GetCodes()
var redisvos []*vo.Redis
res, err := r.RedisApp.GetPageList(queryCond, page, &redisvos)

View File

@@ -2,7 +2,6 @@ package api
import (
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
@@ -54,7 +53,7 @@ func (p *TagTree) complteTags(resourceTags []*dto.SimpleTagTree) []*dto.SimpleTa
// 如tagPath = tag1/tag2/tag3/ 需要转为该路径所关联的所有标签路径即 tag1/ tag1/tag2/ tag1/tag2/tag3/三个相关联标签,才可以构造成一棵树
allTagPaths := make([]string, 0)
for _, tagPath := range collx.MapKeys(codePath2Tag) {
allTagPaths = append(allTagPaths, entity.GetAllCodePath(tagPath)...)
allTagPaths = append(allTagPaths, entity.CodePath(tagPath).GetAllPath()...)
}
allTagPaths = collx.ArrayDeduplicate(allTagPaths)
@@ -82,7 +81,7 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) {
cond.CodePaths = strings.Split(tagPaths, ",")
}
cond.Id = uint64(rc.QueryInt("id"))
cond.Type = entity.TagType(rc.QueryInt("type"))
cond.Types = collx.AsArray(entity.TagType(rc.QueryInt("type")))
codes := rc.Query("codes")
if codes != "" {
cond.Codes = strings.Split(codes, ",")
@@ -117,10 +116,10 @@ func (p *TagTree) MovingTag(rc *req.Ctx) {
func (p *TagTree) TagResources(rc *req.Ctx) {
resourceType := int8(rc.PathParamInt("rtype"))
accountId := rc.GetLoginAccount().Id
tagResources := p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType)})
tagResources := p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{Types: collx.AsArray(entity.TagType(resourceType))})
tagPath2Resource := collx.ArrayToMap[*dto.SimpleTagTree, string](tagResources, func(tagResource *dto.SimpleTagTree) string {
return entity.GetTagPath(tagResource.CodePath)
return string(entity.CodePath(tagResource.CodePath).GetTag())
})
tagPaths := collx.MapKeys(tagPath2Resource)
@@ -133,14 +132,27 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
tagPath := rc.Query("tagPath")
accountId := rc.GetLoginAccount().Id
machineCodes := entity.GetCodeByPath(entity.TagTypeMachine, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeMachineAuthCert, tagPath)...)
dbCodes := entity.GetCodeByPath(entity.TagTypeDb, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeDbName, tagPath)...)
machineCodes := entity.GetCodesByCodePaths(entity.TagTypeMachine, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
Types: collx.AsArray(entity.TagTypeMachineAuthCert),
CodePathLikes: collx.AsArray(tagPath),
}).GetCodePaths()...)
dbCodes := entity.GetCodesByCodePaths(entity.TagTypeDbInstance, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
Types: collx.AsArray(entity.TagTypeDb),
CodePathLikes: collx.AsArray(tagPath),
}).GetCodePaths()...)
rc.ResData = collx.M{
"machine": len(machineCodes),
"db": len(dbCodes),
"redis": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, tagPath)),
"mongo": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, tagPath)),
"redis": len(p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
Types: collx.AsArray(entity.TagTypeRedis),
CodePathLikes: collx.AsArray(tagPath),
}).GetCodes()),
"mongo": len(p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
Types: collx.AsArray(entity.TagTypeMongo),
CodePathLikes: collx.AsArray(tagPath),
}).GetCodes()),
}
}

View File

@@ -34,7 +34,7 @@ func (m *TagTreeVOS) ToTrees(pid uint64) []*TagTreeItem {
if node.Root {
continue
}
parentCodePath := node.GetParentPath(0)
parentCodePath := node.GetParentPath()
parentNode := tagMap[parentCodePath]
if parentNode != nil {
parentNode.Children = append(parentNode.Children, node)

View File

@@ -2,6 +2,7 @@ package dto
import (
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/utils/collx"
"strings"
)
@@ -48,11 +49,33 @@ type SimpleTagTree struct {
func (pt *SimpleTagTree) IsRoot() bool {
// 去除路径两端可能存在的斜杠
path := strings.Trim(pt.CodePath, "/")
path := strings.Trim(string(pt.CodePath), "/")
return len(strings.Split(path, "/")) == 1
}
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/
func (pt *SimpleTagTree) GetParentPath(index int) string {
return entity.GetParentPath(pt.CodePath, index)
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> test/test1/
func (pt *SimpleTagTree) GetParentPath() string {
return string(entity.CodePath(pt.CodePath).GetParent(0))
}
type SimpleTagTrees []*SimpleTagTree
// GetCodes 获取code数组
func (ts SimpleTagTrees) GetCodes() []string {
// resouce code去重
code2Resource := collx.ArrayToMap[*SimpleTagTree, string](ts, func(val *SimpleTagTree) string {
return val.Code
})
return collx.MapKeys(code2Resource)
}
// GetCodePaths 获取codePath数组
func (ts SimpleTagTrees) GetCodePaths() []string {
// codepath去重
codepath2Resource := collx.ArrayToMap[*SimpleTagTree, string](ts, func(val *SimpleTagTree) string {
return val.CodePath
})
return collx.MapKeys(codepath2Resource)
}

View File

@@ -451,7 +451,7 @@ func GetResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
return entity.TagTypeMachineAuthCert
}
if resourceType == entity.TagTypeDb {
if resourceType == entity.TagTypeDbInstance {
return entity.TagTypeDbAuthCert
}

View File

@@ -48,13 +48,7 @@ type TagTree interface {
// GetAccountTags 获取指定账号有权限操作的标签列表
// @param accountId 账号id
// @param query 查询条件
GetAccountTags(accountId uint64, query *entity.TagTreeQuery) []*dto.SimpleTagTree
// GetAccountTagCodes 获取指定账号有权限操作的标签codes
GetAccountTagCodes(accountId uint64, resourceType int8, tagPath string) []string
// GetAccountTagCodePaths 获取指定账号有权限操作的codePaths
GetAccountTagCodePaths(accountId uint64, tagType entity.TagType, tagPath string) []string
GetAccountTags(accountId uint64, query *entity.TagTreeQuery) dto.SimpleTagTrees
// 根据标签类型和标签code获取对应的标签路径列表
ListTagPathByTypeAndCode(resourceType int8, resourceCode string) []string
@@ -111,7 +105,7 @@ func (p *tagTreeAppImpl) SaveTag(ctx context.Context, pid uint64, tag *entity.Ta
// 判断该路径是否存在
var hasLikeTags []entity.TagTree
p.GetRepo().SelectByCondition(&entity.TagTreeQuery{CodePathLike: tag.CodePath}, &hasLikeTags)
p.GetRepo().SelectByCondition(&entity.TagTreeQuery{CodePathLikes: []string{tag.CodePath}}, &hasLikeTags)
if len(hasLikeTags) > 0 {
return errorx.NewBizI(ctx, imsg.ErrTagCodePathLikeExist)
}
@@ -257,16 +251,16 @@ func (p *tagTreeAppImpl) ChangeParentTag(ctx context.Context, tagType entity.Tag
// 更新父标签的codepath
for _, tag := range resourceChildrenTags {
pathSection := entity.GetTagPathSections(tag.CodePath)
for i, ps := range pathSection {
pathSections := entity.CodePath(tag.CodePath).GetPathSections()
for i, ps := range pathSections {
if ps.Type == tagType && ps.Code == tagCode {
// 将父标签编号修改为对应的新编号与类型
pathSection[i-1].Code = newParentCode
pathSection[i-1].Type = parentTagType
pathSections[i-1].Code = newParentCode
pathSections[i-1].Type = parentTagType
}
}
tag.CodePath = pathSection.ToCodePath()
tag.CodePath = pathSections.ToCodePath()
if err := p.UpdateById(ctx, tag); err != nil {
return err
}
@@ -288,10 +282,10 @@ func (p *tagTreeAppImpl) MovingTag(ctx context.Context, fromTagPath string, toTa
// 获取要移动标签的所有子标签
var childrenTags []*entity.TagTree
p.ListByQuery(&entity.TagTreeQuery{CodePathLike: fromTagPath}, &childrenTags)
p.ListByQuery(&entity.TagTreeQuery{CodePathLikes: []string{fromTagPath}}, &childrenTags)
// 获取父路径, 若fromTagPath=tag1/tag2/1|xxx则返回 tag1/tag2/
fromParentPath := entity.GetParentPath(fromTagPath, 0)
fromParentPath := string(entity.CodePath(fromTagPath).GetParent(0))
for _, childTag := range childrenTags {
// 替换path若childPath = tag1/tag2/1|xxx/11|yyy, toTagPath=tag3/tag4则替换为tag3/tag4/1|xxx/11|yyy/
childTag.CodePath = strings.Replace(childTag.CodePath, fromParentPath, toTagPath, 1)
@@ -339,10 +333,10 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
p.GetRepo().SelectByCondition(condition, toEntity)
}
func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQuery) []*dto.SimpleTagTree {
func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQuery) dto.SimpleTagTrees {
types := query.Types
tagResourceQuery := &entity.TagTreeQuery{
Type: query.Type,
Types: query.Types,
Types: types,
}
var tagResources []*dto.SimpleTagTree
@@ -360,7 +354,7 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
tagPaths := collx.ArrayRemoveBlank(query.CodePathLikes)
// 如果需要查询指定标签下的资源标签,则需要与用户拥有的权限进行过滤,避免越权
if len(tagPaths) > 0 {
// admin 则直接赋值需要获取的标签
// 为空则说明为admin 则直接赋值需要获取的标签
if len(accountTagPaths) == 0 {
accountTagPaths = tagPaths
} else {
@@ -374,26 +368,6 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
return tagResources
}
func (p *tagTreeAppImpl) GetAccountTagCodes(accountId uint64, resourceType int8, tagPath string) []string {
tagResources := p.GetAccountTags(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType), CodePathLikes: []string{tagPath}})
// resouce code去重
code2Resource := collx.ArrayToMap[*dto.SimpleTagTree, string](tagResources, func(val *dto.SimpleTagTree) string {
return val.Code
})
return collx.MapKeys(code2Resource)
}
func (p *tagTreeAppImpl) GetAccountTagCodePaths(accountId uint64, tagType entity.TagType, tagPath string) []string {
tagResources := p.GetAccountTags(accountId, &entity.TagTreeQuery{Type: tagType, CodePathLikes: []string{tagPath}})
// resouce code去重
code2Resource := collx.ArrayToMap[*dto.SimpleTagTree, string](tagResources, func(val *dto.SimpleTagTree) string {
return val.CodePath
})
return collx.MapKeys(code2Resource)
}
func (p *tagTreeAppImpl) ListTagPathByTypeAndCode(resourceType int8, resourceCode string) []string {
trs, _ := p.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode})
return collx.ArrayMap(trs, func(tr *entity.TagTree) string {
@@ -439,13 +413,13 @@ func (p *tagTreeAppImpl) FillTagInfo(resourceTagType entity.TagType, resources .
// 获取所有资源code关联的标签列表信息
var tagResources []*entity.TagTree
p.ListByQuery(&entity.TagTreeQuery{Codes: collx.MapKeys(resourceCode2Resouce), Type: resourceTagType}, &tagResources)
p.ListByQuery(&entity.TagTreeQuery{Codes: collx.MapKeys(resourceCode2Resouce), Types: []entity.TagType{resourceTagType}}, &tagResources)
for _, tr := range tagResources {
// 赋值标签信息
resource := resourceCode2Resouce[tr.Code]
if resource != nil {
resource.SetTagInfo(entity.ResourceTag{TagId: tr.Id, CodePath: tr.GetTagPath()})
resource.SetTagInfo(entity.ResourceTag{TagId: tr.Id, CodePath: string(entity.CodePath(tr.CodePath).GetTag())})
}
}
}

View File

@@ -102,7 +102,7 @@ func (tr *tagTreeRelateAppImpl) GetRelateIds(ctx context.Context, relateType ent
poisibleTagPaths := make([]string, 0)
for _, tagPath := range canAccessTagPaths {
// 追加可能关联的标签路径如tagPath = tag1/tag2/1|xxx/需要获取所有关联的自身及父标签tag1/ tag1/tag2/ tag1/tag2/1|xxx
poisibleTagPaths = append(poisibleTagPaths, entity.GetAllCodePath(tagPath)...)
poisibleTagPaths = append(poisibleTagPaths, entity.CodePath(tagPath).GetAllPath()...)
}
return tr.tagTreeRelateRepo.SelectRelateIdsByTagPaths(relateType, poisibleTagPaths...)
}

View File

@@ -5,12 +5,10 @@ import "mayfly-go/pkg/model"
type TagTreeQuery struct {
model.Model
Type TagType `json:"type"`
Types []TagType
Codes []string
CodePaths []string // 标识路径
Name string `json:"name"` // 名称
CodePathLike string // 标识符路径模糊查询
CodePathLikes []string
}

View File

@@ -29,25 +29,20 @@ const (
// 标签路径资源段分隔符
CodePathResourceSeparator = "|"
TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.ResourceTypeMachine)
TagTypeDb TagType = TagType(consts.ResourceTypeDb) // 数据库实例
TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.ResourceTypeMachine)
TagTypeDbInstance TagType = TagType(consts.ResourceTypeDbInstance) // 数据库实例
TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // 数据库-授权凭证
TagTypeDbName TagType = 22 // 数据库名
TagTypeDb TagType = 22 // 数据库名
)
// GetTagPath 获取标签段路径,不获取对应资源相关路径
func (pt *TagTree) GetTagPath() string {
return GetTagPath(pt.CodePath)
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
type ITagResource interface {
// 获取资源code
@@ -80,10 +75,13 @@ func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
r.Tags = append(r.Tags, rt)
}
// GetTagPath 获取标签段路径,不获取对应资源相关路径
func GetTagPath(codePath string) string {
// CodePath 标签编号路径 如: tag1/tag2/resourceType1|xxxcode/resourceType2|yyycode/
type CodePath string
// GetTag 获取标签段路径,不获取对应资源相关路径
func (codePath CodePath) GetTag() CodePath {
// 以 资源分隔符"|" 符号对字符串进行分割
parts := strings.Split(codePath, CodePathResourceSeparator)
parts := strings.Split(string(codePath), CodePathResourceSeparator)
if len(parts) < 2 {
return codePath
}
@@ -96,31 +94,15 @@ func GetTagPath(codePath string) string {
// 如果找到最后一个 "/" 符号,则截取子串
if lastSlashIndex != -1 {
return substringBeforeNumber[:lastSlashIndex+1]
return CodePath(substringBeforeNumber[:lastSlashIndex+1])
}
return codePath
}
// GetCodeByPath 从codePaths中提取指定标签类型的所有tagCode并去重
// 如codePaths = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode
func GetCodeByPath(tagType TagType, codePaths ...string) []string {
var codes []string
for _, codePath := range codePaths {
// tag1/tag2/1|xxxcode/11|yyycode根据 /tagType|resourceCode进行切割
splStrs := strings.Split(codePath, fmt.Sprintf("%s%d%s", CodePathSeparator, tagType, CodePathResourceSeparator))
if len(splStrs) < 2 {
continue
}
codes = append(codes, strings.Split(splStrs[1], CodePathSeparator)[0])
}
return collx.ArrayDeduplicate[string](codes)
}
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/
func GetParentPath(codePath string, index int) string {
// GetParent 获取父标签编号路径, 如CodePath = test/test1/test2/ -> parentLevel = 0 => test/test1/ parentLevel = 1 => test/
func (cp CodePath) GetParent(parentLevel int) CodePath {
codePath := string(cp)
// 去除末尾的斜杠
codePath = strings.TrimSuffix(codePath, CodePathSeparator)
@@ -128,61 +110,29 @@ func GetParentPath(codePath string, index int) string {
paths := strings.Split(codePath, CodePathSeparator)
// 确保索引在有效范围内
if index < 0 {
index = 0
} else if index > len(paths)-2 {
index = len(paths) - 2
if parentLevel < 0 {
parentLevel = 0
} else if parentLevel > len(paths)-2 {
parentLevel = len(paths) - 2
}
// 按索引拼接父标签路径
parentPath := strings.Join(paths[:len(paths)-index-1], CodePathSeparator)
parentPath := strings.Join(paths[:len(paths)-parentLevel-1], CodePathSeparator)
return parentPath + CodePathSeparator
return CodePath(parentPath + CodePathSeparator)
}
// GetAllCodePath 根据标签路径获取所有相关的标签codePath如 test1/test2/ -> test1/ test1/test2/
func GetAllCodePath(codePath string) []string {
// 去除末尾的斜杠
// GetPathSections 根据标签编号路径获取路径段落
func (cp CodePath) GetPathSections() PathSections {
codePath := string(cp)
codePath = strings.TrimSuffix(codePath, CodePathSeparator)
// 使用 Split 方法将路径按斜杠分割成切片
paths := strings.Split(codePath, CodePathSeparator)
var result []string
var partialPath string
for _, path := range paths {
partialPath += path + CodePathSeparator
result = append(result, partialPath)
}
return result
}
// TagPathSection 标签路径段
type TagPathSection struct {
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 标识编码, 若类型不为-1则为对应资源编码
}
type TagPathSections []*TagPathSection // 标签段数组
// 转为codePath
func (tps TagPathSections) ToCodePath() string {
return strings.Join(collx.ArrayMap(tps, func(tp *TagPathSection) string {
if tp.Type == TagTypeTag {
return tp.Code
}
return fmt.Sprintf("%d%s%s", tp.Type, CodePathResourceSeparator, tp.Code)
}), CodePathSeparator) + CodePathSeparator
}
// GetTagPathSections 根据标签路径获取路径段落
func GetTagPathSections(codePath string) TagPathSections {
codePath = strings.TrimSuffix(codePath, CodePathSeparator)
var sections TagPathSections
var sections PathSections
codes := strings.Split(codePath, CodePathSeparator)
path := ""
for _, code := range codes {
path += code + CodePathSeparator
typeAndCode := strings.Split(code, CodePathResourceSeparator)
var tagType TagType
var tagCode string
@@ -195,11 +145,66 @@ func GetTagPathSections(codePath string) TagPathSections {
tagCode = typeAndCode[1]
}
sections = append(sections, &TagPathSection{
sections = append(sections, &PathSection{
Type: tagType,
Code: tagCode,
Path: path,
})
}
return sections
}
// GetCode 从codePath中提取指定标签类型的code
// 如codePath = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode
func (cp CodePath) GetCode(tagType TagType) string {
type2Section := collx.ArrayToMap(cp.GetPathSections(), func(section *PathSection) TagType {
return section.Type
})
section := type2Section[tagType]
if section == nil {
return ""
}
return section.Code
}
// GetAllPath 根据codePath获取所有相关的标签codePath如 test1/test2/ -> test1/ test1/test2/
func (cp CodePath) GetAllPath() []string {
return collx.ArrayMap(cp.GetPathSections(), func(section *PathSection) string {
return section.Path
})
}
// PathSection 标签路径段
type PathSection struct {
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
Code string `json:"code"` // 编码, 若类型不为-1则为对应资源编码
Path string `json:"path"` // 当前路径段对应的完整编号路径
}
type PathSections []*PathSection // 标签段数组
// ToCodePath 转为codePath
func (tps PathSections) ToCodePath() string {
return strings.Join(collx.ArrayMap(tps, func(tp *PathSection) string {
if tp.Type == TagTypeTag {
return tp.Code
}
return fmt.Sprintf("%d%s%s", tp.Type, CodePathResourceSeparator, tp.Code)
}), CodePathSeparator) + CodePathSeparator
}
// GetCodesByCodePaths 从codePaths中提取指定标签类型的所有tagCode并去重
// 如codePaths = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode
func GetCodesByCodePaths(tagType TagType, codePaths ...string) []string {
var codes []string
for _, codePath := range codePaths {
code := CodePath(codePath).GetCode(tagType)
if code == "" {
continue
}
codes = append(codes, code)
}
return collx.ArrayDeduplicate[string](codes)
}

View File

@@ -2,22 +2,37 @@ package entity
import (
"fmt"
"strings"
"testing"
)
func TestGetPathSection(t *testing.T) {
fromPath := "tag1/tag2/1|xx/"
childPath := "tag1/tag2/1|xx/11|yy/"
toPath := "tag3/"
parentSection := GetTagPathSections(GetParentPath(fromPath, 0))
childSection := GetTagPathSections(childPath)
res := toPath + childSection[len(GetTagPathSections(fromPath)):].ToCodePath()
res1 := toPath + childSection[len(parentSection):].ToCodePath()
pPath := GetParentPath(fromPath, 0)
r := strings.Replace(childPath, pPath, toPath, 1)
r1 := strings.Replace(fromPath, pPath, toPath, 1)
fmt.Println(res, res1, r, r1)
func TestGetTag(t *testing.T) {
cp := CodePath("tag1/tag2/1|xx/11|yy/111|zz/")
v := cp.GetTag()
pv := cp.GetParent(0)
av := cp.GetAllPath()
ps := cp.GetPathSections()
code := cp.GetCode(TagType(11))
fmt.Println(v, pv, av, ps, code)
}
// func TestGetPathSection(t *testing.T) {
// fromPath := "tag1/tag2/1|xx/"
// childPath := "tag1/tag2/1|xx/11|yy/"
// toPath := "tag3/"
// parentSection := GetTagPathSections(GetParentPath(fromPath, 0))
// childSection := GetTagPathSections(childPath)
// res := toPath + childSection[len(GetTagPathSections(fromPath)):].ToCodePath()
// res1 := toPath + childSection[len(parentSection):].ToCodePath()
// pPath := GetParentPath(fromPath, 0)
// r := strings.Replace(childPath, pPath, toPath, 1)
// r1 := strings.Replace(fromPath, pPath, toPath, 1)
// fmt.Println(res, res1, r, r1)
// }
// func TestGetPathSection2(t *testing.T) {
// tagpath := "tag1/tag2/1|xx/11|yy/"
// sections := GetTagPathSections(GetParentPath(tagpath, 0))
// fmt.Println(sections)
// }

View File

@@ -20,8 +20,6 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
Eq("id", condition.Id).
In("code", condition.Codes).
In("code_path", condition.CodePaths).
RLike("code_path", condition.CodePathLike).
Eq("type", condition.Type).
In("type", condition.Types).
OrderByAsc("type").OrderByAsc("code_path")

View File

@@ -5,6 +5,11 @@ import (
"strings"
)
// AsArray 将可变参数列表为数组
func AsArray[T comparable](el ...T) []T {
return el
}
// 数组比较
// 依次返回,新增值,删除值,以及不变值
func ArrayCompare[T comparable](newArr []T, oldArr []T) ([]T, []T, []T) {

View File

@@ -163,9 +163,9 @@ func (manager *ClientManager) HeartbeatTimer() {
}
if err := cli.Ping(); err != nil {
manager.CloseClient(cli)
logx.Debugf("WS发送心跳失败: uid=%v, cid=%s, usercount=%d", userId, cli.ClientId, Manager.Count())
logx.Debugf("WS - failed to send heartbeat: uid=%v, cid=%s, usercount=%d", userId, cli.ClientId, Manager.Count())
} else {
logx.Debugf("WS发送心跳成功: uid=%v, cid=%s", userId, cli.ClientId)
logx.Debugf("WS - send heartbeat successfully: uid=%v, cid=%s", userId, cli.ClientId)
}
}
}