mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2deb3109c2 | ||
|
|
a80221a950 | ||
|
|
10630847df |
@@ -10,20 +10,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^10.10.0",
|
||||
"asciinema-player": "^3.7.1",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"asciinema-player": "^3.8.0",
|
||||
"axios": "^1.6.2",
|
||||
"clipboard": "^2.0.11",
|
||||
"cropperjs": "^1.6.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.7.4",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.7.7",
|
||||
"js-base64": "^3.7.7",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.49.0",
|
||||
"monaco-sql-languages": "^0.12.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"monaco-sql-languages": "^0.12.2",
|
||||
"monaco-themes": "^0.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
@@ -34,8 +34,8 @@
|
||||
"sql-formatter": "^15.0.2",
|
||||
"trzsz": "^1.1.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.4.27",
|
||||
"vue-router": "^4.3.2",
|
||||
"vue": "^3.4.32",
|
||||
"vue-router": "^4.4.0",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
"xterm-addon-search": "^0.13.0",
|
||||
@@ -48,16 +48,16 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/compiler-sfc": "^3.4.27",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue/compiler-sfc": "^3.4.32",
|
||||
"code-inspector-plugin": "^0.4.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.77.1",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.12",
|
||||
"sass": "^1.77.8",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.4",
|
||||
"vue-eslint-parser": "^9.4.2"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.8.7',
|
||||
version: 'v1.8.8',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -9,7 +9,19 @@ export function getValueByPath(obj: any, path: string) {
|
||||
const keys = path.split('.');
|
||||
let result = obj;
|
||||
for (let key of keys) {
|
||||
if (!result || typeof result !== 'object') {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
// 如果是字符串,则尝试使用json解析
|
||||
if (typeof result == 'string') {
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (typeof result !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -23,7 +35,18 @@ export function getValueByPath(obj: any, path: string) {
|
||||
}
|
||||
|
||||
const index = parseInt(matchIndex[1]);
|
||||
result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
|
||||
|
||||
let arrValue = result[arrayKey];
|
||||
if (typeof arrValue == 'string') {
|
||||
try {
|
||||
arrValue = JSON.parse(arrValue);
|
||||
} catch (e) {
|
||||
result = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = Array.isArray(arrValue) ? arrValue[index] : undefined;
|
||||
} else {
|
||||
result = result[key];
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item :span="2" label="名称">{{ db?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="id">{{ db?.id }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="db.tags" /></el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="1" label="名称">{{ db?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">
|
||||
<SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="用户名">{{ db?.username }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="数据库">{{ sqlExec.db }}</el-descriptions-item>
|
||||
<el-descriptions-item label="表">
|
||||
@@ -33,7 +30,9 @@ import { dbApi } from '@/views/ops/db/api';
|
||||
import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { getDbDialect } from '@/views/ops/db/dialect';
|
||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
|
||||
import { tagApi } from '@/views/ops/tag/api';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
|
||||
|
||||
const props = defineProps({
|
||||
// 业务key
|
||||
@@ -74,6 +73,10 @@ const getDbSqlExec = async (bizKey: string) => {
|
||||
state.sqlExec = res.list?.[0];
|
||||
const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
|
||||
state.db = dbRes.list?.[0];
|
||||
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
|
||||
state.db.codePaths = res.map((item: any) => item.codePath);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="id">{{ redis?.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="用户名">{{ redis?.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="redis.tags" /></el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="编号">{{ redis?.code }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
|
||||
@@ -22,8 +21,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, onMounted } from 'vue';
|
||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
|
||||
import { redisApi } from '@/views/ops/redis/api';
|
||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
|
||||
import { tagApi } from '@/views/ops/tag/api';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
// 业务表单
|
||||
@@ -75,6 +76,10 @@ const parseRunCmdForm = async (bizForm: string) => {
|
||||
return;
|
||||
}
|
||||
state.redis = res.list?.[0];
|
||||
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.Redis.value, codes: state.redis.code }).then((res) => {
|
||||
state.redis.codePaths = res.map((item: any) => item.codePath);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
||||
import { DbInst, DbThemeConfig, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||
import TagTree from '../component/TagTree.vue';
|
||||
import { dbApi } from './api';
|
||||
@@ -505,7 +505,7 @@ const state = reactive({
|
||||
|
||||
const { nowDbInst, tableCreateDialog } = toRefs(state);
|
||||
|
||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false, locationTreeNode: false });
|
||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
|
||||
|
||||
const serverInfoReqParam = ref({
|
||||
instanceId: 0,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
:clearable="false"
|
||||
type="Date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
:placeholder="`选择日期-${placeholder}`"
|
||||
/>
|
||||
|
||||
<el-date-picker
|
||||
@@ -41,7 +41,7 @@
|
||||
:clearable="false"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择日期时间"
|
||||
:placeholder="`选择日期时间-${placeholder}`"
|
||||
/>
|
||||
|
||||
<el-time-picker
|
||||
@@ -56,7 +56,7 @@
|
||||
v-model="itemValue"
|
||||
:clearable="false"
|
||||
value-format="HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
:placeholder="`选择时间-${placeholder}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElInput, ElMessage } from 'element-plus';
|
||||
import { copyToClipboard } from '@/common/utils/string';
|
||||
import { DbInst } from '@/views/ops/db/db';
|
||||
import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { exportCsv, exportFile } from '@/common/utils/export';
|
||||
@@ -258,12 +258,10 @@ const cmDataDel = new ContextmenuItem('deleteData', '删除')
|
||||
return state.table == '';
|
||||
});
|
||||
|
||||
const cmDataEdit = new ContextmenuItem('editData', '编辑行')
|
||||
.withIcon('edit')
|
||||
.withOnClick(() => onEditRowData())
|
||||
.withHideFunc(() => {
|
||||
return state.table == '';
|
||||
});
|
||||
const cmFormView = new ContextmenuItem('formView', '表单视图').withIcon('Document').withOnClick(() => onEditRowData());
|
||||
// .withHideFunc(() => {
|
||||
// return state.table == '';
|
||||
// });
|
||||
|
||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
|
||||
.withIcon('tickets')
|
||||
@@ -363,7 +361,7 @@ const state = reactive({
|
||||
|
||||
const { tableHeight, datas } = toRefs(state);
|
||||
|
||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
|
||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
|
||||
|
||||
/**
|
||||
* 行号字段列
|
||||
@@ -595,7 +593,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
|
||||
const { clientX, clientY } = event;
|
||||
state.contextmenu.dropdown.x = clientX;
|
||||
state.contextmenu.dropdown.y = clientY;
|
||||
state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataEdit, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
|
||||
state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmFormView, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
|
||||
contextmenuRef.value.openContextmenu({ column, rowData: data });
|
||||
};
|
||||
|
||||
@@ -627,12 +625,12 @@ const onDeleteData = async () => {
|
||||
const onEditRowData = () => {
|
||||
const selectionDatas = Array.from(selectionRowsMap.values());
|
||||
if (selectionDatas.length > 1) {
|
||||
ElMessage.warning('只能编辑一行数据');
|
||||
ElMessage.warning('只能选择一行数据');
|
||||
return;
|
||||
}
|
||||
const data = selectionDatas[0];
|
||||
state.tableDataFormDialog.data = { ...data };
|
||||
state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
|
||||
state.tableDataFormDialog.title = state.table ? `'${props.table}'表单数据` : '表单视图';
|
||||
state.tableDataFormDialog.visible = true;
|
||||
};
|
||||
|
||||
@@ -648,7 +646,7 @@ const onGenerateJson = async () => {
|
||||
// 按列字段重新排序对象key
|
||||
const jsonObj = [];
|
||||
for (let selectionData of selectionDatas) {
|
||||
let obj = {};
|
||||
let obj: any = {};
|
||||
for (let column of state.columns) {
|
||||
if (column.show) {
|
||||
obj[column.title] = selectionData[column.dataKey];
|
||||
@@ -752,7 +750,7 @@ const submitUpdateFields = async () => {
|
||||
|
||||
for (let updateRow of cellUpdateMap.values()) {
|
||||
const rowData = { ...updateRow.rowData };
|
||||
let updateColumnValue = {};
|
||||
let updateColumnValue: any = {};
|
||||
|
||||
for (let k of updateRow.columnsMap.keys()) {
|
||||
const v = updateRow.columnsMap.get(k);
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
:key="column.columnName"
|
||||
class="w100 mb5"
|
||||
:prop="column.columnName"
|
||||
:required="!column.nullable && !column.isPrimaryKey && !column.isIdentity"
|
||||
:required="props.tableName != '' && !column.nullable && !column.isPrimaryKey && !column.isIdentity"
|
||||
>
|
||||
<template #label>
|
||||
<span class="pointer" :title="`${column.columnType} | ${column.columnComment}`">
|
||||
<span class="pointer" :title="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType">
|
||||
{{ column.columnName }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -17,13 +17,13 @@
|
||||
<ColumnFormItem
|
||||
v-model="modelValue[`${column.columnName}`]"
|
||||
:data-type="dbInst.getDialect().getDataType(column.dataType)"
|
||||
:placeholder="`${column.columnType} ${column.columnComment}`"
|
||||
:placeholder="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType"
|
||||
:column-name="column.columnName"
|
||||
:disabled="column.isIdentity"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<template #footer v-if="props.tableName">
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" @click="confirm">确定</el-button>
|
||||
@@ -99,7 +99,7 @@ const confirm = async () => {
|
||||
|
||||
let sql = '';
|
||||
if (oldValue) {
|
||||
const updateColumnValue = {};
|
||||
const updateColumnValue: any = {};
|
||||
Object.keys(oldValue).forEach((key) => {
|
||||
// 如果新旧值不相等,则为需要更新的字段
|
||||
if (oldValue[key] !== modelValue.value[key]) {
|
||||
|
||||
@@ -50,22 +50,6 @@
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<!-- 表数据展示配置 -->
|
||||
<el-popover
|
||||
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
|
||||
placement="bottom"
|
||||
width="auto"
|
||||
title="展示配置"
|
||||
trigger="click"
|
||||
>
|
||||
<el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
|
||||
<template #reference>
|
||||
<el-link type="primary" icon="setting" :underline="false"></el-link>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
|
||||
<el-tooltip :show-after="500" v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
|
||||
<el-link @click="submitUpdateFields()" type="success" :underline="false" class="font12">提交</el-link>
|
||||
</el-tooltip>
|
||||
@@ -258,7 +242,7 @@ import { DbInst } from '@/views/ops/db/db';
|
||||
import DbTableData from './DbTableData.vue';
|
||||
import { DbDialect } from '@/views/ops/db/dialect';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { useEventListener, useStorage } from '@vueuse/core';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
|
||||
import DbTableDataForm from './DbTableDataForm.vue';
|
||||
|
||||
@@ -288,8 +272,6 @@ const condDialogInputRef: Ref = ref(null);
|
||||
|
||||
const defaultPageSize = DbInst.DefaultLimit;
|
||||
|
||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
|
||||
|
||||
const state = reactive({
|
||||
datas: [],
|
||||
sql: '', // 当前数据tab执行的sql
|
||||
|
||||
@@ -842,3 +842,18 @@ function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string)
|
||||
return tables.length > 0 ? tables[0] : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库主题配置
|
||||
*/
|
||||
export const DbThemeConfig = {
|
||||
/**
|
||||
* 表数据表头是否显示备注
|
||||
*/
|
||||
showColumnComment: true,
|
||||
|
||||
/**
|
||||
* 是否自动定位至树节点
|
||||
*/
|
||||
locationTreeNode: true,
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ export enum DataType {
|
||||
}
|
||||
|
||||
/** 列数据类型角标 */
|
||||
export const ColumnTypeSubscript = {
|
||||
export const ColumnTypeSubscript: any = {
|
||||
/** 字符串 */
|
||||
string: 'ab',
|
||||
/** 数字 */
|
||||
|
||||
@@ -3,9 +3,8 @@ module mayfly-go
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
gitee.com/chunanyong/dm v1.8.14
|
||||
gitee.com/chunanyong/dm v1.8.15
|
||||
gitee.com/liuzongyang/libpq v1.0.9
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
@@ -17,29 +16,30 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
|
||||
github.com/may-fly/cast v1.6.1
|
||||
github.com/microsoft/go-mssqldb v1.7.1
|
||||
github.com/microsoft/go-mssqldb v1.7.2
|
||||
github.com/mojocn/base64Captcha v1.3.6 // 验证码
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/redis/go-redis/v9 v9.5.3
|
||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||
github.com/sijms/go-ora/v2 v2.8.19
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tidwall/gjson v1.17.1
|
||||
github.com/veops/go-ansiterm v0.0.5
|
||||
go.mongodb.org/mongo-driver v1.15.0 // mongo
|
||||
golang.org/x/crypto v0.24.0 // ssh
|
||||
go.mongodb.org/mongo-driver v1.16.0 // mongo
|
||||
golang.org/x/crypto v0.25.0 // ssh
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/sync v0.7.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
// gorm
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/gorm v1.25.10
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -77,12 +77,14 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
@@ -94,7 +96,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
|
||||
google.golang.org/grpc v1.52.3 // indirect
|
||||
|
||||
@@ -66,6 +66,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
for _, dbvo := range dbvos {
|
||||
di := instancesMap[dbvo.InstanceId]
|
||||
if di != nil {
|
||||
dbvo.InstanceCode = di.Code
|
||||
dbvo.InstanceType = di.Type
|
||||
dbvo.Host = di.Host
|
||||
dbvo.Port = di.Port
|
||||
|
||||
@@ -15,6 +15,7 @@ type DbListVO struct {
|
||||
InstanceId uint64 `json:"instanceId"`
|
||||
AuthCertName string `json:"authCertName"`
|
||||
|
||||
InstanceCode string `json:"instanceCode" gorm:"-"`
|
||||
InstanceType string `json:"type" gorm:"-"`
|
||||
Host string `json:"host" gorm:"-"`
|
||||
Port int `json:"port" gorm:"-"`
|
||||
|
||||
@@ -67,17 +67,9 @@ func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, pagePar
|
||||
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error {
|
||||
instanceEntity.Network = instanceEntity.GetNetwork()
|
||||
|
||||
if authCert.Id != 0 {
|
||||
// 密文可能被清除,故需要重新获取
|
||||
authCert, _ = app.resourceAuthCertApp.GetAuthCert(authCert.Name)
|
||||
} else {
|
||||
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
|
||||
publicAuthCert, err := app.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authCert = publicAuthCert
|
||||
}
|
||||
authCert, err := app.resourceAuthCertApp.GetRealAuthCert(authCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbConn, err := dbm.Conn(app.toDbInfoByAc(instanceEntity, authCert, ""))
|
||||
@@ -176,7 +168,7 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
|
||||
DbInstanceId: instanceId,
|
||||
}
|
||||
err = app.restoreApp.restoreRepo.GetByCond(restore)
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库恢复任务。", instance.Name)
|
||||
}
|
||||
|
||||
@@ -184,7 +176,7 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
|
||||
DbInstanceId: instanceId,
|
||||
}
|
||||
err = app.backupApp.backupRepo.GetByCond(backup)
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库备份任务。", instance.Name)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,17 +28,19 @@ type Dialect interface {
|
||||
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
||||
GetDbProgram() (DbProgram, error)
|
||||
|
||||
// 批量保存数据
|
||||
// BatchInsert 批量insert数据
|
||||
BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error)
|
||||
|
||||
// 拷贝表
|
||||
// CopyTable 拷贝表
|
||||
CopyTable(copy *DbCopyTable) error
|
||||
|
||||
// CreateTable 创建表
|
||||
CreateTable(columns []Column, tableInfo Table, dropOldTable bool) (int, error)
|
||||
|
||||
// CreateIndex 创建索引
|
||||
CreateIndex(tableInfo Table, indexs []Index) error
|
||||
|
||||
// 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
|
||||
// UpdateSequence 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
|
||||
UpdateSequence(tableName string, columns []Column)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,37 +13,36 @@ import (
|
||||
type MetaData interface {
|
||||
BaseMetaData
|
||||
|
||||
// 获取数据库服务实例信息
|
||||
// GetDbServer 获取数据库服务实例信息
|
||||
GetDbServer() (*DbServer, error)
|
||||
|
||||
// 获取数据库名称列表
|
||||
// GetDbNames 获取数据库名称列表
|
||||
GetDbNames() ([]string, error)
|
||||
|
||||
// 获取表信息
|
||||
// GetTables 获取表信息
|
||||
GetTables(tableNames ...string) ([]Table, error)
|
||||
|
||||
// 获取指定表名的所有列元信息
|
||||
// GetColumns 获取指定表名的所有列元信息
|
||||
GetColumns(tableNames ...string) ([]Column, error)
|
||||
|
||||
// 根据数据库类型修复字段长度、精度等
|
||||
// FixColumn(column *Column)
|
||||
|
||||
// 获取表主键字段名,没有主键标识则默认第一个字段
|
||||
// GetPrimaryKey 获取表主键字段名,没有主键标识则默认第一个字段
|
||||
GetPrimaryKey(tableName string) (string, error)
|
||||
|
||||
// 获取表索引信息
|
||||
// GetTableIndex 获取表索引信息
|
||||
GetTableIndex(tableName string) ([]Index, error)
|
||||
|
||||
// 获取建表ddl
|
||||
// GetTableDDL 获取建表ddl
|
||||
GetTableDDL(tableName string, dropBeforeCreate bool) (string, error)
|
||||
|
||||
// GenerateTableDDL 生成建表ddl
|
||||
GenerateTableDDL(columns []Column, tableInfo Table, dropBeforeCreate bool) []string
|
||||
|
||||
// GenerateIndexDDL 生成索引ddl
|
||||
GenerateIndexDDL(indexs []Index, tableInfo Table) []string
|
||||
|
||||
GetSchemas() ([]string, error)
|
||||
|
||||
// 获取数据处理助手 用于解析格式化列数据等
|
||||
// GetDataHelper 获取数据处理助手 用于解析格式化列数据等
|
||||
GetDataHelper() DataHelper
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type DbInstance struct {
|
||||
Network string `json:"network"`
|
||||
Extra *string `json:"extra"` // 连接需要的其他额外参数(json格式), 如oracle需要sid等
|
||||
Params *string `json:"params"` // 使用指针类型,可更新为零值(空字符串)
|
||||
Remark string `json:"remark"`
|
||||
Remark *string `json:"remark"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,6 @@ func newDbRepo() repository.Db {
|
||||
|
||||
// 分页获取数据库信息列表
|
||||
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
pd := model.NewCond().Eq("instance_id", condition.InstanceId).In("code", condition.Codes)
|
||||
pd := model.NewCond().Eq("instance_id", condition.InstanceId).In("code", condition.Codes).Eq("id", condition.Id)
|
||||
return d.PageByCondToAny(pd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
@@ -159,17 +159,9 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
|
||||
func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error {
|
||||
me.Id = 0
|
||||
|
||||
if authCert.Id != 0 {
|
||||
// 密文可能被清除,故需要重新获取
|
||||
authCert, _ = m.resourceAuthCertApp.GetAuthCert(authCert.Name)
|
||||
} else {
|
||||
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
|
||||
publicAuthCert, err := m.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authCert = publicAuthCert
|
||||
}
|
||||
authCert, err := m.resourceAuthCertApp.GetRealAuthCert(authCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mi, err := m.toMi(me, authCert)
|
||||
|
||||
@@ -98,18 +98,19 @@ func NewParserByteStream(width, height int) *ansiterm.ByteStream {
|
||||
|
||||
var (
|
||||
enterMarks = [][]byte{
|
||||
[]byte("\x1b[?1049h"),
|
||||
[]byte("\x1b[?1049h"), // 从备用屏幕缓冲区恢复屏幕内容
|
||||
[]byte("\x1b[?1048h"),
|
||||
[]byte("\x1b[?1047h"),
|
||||
[]byte("\x1b[?47h"),
|
||||
[]byte("\x1b[?25l"),
|
||||
[]byte("\x1b[?25l"), // 隐藏光标
|
||||
}
|
||||
|
||||
exitMarks = [][]byte{
|
||||
[]byte("\x1b[?1049l"),
|
||||
[]byte("\x1b[?1049l"), // 从备用屏幕缓冲区恢复屏幕内容
|
||||
[]byte("\x1b[?1048l"),
|
||||
[]byte("\x1b[?1047l"),
|
||||
[]byte("\x1b[?47l"),
|
||||
[]byte("\x1b[?25h"), // 显示光标
|
||||
}
|
||||
|
||||
screenMarks = [][]byte{
|
||||
|
||||
@@ -76,18 +76,9 @@ func (r *redisAppImpl) TestConn(param *dto.SaveRedis) error {
|
||||
db = cast.ToInt(strings.Split(re.Db, ",")[0])
|
||||
}
|
||||
|
||||
authCert := param.AuthCert
|
||||
if authCert.Id != 0 {
|
||||
// 密文可能被清除,故需要重新获取
|
||||
authCert, _ = r.resourceAuthCertApp.GetAuthCert(authCert.Name)
|
||||
} else {
|
||||
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
|
||||
publicAuthCert, err := r.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authCert = publicAuthCert
|
||||
}
|
||||
authCert, err := r.resourceAuthCertApp.GetRealAuthCert(param.AuthCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := re.ToRedisInfo(db, authCert).Conn()
|
||||
|
||||
@@ -112,6 +112,7 @@ func (re *RedisInfo) connSentinel() (*RedisConn, error) {
|
||||
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
|
||||
Username: re.Username,
|
||||
Password: re.Password, // no password set
|
||||
SentinelUsername: re.Username,
|
||||
SentinelPassword: re.Password, // 哨兵节点密码需与redis节点密码一致
|
||||
DB: re.Db, // use default DB
|
||||
DialTimeout: 8 * time.Second,
|
||||
|
||||
@@ -82,6 +82,11 @@ 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"))
|
||||
codes := rc.Query("codes")
|
||||
if codes != "" {
|
||||
cond.Codes = strings.Split(codes, ",")
|
||||
}
|
||||
|
||||
var tagTrees []entity.TagTree
|
||||
p.TagTreeApp.ListByQuery(cond, &tagTrees)
|
||||
|
||||
@@ -26,6 +26,9 @@ type ResourceAuthCert interface {
|
||||
// GetAuthCert 根据授权凭证名称获取授权凭证
|
||||
GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
|
||||
|
||||
//GetRealAuthCert 获取真实可连接鉴权的授权凭证,主要用于资源测试连接时
|
||||
GetRealAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error)
|
||||
|
||||
// GetResourceAuthCert 获取资源授权凭证,优先获取默认账号,若不存在默认账号则返回特权账号,都不存在则返回第一个
|
||||
GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
|
||||
|
||||
@@ -211,6 +214,25 @@ func (r *resourceAuthCertAppImpl) GetAuthCert(authCertName string) (*entity.Reso
|
||||
return r.decryptAuthCert(authCert)
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) GetRealAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
|
||||
// 如果使用的是公共授权凭证,则密文为凭证名称
|
||||
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
|
||||
return r.GetAuthCert(authCert.Ciphertext)
|
||||
}
|
||||
|
||||
if authCert.Id != 0 && authCert.Ciphertext == "" {
|
||||
// 密文可能被清除,故需要重新获取
|
||||
ac, err := r.GetAuthCert(authCert.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authCert.Ciphertext = ac.Ciphertext
|
||||
return authCert, nil
|
||||
}
|
||||
|
||||
return authCert, nil
|
||||
}
|
||||
|
||||
func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error) {
|
||||
resourceAuthCerts, err := r.ListByCond(&entity.ResourceAuthCert{
|
||||
ResourceType: int8(resourceType),
|
||||
|
||||
@@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.8.7"
|
||||
Version = "v1.8.8"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
|
||||
@@ -3,9 +3,8 @@ package jsonx
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/pkg/logx"
|
||||
"strings"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// json字符串转map
|
||||
@@ -42,35 +41,35 @@ func ToStr(val any) string {
|
||||
//
|
||||
// @param fieldPath字段路径。如user.username等
|
||||
func GetStringByBytes(bytes []byte, fieldPath string) (string, error) {
|
||||
return jsonparser.GetString(bytes, strings.Split(fieldPath, ".")...)
|
||||
return gjson.GetBytes(bytes, fieldPath).String(), nil
|
||||
}
|
||||
|
||||
// 根据json字符串获取对应字段路径的string类型值
|
||||
//
|
||||
// @param fieldPath字段路径。如user.username等
|
||||
func GetString(jsonStr string, fieldPath string) (string, error) {
|
||||
return GetStringByBytes([]byte(jsonStr), fieldPath)
|
||||
return gjson.Get(jsonStr, fieldPath).String(), nil
|
||||
}
|
||||
|
||||
// 根据json字节数组获取对应字段路径的int类型值
|
||||
//
|
||||
// @param fieldPath字段路径。如user.age等
|
||||
func GetIntByBytes(bytes []byte, fieldPath string) (int64, error) {
|
||||
return jsonparser.GetInt(bytes, strings.Split(fieldPath, ".")...)
|
||||
return gjson.GetBytes(bytes, fieldPath).Int(), nil
|
||||
}
|
||||
|
||||
// 根据json字符串获取对应字段路径的int类型值
|
||||
//
|
||||
// @param fieldPath字段路径。如user.age等
|
||||
func GetInt(jsonStr string, fieldPath string) (int64, error) {
|
||||
return GetIntByBytes([]byte(jsonStr), fieldPath)
|
||||
return gjson.Get(jsonStr, fieldPath).Int(), nil
|
||||
}
|
||||
|
||||
// 根据json字节数组获取对应字段路径的bool类型值
|
||||
//
|
||||
// @param fieldPath字段路径。如user.isDeleted等
|
||||
func GetBoolByBytes(bytes []byte, fieldPath string) (bool, error) {
|
||||
return jsonparser.GetBoolean(bytes, strings.Split(fieldPath, ".")...)
|
||||
return gjson.GetBytes(bytes, fieldPath).Bool(), nil
|
||||
}
|
||||
|
||||
// 根据json字符串获取对应字段路径的bool类型值
|
||||
|
||||
@@ -3,8 +3,6 @@ package jsonx
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/buger/jsonparser"
|
||||
)
|
||||
|
||||
const jsonStr = `{
|
||||
@@ -36,7 +34,7 @@ func TestGetString(t *testing.T) {
|
||||
// val, err := GetString(jsonStr, "username1")
|
||||
|
||||
// 含有数组的
|
||||
val, err := GetString(jsonStr, "person.avatars.[0].url")
|
||||
val, err := GetString(jsonStr, "person.avatars.0.url")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("error: ", err.Error())
|
||||
@@ -50,60 +48,3 @@ func TestGetInt(t *testing.T) {
|
||||
val2, _ := GetInt(jsonStr, "person.github.followers")
|
||||
fmt.Println(val, ",", val2)
|
||||
}
|
||||
|
||||
// 官方demo
|
||||
func TestJsonParser(t *testing.T) {
|
||||
data := []byte(jsonStr)
|
||||
// You can specify key path by providing arguments to Get function
|
||||
jsonparser.Get(data, "person", "name", "fullName")
|
||||
|
||||
// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type
|
||||
jsonparser.GetInt(data, "person", "github", "followers")
|
||||
|
||||
// When you try to get object, it will return you []byte slice pointer to data containing it
|
||||
// In `company` it will be `{"name": "Acme"}`
|
||||
jsonparser.Get(data, "company")
|
||||
|
||||
// If the key doesn't exist it will throw an error
|
||||
var size int64
|
||||
if value, err := jsonparser.GetInt(data, "company", "size"); err == nil {
|
||||
size = value
|
||||
fmt.Println(size)
|
||||
}
|
||||
|
||||
// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN]
|
||||
jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
fmt.Println(jsonparser.Get(value, "url"))
|
||||
}, "person", "avatars")
|
||||
|
||||
// Or use can access fields by index!
|
||||
jsonparser.GetString(data, "person", "avatars", "[0]", "url")
|
||||
|
||||
// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN }
|
||||
jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
|
||||
fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType)
|
||||
return nil
|
||||
}, "person", "name")
|
||||
|
||||
// The most efficient way to extract multiple keys is `EachKey`
|
||||
|
||||
paths := [][]string{
|
||||
[]string{"person", "name", "fullName"},
|
||||
[]string{"person", "avatars", "[0]", "url"},
|
||||
[]string{"company", "url"},
|
||||
}
|
||||
|
||||
jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error) {
|
||||
switch idx {
|
||||
case 0: // []string{"person", "name", "fullName"}
|
||||
{
|
||||
}
|
||||
case 1: // []string{"person", "avatars", "[0]", "url"}
|
||||
{
|
||||
}
|
||||
case 2: // []string{"company", "url"},
|
||||
{
|
||||
}
|
||||
}
|
||||
}, paths...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user