mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 07:50:25 +08:00
refactor: 机器相关配置迁移至系统配置、pgsql数据操作完善、新增context-path
This commit is contained in:
@@ -3,6 +3,10 @@ function getBaseApiUrl() {
|
|||||||
if (path == '/') {
|
if (path == '/') {
|
||||||
return window.location.host;
|
return window.location.host;
|
||||||
}
|
}
|
||||||
|
if (path.endsWith('/')) {
|
||||||
|
// 去除最后一个/
|
||||||
|
return window.location.host + path.replace(/\/$/, '');
|
||||||
|
}
|
||||||
return window.location.host + path;
|
return window.location.host + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
@click="maximize(minimizeTerminal.terminalId)"
|
@click="maximize(minimizeTerminal.terminalId)"
|
||||||
>
|
>
|
||||||
<el-tooltip effect="customized" :content="minimizeTerminal.desc" placement="top">
|
<el-tooltip :content="minimizeTerminal.desc" placement="top">
|
||||||
<span>
|
<span>
|
||||||
{{ minimizeTerminal.title }}
|
{{ minimizeTerminal.title }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
<span v-for="(v, i) in tags" :key="i">
|
<span v-for="(v, i) in tags" :key="i">
|
||||||
<el-tooltip effect="customized" :content="v.remark" placement="top">
|
<el-tooltip :content="v.remark" placement="top">
|
||||||
<span class="color-success">{{ v.name }}</span>
|
<span class="color-success">{{ v.name }}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<span v-if="i != state.tags.length - 1" class="color-primary"> / </span>
|
<span v-if="i != state.tags.length - 1" class="color-primary"> / </span>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export class TagTreeNode {
|
|||||||
*/
|
*/
|
||||||
params: any;
|
params: any;
|
||||||
|
|
||||||
|
icon: any;
|
||||||
|
|
||||||
static TagPath = -1;
|
static TagPath = -1;
|
||||||
|
|
||||||
constructor(key: any, label: string, type?: NodeType) {
|
constructor(key: any, label: string, type?: NodeType) {
|
||||||
@@ -42,6 +44,11 @@ export class TagTreeNode {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withIcon(icon: any) {
|
||||||
|
this.icon = icon;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载子节点,使用节点类型的loadNodesFunc去加载子节点
|
* 加载子节点,使用节点类型的loadNodesFunc去加载子节点
|
||||||
* @returns 子节点信息
|
* @returns 子节点信息
|
||||||
|
|||||||
@@ -57,34 +57,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #action="{ data }">
|
<template #action="{ data }">
|
||||||
<el-popover placement="left" trigger="click" :width="300">
|
<span v-if="actionBtns[perms.saveDb]">
|
||||||
<template #reference>
|
<el-button type="primary" @click="editDb(data)" link>编辑</el-button>
|
||||||
<el-button type="primary" @click="selectDb(data.dbs)" link>库操作</el-button>
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
</template>
|
</span>
|
||||||
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索" size="small">
|
|
||||||
<template #prefix>
|
|
||||||
<el-icon class="el-input__icon">
|
|
||||||
<search-icon />
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<div
|
|
||||||
class="el-tag--plain el-tag--success"
|
|
||||||
v-for="db in filterDb.list"
|
|
||||||
:key="db"
|
|
||||||
style="border: 1px var(--color-success-light-3) solid; margin-top: 3px; border-radius: 5px; padding: 2px; position: relative"
|
|
||||||
>
|
|
||||||
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
|
|
||||||
<el-link type="primary" plain size="small" :underline="false" @click="showTableInfo(data, db)" style="position: absolute; right: 4px"
|
|
||||||
>操作
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
|
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
|
||||||
<el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
|
<el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
|
||||||
|
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
<el-dropdown @command="handleMoreActionCommand">
|
<el-dropdown @command="handleMoreActionCommand">
|
||||||
<span class="el-dropdown-link-more">
|
<span class="el-dropdown-link-more">
|
||||||
更多
|
更多
|
||||||
@@ -96,7 +76,7 @@
|
|||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
|
<el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
|
||||||
|
|
||||||
<el-dropdown-item :command="{ type: 'edit', data }" v-if="actionBtns[perms.saveDb]"> 编辑 </el-dropdown-item>
|
<!-- <el-dropdown-item :command="{ type: 'edit', data }" v-if="actionBtns[perms.saveDb]"> 编辑 </el-dropdown-item> -->
|
||||||
|
|
||||||
<el-dropdown-item :command="{ type: 'dumpDb', data }" v-if="data.type == 'mysql'"> 导出 </el-dropdown-item>
|
<el-dropdown-item :command="{ type: 'dumpDb', data }" v-if="data.type == 'mysql'"> 导出 </el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
@@ -105,10 +85,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</page-table>
|
</page-table>
|
||||||
|
|
||||||
<el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
|
||||||
<db-table-list :db-id="dbId" :db="db" :db-type="state.row.type" />
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog width="620" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
|
<el-dialog width="620" :title="`${db} 数据库导出`" v-model="exportDialog.visible">
|
||||||
<el-row justify="space-between">
|
<el-row justify="space-between">
|
||||||
<el-col :span="9">
|
<el-col :span="9">
|
||||||
@@ -190,7 +166,6 @@ import { dbApi } from './api';
|
|||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { joinClientParams } from '@/common/request';
|
import { joinClientParams } from '@/common/request';
|
||||||
import { isTrue } from '@/common/assert';
|
import { isTrue } from '@/common/assert';
|
||||||
import { Search as SearchIcon } from '@element-plus/icons-vue';
|
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
import TagInfo from '../component/TagInfo.vue';
|
import TagInfo from '../component/TagInfo.vue';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
@@ -199,7 +174,6 @@ import { hasPerms } from '@/components/auth/auth';
|
|||||||
import DbSqlExecLog from './DbSqlExecLog.vue';
|
import DbSqlExecLog from './DbSqlExecLog.vue';
|
||||||
|
|
||||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||||
const DbTableList = defineAsyncComponent(() => import('./table/DbTableList.vue'));
|
|
||||||
|
|
||||||
const perms = {
|
const perms = {
|
||||||
base: 'db',
|
base: 'db',
|
||||||
@@ -254,13 +228,6 @@ const state = reactive({
|
|||||||
instanceId: 0,
|
instanceId: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
showDumpInfo: false,
|
|
||||||
dumpInfo: {
|
|
||||||
id: 0,
|
|
||||||
db: '',
|
|
||||||
type: 3,
|
|
||||||
tables: [],
|
|
||||||
},
|
|
||||||
// sql执行记录弹框
|
// sql执行记录弹框
|
||||||
sqlExecLogDialog: {
|
sqlExecLogDialog: {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -268,10 +235,6 @@ const state = reactive({
|
|||||||
dbs: [],
|
dbs: [],
|
||||||
dbId: 0,
|
dbId: 0,
|
||||||
},
|
},
|
||||||
chooseTableName: '',
|
|
||||||
tableInfoDialog: {
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
exportDialog: {
|
exportDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
dbId: 0,
|
dbId: 0,
|
||||||
@@ -293,8 +256,7 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dbId, db, tags, selectionData, query, datas, total, infoDialog, sqlExecLogDialog, tableInfoDialog, exportDialog, dbEditDialog, filterDb } =
|
const { db, tags, selectionData, query, datas, total, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog } = toRefs(state);
|
||||||
toRefs(state);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (Object.keys(actionBtns).length > 0) {
|
if (Object.keys(actionBtns).length > 0) {
|
||||||
@@ -449,36 +411,6 @@ const dumpDbs = () => {
|
|||||||
a.click();
|
a.click();
|
||||||
state.exportDialog.visible = false;
|
state.exportDialog.visible = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showTableInfo = async (row: any, db: string) => {
|
|
||||||
state.dbId = row.id;
|
|
||||||
state.row = row;
|
|
||||||
state.db = db;
|
|
||||||
state.tableInfoDialog.visible = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeTableInfo = () => {
|
|
||||||
state.showDumpInfo = false;
|
|
||||||
state.tableInfoDialog.visible = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 点击查看时初始化数据
|
|
||||||
const selectDb = (row: any) => {
|
|
||||||
state.filterDb.param = '';
|
|
||||||
state.filterDb.cache = row;
|
|
||||||
state.filterDb.list = row;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 输入字符过滤schema
|
|
||||||
const filterSchema = () => {
|
|
||||||
if (state.filterDb.param) {
|
|
||||||
state.filterDb.list = state.filterDb.cache.filter((a) => {
|
|
||||||
return String(a).toLowerCase().indexOf(state.filterDb.param) > -1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
state.filterDb.list = state.filterDb.cache;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.el-dropdown-link-more {
|
.el-dropdown-link-more {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, toRefs,watch, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
|
import { toRefs, watch, reactive, onMounted } from 'vue';
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { DbSqlExecTypeEnum } from './enums';
|
import { DbSqlExecTypeEnum } from './enums';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
@@ -103,13 +103,12 @@ onMounted(async () => {
|
|||||||
searchSqlExecLog();
|
searchSqlExecLog();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(props, async (newValue: any) => {
|
watch(props, async () => {
|
||||||
await searchSqlExecLog();
|
await searchSqlExecLog();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const searchSqlExecLog = async () => {
|
const searchSqlExecLog = async () => {
|
||||||
state.query.dbId = props.dbId
|
state.query.dbId = props.dbId;
|
||||||
const res = await dbApi.getSqlExecs.request(state.query);
|
const res = await dbApi.getSqlExecs.request(state.query);
|
||||||
state.data = res.list;
|
state.data = res.list;
|
||||||
state.total = res.total;
|
state.total = res.total;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
|
||||||
<el-button @click="cancel()">取 消</el-button>
|
<el-button @click="cancel()">取 消</el-button>
|
||||||
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,6 +154,7 @@ const state = reactive({
|
|||||||
// 原用户名
|
// 原用户名
|
||||||
oldUserName: null,
|
oldUserName: null,
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
|
testConnBtnLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dialogVisible, tabActiveName, form, pwd, btnLoading } = toRefs(state);
|
const { dialogVisible, tabActiveName, form, pwd, btnLoading } = toRefs(state);
|
||||||
@@ -176,6 +178,32 @@ const getDbPwd = async () => {
|
|||||||
state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
|
state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getReqForm = async () => {
|
||||||
|
const reqForm = { ...state.form };
|
||||||
|
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||||
|
if (!state.form.sshTunnelMachineId) {
|
||||||
|
reqForm.sshTunnelMachineId = -1;
|
||||||
|
}
|
||||||
|
return reqForm;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConn = async () => {
|
||||||
|
dbForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
state.testConnBtnLoading = true;
|
||||||
|
try {
|
||||||
|
await dbApi.testConn.request(await getReqForm());
|
||||||
|
ElMessage.success('连接成功');
|
||||||
|
} finally {
|
||||||
|
state.testConnBtnLoading = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请正确填写信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const btnOk = async () => {
|
const btnOk = async () => {
|
||||||
if (!state.form.id) {
|
if (!state.form.id) {
|
||||||
notBlank(state.form.password, '新增操作,密码不可为空');
|
notBlank(state.form.password, '新增操作,密码不可为空');
|
||||||
@@ -185,12 +213,7 @@ const btnOk = async () => {
|
|||||||
|
|
||||||
dbForm.value.validate(async (valid: boolean) => {
|
dbForm.value.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const reqForm = { ...state.form };
|
dbApi.saveInstance.request(await getReqForm()).then(() => {
|
||||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
|
||||||
if (!state.form.sshTunnelMachineId) {
|
|
||||||
reqForm.sshTunnelMachineId = -1;
|
|
||||||
}
|
|
||||||
dbApi.saveInstance.request(reqForm).then(() => {
|
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const columns = ref([
|
|||||||
|
|
||||||
// 该用户拥有的的操作列按钮权限
|
// 该用户拥有的的操作列按钮权限
|
||||||
const actionBtns = hasPerms([perms.saveInstance]);
|
const actionBtns = hasPerms([perms.saveInstance]);
|
||||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(65).fixedRight().alignCenter();
|
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
|
||||||
|
|
||||||
const pageTableRef: any = ref(null);
|
const pageTableRef: any = ref(null);
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<tag-tree ref="tagTreeRef" :loadTags="loadTags" @current-contextmenu-click="onCurrentContextmenuClick" :height="state.tagTreeHeight">
|
<tag-tree ref="tagTreeRef" :loadTags="loadTags" @current-contextmenu-click="onCurrentContextmenuClick" :height="state.tagTreeHeight">
|
||||||
<template #prefix="{ data }">
|
<template #prefix="{ data }">
|
||||||
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
<span v-if="data.type.value == SqlExecNodeType.DbInst">
|
||||||
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="210">
|
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
|
<SvgIcon v-if="data.params.type === 'mysql'" name="iconfont icon-op-mysql" :size="18" />
|
||||||
<SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
|
<SvgIcon v-if="data.params.type === 'postgres'" name="iconfont icon-op-postgres" :size="18" />
|
||||||
@@ -42,32 +42,31 @@
|
|||||||
<SvgIcon name="InfoFilled" v-else />
|
<SvgIcon name="InfoFilled" v-else />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-form class="instances-pop-form" label-width="auto" :size="'small'">
|
<el-descriptions :column="1" size="small">
|
||||||
<el-form-item label="类型:">{{ data.params.type }}</el-form-item>
|
<el-descriptions-item label="名称">
|
||||||
<el-form-item label="host:">{{ `${data.params.host}:${data.params.port}` }}</el-form-item>
|
{{ data.params.name }}
|
||||||
<el-form-item label="user:">{{ data.params.username }}</el-form-item>
|
</el-descriptions-item>
|
||||||
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
|
<el-descriptions-item label="host">
|
||||||
<el-form-item v-if="data.params.remark" label="备注:">{{ data.params.remark }}</el-form-item>
|
{{ `${data.params.host}:${data.params.port}` }}
|
||||||
</el-form>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="user">
|
||||||
|
{{ data.params.username }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注">
|
||||||
|
{{ data.params.remark }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<SvgIcon v-if="data.type.value == SqlExecNodeType.Db" name="Coin" color="#67c23a" />
|
<SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<SvgIcon name="Calendar" v-if="data.type.value == SqlExecNodeType.TableMenu" color="#409eff" />
|
<template #label="{ data }">
|
||||||
|
<el-tooltip placement="left" :show-after="1000" v-if="data.type.value == SqlExecNodeType.Table" :content="data.params.tableComment">
|
||||||
<el-tooltip
|
{{ data.label }}
|
||||||
:show-after="500"
|
|
||||||
v-if="data.type.value == SqlExecNodeType.Table"
|
|
||||||
effect="customized"
|
|
||||||
:content="data.params.tableComment"
|
|
||||||
placement="top-end"
|
|
||||||
>
|
|
||||||
<SvgIcon name="Calendar" color="#409eff" />
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<SvgIcon name="Files" v-if="data.type.value == SqlExecNodeType.SqlMenu || data.type.value == SqlExecNodeType.Sql" color="#f56c6c" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #suffix="{ data }">
|
<template #suffix="{ data }">
|
||||||
@@ -81,32 +80,61 @@
|
|||||||
|
|
||||||
<el-col :span="20">
|
<el-col :span="20">
|
||||||
<el-container id="data-exec" class="mt5 ml5">
|
<el-container id="data-exec" class="mt5 ml5">
|
||||||
<el-tabs @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%" v-model="state.activeName">
|
<el-tabs type="card" @tab-remove="onRemoveTab" @tab-change="onTabChange" style="width: 100%" v-model="state.activeName">
|
||||||
<el-tab-pane closable v-for="dt in state.tabs.values()" :key="dt.key" :label="dt.key" :name="dt.key">
|
<el-tab-pane closable v-for="dt in state.tabs.values()" :label="dt.label" :name="dt.key" :key="dt.key">
|
||||||
<table-data
|
<!-- <template #label>
|
||||||
v-if="dt.type === TabType.TableData"
|
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
|
||||||
@gen-insert-sql="onGenerateInsertSql"
|
<template #reference> {{ dt.label }} </template>
|
||||||
:data="dt"
|
<template #default>
|
||||||
:table-height="state.dataTabsTableHeight"
|
<el-descriptions :column="1" size="small">
|
||||||
></table-data>
|
<el-descriptions-item label="名称">
|
||||||
|
{{ dt.params.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="host">
|
||||||
|
{{ `${dt.params.host}:${dt.params.port}` }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="user">
|
||||||
|
{{ dt.params.username }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注">
|
||||||
|
{{ dt.params.remark }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template> -->
|
||||||
|
|
||||||
<query
|
<db-table-data-op
|
||||||
v-else
|
v-if="dt.type === TabType.TableData"
|
||||||
|
:db-id="dt.dbId"
|
||||||
|
:db-name="dt.db"
|
||||||
|
:table-name="dt.params.table"
|
||||||
|
:table-height="state.dataTabsTableHeight"
|
||||||
|
></db-table-data-op>
|
||||||
|
|
||||||
|
<db-sql-editor
|
||||||
|
v-if="dt.type === TabType.Query"
|
||||||
|
:db-id="dt.dbId"
|
||||||
|
:db-name="dt.db"
|
||||||
|
:sql-name="dt.params.sqlName"
|
||||||
@save-sql-success="reloadSqls"
|
@save-sql-success="reloadSqls"
|
||||||
@delete-sql-success="deleteSqlScript(dt)"
|
@delete-sql-success="deleteSqlScript(dt)"
|
||||||
:data="dt"
|
|
||||||
:editor-height="state.editorHeight"
|
:editor-height="state.editorHeight"
|
||||||
>
|
>
|
||||||
</query>
|
</db-sql-editor>
|
||||||
|
|
||||||
|
<db-tables-op
|
||||||
|
v-if="dt.type == TabType.TablesOp"
|
||||||
|
:db-id="dt.params.id"
|
||||||
|
:db="dt.params.db"
|
||||||
|
:db-type="dt.params.type"
|
||||||
|
:height="state.tablesOpHeight"
|
||||||
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL" width="1000px">
|
|
||||||
<el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -120,8 +148,10 @@ import TagTree from '../component/TagTree.vue';
|
|||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
|
import { dispposeCompletionItemProvider } from '../../../components/monaco/completionItemProvider';
|
||||||
|
|
||||||
const Query = defineAsyncComponent(() => import('./component/tab/Query.vue'));
|
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||||
const TableData = defineAsyncComponent(() => import('./component/tab/TableData.vue'));
|
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
||||||
|
const DbTablesOp = defineAsyncComponent(() => import('./component/table/DbTablesOp.vue'));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树节点类型
|
* 树节点类型
|
||||||
*/
|
*/
|
||||||
@@ -132,16 +162,40 @@ class SqlExecNodeType {
|
|||||||
static SqlMenu = 4;
|
static SqlMenu = 4;
|
||||||
static Table = 5;
|
static Table = 5;
|
||||||
static Sql = 6;
|
static Sql = 6;
|
||||||
|
static PgSchemaMenu = 7;
|
||||||
|
static PgSchema = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DbIcon = {
|
||||||
|
name: 'Coin',
|
||||||
|
color: '#67c23a',
|
||||||
|
};
|
||||||
|
|
||||||
|
// pgsql schema icon
|
||||||
|
const SchemaIcon = {
|
||||||
|
name: 'List',
|
||||||
|
color: '#67c23a',
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableIcon = {
|
||||||
|
name: 'Calendar',
|
||||||
|
color: '#409eff',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SqlIcon = {
|
||||||
|
name: 'Files',
|
||||||
|
color: '#f56c6c',
|
||||||
|
};
|
||||||
|
|
||||||
class ContextmenuClickId {
|
class ContextmenuClickId {
|
||||||
static ReloadTable = 0;
|
static ReloadTable = 0;
|
||||||
|
static TableOp = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// node节点点击时,触发改变db事件
|
// node节点点击时,触发改变db事件
|
||||||
const changeDb = (nodeData: TagTreeNode) => {
|
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
||||||
const params = nodeData.params;
|
const params = nodeData.params;
|
||||||
changeSchema({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.database }, params.db);
|
changeDb({ id: params.id, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.database }, params.db);
|
||||||
};
|
};
|
||||||
|
|
||||||
// tagpath 节点类型
|
// tagpath 节点类型
|
||||||
@@ -161,54 +215,93 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst)
|
|||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
const dbs = params.database.split(' ')?.sort();
|
const dbs = params.database.split(' ')?.sort();
|
||||||
return dbs.map((x: any) => {
|
return dbs.map((x: any) => {
|
||||||
return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb).withParams({
|
return new TagTreeNode(`${parentNode.key}.${x}`, x, NodeTypeDb)
|
||||||
tagPath: params.tagPath,
|
.withParams({
|
||||||
id: params.id,
|
tagPath: params.tagPath,
|
||||||
name: params.name,
|
id: params.id,
|
||||||
type: params.type,
|
name: params.name,
|
||||||
dbs: dbs,
|
type: params.type,
|
||||||
db: x,
|
dbs: dbs,
|
||||||
});
|
db: x,
|
||||||
|
})
|
||||||
|
.withIcon(DbIcon);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.withNodeClickFunc(changeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 数据库节点
|
// 数据库节点
|
||||||
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
|
if (params.type == 'postgres') {
|
||||||
|
return [new TagTreeNode(`${params.id}.${params.db}.schema-menu`, 'schema', NodeTypePostgresScheamMenu).withParams(params).withIcon(SchemaIcon)];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params),
|
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params),
|
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
.withNodeClickFunc(changeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 数据库表菜单节点
|
// postgres schema模式菜单
|
||||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
const NodeTypePostgresScheamMenu = new NodeType(SqlExecNodeType.PgSchemaMenu)
|
||||||
.withContextMenuItems([{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' }] as any)
|
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
const { id, db } = params;
|
const { id, db } = params;
|
||||||
|
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
||||||
|
return schemaNames.map((sn: any) => {
|
||||||
|
// 将db变更为 db/schema;
|
||||||
|
const nParams = { ...params };
|
||||||
|
nParams.schema = sn;
|
||||||
|
nParams.db = nParams.db + '/' + sn;
|
||||||
|
return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresScheam).withParams(nParams).withIcon(SchemaIcon);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
|
// postgres schema模式
|
||||||
|
const NodeTypePostgresScheam = new NodeType(SqlExecNodeType.PgSchema)
|
||||||
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
|
const params = parentNode.params;
|
||||||
|
return [
|
||||||
|
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||||
|
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
|
// 数据库表菜单节点
|
||||||
|
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||||
|
.withContextMenuItems([
|
||||||
|
{ contextMenuClickId: ContextmenuClickId.ReloadTable, txt: '刷新', icon: 'RefreshRight' },
|
||||||
|
{ contextMenuClickId: ContextmenuClickId.TableOp, txt: '表操作', icon: 'Setting' },
|
||||||
|
] as any)
|
||||||
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
|
const params = parentNode.params;
|
||||||
|
let { id, db } = params;
|
||||||
// 获取当前库的所有表信息
|
// 获取当前库的所有表信息
|
||||||
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
||||||
state.reloadStatus = false;
|
state.reloadStatus = false;
|
||||||
let dbTableSize = 0;
|
let dbTableSize = 0;
|
||||||
const tablesNode = tables.map((x: any) => {
|
const tablesNode = tables.map((x: any) => {
|
||||||
dbTableSize += x.dataLength + x.indexLength;
|
dbTableSize += x.dataLength + x.indexLength;
|
||||||
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeTypeTable).withIsLeaf(true).withParams({
|
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeTypeTable)
|
||||||
id,
|
.withIsLeaf(true)
|
||||||
db,
|
.withParams({
|
||||||
tableName: x.tableName,
|
id,
|
||||||
tableComment: x.tableComment,
|
db,
|
||||||
size: formatByteSize(x.dataLength + x.indexLength, 1),
|
tableName: x.tableName,
|
||||||
});
|
tableComment: x.tableComment,
|
||||||
|
size: formatByteSize(x.dataLength + x.indexLength, 1),
|
||||||
|
})
|
||||||
|
.withIcon(TableIcon);
|
||||||
});
|
});
|
||||||
// 设置父节点参数的表大小
|
// 设置父节点参数的表大小
|
||||||
parentNode.params.dbTableSize = formatByteSize(dbTableSize);
|
parentNode.params.dbTableSize = formatByteSize(dbTableSize);
|
||||||
return tablesNode;
|
return tablesNode;
|
||||||
})
|
})
|
||||||
.withNodeClickFunc(changeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 数据库sql模板菜单节点
|
// 数据库sql模板菜单节点
|
||||||
const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
|
const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
|
||||||
@@ -220,15 +313,18 @@ const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
|
|||||||
// 加载用户保存的sql脚本
|
// 加载用户保存的sql脚本
|
||||||
const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
|
const sqls = await dbApi.getSqlNames.request({ id: id, db: db });
|
||||||
return sqls.map((x: any) => {
|
return sqls.map((x: any) => {
|
||||||
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql).withIsLeaf(true).withParams({
|
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql)
|
||||||
id,
|
.withIsLeaf(true)
|
||||||
db,
|
.withParams({
|
||||||
dbs,
|
id,
|
||||||
sqlName: x.name,
|
db,
|
||||||
});
|
dbs,
|
||||||
|
sqlName: x.name,
|
||||||
|
})
|
||||||
|
.withIcon(SqlIcon);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.withNodeClickFunc(changeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 表节点类型
|
// 表节点类型
|
||||||
const NodeTypeTable = new NodeType(SqlExecNodeType.Table).withNodeClickFunc((nodeData: TagTreeNode) => {
|
const NodeTypeTable = new NodeType(SqlExecNodeType.Table).withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||||
@@ -256,11 +352,8 @@ const state = reactive({
|
|||||||
tabs,
|
tabs,
|
||||||
dataTabsTableHeight: '600',
|
dataTabsTableHeight: '600',
|
||||||
editorHeight: '600',
|
editorHeight: '600',
|
||||||
|
tablesOpHeight: '600',
|
||||||
tagTreeHeight: window.innerHeight - 178 + 'px',
|
tagTreeHeight: window.innerHeight - 178 + 'px',
|
||||||
genSqlDialog: {
|
|
||||||
visible: false,
|
|
||||||
sql: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { nowDbInst } = toRefs(state);
|
const { nowDbInst } = toRefs(state);
|
||||||
@@ -280,7 +373,8 @@ onBeforeUnmount(() => {
|
|||||||
*/
|
*/
|
||||||
const setHeight = () => {
|
const setHeight = () => {
|
||||||
state.editorHeight = window.innerHeight - 518 + 'px';
|
state.editorHeight = window.innerHeight - 518 + 'px';
|
||||||
state.dataTabsTableHeight = window.innerHeight - 256 + 'px';
|
state.dataTabsTableHeight = window.innerHeight - 262 + 'px';
|
||||||
|
state.tablesOpHeight = window.innerHeight - 240 + 'px';
|
||||||
state.tagTreeHeight = window.innerHeight - 165 + 'px';
|
state.tagTreeHeight = window.innerHeight - 165 + 'px';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -318,54 +412,62 @@ const onCurrentContextmenuClick = (clickData: any) => {
|
|||||||
const clickId = clickData.id;
|
const clickId = clickData.id;
|
||||||
if (clickId == ContextmenuClickId.ReloadTable) {
|
if (clickId == ContextmenuClickId.ReloadTable) {
|
||||||
reloadTables(clickData.item.key);
|
reloadTables(clickData.item.key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (clickId == ContextmenuClickId.TableOp) {
|
||||||
|
const params = clickData.item.params;
|
||||||
|
addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: clickData.item.key });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 选择数据库
|
// 选择数据库
|
||||||
const changeSchema = (inst: any, schema: string) => {
|
const changeDb = (db: any, dbName: string) => {
|
||||||
state.nowDbInst = DbInst.getOrNewInst(inst);
|
state.nowDbInst = DbInst.getOrNewInst(db);
|
||||||
state.db = schema;
|
state.db = dbName;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载选中的表数据,即新增表数据操作tab
|
// 加载选中的表数据,即新增表数据操作tab
|
||||||
const loadTableData = async (inst: any, schema: string, tableName: string) => {
|
const loadTableData = async (db: any, dbName: string, tableName: string) => {
|
||||||
changeSchema(inst, schema);
|
changeDb(db, dbName);
|
||||||
if (tableName == '') {
|
if (tableName == '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = `${inst.id}:\`${schema}\`.${tableName}`;
|
const key = `${db.id}:\`${dbName}\`.${tableName}`;
|
||||||
let tab = state.tabs.get(label);
|
let tab = state.tabs.get(key);
|
||||||
state.activeName = label;
|
state.activeName = key;
|
||||||
// 如果存在该表tab,则直接返回
|
// 如果存在该表tab,则直接返回
|
||||||
if (tab) {
|
if (tab) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tab = new TabInfo();
|
tab = new TabInfo();
|
||||||
tab.key = label;
|
tab.label = tableName;
|
||||||
tab.treeNodeKey = inst.nodeKey;
|
tab.key = key;
|
||||||
tab.dbId = inst.id;
|
tab.treeNodeKey = db.nodeKey;
|
||||||
tab.db = schema;
|
tab.dbId = db.id;
|
||||||
|
tab.db = dbName;
|
||||||
tab.type = TabType.TableData;
|
tab.type = TabType.TableData;
|
||||||
tab.params = {
|
tab.params = {
|
||||||
table: tableName,
|
table: tableName,
|
||||||
};
|
};
|
||||||
state.tabs.set(label, tab);
|
state.tabs.set(key, tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新建查询panel
|
// 新建查询tab
|
||||||
const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
const addQueryTab = async (db: any, dbName: string, sqlName: string = '') => {
|
||||||
if (!db || !inst.id) {
|
if (!dbName || !db.id) {
|
||||||
ElMessage.warning('请选择数据库实例及对应的schema');
|
ElMessage.warning('请选择数据库实例及对应的schema');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
changeSchema(inst, db);
|
changeDb(db, dbName);
|
||||||
|
|
||||||
const dbId = inst.id;
|
const dbId = db.id;
|
||||||
let label;
|
let label;
|
||||||
|
let key;
|
||||||
// 存在sql模板名,则该模板名只允许一个tab
|
// 存在sql模板名,则该模板名只允许一个tab
|
||||||
if (sqlName) {
|
if (sqlName) {
|
||||||
label = `查询:${dbId}:${db}.${sqlName}`;
|
label = `查询-${sqlName}`;
|
||||||
|
key = `查询:${dbId}:${dbName}.${sqlName}`;
|
||||||
} else {
|
} else {
|
||||||
let count = 1;
|
let count = 1;
|
||||||
state.tabs.forEach((v) => {
|
state.tabs.forEach((v) => {
|
||||||
@@ -373,29 +475,66 @@ const addQueryTab = async (inst: any, db: string, sqlName: string = '') => {
|
|||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
label = `新查询${count}:${dbId}:${db}`;
|
label = `新查询-${count}`;
|
||||||
|
key = `新查询${count}:${dbId}:${dbName}`;
|
||||||
}
|
}
|
||||||
state.activeName = label;
|
state.activeName = key;
|
||||||
let tab = state.tabs.get(label);
|
let tab = state.tabs.get(key);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tab = new TabInfo();
|
tab = new TabInfo();
|
||||||
tab.key = label;
|
tab.key = key;
|
||||||
tab.treeNodeKey = inst.nodeKey;
|
tab.label = label;
|
||||||
|
tab.treeNodeKey = db.nodeKey;
|
||||||
tab.dbId = dbId;
|
tab.dbId = dbId;
|
||||||
tab.db = db;
|
tab.db = dbName;
|
||||||
tab.type = TabType.Query;
|
tab.type = TabType.Query;
|
||||||
tab.params = {
|
tab.params = {
|
||||||
sqlName: sqlName,
|
sqlName: sqlName,
|
||||||
dbs: inst.dbs,
|
dbs: db.dbs,
|
||||||
};
|
};
|
||||||
state.tabs.set(label, tab);
|
state.tabs.set(key, tab);
|
||||||
|
|
||||||
// 注册当前sql编辑框提示词
|
// 注册当前sql编辑框提示词
|
||||||
registerDbCompletionItemProvider('sql', tab.dbId, tab.db, tab.params.dbs);
|
registerDbCompletionItemProvider('sql', tab.dbId, tab.db, tab.params.dbs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据操作tab
|
||||||
|
* @param inst
|
||||||
|
*/
|
||||||
|
const addTablesOpTab = async (db: any) => {
|
||||||
|
const dbName = db.db;
|
||||||
|
if (!db || !db.id) {
|
||||||
|
ElMessage.warning('请选择数据库实例及对应的schema');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
changeDb(db, dbName);
|
||||||
|
|
||||||
|
const dbId = db.id;
|
||||||
|
let key = `表操作:${dbId}:${dbName}.tablesOp`;
|
||||||
|
state.activeName = key;
|
||||||
|
|
||||||
|
let tab = state.tabs.get(key);
|
||||||
|
if (tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tab = new TabInfo();
|
||||||
|
tab.key = key;
|
||||||
|
tab.label = `表操作-${dbName}`;
|
||||||
|
tab.treeNodeKey = db.nodeKey;
|
||||||
|
tab.dbId = dbId;
|
||||||
|
tab.db = dbName;
|
||||||
|
tab.type = TabType.TablesOp;
|
||||||
|
tab.params = {
|
||||||
|
id: db.id,
|
||||||
|
db: dbName,
|
||||||
|
type: db.type,
|
||||||
|
};
|
||||||
|
state.tabs.set(key, tab);
|
||||||
|
};
|
||||||
|
|
||||||
const onRemoveTab = (targetName: string) => {
|
const onRemoveTab = (targetName: string) => {
|
||||||
let activeName = state.activeName;
|
let activeName = state.activeName;
|
||||||
const tabNames = [...state.tabs.keys()];
|
const tabNames = [...state.tabs.keys()];
|
||||||
@@ -412,6 +551,7 @@ const onRemoveTab = (targetName: string) => {
|
|||||||
}
|
}
|
||||||
state.tabs.delete(targetName);
|
state.tabs.delete(targetName);
|
||||||
state.activeName = activeName;
|
state.activeName = activeName;
|
||||||
|
onTabChange();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,11 +572,6 @@ const onTabChange = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onGenerateInsertSql = async (sql: string) => {
|
|
||||||
state.genSqlDialog.sql = sql;
|
|
||||||
state.genSqlDialog.visible = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reloadSqls = (dbId: number, db: string) => {
|
const reloadSqls = (dbId: number, db: string) => {
|
||||||
tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
|
tagTreeRef.value.reloadNode(getSqlMenuNodeKey(dbId, db));
|
||||||
};
|
};
|
||||||
@@ -467,10 +602,10 @@ const reloadTables = (nodeKey: string) => {
|
|||||||
min-height: calc(100vh - 155px);
|
min-height: calc(100vh - 155px);
|
||||||
|
|
||||||
.el-tabs__header {
|
.el-tabs__header {
|
||||||
margin: 0 0 5px;
|
margin: 0 0 10px;
|
||||||
|
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
||||||
padding: 0 5px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,11 +613,5 @@ const reloadTables = (nodeKey: string) => {
|
|||||||
.update_field_active {
|
.update_field_active {
|
||||||
background-color: var(--el-color-success);
|
background-color: var(--el-color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.instances-pop-form {
|
|
||||||
.el-form-item {
|
|
||||||
margin-bottom: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const dbApi = {
|
|||||||
tableIndex: Api.newGet('/dbs/{id}/t-index'),
|
tableIndex: Api.newGet('/dbs/{id}/t-index'),
|
||||||
tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
|
tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
|
||||||
columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
|
columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
|
||||||
|
pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
|
||||||
// 获取表即列提示
|
// 获取表即列提示
|
||||||
hintTables: Api.newGet('/dbs/{id}/hint-tables'),
|
hintTables: Api.newGet('/dbs/{id}/hint-tables'),
|
||||||
sqlExec: Api.newPost('/dbs/{id}/exec-sql'),
|
sqlExec: Api.newPost('/dbs/{id}/exec-sql'),
|
||||||
@@ -28,6 +29,7 @@ export const dbApi = {
|
|||||||
instances: Api.newGet('/instances'),
|
instances: Api.newGet('/instances'),
|
||||||
getInstance: Api.newGet('/instances/{instanceId}'),
|
getInstance: Api.newGet('/instances/{instanceId}'),
|
||||||
getAllDatabase: Api.newGet('/instances/{instanceId}/databases'),
|
getAllDatabase: Api.newGet('/instances/{instanceId}/databases'),
|
||||||
|
testConn: Api.newPost('/instances/test-conn'),
|
||||||
saveInstance: Api.newPost('/instances'),
|
saveInstance: Api.newPost('/instances'),
|
||||||
getInstancePwd: Api.newGet('/instances/{id}/pwd'),
|
getInstancePwd: Api.newGet('/instances/{id}/pwd'),
|
||||||
deleteInstance: Api.newDelete('/instances/{id}'),
|
deleteInstance: Api.newDelete('/instances/{id}'),
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonacoEditor ref="monacoEditorRef" class="mt5" v-model="state.sql" language="sql" :height="state.editorHeight" :id="'MonacoTextarea-' + ti.key" />
|
<MonacoEditor ref="monacoEditorRef" class="mt5" v-model="state.sql" language="sql" :height="state.editorHeight" :id="'MonacoTextarea-' + getKey()" />
|
||||||
|
|
||||||
<div class="editor-move-resize" @mousedown="onDragSetHeight">
|
<div class="editor-move-resize" @mousedown="onDragSetHeight">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
@@ -69,10 +69,10 @@
|
|||||||
<el-link type="warning" :underline="false" @click="cancelUpdateFields"><span style="font-size: 12px">取消</span></el-link>
|
<el-link type="warning" :underline="false" @click="cancelUpdateFields"><span style="font-size: 12px">取消</span></el-link>
|
||||||
</span>
|
</span>
|
||||||
</el-row>
|
</el-row>
|
||||||
<db-table
|
<db-table-data
|
||||||
ref="dbTableRef"
|
ref="dbTableRef"
|
||||||
:db-id="state.ti.dbId"
|
:db-id="dbId"
|
||||||
:db="state.ti.db"
|
:db="dbName"
|
||||||
:data="execRes.data"
|
:data="execRes.data"
|
||||||
:table="state.table"
|
:table="state.table"
|
||||||
:columns="execRes.tableColumn"
|
:columns="execRes.tableColumn"
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
|
||||||
@selection-change="onDataSelectionChange"
|
@selection-change="onDataSelectionChange"
|
||||||
@change-updated-field="changeUpdatedField"
|
@change-updated-field="changeUpdatedField"
|
||||||
></db-table>
|
></db-table-data>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -97,8 +97,8 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
|||||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
import { editor } from 'monaco-editor';
|
import { editor } from 'monaco-editor';
|
||||||
|
|
||||||
import DbTable from '../DbTable.vue';
|
import DbTableData from '@/views/ops/db/component/table/DbTableData.vue';
|
||||||
import { TabInfo } from '../../db';
|
import { DbInst } from '../../db';
|
||||||
import { exportCsv } from '@/common/utils/export';
|
import { exportCsv } from '@/common/utils/export';
|
||||||
import { dateStrFormat } from '@/common/utils/date';
|
import { dateStrFormat } from '@/common/utils/date';
|
||||||
import { dbApi } from '../../api';
|
import { dbApi } from '../../api';
|
||||||
@@ -113,15 +113,18 @@ import syssocket from '@/common/syssocket';
|
|||||||
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess']);
|
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess']);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
dbId: {
|
||||||
type: TabInfo,
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dbName: {
|
||||||
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
// sql脚本名,若有则去加载该sql内容
|
// sql脚本名,若有则去加载该sql内容
|
||||||
// sqlName: {
|
sqlName: {
|
||||||
// type: String,
|
type: String,
|
||||||
// default: '',
|
},
|
||||||
// },
|
|
||||||
editorHeight: {
|
editorHeight: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '600',
|
default: '600',
|
||||||
@@ -136,9 +139,6 @@ let monacoEditor: editor.IStandaloneCodeEditor;
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
token,
|
token,
|
||||||
ti: {} as TabInfo,
|
|
||||||
dbs: [],
|
|
||||||
dbId: null, // 当前选中操作的数据库实例
|
|
||||||
table: '', // 当前单表操作sql的表信息
|
table: '', // 当前单表操作sql的表信息
|
||||||
sqlName: '',
|
sqlName: '',
|
||||||
sql: '', // 当前编辑器的sql内容
|
sql: '', // 当前编辑器的sql内容
|
||||||
@@ -153,7 +153,7 @@ const state = reactive({
|
|||||||
hasUpdatedFileds: false,
|
hasUpdatedFileds: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { tableDataHeight, ti, execRes, table, sqlName, loading, hasUpdatedFileds } = toRefs(state);
|
const { tableDataHeight, execRes, table, loading, hasUpdatedFileds } = toRefs(state);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editorHeight,
|
() => props.editorHeight,
|
||||||
@@ -162,22 +162,22 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getNowDbInst = () => {
|
||||||
|
return DbInst.getInst(props.dbId);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('in query mounted');
|
console.log('in query mounted');
|
||||||
state.ti = props.data;
|
|
||||||
state.editorHeight = props.editorHeight;
|
state.editorHeight = props.editorHeight;
|
||||||
const params = state.ti.params;
|
|
||||||
state.dbs = params && params.dbs;
|
|
||||||
|
|
||||||
if (params && params.sqlName) {
|
if (props.sqlName) {
|
||||||
state.sqlName = params.sqlName;
|
const res = await dbApi.getSql.request({ id: props.dbId, type: 1, name: state.sqlName, db: props.dbName });
|
||||||
const res = await dbApi.getSql.request({ id: state.ti.dbId, type: 1, name: state.sqlName, db: state.ti.db });
|
|
||||||
state.sql = res.sql;
|
state.sql = res.sql;
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => initMonacoEditor(), 50);
|
setTimeout(() => initMonacoEditor(), 50);
|
||||||
});
|
});
|
||||||
await state.ti.getNowDbInst().loadDbHints(state.ti.db);
|
await getNowDbInst().loadDbHints(props.dbName);
|
||||||
});
|
});
|
||||||
|
|
||||||
const initMonacoEditor = () => {
|
const initMonacoEditor = () => {
|
||||||
@@ -186,7 +186,8 @@ const initMonacoEditor = () => {
|
|||||||
// 注册快捷键:ctrl + R 运行选中的sql
|
// 注册快捷键:ctrl + R 运行选中的sql
|
||||||
monacoEditor.addAction({
|
monacoEditor.addAction({
|
||||||
// An unique identifier of the contributed action.
|
// An unique identifier of the contributed action.
|
||||||
id: 'run-sql-action' + state.ti.key,
|
// id: 'run-sql-action' + state.ti.key,
|
||||||
|
id: 'run-sql-action' + getKey(),
|
||||||
// A label of the action that will be presented to the user.
|
// A label of the action that will be presented to the user.
|
||||||
label: '执行SQL',
|
label: '执行SQL',
|
||||||
// A precondition for this action.
|
// A precondition for this action.
|
||||||
@@ -213,7 +214,7 @@ const initMonacoEditor = () => {
|
|||||||
// 注册快捷键:ctrl + shift + f 格式化sql
|
// 注册快捷键:ctrl + shift + f 格式化sql
|
||||||
monacoEditor.addAction({
|
monacoEditor.addAction({
|
||||||
// An unique identifier of the contributed action.
|
// An unique identifier of the contributed action.
|
||||||
id: 'format-sql-action' + state.ti.key,
|
id: 'format-sql-action' + getKey(),
|
||||||
// A label of the action that will be presented to the user.
|
// A label of the action that will be presented to the user.
|
||||||
label: '格式化SQL',
|
label: '格式化SQL',
|
||||||
// A precondition for this action.
|
// A precondition for this action.
|
||||||
@@ -245,7 +246,7 @@ const onDragSetHeight = () => {
|
|||||||
document.onmousemove = (e) => {
|
document.onmousemove = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
//得到鼠标拖动的宽高距离:取绝对值
|
//得到鼠标拖动的宽高距离:取绝对值
|
||||||
state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.clientHeight + e.movementY}px`;
|
state.editorHeight = `${document.getElementById('MonacoTextarea-' + getKey())!.clientHeight + e.movementY}px`;
|
||||||
state.tableDataHeight -= e.movementY;
|
state.tableDataHeight -= e.movementY;
|
||||||
};
|
};
|
||||||
document.onmouseup = () => {
|
document.onmouseup = () => {
|
||||||
@@ -253,6 +254,10 @@ const onDragSetHeight = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getKey = () => {
|
||||||
|
return props.dbId + ':' + props.dbName;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行sql
|
* 执行sql
|
||||||
*/
|
*/
|
||||||
@@ -290,7 +295,7 @@ const onRunSql = async () => {
|
|||||||
try {
|
try {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
|
|
||||||
const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
|
const colAndData: any = await getNowDbInst().runSql(props.dbName, sql, execRemark);
|
||||||
if (!colAndData.res || colAndData.res.length === 0) {
|
if (!colAndData.res || colAndData.res.length === 0) {
|
||||||
ElMessage.warning('未查询到结果集');
|
ElMessage.warning('未查询到结果集');
|
||||||
}
|
}
|
||||||
@@ -370,16 +375,17 @@ const saveSql = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbApi.saveSql.request({ id: state.ti.dbId, db: state.ti.db, sql: sql, type: 1, name: sqlName });
|
await dbApi.saveSql.request({ id: props.dbId, db: props.dbName, sql: sql, type: 1, name: sqlName });
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
// 保存sql脚本成功事件
|
// 保存sql脚本成功事件
|
||||||
emits('saveSqlSuccess', state.ti.dbId, state.ti.db);
|
emits('saveSqlSuccess', props.dbId, props.dbName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSql = async () => {
|
const deleteSql = async () => {
|
||||||
const sqlName = state.sqlName;
|
const sqlName = state.sqlName;
|
||||||
notBlank(sqlName, '该sql内容未保存');
|
notBlank(sqlName, '该sql内容未保存');
|
||||||
const { dbId, db } = state.ti;
|
const dbId = props.dbId;
|
||||||
|
const db = props.dbName;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
|
await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -415,7 +421,7 @@ const formatSql = () => {
|
|||||||
* 提交事务,用于没有开启自动提交事务
|
* 提交事务,用于没有开启自动提交事务
|
||||||
*/
|
*/
|
||||||
const onCommit = () => {
|
const onCommit = () => {
|
||||||
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
|
getNowDbInst().runSql(props.dbName, 'COMMIT;');
|
||||||
ElMessage.success('COMMIT success');
|
ElMessage.success('COMMIT success');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -528,7 +534,7 @@ const execSqlFileSuccess = (res: any) => {
|
|||||||
|
|
||||||
// 获取sql文件上传执行url
|
// 获取sql文件上传执行url
|
||||||
const getUploadSqlFileUrl = () => {
|
const getUploadSqlFileUrl = () => {
|
||||||
return `${config.baseApiUrl}/dbs/${state.ti.dbId}/exec-sql-file?db=${state.ti.db}&${joinClientParams()}`;
|
return `${config.baseApiUrl}/dbs/${props.dbId}/exec-sql-file?db=${props.dbName}&${joinClientParams()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDataSelectionChange = (datas: []) => {
|
const onDataSelectionChange = (datas: []) => {
|
||||||
@@ -546,8 +552,8 @@ const changeUpdatedField = (updatedFields: []) => {
|
|||||||
const onDeleteData = async () => {
|
const onDeleteData = async () => {
|
||||||
const deleteDatas = state.selectionDatas;
|
const deleteDatas = state.selectionDatas;
|
||||||
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
||||||
const { db } = state.ti;
|
const db = props.dbName;
|
||||||
const dbInst = state.ti.getNowDbInst();
|
const dbInst = getNowDbInst();
|
||||||
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
||||||
const primaryKeyColumnName = primaryKey.columnName;
|
const primaryKeyColumnName = primaryKey.columnName;
|
||||||
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
|
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, ref, nextTick, reactive } from 'vue';
|
import { toRefs, ref, nextTick, reactive } from 'vue';
|
||||||
import { dbApi } from '../api';
|
import { dbApi } from '@/views/ops/db/api';
|
||||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
|
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
|
||||||
// import base style
|
// import base style
|
||||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
:sortable="sortable"
|
:sortable="sortable"
|
||||||
>
|
>
|
||||||
<template #header v-if="showColumnTip">
|
<template #header v-if="showColumnTip">
|
||||||
<el-tooltip :show-after="500" raw-content placement="top" effect="customized">
|
<el-tooltip :show-after="500" raw-content placement="top">
|
||||||
<template #content> {{ getColumnTip(item) }} </template>
|
<template #content> {{ getColumnTip(item) }} </template>
|
||||||
{{ item.columnName }}
|
{{ item.columnName }}
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch, reactive, toRefs } from 'vue';
|
import { onMounted, watch, reactive, toRefs } from 'vue';
|
||||||
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '../db';
|
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '@/views/ops/db/db';
|
||||||
|
|
||||||
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
|
||||||
|
|
||||||
@@ -94,12 +94,12 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<db-table
|
<db-table-data
|
||||||
ref="dbTableRef"
|
ref="dbTableRef"
|
||||||
:db-id="state.ti.dbId"
|
:db-id="dbId"
|
||||||
:db="state.ti.db"
|
:db="dbName"
|
||||||
:data="datas"
|
:data="datas"
|
||||||
:table="state.table"
|
:table="tableName"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:height="tableHeight"
|
:height="tableHeight"
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
@sort-change="(sort: any) => onTableSortChange(sort)"
|
@sort-change="(sort: any) => onTableSortChange(sort)"
|
||||||
@selection-change="onDataSelectionChange"
|
@selection-change="onDataSelectionChange"
|
||||||
@change-updated-field="changeUpdatedField"
|
@change-updated-field="changeUpdatedField"
|
||||||
></db-table>
|
></db-table-data>
|
||||||
|
|
||||||
<el-row type="flex" class="mt5" justify="center">
|
<el-row type="flex" class="mt5" justify="center">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@@ -177,26 +177,37 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog @close="state.genSqlDialog.visible = false" v-model="state.genSqlDialog.visible" title="SQL" width="1000px">
|
||||||
|
<el-input v-model="state.genSqlDialog.sql" type="textarea" rows="20" />
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
|
import { onMounted, watch, reactive, toRefs, ref, Ref, onUnmounted } from 'vue';
|
||||||
import { isTrue, notEmpty, notBlank } from '@/common/assert';
|
import { isTrue, notEmpty } from '@/common/assert';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import { DbInst, TabInfo } from '../../db';
|
import { DbInst } from '@/views/ops/db/db';
|
||||||
import { exportCsv } from '@/common/utils/export';
|
import { exportCsv } from '@/common/utils/export';
|
||||||
import { dateStrFormat } from '@/common/utils/date';
|
import { dateStrFormat } from '@/common/utils/date';
|
||||||
import DbTable from '../DbTable.vue';
|
import DbTableData from './DbTableData.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['genInsertSql']);
|
|
||||||
const dataForm: any = ref(null);
|
const dataForm: any = ref(null);
|
||||||
const conditionInputRef: any = ref();
|
const conditionInputRef: any = ref();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
dbId: {
|
||||||
type: TabInfo,
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dbName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
tableName: {
|
||||||
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
tableHeight: {
|
tableHeight: {
|
||||||
@@ -208,8 +219,6 @@ const props = defineProps({
|
|||||||
const dbTableRef = ref(null) as Ref;
|
const dbTableRef = ref(null) as Ref;
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
ti: {} as TabInfo,
|
|
||||||
table: '', // 当前的表名
|
|
||||||
datas: [],
|
datas: [],
|
||||||
sql: '', // 当前数据tab执行的sql
|
sql: '', // 当前数据tab执行的sql
|
||||||
orderBy: '',
|
orderBy: '',
|
||||||
@@ -237,6 +246,10 @@ const state = reactive({
|
|||||||
placeholder: '',
|
placeholder: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
|
genSqlDialog: {
|
||||||
|
visible: false,
|
||||||
|
sql: '',
|
||||||
|
},
|
||||||
tableHeight: '600',
|
tableHeight: '600',
|
||||||
hasUpdatedFileds: false,
|
hasUpdatedFileds: false,
|
||||||
});
|
});
|
||||||
@@ -250,14 +263,15 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getNowDbInst = () => {
|
||||||
|
return DbInst.getInst(props.dbId);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log('in table data mounted');
|
console.log('in table data mounted');
|
||||||
state.ti = props.data;
|
|
||||||
state.tableHeight = props.tableHeight;
|
state.tableHeight = props.tableHeight;
|
||||||
state.table = state.ti.params.table;
|
|
||||||
notBlank(state.table, 'TableData组件params.table信息不能为空');
|
|
||||||
|
|
||||||
const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
|
const columns = await getNowDbInst().loadColumns(props.dbName, props.tableName);
|
||||||
columns.forEach((x: any) => {
|
columns.forEach((x: any) => {
|
||||||
x.show = true;
|
x.show = true;
|
||||||
});
|
});
|
||||||
@@ -297,12 +311,13 @@ const pageChange = async () => {
|
|||||||
*/
|
*/
|
||||||
const selectData = async () => {
|
const selectData = async () => {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
const dbInst = state.ti.getNowDbInst();
|
const dbInst = getNowDbInst();
|
||||||
const { db } = state.ti;
|
const db = props.dbName;
|
||||||
|
const table = props.tableName;
|
||||||
try {
|
try {
|
||||||
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(state.table, state.condition));
|
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
|
||||||
state.count = countRes.res[0].count;
|
state.count = countRes.res[0].count;
|
||||||
let sql = dbInst.getDefaultSelectSql(state.table, state.condition, state.orderBy, state.pageNum, state.pageSize);
|
let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
|
||||||
state.sql = sql;
|
state.sql = sql;
|
||||||
if (state.count > 0) {
|
if (state.count > 0) {
|
||||||
const colAndData: any = await dbInst.runSql(db, sql);
|
const colAndData: any = await dbInst.runSql(db, sql);
|
||||||
@@ -333,7 +348,7 @@ const exportData = () => {
|
|||||||
columnNames.push(column.columnName);
|
columnNames.push(column.columnName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
exportCsv(`数据导出-${props.tableName}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -376,7 +391,7 @@ const onCancelCondition = () => {
|
|||||||
* 提交事务,用于没有开启自动提交事务
|
* 提交事务,用于没有开启自动提交事务
|
||||||
*/
|
*/
|
||||||
const onCommit = () => {
|
const onCommit = () => {
|
||||||
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
|
getNowDbInst().runSql(props.dbName, 'COMMIT;');
|
||||||
ElMessage.success('COMMIT success');
|
ElMessage.success('COMMIT success');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -413,16 +428,17 @@ const changeUpdatedField = (updatedFields: []) => {
|
|||||||
const onDeleteData = async () => {
|
const onDeleteData = async () => {
|
||||||
const deleteDatas = state.selectionDatas;
|
const deleteDatas = state.selectionDatas;
|
||||||
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
||||||
const { db } = state.ti;
|
const db = props.dbName;
|
||||||
const dbInst = state.ti.getNowDbInst();
|
const dbInst = getNowDbInst();
|
||||||
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
|
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, props.tableName, deleteDatas), null, () => {
|
||||||
onRefresh();
|
onRefresh();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onGenerateInsertSql = async () => {
|
const onGenerateInsertSql = async () => {
|
||||||
isTrue(state.selectionDatas && state.selectionDatas.length > 0, '请先选择数据');
|
isTrue(state.selectionDatas && state.selectionDatas.length > 0, '请先选择数据');
|
||||||
emits('genInsertSql', state.ti.getNowDbInst().genInsertSql(state.ti.db, state.table, state.selectionDatas));
|
state.genSqlDialog.sql = getNowDbInst().genInsertSql(props.dbName, props.tableName, state.selectionDatas);
|
||||||
|
state.genSqlDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitUpdateFields = () => {
|
const submitUpdateFields = () => {
|
||||||
@@ -434,7 +450,7 @@ const cancelUpdateFields = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onShowAddDataDialog = async () => {
|
const onShowAddDataDialog = async () => {
|
||||||
state.addDataDialog.title = `添加'${state.table}'表数据`;
|
state.addDataDialog.title = `添加'${props.tableName}'表数据`;
|
||||||
state.addDataDialog.visible = true;
|
state.addDataDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -447,7 +463,7 @@ const closeAddDataDialog = () => {
|
|||||||
const addRow = async () => {
|
const addRow = async () => {
|
||||||
dataForm.value.validate(async (valid: boolean) => {
|
dataForm.value.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const dbInst = state.ti.getNowDbInst();
|
const dbInst = getNowDbInst();
|
||||||
const data = state.addDataDialog.data;
|
const data = state.addDataDialog.data;
|
||||||
// key: 字段名,value: 字段名提示
|
// key: 字段名,value: 字段名提示
|
||||||
let obj: any = {};
|
let obj: any = {};
|
||||||
@@ -460,8 +476,8 @@ const addRow = async () => {
|
|||||||
}
|
}
|
||||||
let columnNames = Object.keys(obj).join(',');
|
let columnNames = Object.keys(obj).join(',');
|
||||||
let values = Object.values(obj).join(',');
|
let values = Object.values(obj).join(',');
|
||||||
let sql = `INSERT INTO ${dbInst.wrapName(state.table)} (${columnNames}) VALUES (${values});`;
|
let sql = `INSERT INTO ${dbInst.wrapName(props.tableName)} (${columnNames}) VALUES (${values});`;
|
||||||
dbInst.promptExeSql(state.ti.db, sql, null, () => {
|
dbInst.promptExeSql(props.dbName, sql, null, () => {
|
||||||
closeAddDataDialog();
|
closeAddDataDialog();
|
||||||
onRefresh();
|
onRefresh();
|
||||||
});
|
});
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
import { watch, toRefs, reactive, ref } from 'vue';
|
import { watch, toRefs, reactive, ref } from 'vue';
|
||||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service';
|
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import SqlExecBox from '../component/SqlExecBox';
|
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" height="65vh">
|
<el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" :height="height">
|
||||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
<el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
|
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<db-table-edit
|
<db-table-op
|
||||||
:title="tableCreateDialog.title"
|
:title="tableCreateDialog.title"
|
||||||
:active-name="tableCreateDialog.activeName"
|
:active-name="tableCreateDialog.activeName"
|
||||||
:dbId="dbId"
|
:dbId="dbId"
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
v-model:visible="tableCreateDialog.visible"
|
v-model:visible="tableCreateDialog.visible"
|
||||||
@submit-sql="onSubmitSql"
|
@submit-sql="onSubmitSql"
|
||||||
>
|
>
|
||||||
</db-table-edit>
|
</db-table-op>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -121,15 +121,19 @@
|
|||||||
import { toRefs, reactive, watch, computed, onMounted, defineAsyncComponent } from 'vue';
|
import { toRefs, reactive, watch, computed, onMounted, defineAsyncComponent } from 'vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { formatByteSize } from '@/common/utils/format';
|
import { formatByteSize } from '@/common/utils/format';
|
||||||
import { dbApi } from '../api';
|
import { dbApi } from '@/views/ops/db/api';
|
||||||
import SqlExecBox from '../component/SqlExecBox';
|
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { joinClientParams } from '@/common/request';
|
import { joinClientParams } from '@/common/request';
|
||||||
import { isTrue } from '@/common/assert';
|
import { isTrue } from '@/common/assert';
|
||||||
|
|
||||||
const DbTableEdit = defineAsyncComponent(() => import('./DbTableEdit.vue'));
|
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
height: {
|
||||||
|
type: [String],
|
||||||
|
default: '65vh',
|
||||||
|
},
|
||||||
dbId: {
|
dbId: {
|
||||||
type: [Number],
|
type: [Number],
|
||||||
required: true,
|
required: true,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { getTextWidth } from '@/common/utils/string';
|
import { getTextWidth } from '@/common/utils/string';
|
||||||
import SqlExecBox from './component/SqlExecBox';
|
import SqlExecBox from './component/sqleditor/SqlExecBox';
|
||||||
|
|
||||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
|
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
|
||||||
import { language as addSqlLanguage } from './lang/mysql.js';
|
import { language as addSqlLanguage } from './lang/mysql.js';
|
||||||
@@ -39,11 +39,11 @@ export class DbInst {
|
|||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* schema -> db
|
* dbName -> db
|
||||||
*/
|
*/
|
||||||
dbs: Map<string, Db> = new Map();
|
dbs: Map<string, Db> = new Map();
|
||||||
|
|
||||||
/** 数据库schema,多个用空格隔开 */
|
/** 数据库,多个用空格隔开 */
|
||||||
databases: string;
|
databases: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,9 +281,9 @@ export class DbInst {
|
|||||||
if (this.type == 'mysql') {
|
if (this.type == 'mysql') {
|
||||||
return `\`${name}\``;
|
return `\`${name}\``;
|
||||||
}
|
}
|
||||||
if (this.type == 'postgres') {
|
// if (this.type == 'postgres') {
|
||||||
return `"${name}"`;
|
// return `"${name}"`;
|
||||||
}
|
// }
|
||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -451,11 +451,18 @@ export enum TabType {
|
|||||||
* 查询框
|
* 查询框
|
||||||
*/
|
*/
|
||||||
Query,
|
Query,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表操作
|
||||||
|
*/
|
||||||
|
TablesOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TabInfo {
|
export class TabInfo {
|
||||||
|
label: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tab唯一key。与label、name都一致
|
* tab唯一key。与name都一致
|
||||||
*/
|
*/
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
<template #action="{ data }">
|
<template #action="{ data }">
|
||||||
<span v-auth="'machine:terminal'">
|
<span v-auth="'machine:terminal'">
|
||||||
<el-tooltip :show-after="500" effect="customized" content="按住ctrl则为新标签打开" placement="top">
|
<el-tooltip :show-after="500" content="按住ctrl则为新标签打开" placement="top">
|
||||||
<el-button :disabled="data.status == -1" type="primary" @click="showTerminal(data, $event)" link>终端</el-button>
|
<el-button :disabled="data.status == -1" type="primary" @click="showTerminal(data, $event)" link>终端</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,19 @@
|
|||||||
<tag-tree :loadTags="loadTags">
|
<tag-tree :loadTags="loadTags">
|
||||||
<template #prefix="{ data }">
|
<template #prefix="{ data }">
|
||||||
<span v-if="data.type.value == MongoNodeType.Mongo">
|
<span v-if="data.type.value == MongoNodeType.Mongo">
|
||||||
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="210">
|
<el-popover :show-after="500" placement="right-start" title="mongo实例信息" trigger="hover" :width="250">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
|
<SvgIcon name="iconfont icon-op-mongo" :size="18" />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-form class="instances-pop-form" label-width="auto" :size="'small'">
|
<el-descriptions :column="1" size="small">
|
||||||
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
|
<el-descriptions-item label="名称">
|
||||||
<el-form-item label="链接:">{{ data.params.uri }}</el-form-item>
|
{{ data.params.name }}
|
||||||
</el-form>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="链接">
|
||||||
|
{{ data.params.uri }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
|
||||||
<el-button @click="cancel()">取 消</el-button>
|
<el-button @click="cancel()">取 消</el-button>
|
||||||
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +100,7 @@ const state = reactive({
|
|||||||
tagPath: null as any,
|
tagPath: null as any,
|
||||||
},
|
},
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
|
testConnBtnLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dialogVisible, tabActiveName, form, btnLoading } = toRefs(state);
|
const { dialogVisible, tabActiveName, form, btnLoading } = toRefs(state);
|
||||||
@@ -116,15 +118,35 @@ watch(props, async (newValue: any) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getReqForm = () => {
|
||||||
|
const reqForm = { ...state.form };
|
||||||
|
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||||
|
reqForm.sshTunnelMachineId = -1;
|
||||||
|
}
|
||||||
|
return reqForm;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConn = async () => {
|
||||||
|
mongoForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
state.testConnBtnLoading = true;
|
||||||
|
try {
|
||||||
|
await mongoApi.testConn.request(getReqForm());
|
||||||
|
ElMessage.success('连接成功');
|
||||||
|
} finally {
|
||||||
|
state.testConnBtnLoading = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请正确填写信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const btnOk = async () => {
|
const btnOk = async () => {
|
||||||
mongoForm.value.validate(async (valid: boolean) => {
|
mongoForm.value.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const reqForm = { ...state.form };
|
mongoApi.saveMongo.request(getReqForm).then(() => {
|
||||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
|
||||||
reqForm.sshTunnelMachineId = -1;
|
|
||||||
}
|
|
||||||
// reqForm.uri = await RsaEncrypt(reqForm.uri);
|
|
||||||
mongoApi.saveMongo.request(reqForm).then(() => {
|
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Api from '@/common/Api';
|
|||||||
export const mongoApi = {
|
export const mongoApi = {
|
||||||
mongoList: Api.newGet('/mongos'),
|
mongoList: Api.newGet('/mongos'),
|
||||||
mongoTags: Api.newGet('/mongos/tags'),
|
mongoTags: Api.newGet('/mongos/tags'),
|
||||||
|
testConn: Api.newPost('/mongos/test-conn'),
|
||||||
saveMongo: Api.newPost('/mongos'),
|
saveMongo: Api.newPost('/mongos'),
|
||||||
deleteMongo: Api.newDelete('/mongos/{id}'),
|
deleteMongo: Api.newDelete('/mongos/{id}'),
|
||||||
databases: Api.newGet('/mongos/{id}/databases'),
|
databases: Api.newGet('/mongos/{id}/databases'),
|
||||||
|
|||||||
@@ -7,17 +7,25 @@
|
|||||||
<tag-tree :loadTags="loadTags">
|
<tag-tree :loadTags="loadTags">
|
||||||
<template #prefix="{ data }">
|
<template #prefix="{ data }">
|
||||||
<span v-if="data.type.value == RedisNodeType.Redis">
|
<span v-if="data.type.value == RedisNodeType.Redis">
|
||||||
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="210">
|
<el-popover :show-after="500" placement="right-start" title="redis实例信息" trigger="hover" :width="250">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<SvgIcon name="iconfont icon-op-redis" :size="18" />
|
<SvgIcon name="iconfont icon-op-redis" :size="18" />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-form class="instances-pop-form" label-width="auto" :size="'small'">
|
<el-descriptions :column="1" size="small">
|
||||||
<el-form-item label="名称:">{{ data.params.name }}</el-form-item>
|
<el-descriptions-item label="名称">
|
||||||
<el-form-item label="模式:">{{ data.params.mode }}</el-form-item>
|
{{ data.params.name }}
|
||||||
<el-form-item label="链接:">{{ data.params.host }}</el-form-item>
|
</el-descriptions-item>
|
||||||
<el-form-item label="备注:">{{ data.params.remark }}</el-form-item>
|
<el-descriptions-item label="模式">
|
||||||
</el-form>
|
{{ data.params.mode }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="host">
|
||||||
|
{{ data.params.host }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" label-align="right">
|
||||||
|
{{ data.params.remark }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</span>
|
</span>
|
||||||
@@ -615,12 +623,6 @@ const delKey = (key: string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.instances-pop-form {
|
|
||||||
.el-form-item {
|
|
||||||
margin-bottom: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-list-vtree {
|
.key-list-vtree {
|
||||||
height: calc(100vh - 250px);
|
height: calc(100vh - 250px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
|
||||||
<el-button @click="cancel()">取 消</el-button>
|
<el-button @click="cancel()">取 消</el-button>
|
||||||
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,6 +162,7 @@ const state = reactive({
|
|||||||
dbList: [0],
|
dbList: [0],
|
||||||
pwd: '',
|
pwd: '',
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
|
testConnBtnLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dialogVisible, tabActiveName, form, dbList, pwd, btnLoading } = toRefs(state);
|
const { dialogVisible, tabActiveName, form, dbList, pwd, btnLoading } = toRefs(state);
|
||||||
@@ -195,19 +197,40 @@ const getPwd = async () => {
|
|||||||
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
|
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getReqForm = async () => {
|
||||||
|
const reqForm = { ...state.form };
|
||||||
|
if (reqForm.mode == 'sentinel' && reqForm.host.split('=').length != 2) {
|
||||||
|
ElMessage.error('sentinel模式host需为: mastername=sentinelhost:sentinelport模式');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||||
|
reqForm.sshTunnelMachineId = -1;
|
||||||
|
}
|
||||||
|
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||||
|
return reqForm;
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConn = async () => {
|
||||||
|
redisForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
state.testConnBtnLoading = true;
|
||||||
|
try {
|
||||||
|
await redisApi.testConn.request(await getReqForm());
|
||||||
|
ElMessage.success('连接成功');
|
||||||
|
} finally {
|
||||||
|
state.testConnBtnLoading = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请正确填写信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const btnOk = async () => {
|
const btnOk = async () => {
|
||||||
redisForm.value.validate(async (valid: boolean) => {
|
redisForm.value.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const reqForm = { ...state.form };
|
redisApi.saveRedis.request(await getReqForm()).then(() => {
|
||||||
if (reqForm.mode == 'sentinel' && reqForm.host.split('=').length != 2) {
|
|
||||||
ElMessage.error('sentinel模式host需为: mastername=sentinelhost:sentinelport模式');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
|
||||||
reqForm.sshTunnelMachineId = -1;
|
|
||||||
}
|
|
||||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
|
||||||
redisApi.saveRedis.request(reqForm).then(() => {
|
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const redisApi = {
|
|||||||
getRedisPwd: Api.newGet('/redis/{id}/pwd'),
|
getRedisPwd: Api.newGet('/redis/{id}/pwd'),
|
||||||
redisInfo: Api.newGet('/redis/{id}/info'),
|
redisInfo: Api.newGet('/redis/{id}/info'),
|
||||||
clusterInfo: Api.newGet('/redis/{id}/cluster-info'),
|
clusterInfo: Api.newGet('/redis/{id}/cluster-info'),
|
||||||
|
testConn: Api.newPost('/redis/test-conn'),
|
||||||
saveRedis: Api.newPost('/redis'),
|
saveRedis: Api.newPost('/redis'),
|
||||||
delRedis: Api.newDelete('/redis/{id}'),
|
delRedis: Api.newDelete('/redis/{id}'),
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ server:
|
|||||||
# debug release test
|
# debug release test
|
||||||
model: release
|
model: release
|
||||||
port: 18888
|
port: 18888
|
||||||
|
# 上下文路径, 若设置了该值, 则请求地址为ip:port/context-path
|
||||||
|
# context-path: /mayfly
|
||||||
cors: true
|
cors: true
|
||||||
tls:
|
tls:
|
||||||
enable: false
|
enable: false
|
||||||
key-file: ./default.key
|
key-file: ./default.key
|
||||||
cert-file: ./default.pem
|
cert-file: ./default.pem
|
||||||
# 机器终端操作回放文件存储路径
|
|
||||||
machine-rec-path: ./rec
|
|
||||||
jwt:
|
jwt:
|
||||||
# jwt key,不设置默认使用随机字符串
|
# jwt key,不设置默认使用随机字符串
|
||||||
key:
|
key:
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func InitRouter() *gin.Engine {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 设置静态资源
|
// 设置静态资源
|
||||||
setStatic(router)
|
setStatic(serverConfig.ContextPath, router)
|
||||||
|
|
||||||
// 是否允许跨域
|
// 是否允许跨域
|
||||||
if serverConfig.Cors {
|
if serverConfig.Cors {
|
||||||
@@ -42,7 +42,7 @@ func InitRouter() *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置路由组
|
// 设置路由组
|
||||||
api := router.Group("/api")
|
api := router.Group(serverConfig.ContextPath + "/api")
|
||||||
{
|
{
|
||||||
common_router.Init(api)
|
common_router.Init(api)
|
||||||
|
|
||||||
@@ -61,16 +61,17 @@ func InitRouter() *gin.Engine {
|
|||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
func setStatic(router *gin.Engine) {
|
func setStatic(contextPath string, router *gin.Engine) {
|
||||||
// 使用embed打包静态资源至二进制文件中
|
// 使用embed打包静态资源至二进制文件中
|
||||||
fsys, _ := fs.Sub(static.Static, "static")
|
fsys, _ := fs.Sub(static.Static, "static")
|
||||||
fileServer := http.FileServer(http.FS(fsys))
|
fileServer := http.FileServer(http.FS(fsys))
|
||||||
handler := WrapStaticHandler(fileServer)
|
handler := WrapStaticHandler(http.StripPrefix(contextPath, fileServer))
|
||||||
router.GET("/", handler)
|
|
||||||
router.GET("/favicon.ico", handler)
|
router.GET(contextPath+"/", handler)
|
||||||
router.GET("/config.js", handler)
|
router.GET(contextPath+"/favicon.ico", handler)
|
||||||
|
router.GET(contextPath+"/config.js", handler)
|
||||||
// 所有/assets/**开头的都是静态资源文件
|
// 所有/assets/**开头的都是静态资源文件
|
||||||
router.GET("/assets/*file", handler)
|
router.GET(contextPath+"/assets/*file", handler)
|
||||||
|
|
||||||
// 设置静态资源
|
// 设置静态资源
|
||||||
if staticConfs := config.Conf.Server.Static; staticConfs != nil {
|
if staticConfs := config.Conf.Server.Static; staticConfs != nil {
|
||||||
|
|||||||
@@ -445,6 +445,14 @@ func (d *Db) GetCreateTableDdl(rc *req.Ctx) {
|
|||||||
rc.ResData = res
|
rc.ResData = res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Db) GetPgsqlSchemas(rc *req.Ctx) {
|
||||||
|
conn := d.getDbConn(rc.GinCtx)
|
||||||
|
biz.IsTrue(conn.Info.Type == dbm.DbTypePostgres, "非postgres无法获取该schemas")
|
||||||
|
res, err := d.getDbConn(rc.GinCtx).GetMeta().(*dbm.PgsqlMetadata).GetSchemas()
|
||||||
|
biz.ErrIsNilAppendErr(err, "获取schemas失败: %s")
|
||||||
|
rc.ResData = res
|
||||||
|
}
|
||||||
|
|
||||||
func getDbId(g *gin.Context) uint64 {
|
func getDbId(g *gin.Context) uint64 {
|
||||||
dbId, _ := strconv.Atoi(g.Param("dbId"))
|
dbId, _ := strconv.Atoi(g.Param("dbId"))
|
||||||
biz.IsTrue(dbId > 0, "dbId错误")
|
biz.IsTrue(dbId > 0, "dbId错误")
|
||||||
|
|||||||
@@ -29,6 +29,18 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
|||||||
rc.ResData = res
|
rc.ResData = res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Instance) TestConn(rc *req.Ctx) {
|
||||||
|
form := &form.InstanceForm{}
|
||||||
|
instance := ginx.BindJsonAndCopyTo[*entity.DbInstance](rc.GinCtx, form, new(entity.DbInstance))
|
||||||
|
|
||||||
|
// 密码解密,并使用解密后的赋值
|
||||||
|
originPwd, err := cryptox.DefaultRsaDecrypt(form.Password, true)
|
||||||
|
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
|
||||||
|
instance.Password = originPwd
|
||||||
|
|
||||||
|
biz.ErrIsNil(d.InstanceApp.TestConn(instance))
|
||||||
|
}
|
||||||
|
|
||||||
// SaveInstance 保存数据库实例信息
|
// SaveInstance 保存数据库实例信息
|
||||||
// @router /api/instances [post]
|
// @router /api/instances [post]
|
||||||
func (d *Instance) SaveInstance(rc *req.Ctx) {
|
func (d *Instance) SaveInstance(rc *req.Ctx) {
|
||||||
|
|||||||
@@ -118,14 +118,24 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.NewBiz("数据库信息不存在")
|
return nil, errorx.NewBiz("数据库信息不存在")
|
||||||
}
|
}
|
||||||
if !strings.Contains(" "+db.Database+" ", " "+dbName+" ") {
|
|
||||||
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
|
|
||||||
}
|
|
||||||
|
|
||||||
instance, err := d.dbInstanceApp.GetById(new(entity.DbInstance), db.InstanceId)
|
instance, err := d.dbInstanceApp.GetById(new(entity.DbInstance), db.InstanceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.NewBiz("数据库实例不存在")
|
return nil, errorx.NewBiz("数据库实例不存在")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkDb := dbName
|
||||||
|
// 兼容pgsql db/schema模式
|
||||||
|
if instance.Type == dbm.DbTypePostgres {
|
||||||
|
ss := strings.Split(dbName, "/")
|
||||||
|
if len(ss) > 1 {
|
||||||
|
checkDb = ss[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") {
|
||||||
|
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
|
||||||
|
}
|
||||||
|
|
||||||
// 密码解密
|
// 密码解密
|
||||||
instance.PwdDecrypt()
|
instance.PwdDecrypt()
|
||||||
return toDbInfo(instance, dbId, dbName, db.TagPath), nil
|
return toDbInfo(instance, dbId, dbName, db.TagPath), nil
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type Instance interface {
|
|||||||
|
|
||||||
Count(condition *entity.InstanceQuery) int64
|
Count(condition *entity.InstanceQuery) int64
|
||||||
|
|
||||||
|
TestConn(instanceEntity *entity.DbInstance) error
|
||||||
|
|
||||||
Save(ctx context.Context, instanceEntity *entity.DbInstance) error
|
Save(ctx context.Context, instanceEntity *entity.DbInstance) error
|
||||||
|
|
||||||
// Delete 删除数据库信息
|
// Delete 删除数据库信息
|
||||||
@@ -45,19 +47,19 @@ func (app *instanceAppImpl) Count(condition *entity.InstanceQuery) int64 {
|
|||||||
return app.CountByCond(condition)
|
return app.CountByCond(condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance) error {
|
||||||
|
dbConn, err := toDbInfo(instanceEntity, 0, "", "").Conn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbConn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbInstance) error {
|
func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbInstance) error {
|
||||||
// 默认tcp连接
|
// 默认tcp连接
|
||||||
instanceEntity.Network = instanceEntity.GetNetwork()
|
instanceEntity.Network = instanceEntity.GetNetwork()
|
||||||
|
|
||||||
// 测试连接
|
|
||||||
if instanceEntity.Password != "" {
|
|
||||||
dbConn, err := toDbInfo(instanceEntity, 0, "", "").Conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dbConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找是否存在该库
|
// 查找是否存在该库
|
||||||
oldInstance := &entity.DbInstance{Host: instanceEntity.Host, Port: instanceEntity.Port, Username: instanceEntity.Username}
|
oldInstance := &entity.DbInstance{Host: instanceEntity.Host, Port: instanceEntity.Port, Username: instanceEntity.Username}
|
||||||
if instanceEntity.SshTunnelMachineId > 0 {
|
if instanceEntity.SshTunnelMachineId > 0 {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (dbType DbType) StmtSelectDbName() string {
|
|||||||
case DbTypeMysql:
|
case DbTypeMysql:
|
||||||
return "SELECT SCHEMA_NAME AS dbname FROM SCHEMATA"
|
return "SELECT SCHEMA_NAME AS dbname FROM SCHEMATA"
|
||||||
case DbTypePostgres:
|
case DbTypePostgres:
|
||||||
return "SELECT datname AS dbname FROM pg_database"
|
return "SELECT datname AS dbname FROM pg_database WHERE datistemplate = false AND has_database_privilege(datname, 'CONNECT')"
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("invalid database type: %s", dbType))
|
panic(fmt.Sprintf("invalid database type: %s", dbType))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
--PGSQL_DB_SCHEMAS 库schemas
|
||||||
|
select
|
||||||
|
n.nspname as "schemaName"
|
||||||
|
from
|
||||||
|
pg_namespace n
|
||||||
|
where
|
||||||
|
has_schema_privilege(n.nspname, 'USAGE')
|
||||||
|
and n.nspname not like 'pg_%'
|
||||||
|
and n.nspname != 'information_schema'
|
||||||
|
---------------------------------------
|
||||||
--PGSQL_TABLE_INFO 表详细信息
|
--PGSQL_TABLE_INFO 表详细信息
|
||||||
select
|
select
|
||||||
c.relname as "tableName",
|
c.relname as "tableName",
|
||||||
@@ -10,12 +20,10 @@ from
|
|||||||
join pg_namespace n on
|
join pg_namespace n on
|
||||||
c.relnamespace = n.oid
|
c.relnamespace = n.oid
|
||||||
join pg_stat_user_tables psut on
|
join pg_stat_user_tables psut on
|
||||||
psut.relid = c."oid"
|
psut.relid = c.oid
|
||||||
where
|
where
|
||||||
n.nspname = (
|
has_table_privilege(CAST(c.oid AS regclass), 'SELECT')
|
||||||
select
|
and n.nspname = current_schema()
|
||||||
current_schema ()
|
|
||||||
)
|
|
||||||
and c.reltype > 0
|
and c.reltype > 0
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
--PGSQL_INDEX_INFO 表索引信息
|
--PGSQL_INDEX_INFO 表索引信息
|
||||||
|
|||||||
@@ -29,13 +29,38 @@ func getPgsqlDB(d *DbInfo) (*sql.DB, error) {
|
|||||||
|
|
||||||
db := d.Database
|
db := d.Database
|
||||||
var dbParam string
|
var dbParam string
|
||||||
|
exsitSchema := false
|
||||||
if db != "" {
|
if db != "" {
|
||||||
dbParam = "dbname=" + db
|
// postgres database可以使用db/schema表示,方便连接指定schema, 若不存在schema则使用默认schema
|
||||||
|
ss := strings.Split(db, "/")
|
||||||
|
if len(ss) > 1 {
|
||||||
|
exsitSchema = true
|
||||||
|
dbParam = fmt.Sprintf("dbname=%s search_path=%s", ss[0], ss[len(ss)-1])
|
||||||
|
} else {
|
||||||
|
dbParam = "dbname=" + db
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s %s sslmode=disable", d.Host, d.Port, d.Username, d.Password, dbParam)
|
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s %s sslmode=disable", d.Host, d.Port, d.Username, d.Password, dbParam)
|
||||||
|
// 存在额外指定参数,则拼接该连接参数
|
||||||
if d.Params != "" {
|
if d.Params != "" {
|
||||||
|
// 存在指定的db,则需要将dbInstance配置中的parmas排除掉dbname和search_path
|
||||||
|
if db != "" {
|
||||||
|
paramArr := strings.Split(d.Params, "&")
|
||||||
|
paramArr = collx.ArrayRemoveFunc(paramArr, func(param string) bool {
|
||||||
|
if strings.HasPrefix(param, "dbname=") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if exsitSchema && strings.HasPrefix(param, "search_path") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
d.Params = strings.Join(paramArr, " ")
|
||||||
|
}
|
||||||
dsn = fmt.Sprintf("%s %s", dsn, strings.Join(strings.Split(d.Params, "&"), " "))
|
dsn = fmt.Sprintf("%s %s", dsn, strings.Join(strings.Split(d.Params, "&"), " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql.Open(driverName, dsn)
|
return sql.Open(driverName, dsn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +92,7 @@ func (pd *PqSqlDialer) DialTimeout(network, address string, timeout time.Duratio
|
|||||||
// ---------------------------------- pgsql元数据 -----------------------------------
|
// ---------------------------------- pgsql元数据 -----------------------------------
|
||||||
const (
|
const (
|
||||||
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
||||||
|
PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS"
|
||||||
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
||||||
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
||||||
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
||||||
@@ -192,3 +218,17 @@ func (pm *PgsqlMetadata) GetTableRecord(tableName string, pageNum, pageSize int)
|
|||||||
func (pm *PgsqlMetadata) WalkTableRecord(tableName string, walk func(record map[string]any, columns []string)) error {
|
func (pm *PgsqlMetadata) WalkTableRecord(tableName string, walk func(record map[string]any, columns []string)) error {
|
||||||
return pm.dc.WalkTableRecord(fmt.Sprintf("SELECT * FROM %s", tableName), walk)
|
return pm.dc.WalkTableRecord(fmt.Sprintf("SELECT * FROM %s", tableName), walk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取pgsql当前连接的库可访问的schemaNames
|
||||||
|
func (pm *PgsqlMetadata) GetSchemas() ([]string, error) {
|
||||||
|
sql := GetLocalSql(PGSQL_META_FILE, PGSQL_DB_SCHEMAS)
|
||||||
|
_, res, err := pm.dc.SelectData(sql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
schemaNames := make([]string, 0)
|
||||||
|
for _, re := range res {
|
||||||
|
schemaNames = append(schemaNames, anyx.ConvString(re["schemaName"]))
|
||||||
|
}
|
||||||
|
return schemaNames, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ func newDbSqlExecRepo() repository.DbSqlExec {
|
|||||||
|
|
||||||
// 分页获取
|
// 分页获取
|
||||||
func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
qd := gormx.NewQuery(new(entity.DbSqlExec)).WithCondModel(condition).WithOrderBy(orderBy...)
|
qd := gormx.NewQuery(new(entity.DbSqlExec)).
|
||||||
|
Eq("db_id", condition.DbId).
|
||||||
|
Eq("`table`", condition.Table).
|
||||||
|
Eq("type", condition.Type).
|
||||||
|
Eq("creator_id", condition.CreatorId).
|
||||||
|
RLike("db", condition.Db).WithOrderBy(orderBy...)
|
||||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ func InitDbRouter(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
req.NewGet(":dbId/t-create-ddl", d.GetCreateTableDdl),
|
req.NewGet(":dbId/t-create-ddl", d.GetCreateTableDdl),
|
||||||
|
|
||||||
|
req.NewGet(":dbId/pg/schemas", d.GetPgsqlSchemas),
|
||||||
|
|
||||||
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLog("db-执行Sql")),
|
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLog("db-执行Sql")),
|
||||||
|
|
||||||
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSave("db-执行Sql文件")),
|
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSave("db-执行Sql文件")),
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ func InitInstanceRouter(router *gin.RouterGroup) {
|
|||||||
// 获取数据库列表
|
// 获取数据库列表
|
||||||
req.NewGet("", d.Instances),
|
req.NewGet("", d.Instances),
|
||||||
|
|
||||||
|
req.NewPost("/test-conn", d.TestConn),
|
||||||
|
|
||||||
req.NewPost("", d.SaveInstance).Log(req.NewLogSave("db-保存数据库实例信息")),
|
req.NewPost("", d.SaveInstance).Log(req.NewLogSave("db-保存数据库实例信息")),
|
||||||
|
|
||||||
req.NewGet(":instanceId", d.GetInstance),
|
req.NewGet(":instanceId", d.GetInstance),
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"mayfly-go/internal/machine/api/form"
|
"mayfly-go/internal/machine/api/form"
|
||||||
"mayfly-go/internal/machine/api/vo"
|
"mayfly-go/internal/machine/api/vo"
|
||||||
"mayfly-go/internal/machine/application"
|
"mayfly-go/internal/machine/application"
|
||||||
|
"mayfly-go/internal/machine/config"
|
||||||
"mayfly-go/internal/machine/domain/entity"
|
"mayfly-go/internal/machine/domain/entity"
|
||||||
"mayfly-go/internal/machine/mcm"
|
"mayfly-go/internal/machine/mcm"
|
||||||
tagapp "mayfly-go/internal/tag/application"
|
tagapp "mayfly-go/internal/tag/application"
|
||||||
"mayfly-go/pkg/biz"
|
"mayfly-go/pkg/biz"
|
||||||
"mayfly-go/pkg/config"
|
|
||||||
"mayfly-go/pkg/errorx"
|
"mayfly-go/pkg/errorx"
|
||||||
"mayfly-go/pkg/ginx"
|
"mayfly-go/pkg/ginx"
|
||||||
"mayfly-go/pkg/model"
|
"mayfly-go/pkg/model"
|
||||||
@@ -189,7 +189,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
|
|||||||
if cli.Info.EnableRecorder == 1 {
|
if cli.Info.EnableRecorder == 1 {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
// 回放文件路径为: 基础配置路径/机器id/操作日期/操作者账号/操作时间.cast
|
// 回放文件路径为: 基础配置路径/机器id/操作日期/操作者账号/操作时间.cast
|
||||||
recPath := fmt.Sprintf("%s/%d/%s/%s", config.Conf.Server.GetMachineRecPath(), cli.Info.Id, now.Format("20060102"), rc.GetLoginAccount().Username)
|
recPath := fmt.Sprintf("%s/%d/%s/%s", config.GetMachine().TerminalRecPath, cli.Info.Id, now.Format("20060102"), rc.GetLoginAccount().Username)
|
||||||
os.MkdirAll(recPath, 0766)
|
os.MkdirAll(recPath, 0766)
|
||||||
fileName := path.Join(recPath, fmt.Sprintf("%s.cast", now.Format("20060102_150405")))
|
fileName := path.Join(recPath, fmt.Sprintf("%s.cast", now.Format("20060102_150405")))
|
||||||
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||||
@@ -214,7 +214,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
|
|||||||
func (m *Machine) MachineRecDirNames(rc *req.Ctx) {
|
func (m *Machine) MachineRecDirNames(rc *req.Ctx) {
|
||||||
readPath := rc.GinCtx.Query("path")
|
readPath := rc.GinCtx.Query("path")
|
||||||
biz.NotEmpty(readPath, "path不能为空")
|
biz.NotEmpty(readPath, "path不能为空")
|
||||||
path_ := path.Join(config.Conf.Server.GetMachineRecPath(), readPath)
|
path_ := path.Join(config.GetMachine().TerminalRecPath, readPath)
|
||||||
|
|
||||||
// 如果是读取文件内容,则读取对应回放记录文件内容,否则读取文件夹名列表。小小偷懒一会不想再加个接口
|
// 如果是读取文件内容,则读取对应回放记录文件内容,否则读取文件夹名列表。小小偷懒一会不想再加个接口
|
||||||
isFile := rc.GinCtx.Query("isFile")
|
isFile := rc.GinCtx.Query("isFile")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"mayfly-go/internal/machine/api/form"
|
"mayfly-go/internal/machine/api/form"
|
||||||
"mayfly-go/internal/machine/api/vo"
|
"mayfly-go/internal/machine/api/vo"
|
||||||
"mayfly-go/internal/machine/application"
|
"mayfly-go/internal/machine/application"
|
||||||
|
"mayfly-go/internal/machine/config"
|
||||||
"mayfly-go/internal/machine/domain/entity"
|
"mayfly-go/internal/machine/domain/entity"
|
||||||
"mayfly-go/internal/machine/mcm"
|
"mayfly-go/internal/machine/mcm"
|
||||||
msgapp "mayfly-go/internal/msg/application"
|
msgapp "mayfly-go/internal/msg/application"
|
||||||
@@ -175,8 +176,6 @@ func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
|
|||||||
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
|
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxUploadFileSize int64 = 1024 * 1024 * 1024
|
|
||||||
|
|
||||||
func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
||||||
g := rc.GinCtx
|
g := rc.GinCtx
|
||||||
fid := GetMachineFileId(g)
|
fid := GetMachineFileId(g)
|
||||||
@@ -184,7 +183,9 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
|
|||||||
|
|
||||||
fileheader, err := g.FormFile("file")
|
fileheader, err := g.FormFile("file")
|
||||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||||
biz.IsTrue(fileheader.Size <= MaxUploadFileSize, "文件大小不能超过%d字节", MaxUploadFileSize)
|
|
||||||
|
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
|
||||||
|
biz.IsTrue(fileheader.Size <= maxUploadFileSize, "文件大小不能超过%d字节", maxUploadFileSize)
|
||||||
|
|
||||||
file, _ := fileheader.Open()
|
file, _ := fileheader.Open()
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@@ -223,7 +224,9 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
|
|||||||
allFileSize := collx.ArrayReduce(fileheaders, 0, func(i int64, fh *multipart.FileHeader) int64 {
|
allFileSize := collx.ArrayReduce(fileheaders, 0, func(i int64, fh *multipart.FileHeader) int64 {
|
||||||
return i + fh.Size
|
return i + fh.Size
|
||||||
})
|
})
|
||||||
biz.IsTrue(allFileSize <= MaxUploadFileSize, "文件夹总大小不能超过%d字节", MaxUploadFileSize)
|
|
||||||
|
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
|
||||||
|
biz.IsTrue(allFileSize <= maxUploadFileSize, "文件夹总大小不能超过%d字节", maxUploadFileSize)
|
||||||
|
|
||||||
paths := mf.Value["paths"]
|
paths := mf.Value["paths"]
|
||||||
|
|
||||||
|
|||||||
43
server/internal/machine/config/config.go
Normal file
43
server/internal/machine/config/config.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
sysapp "mayfly-go/internal/sys/application"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/utils/bytex"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigKeyMachine string = "MachineConfig" // 机器相关配置
|
||||||
|
)
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
TerminalRecPath string // 终端操作记录存储位置
|
||||||
|
UploadMaxFileSize int64 // 允许上传的最大文件size
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取机器相关配置
|
||||||
|
func GetMachine() *Machine {
|
||||||
|
c := sysapp.GetConfigApp().GetConfig(ConfigKeyMachine)
|
||||||
|
jm := c.GetJsonMap()
|
||||||
|
|
||||||
|
mc := new(Machine)
|
||||||
|
|
||||||
|
terminalRecPath := jm["terminalRecPath"]
|
||||||
|
if terminalRecPath == "" {
|
||||||
|
terminalRecPath = "./rec"
|
||||||
|
}
|
||||||
|
mc.TerminalRecPath = terminalRecPath
|
||||||
|
|
||||||
|
// 将1GB等字符串转为int64的byte
|
||||||
|
uploadMaxFileSizeStr := jm["uploadMaxFileSize"]
|
||||||
|
var uploadMaxFileSize int64 = 1 * bytex.GB
|
||||||
|
if uploadMaxFileSizeStr != "" {
|
||||||
|
var err error
|
||||||
|
uploadMaxFileSize, err = bytex.ParseSize(uploadMaxFileSizeStr)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("解析机器配置的最大上传文件大小失败: uploadMaxFileSize=%s, 使用系统默认值1GB", uploadMaxFileSizeStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mc.UploadMaxFileSize = uploadMaxFileSize
|
||||||
|
return mc
|
||||||
|
}
|
||||||
@@ -46,6 +46,12 @@ func (m *Mongo) MongoTags(rc *req.Ctx) {
|
|||||||
rc.ResData = m.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Mongo))
|
rc.ResData = m.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Mongo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) TestConn(rc *req.Ctx) {
|
||||||
|
form := &form.Mongo{}
|
||||||
|
mongo := ginx.BindJsonAndCopyTo[*entity.Mongo](rc.GinCtx, form, new(entity.Mongo))
|
||||||
|
biz.ErrIsNilAppendErr(m.MongoApp.TestConn(mongo), "连接失败: %s")
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Mongo) Save(rc *req.Ctx) {
|
func (m *Mongo) Save(rc *req.Ctx) {
|
||||||
form := &form.Mongo{}
|
form := &form.Mongo{}
|
||||||
mongo := ginx.BindJsonAndCopyTo[*entity.Mongo](rc.GinCtx, form, new(entity.Mongo))
|
mongo := ginx.BindJsonAndCopyTo[*entity.Mongo](rc.GinCtx, form, new(entity.Mongo))
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type Mongo interface {
|
|||||||
|
|
||||||
Count(condition *entity.MongoQuery) int64
|
Count(condition *entity.MongoQuery) int64
|
||||||
|
|
||||||
|
TestConn(entity *entity.Mongo) error
|
||||||
|
|
||||||
Save(ctx context.Context, entity *entity.Mongo) error
|
Save(ctx context.Context, entity *entity.Mongo) error
|
||||||
|
|
||||||
// 删除数据库信息
|
// 删除数据库信息
|
||||||
@@ -52,6 +54,15 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
|
|||||||
return d.GetRepo().DeleteById(ctx, id)
|
return d.GetRepo().DeleteById(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *mongoAppImpl) TestConn(me *entity.Mongo) error {
|
||||||
|
conn, err := me.ToMongoInfo().Conn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *mongoAppImpl) Save(ctx context.Context, m *entity.Mongo) error {
|
func (d *mongoAppImpl) Save(ctx context.Context, m *entity.Mongo) error {
|
||||||
if m.Id == 0 {
|
if m.Id == 0 {
|
||||||
return d.GetRepo().Insert(ctx, m)
|
return d.GetRepo().Insert(ctx, m)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type MongoInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mi *MongoInfo) Conn() (*MongoConn, error) {
|
func (mi *MongoInfo) Conn() (*MongoConn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
mongoOptions := options.Client().ApplyURI(mi.Uri).
|
mongoOptions := options.Client().ApplyURI(mi.Uri).
|
||||||
@@ -40,7 +40,7 @@ func (mi *MongoInfo) Conn() (*MongoConn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = client.Ping(context.TODO(), nil); err != nil {
|
if err = client.Ping(ctx, nil); err != nil {
|
||||||
client.Disconnect(ctx)
|
client.Disconnect(ctx)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ func InitMongoRouter(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
req.NewGet("/tags", ma.MongoTags),
|
req.NewGet("/tags", ma.MongoTags),
|
||||||
|
|
||||||
|
req.NewPost("/test-conn", ma.TestConn),
|
||||||
|
|
||||||
req.NewPost("", ma.Save).Log(req.NewLogSave("mongo-保存信息")),
|
req.NewPost("", ma.Save).Log(req.NewLogSave("mongo-保存信息")),
|
||||||
|
|
||||||
req.NewDelete(":id", ma.DeleteMongo).Log(req.NewLogSave("mongo-删除信息")),
|
req.NewDelete(":id", ma.DeleteMongo).Log(req.NewLogSave("mongo-删除信息")),
|
||||||
|
|||||||
@@ -47,6 +47,18 @@ func (r *Redis) RedisTags(rc *req.Ctx) {
|
|||||||
rc.ResData = r.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Redis))
|
rc.ResData = r.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Redis))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Redis) TestConn(rc *req.Ctx) {
|
||||||
|
form := &form.Redis{}
|
||||||
|
redis := ginx.BindJsonAndCopyTo[*entity.Redis](rc.GinCtx, form, new(entity.Redis))
|
||||||
|
|
||||||
|
// 密码解密,并使用解密后的赋值
|
||||||
|
originPwd, err := cryptox.DefaultRsaDecrypt(redis.Password, true)
|
||||||
|
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
|
||||||
|
redis.Password = originPwd
|
||||||
|
|
||||||
|
biz.ErrIsNil(r.RedisApp.TestConn(redis))
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Redis) Save(rc *req.Ctx) {
|
func (r *Redis) Save(rc *req.Ctx) {
|
||||||
form := &form.Redis{}
|
form := &form.Redis{}
|
||||||
redis := ginx.BindJsonAndCopyTo[*entity.Redis](rc.GinCtx, form, new(entity.Redis))
|
redis := ginx.BindJsonAndCopyTo[*entity.Redis](rc.GinCtx, form, new(entity.Redis))
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ type Redis interface {
|
|||||||
|
|
||||||
Count(condition *entity.RedisQuery) int64
|
Count(condition *entity.RedisQuery) int64
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
TestConn(re *entity.Redis) error
|
||||||
|
|
||||||
Save(ctx context.Context, re *entity.Redis) error
|
Save(ctx context.Context, re *entity.Redis) error
|
||||||
|
|
||||||
// 删除数据库信息
|
// 删除数据库信息
|
||||||
@@ -29,9 +32,6 @@ type Redis interface {
|
|||||||
// id: 数据库实例id
|
// id: 数据库实例id
|
||||||
// db: 库号
|
// db: 库号
|
||||||
GetRedisConn(id uint64, db int) (*rdm.RedisConn, error)
|
GetRedisConn(id uint64, db int) (*rdm.RedisConn, error)
|
||||||
|
|
||||||
// 测试连接
|
|
||||||
TestConn(re *entity.Redis) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRedisApp(redisRepo repository.Redis) Redis {
|
func newRedisApp(redisRepo repository.Redis) Redis {
|
||||||
@@ -53,14 +53,21 @@ func (r *redisAppImpl) Count(condition *entity.RedisQuery) int64 {
|
|||||||
return r.GetRepo().Count(condition)
|
return r.GetRepo().Count(condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis) error {
|
func (r *redisAppImpl) TestConn(re *entity.Redis) error {
|
||||||
// ’修改信息且密码不为空‘ or ‘新增’需要测试是否可连接
|
db := 0
|
||||||
if (re.Id != 0 && re.Password != "") || re.Id == 0 {
|
if re.Db != "" {
|
||||||
if err := r.TestConn(re); err != nil {
|
db, _ = strconv.Atoi(strings.Split(re.Db, ",")[0])
|
||||||
return errorx.NewBiz("Redis连接失败: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc, err := re.ToRedisInfo(db).Conn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rc.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis) error {
|
||||||
// 查找是否存在该库
|
// 查找是否存在该库
|
||||||
oldRedis := &entity.Redis{Host: re.Host}
|
oldRedis := &entity.Redis{Host: re.Host}
|
||||||
if re.SshTunnelMachineId > 0 {
|
if re.SshTunnelMachineId > 0 {
|
||||||
@@ -118,17 +125,3 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
|
|||||||
return re.ToRedisInfo(db), nil
|
return re.ToRedisInfo(db), nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisAppImpl) TestConn(re *entity.Redis) error {
|
|
||||||
db := 0
|
|
||||||
if re.Db != "" {
|
|
||||||
db, _ = strconv.Atoi(strings.Split(re.Db, ",")[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, err := re.ToRedisInfo(db).Conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rc.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ func InitRedisRouter(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
req.NewGet("/tags", rs.RedisTags),
|
req.NewGet("/tags", rs.RedisTags),
|
||||||
|
|
||||||
|
req.NewPost("/test-conn", rs.TestConn),
|
||||||
|
|
||||||
req.NewPost("", rs.Save).Log(req.NewLogSave("redis-保存信息")),
|
req.NewPost("", rs.Save).Log(req.NewLogSave("redis-保存信息")),
|
||||||
|
|
||||||
req.NewGet(":id/pwd", rs.GetRedisPwd),
|
req.NewGet(":id/pwd", rs.GetRedisPwd),
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
Model string `yaml:"model"`
|
Model string `yaml:"model"`
|
||||||
Cors bool `yaml:"cors"`
|
ContextPath string `yaml:"context-path"` // 请求路径上下文
|
||||||
Tls *Tls `yaml:"tls"`
|
Cors bool `yaml:"cors"`
|
||||||
Static *[]*Static `yaml:"static"`
|
Tls *Tls `yaml:"tls"`
|
||||||
StaticFile *[]*StaticFile `yaml:"static-file"`
|
Static *[]*Static `yaml:"static"`
|
||||||
MachineRecPath string `yaml:"machine-rec-path"` // 机器终端操作回放文件存储路径
|
StaticFile *[]*StaticFile `yaml:"static-file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Default() {
|
func (s *Server) Default() {
|
||||||
@@ -21,24 +21,12 @@ func (s *Server) Default() {
|
|||||||
if s.Port == 0 {
|
if s.Port == 0 {
|
||||||
s.Port = 8888
|
s.Port = 8888
|
||||||
}
|
}
|
||||||
if s.MachineRecPath == "" {
|
|
||||||
s.MachineRecPath = "./rec"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetPort() string {
|
func (s *Server) GetPort() string {
|
||||||
return fmt.Sprintf(":%d", s.Port)
|
return fmt.Sprintf(":%d", s.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取终端回访记录存放基础路径, 如果配置文件未配置,则默认为./rec
|
|
||||||
func (s *Server) GetMachineRecPath() string {
|
|
||||||
path := s.MachineRecPath
|
|
||||||
if path == "" {
|
|
||||||
return "./rec"
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
type Static struct {
|
type Static struct {
|
||||||
RelativePath string `yaml:"relative-path"`
|
RelativePath string `yaml:"relative-path"`
|
||||||
Root string `yaml:"root"`
|
Root string `yaml:"root"`
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func runWebServer() {
|
|||||||
|
|
||||||
server := config.Conf.Server
|
server := config.Conf.Server
|
||||||
port := server.GetPort()
|
port := server.GetPort()
|
||||||
logx.Infof("Listening and serving HTTP on %s", port)
|
logx.Infof("Listening and serving HTTP on %s", port+server.ContextPath)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if server.Tls != nil && server.Tls.Enable {
|
if server.Tls != nil && server.Tls.Enable {
|
||||||
|
|||||||
43
server/pkg/utils/bytex/bytex.go
Normal file
43
server/pkg/utils/bytex/bytex.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package bytex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KB = 1024
|
||||||
|
MB = KB * 1024
|
||||||
|
GB = MB * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// 解析字符串byte size
|
||||||
|
//
|
||||||
|
// 1kb -> 1024
|
||||||
|
// 1mb -> 1024 * 1024
|
||||||
|
func ParseSize(sizeStr string) (int64, error) {
|
||||||
|
sizeStr = strings.TrimSpace(sizeStr)
|
||||||
|
unit := sizeStr[len(sizeStr)-2:]
|
||||||
|
|
||||||
|
valueStr := sizeStr[:len(sizeStr)-2]
|
||||||
|
value, err := strconv.ParseInt(valueStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes int64
|
||||||
|
|
||||||
|
switch strings.ToUpper(unit) {
|
||||||
|
case "KB":
|
||||||
|
bytes = value * KB
|
||||||
|
case "MB":
|
||||||
|
bytes = value * MB
|
||||||
|
case "GB":
|
||||||
|
bytes = value * GB
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid size unit")
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
11
server/pkg/utils/bytex/bytex_test.go
Normal file
11
server/pkg/utils/bytex/bytex_test.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package bytex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSize(t *testing.T) {
|
||||||
|
res, _ := ParseSize("1MB")
|
||||||
|
fmt.Println(res)
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -469,6 +469,7 @@ INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, crea
|
|||||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('是否启用水印', 'UseWatermark', '[{"name":"是否启用","model":"isUse","placeholder":"是否启用水印","options":"true,false"},{"name":"自定义信息","model":"content","placeholder":"额外添加的水印内容,可添加公司名称等"}]', '', '水印信息配置', 'all', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin', 0, NULL);
|
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('是否启用水印', 'UseWatermark', '[{"name":"是否启用","model":"isUse","placeholder":"是否启用水印","options":"true,false"},{"name":"自定义信息","model":"content","placeholder":"额外添加的水印内容,可添加公司名称等"}]', '', '水印信息配置', 'all', '2022-08-25 23:36:35', 1, 'admin', '2022-08-26 10:02:52', 1, 'admin', 0, NULL);
|
||||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
|
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库查询最大结果集', 'DbQueryMaxCount', '[]', '200', '允许sql查询的最大结果集数。注: 0=不限制', '2023-02-11 14:29:03', 1, 'admin', '2023-02-11 14:40:56', 1, 'admin');
|
||||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
|
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, creator_id, creator, update_time, modifier_id, modifier)VALUES ('数据库是否记录查询SQL', 'DbSaveQuerySQL', '[]', '0', '1: 记录、0:不记录', '2023-02-11 16:07:14', 1, 'admin', '2023-02-11 16:44:17', 1, 'admin');
|
||||||
|
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB\\\\2GB等)"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1GB"}', '机器相关配置,如终端回放路径等', 'admin,', '2023-07-13 16:26:44', 1, 'admin', '2023-11-09 22:01:31', 1, 'admin', 0, NULL);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|||||||
2
server/resources/script/sql/v1.5.4.sql
Normal file
2
server/resources/script/sql/v1.5.4.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- 新增机器相关系统配置
|
||||||
|
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB\\\\2GB等)"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1GB"}', '机器相关配置,如终端回放路径等', 'admin,', '2023-07-13 16:26:44', 1, 'admin', '2023-11-09 22:01:31', 1, 'admin', 0, NULL);
|
||||||
Reference in New Issue
Block a user