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 * @returns
*/ */
export async function useI18nDeleteConfirm(name: string = '') { 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}', createTitle: 'Create {name}',
editTitle: 'Edit {name}', editTitle: 'Edit {name}',
detailTitle: '{name} Details', 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', saveSuccess: 'save successfully',
deleteSuccess: 'delete successfully', deleteSuccess: 'delete successfully',
operateSuccess: 'operate successfully', operateSuccess: 'operate successfully',

View File

@@ -73,6 +73,21 @@ export default {
newTabRunSql: 'NewTab Run SQL', newTabRunSql: 'NewTab Run SQL',
formatSql: 'Format 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', execTime: 'execution time',
oneClickCopy: 'One click copy', oneClickCopy: 'One click copy',
asc: 'Asc', asc: 'Asc',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id 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{ rc.ResData = collx.M{
"dbNum": len(tagCodePaths), "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)) queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, int8(tagentity.TagTypeDbName), queryCond.TagPath) tags := d.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
if len(codes) == 0 { Types: collx.AsArray(tagentity.TagTypeDb),
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
} }
queryCond.Codes = codes queryCond.Codes = tags.GetCodes()
var dbvos []*vo.DbListVO var dbvos []*vo.DbListVO
res, err := d.DbApp.GetPageList(queryCond, page, &dbvos) res, err := d.DbApp.GetPageList(queryCond, page, &dbvos)

View File

@@ -31,14 +31,18 @@ type Instance struct {
func (d *Instance) Instances(rc *req.Ctx) { func (d *Instance) Instances(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery)) 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]() rc.ResData = model.EmptyPageResult[any]()
return return
} }
dbInstCodes := tagentity.GetCodeByPath(tagentity.TagTypeDb, tagCodePaths...) tagCodePaths := tags.GetCodePaths()
dbInstCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeDbInstance, tagCodePaths...)
queryCond.Codes = dbInstCodes queryCond.Codes = dbInstCodes
var instvos []*vo.InstanceListVO var instvos []*vo.InstanceListVO
@@ -46,12 +50,12 @@ func (d *Instance) Instances(rc *req.Ctx) {
biz.ErrIsNil(err) 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 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 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{ return d.tagApp.RelateTagsByCodeAndType(ctx, &tagdto.RelateTagsByCodeAndType{
Tags: []*tagdto.ResourceTag{{ Tags: []*tagdto.ResourceTag{{
Code: dbEntity.Code, Code: dbEntity.Code,
Type: tagentity.TagTypeDbName, Type: tagentity.TagTypeDb,
Name: dbEntity.Name, Name: dbEntity.Name,
}}, }},
ParentTagCode: authCert.Name, ParentTagCode: authCert.Name,
@@ -136,12 +136,12 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
return d.UpdateById(ctx, dbEntity) return d.UpdateById(ctx, dbEntity)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
if old.Name != dbEntity.Name { 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 return err
} }
} }
if authCert.Name != old.AuthCertName { 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 return nil
}) })
@@ -170,7 +170,7 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{ return d.tagApp.DeleteTagByParam(ctx, &tagdto.DelResourceTag{
ResourceCode: db.Code, 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 { if err != nil {
return nil, err 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 di.Id = db.Id
checkDb := di.GetDatabase() checkDb := di.GetDatabase()

View File

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

View File

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

View File

@@ -43,14 +43,18 @@ type Machine struct {
func (m *Machine) Machines(rc *req.Ctx) { func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery)) 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]() rc.ResData = model.EmptyPageResult[any]()
return return
} }
machineCodes := tagentity.GetCodeByPath(tagentity.TagTypeMachine, tagCodePaths...) tagCodePaths := tags.GetCodePaths()
machineCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeMachine, tagCodePaths...)
condition.Codes = collx.ArrayDeduplicate(machineCodes) condition.Codes = collx.ArrayDeduplicate(machineCodes)
var machinevos []*vo.MachineVO 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 return mvo
})...) })...)

View File

@@ -151,7 +151,7 @@ func (m *machineCronJobAppImpl) RunCronJob(key string) {
relateCodePaths := m.tagTreeRelateApp.GetTagPathsByRelate(tagentity.TagRelateTypeMachineCronJob, cronJob.Id) relateCodePaths := m.tagTreeRelateApp.GetTagPathsByRelate(tagentity.TagRelateTypeMachineCronJob, cronJob.Id)
var machineTags []tagentity.TagTree 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 { machines, _ := m.machineApp.ListByCond(model.NewCond().In("code", collx.ArrayMap(machineTags, func(tag tagentity.TagTree) string {
return tag.Code return tag.Code
})), "id") })), "id")

View File

@@ -31,5 +31,7 @@ var En = map[i18n.MsgId]string{
ErrFileUploadFail: "File upload failure", ErrFileUploadFail: "File upload failure",
MsgUploadFileSuccess: "File uploaded successfully", 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 ErrFileUploadFail
MsgUploadFileSuccess MsgUploadFileSuccess
// security
LogMachineSecurityCmdSave
LogMachineSecurityCmdDelete
TerminalCmdDisable TerminalCmdDisable
) )

View File

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

View File

@@ -2,6 +2,7 @@ package router
import ( import (
"mayfly-go/internal/machine/api" "mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/imsg"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc" "mayfly-go/pkg/ioc"
"mayfly-go/pkg/req" "mayfly-go/pkg/req"
@@ -18,9 +19,9 @@ func InitMachineCmdConfRouter(router *gin.RouterGroup) {
reqs := [...]*req.Conf{ reqs := [...]*req.Conf{
req.NewGet("", mcc.MachineCmdConfs), 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[:]) req.BatchSetGroup(mccs, reqs[:])

View File

@@ -1,8 +1,8 @@
package api package api
import ( import (
"mayfly-go/internal/common/consts"
tagapp "mayfly-go/internal/tag/application" tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req" "mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
) )
@@ -13,7 +13,9 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id 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{ rc.ResData = collx.M{
"mongoNum": mongoNum, "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)) queryCond, page := req.BindQueryAndPage[*entity.MongoQuery](rc, new(entity.MongoQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := m.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeMongo, queryCond.TagPath) tags := m.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
if len(codes) == 0 { Types: []tagentity.TagType{tagentity.TagTypeMongo},
CodePathLikes: []string{queryCond.TagPath},
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
} }
queryCond.Codes = codes queryCond.Codes = tags.GetCodes()
var mongovos []*vo.Mongo var mongovos []*vo.Mongo
res, err := m.MongoApp.GetPageList(queryCond, page, &mongovos) res, err := m.MongoApp.GetPageList(queryCond, page, &mongovos)

View File

@@ -1,8 +1,8 @@
package api package api
import ( import (
"mayfly-go/internal/common/consts"
tagapp "mayfly-go/internal/tag/application" tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/req" "mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
) )
@@ -13,7 +13,9 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id 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{ rc.ResData = collx.M{
"redisNum": redisNum, "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)) queryCond, page := req.BindQueryAndPage[*entity.RedisQuery](rc, new(entity.RedisQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := r.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeRedis, queryCond.TagPath) tags := r.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
if len(codes) == 0 { Types: collx.AsArray(tagentity.TagTypeRedis),
CodePathLikes: collx.AsArray(queryCond.TagPath),
})
if len(tags) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
} }
queryCond.Codes = codes queryCond.Codes = tags.GetCodes()
var redisvos []*vo.Redis var redisvos []*vo.Redis
res, err := r.RedisApp.GetPageList(queryCond, page, &redisvos) res, err := r.RedisApp.GetPageList(queryCond, page, &redisvos)

View File

@@ -2,7 +2,6 @@ package api
import ( import (
"fmt" "fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/api/form" "mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/api/vo" "mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application" "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/三个相关联标签,才可以构造成一棵树 // 如tagPath = tag1/tag2/tag3/ 需要转为该路径所关联的所有标签路径即 tag1/ tag1/tag2/ tag1/tag2/tag3/三个相关联标签,才可以构造成一棵树
allTagPaths := make([]string, 0) allTagPaths := make([]string, 0)
for _, tagPath := range collx.MapKeys(codePath2Tag) { for _, tagPath := range collx.MapKeys(codePath2Tag) {
allTagPaths = append(allTagPaths, entity.GetAllCodePath(tagPath)...) allTagPaths = append(allTagPaths, entity.CodePath(tagPath).GetAllPath()...)
} }
allTagPaths = collx.ArrayDeduplicate(allTagPaths) allTagPaths = collx.ArrayDeduplicate(allTagPaths)
@@ -82,7 +81,7 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) {
cond.CodePaths = strings.Split(tagPaths, ",") cond.CodePaths = strings.Split(tagPaths, ",")
} }
cond.Id = uint64(rc.QueryInt("id")) 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") codes := rc.Query("codes")
if codes != "" { if codes != "" {
cond.Codes = strings.Split(codes, ",") cond.Codes = strings.Split(codes, ",")
@@ -117,10 +116,10 @@ func (p *TagTree) MovingTag(rc *req.Ctx) {
func (p *TagTree) TagResources(rc *req.Ctx) { func (p *TagTree) TagResources(rc *req.Ctx) {
resourceType := int8(rc.PathParamInt("rtype")) resourceType := int8(rc.PathParamInt("rtype"))
accountId := rc.GetLoginAccount().Id 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 { 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) tagPaths := collx.MapKeys(tagPath2Resource)
@@ -133,14 +132,27 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
tagPath := rc.Query("tagPath") tagPath := rc.Query("tagPath")
accountId := rc.GetLoginAccount().Id accountId := rc.GetLoginAccount().Id
machineCodes := entity.GetCodeByPath(entity.TagTypeMachine, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeMachineAuthCert, tagPath)...) machineCodes := entity.GetCodesByCodePaths(entity.TagTypeMachine, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
dbCodes := entity.GetCodeByPath(entity.TagTypeDb, p.TagTreeApp.GetAccountTagCodePaths(accountId, entity.TagTypeDbName, tagPath)...) 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{ rc.ResData = collx.M{
"machine": len(machineCodes), "machine": len(machineCodes),
"db": len(dbCodes), "db": len(dbCodes),
"redis": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, tagPath)), "redis": len(p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
"mongo": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, tagPath)), 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 { if node.Root {
continue continue
} }
parentCodePath := node.GetParentPath(0) parentCodePath := node.GetParentPath()
parentNode := tagMap[parentCodePath] parentNode := tagMap[parentCodePath]
if parentNode != nil { if parentNode != nil {
parentNode.Children = append(parentNode.Children, node) parentNode.Children = append(parentNode.Children, node)

View File

@@ -2,6 +2,7 @@ package dto
import ( import (
"mayfly-go/internal/tag/domain/entity" "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/utils/collx"
"strings" "strings"
) )
@@ -48,11 +49,33 @@ type SimpleTagTree struct {
func (pt *SimpleTagTree) IsRoot() bool { func (pt *SimpleTagTree) IsRoot() bool {
// 去除路径两端可能存在的斜杠 // 去除路径两端可能存在的斜杠
path := strings.Trim(pt.CodePath, "/") path := strings.Trim(string(pt.CodePath), "/")
return len(strings.Split(path, "/")) == 1 return len(strings.Split(path, "/")) == 1
} }
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> index = 0 => test/test1/ index = 1 => test/ // GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/ -> test/test1/
func (pt *SimpleTagTree) GetParentPath(index int) string { func (pt *SimpleTagTree) GetParentPath() string {
return entity.GetParentPath(pt.CodePath, index) 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 return entity.TagTypeMachineAuthCert
} }
if resourceType == entity.TagTypeDb { if resourceType == entity.TagTypeDbInstance {
return entity.TagTypeDbAuthCert return entity.TagTypeDbAuthCert
} }

View File

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

View File

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

View File

@@ -29,25 +29,20 @@ const (
// 标签路径资源段分隔符 // 标签路径资源段分隔符
CodePathResourceSeparator = "|" CodePathResourceSeparator = "|"
TagTypeTag TagType = -1 TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.ResourceTypeMachine) TagTypeMachine TagType = TagType(consts.ResourceTypeMachine)
TagTypeDb TagType = TagType(consts.ResourceTypeDb) // 数据库实例 TagTypeDbInstance TagType = TagType(consts.ResourceTypeDbInstance) // 数据库实例
TagTypeRedis TagType = TagType(consts.ResourceTypeRedis) TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.ResourceTypeMongo) TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证) // ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证 TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // 数据库-授权凭证 TagTypeDbAuthCert TagType = 21 // 数据库-授权凭证
TagTypeDbName TagType = 22 // 数据库名 TagTypeDb TagType = 22 // 数据库名
) )
// GetTagPath 获取标签段路径,不获取对应资源相关路径
func (pt *TagTree) GetTagPath() string {
return GetTagPath(pt.CodePath)
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口 // 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
type ITagResource interface { type ITagResource interface {
// 获取资源code // 获取资源code
@@ -80,10 +75,13 @@ func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
r.Tags = append(r.Tags, rt) r.Tags = append(r.Tags, rt)
} }
// GetTagPath 获取标签段路径,不获取对应资源相关路径 // CodePath 标签编号路径 如: tag1/tag2/resourceType1|xxxcode/resourceType2|yyycode/
func GetTagPath(codePath string) string { type CodePath string
// GetTag 获取标签段路径,不获取对应资源相关路径
func (codePath CodePath) GetTag() CodePath {
// 以 资源分隔符"|" 符号对字符串进行分割 // 以 资源分隔符"|" 符号对字符串进行分割
parts := strings.Split(codePath, CodePathResourceSeparator) parts := strings.Split(string(codePath), CodePathResourceSeparator)
if len(parts) < 2 { if len(parts) < 2 {
return codePath return codePath
} }
@@ -96,31 +94,15 @@ func GetTagPath(codePath string) string {
// 如果找到最后一个 "/" 符号,则截取子串 // 如果找到最后一个 "/" 符号,则截取子串
if lastSlashIndex != -1 { if lastSlashIndex != -1 {
return substringBeforeNumber[:lastSlashIndex+1] return CodePath(substringBeforeNumber[:lastSlashIndex+1])
} }
return codePath return codePath
} }
// GetCodeByPath 从codePaths中提取指定标签类型的所有tagCode并去重 // GetParent 获取父标签编号路径, 如CodePath = test/test1/test2/ -> parentLevel = 0 => test/test1/ parentLevel = 1 => test/
// 如codePaths = tag1/tag2/1|xxxcode/11|yyycode/, tagType = 1 -> xxxcode, tagType = 11 -> yyycode func (cp CodePath) GetParent(parentLevel int) CodePath {
func GetCodeByPath(tagType TagType, codePaths ...string) []string { codePath := string(cp)
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 {
// 去除末尾的斜杠 // 去除末尾的斜杠
codePath = strings.TrimSuffix(codePath, CodePathSeparator) codePath = strings.TrimSuffix(codePath, CodePathSeparator)
@@ -128,61 +110,29 @@ func GetParentPath(codePath string, index int) string {
paths := strings.Split(codePath, CodePathSeparator) paths := strings.Split(codePath, CodePathSeparator)
// 确保索引在有效范围内 // 确保索引在有效范围内
if index < 0 { if parentLevel < 0 {
index = 0 parentLevel = 0
} else if index > len(paths)-2 { } else if parentLevel > len(paths)-2 {
index = 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/ // GetPathSections 根据标签编号路径获取路径段落
func GetAllCodePath(codePath string) []string { func (cp CodePath) GetPathSections() PathSections {
// 去除末尾的斜杠 codePath := string(cp)
codePath = strings.TrimSuffix(codePath, CodePathSeparator) codePath = strings.TrimSuffix(codePath, CodePathSeparator)
var sections PathSections
// 使用 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
codes := strings.Split(codePath, CodePathSeparator) codes := strings.Split(codePath, CodePathSeparator)
path := ""
for _, code := range codes { for _, code := range codes {
path += code + CodePathSeparator
typeAndCode := strings.Split(code, CodePathResourceSeparator) typeAndCode := strings.Split(code, CodePathResourceSeparator)
var tagType TagType var tagType TagType
var tagCode string var tagCode string
@@ -195,11 +145,66 @@ func GetTagPathSections(codePath string) TagPathSections {
tagCode = typeAndCode[1] tagCode = typeAndCode[1]
} }
sections = append(sections, &TagPathSection{ sections = append(sections, &PathSection{
Type: tagType, Type: tagType,
Code: tagCode, Code: tagCode,
Path: path,
}) })
} }
return sections 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 ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
) )
func TestGetPathSection(t *testing.T) { func TestGetTag(t *testing.T) {
fromPath := "tag1/tag2/1|xx/" cp := CodePath("tag1/tag2/1|xx/11|yy/111|zz/")
childPath := "tag1/tag2/1|xx/11|yy/" v := cp.GetTag()
toPath := "tag3/" pv := cp.GetParent(0)
parentSection := GetTagPathSections(GetParentPath(fromPath, 0)) av := cp.GetAllPath()
ps := cp.GetPathSections()
childSection := GetTagPathSections(childPath) code := cp.GetCode(TagType(11))
res := toPath + childSection[len(GetTagPathSections(fromPath)):].ToCodePath() fmt.Println(v, pv, av, ps, code)
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 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). Eq("id", condition.Id).
In("code", condition.Codes). In("code", condition.Codes).
In("code_path", condition.CodePaths). In("code_path", condition.CodePaths).
RLike("code_path", condition.CodePathLike).
Eq("type", condition.Type).
In("type", condition.Types). In("type", condition.Types).
OrderByAsc("type").OrderByAsc("code_path") OrderByAsc("type").OrderByAsc("code_path")

View File

@@ -5,6 +5,11 @@ import (
"strings" "strings"
) )
// AsArray 将可变参数列表为数组
func AsArray[T comparable](el ...T) []T {
return el
}
// 数组比较 // 数组比较
// 依次返回,新增值,删除值,以及不变值 // 依次返回,新增值,删除值,以及不变值
func ArrayCompare[T comparable](newArr []T, oldArr []T) ([]T, []T, []T) { 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 { if err := cli.Ping(); err != nil {
manager.CloseClient(cli) 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 { } else {
logx.Debugf("WS发送心跳成功: uid=%v, cid=%s", userId, cli.ClientId) logx.Debugf("WS - send heartbeat successfully: uid=%v, cid=%s", userId, cli.ClientId)
} }
} }
} }