feat: 新增mongo管理与数据操作

This commit is contained in:
meilin.huang
2022-05-17 20:23:08 +08:00
parent ffb91e9169
commit 56e7a8843b
17 changed files with 1648 additions and 27 deletions

View File

@@ -18,4 +18,8 @@ export const imports = {
// redis
"RedisList": () => import('@/views/ops/redis'),
"DataOperation": () => import('@/views/ops/redis/DataOperation.vue'),
// mongo
"MongoDataOp": () => import('@/views/ops/mongo/MongoDataOp.vue'),
// redis
"MongoList": () => import('@/views/ops/mongo/MongoList.vue'),
}

View File

@@ -221,26 +221,32 @@ router.beforeEach((to, from, next) => {
if (to.path === '/login' && !token) {
next();
NProgress.done();
} else {
if (!token) {
next(`/login?redirect=${to.path}`);
clearSession();
resetRoute();
NProgress.done();
return;
}
if (!token) {
next(`/login?redirect=${to.path}`);
clearSession();
resetRoute();
NProgress.done();
if (SysWs) {
SysWs.close();
SysWs = null;
}
} else if (token && to.path === '/login') {
next('/');
NProgress.done();
} else {
if (!SysWs) {
SysWs = sockets.sysMsgSocket();
}
if (store.state.routesList.routesList.length > 0) next();
if (SysWs) {
SysWs.close();
SysWs = null;
}
return;
}
if (token && to.path === '/login') {
next('/');
NProgress.done();
return;
}
// 终端不需要连接系统websocket消息
if (!SysWs && to.path != '/machine/terminal') {
SysWs = sockets.sysMsgSocket();
}
if (store.state.routesList.routesList.length > 0) {
next();
}
});

View File

@@ -0,0 +1,420 @@
<template>
<div>
<div class="toolbar">
<el-row type="flex" justify="space-between">
<el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv">
<template #default>
<el-form-item label="实例" label-width="40px">
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo">
<el-option v-for="item in mongoList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ ` [${item.uri}]` }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase">
<el-option v-for="item in databases" :key="item.Name" :label="item.Name" :value="item.Name">
<span style="float: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection">
<el-option v-for="item in collections" :key="item" :label="item" :value="item">
<!-- <span style="float: left">{{ item.uri }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
` [${item.name}]`
}}</span> -->
</el-option>
</el-select>
</el-form-item>
</template>
</project-env-select>
</el-col>
</el-row>
</div>
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px">
<el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 100%; margin-left: 5px" v-model="activeName">
<el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.name" :name="dt.name">
<el-row v-if="mongoId">
<el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5"></el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false"></el-link>
</el-row>
<el-row class="mt5 mb5">
<el-input
ref="findParamInputRef"
v-model="dt.findParamStr"
placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.name)"
>
<template #prepend>查询参数</template>
</el-input>
</el-row>
<el-row>
<el-col :span="6" v-for="item in dt.datas" :key="item">
<el-card :body-style="{ padding: '0px', position: 'relative' }">
<el-input type="textarea" v-model="item.value" :rows="12" />
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
<div>
<el-button @click="onSaveDoc(item.value)" type="warning" plain size="small">保存</el-button>
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
<template #reference>
<el-button type="danger" plain size="small">删除</el-button>
</template>
</el-popconfirm>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-container>
<el-dialog width="400px" title="find参数" v-model="findDialog.visible">
<el-form label-width="70px">
<el-form-item label="filter">
<el-input v-model="findDialog.findParam.filter" type="textarea" :rows="6" clearable auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="sort">
<el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="limit">
<el-input v-model.number="findDialog.findParam.limit" type="number" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="skip">
<el-input v-model.number="findDialog.findParam.skip" type="number" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div>
<el-button @click="findDialog.visible = false"> </el-button>
<el-button @click="confirmFindDialog" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog width="600px" :title="`新增'${activeName}'集合文档`" v-model="insertDocDialog.visible" :close-on-click-modal="false">
<el-input v-model="insertDocDialog.doc" type="textarea" :rows="12" clearable auto-complete="off"></el-input>
<template #footer>
<div>
<el-button @click="insertDocDialog.visible = false"> </el-button>
<el-button @click="onInsertDoc" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<div style="text-align: center; margin-top: 10px"></div>
</div>
</template>
<script lang="ts">
import { mongoApi } from './api';
import { toRefs, ref, reactive, defineComponent } from 'vue';
import { ElMessage } from 'element-plus';
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { formatByteSize, formatJsonString } from '@/common/utils/format';
export default defineComponent({
name: 'MongoDataOp',
components: {
ProjectEnvSelect,
},
setup() {
const findParamInputRef: any = ref(null);
const state = reactive({
loading: false,
mongoList: [],
query: {
envId: 0,
},
mongoId: null, // 当前选择操作的mongo
database: '', // 当前选择操作的库
collection: '', //当前选中的collection
activeName: '', // 当前操作的tab
databases: [],
collections: [],
dataTabs: {}, // 数据tabs
findDialog: {
visible: false,
findParam: {
filter: '',
sort: '',
},
},
insertDocDialog: {
visible: false,
doc: '',
},
});
const searchMongo = async () => {
notNull(state.query.envId, '请先选择项目环境');
const res = await mongoApi.mongoList.request(state.query);
state.mongoList = res.list;
};
const changeProjectEnv = (projectId: any, envId: any) => {
state.databases = [];
state.collections = [];
state.mongoId = null;
state.collection = '';
state.database = '';
state.dataTabs = {};
if (envId != null) {
state.query.envId = envId;
searchMongo();
}
};
const changeMongo = () => {
state.databases = [];
state.collections = [];
state.dataTabs = {};
getDatabases();
};
const getDatabases = async () => {
const res = await mongoApi.databases.request({ id: state.mongoId });
state.databases = res.Databases;
};
const changeDatabase = () => {
state.collections = [];
state.collection = '';
state.dataTabs = {};
getCollections();
};
const getCollections = async () => {
state.collections = await mongoApi.collections.request({ id: state.mongoId, database: state.database });
};
const changeCollection = () => {
const collection = state.collection;
let dataTab = state.dataTabs[collection];
if (!dataTab) {
// 默认查询参数
const findParam = {
filter: '{}',
sort: '{"_id": -1}',
skip: 0,
limit: 12,
},
dataTab = {
name: collection,
datas: [],
findParamStr: JSON.stringify(findParam),
findParam,
};
state.dataTabs[collection] = dataTab;
}
state.activeName = collection;
findCommand(collection);
};
const showFindDialog = (collection: string) => {
// 获取当前tab的索引位置将其输入框失去焦点防止输入以及重复获取焦点
const dataTabNames = Object.keys(state.dataTabs);
for (let i = 0; i < dataTabNames.length; i++) {
if (collection == dataTabNames[i]) {
findParamInputRef.value[i].blur();
}
}
state.findDialog.findParam = state.dataTabs[collection].findParam;
state.findDialog.visible = true;
};
const confirmFindDialog = () => {
state.dataTabs[state.activeName].findParam = state.findDialog.findParam;
state.dataTabs[state.activeName].findParamStr = JSON.stringify(state.findDialog.findParam);
state.findDialog.visible = false;
findCommand(state.activeName);
};
const findCommand = async (collection: string) => {
const dataTab = state.dataTabs[collection];
const findParma = dataTab.findParam;
let filter, sort;
try {
filter = findParma.filter ? JSON.parse(findParma.filter) : {};
sort = findParma.sort ? JSON.parse(findParma.sort) : {};
} catch (e) {
ElMessage.error('filter或sort字段json字符串值错误。注意: json key需双引号');
return;
}
const datas = await mongoApi.findCommand.request({
id: state.mongoId,
database: state.database,
collection,
filter,
sort,
limit: findParma.limit || 12,
skip: findParma.skip || 0,
});
state.dataTabs[collection].datas = wrapDatas(datas);
};
/**
* 包装mongo查询回来的对象即将其都转为json字符串并用value属性值描述方便显示
*/
const wrapDatas = (datas: any) => {
const wrapDatas = [] as any;
if (!datas) {
return wrapDatas;
}
for (let data of datas) {
wrapDatas.push({ value: formatJsonString(JSON.stringify(data), false) });
}
return wrapDatas;
};
const showInsertDocDialog = () => {
// tab数据中的第一个文档因为该集合的文档都类似故使用第一个文档赋值至需要新增的文档输入框方便直接修改新增
const datasFirstDoc = state.dataTabs[state.activeName].datas[0];
let doc = '';
if (datasFirstDoc) {
// 移除_id字段因为新增无需该字段
const docObj = JSON.parse(datasFirstDoc.value);
delete docObj['_id'];
doc = formatJsonString(JSON.stringify(docObj), false);
}
state.insertDocDialog.doc = doc;
state.insertDocDialog.visible = true;
};
const onInsertDoc = async () => {
let docObj;
try {
docObj = JSON.parse(state.insertDocDialog.doc);
} catch (e) {
ElMessage.error('文档内容错误,无法解析为json对象');
}
const res = await mongoApi.insertCommand.request({
id: state.mongoId,
database: state.database,
collection: state.activeName,
doc: docObj,
});
isTrue(res.InsertedID, '新增失败');
ElMessage.success('新增成功');
findCommand(state.activeName);
state.insertDocDialog.visible = false;
};
const onSaveDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc);
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
delete docObj['_id'];
const res = await mongoApi.updateByIdCommand.request({
id: state.mongoId,
database: state.database,
collection: state.collection,
docId: id,
update: { $set: docObj },
});
isTrue(res.ModifiedCount == 1, '修改失败');
ElMessage.success('保存成功');
};
const onDeleteDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc);
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
const res = await mongoApi.deleteByIdCommand.request({
id: state.mongoId,
database: state.database,
collection: state.collection,
docId: id,
});
isTrue(res.DeletedCount == 1, '删除失败');
ElMessage.success('删除成功');
findCommand(state.activeName);
};
/**
* 将json字符串解析为json对象
*/
const parseDocJsonString = (doc: string) => {
try {
return JSON.parse(doc);
} catch (e) {
ElMessage.error('文档内容解析为json对象失败');
throw e;
}
};
/**
* 数据tab点击
*/
const onDataTabClick = (tab: any) => {
const name = tab.props.name;
// 修改选择框绑定的表信息
state.collection = name;
};
const removeDataTab = (targetName: string) => {
const tabNames = Object.keys(state.dataTabs);
let activeName = state.activeName;
tabNames.forEach((name, index) => {
if (name === targetName) {
const nextTab = tabNames[index + 1] || tabNames[index - 1];
if (nextTab) {
activeName = nextTab;
}
}
});
state.activeName = activeName;
// 如果移除最后一个数据tab则将选择框绑定的collection置空
if (activeName == targetName) {
state.collection = '';
} else {
state.collection = activeName;
}
delete state.dataTabs[targetName];
};
return {
...toRefs(state),
findParamInputRef,
changeProjectEnv,
changeMongo,
changeDatabase,
changeCollection,
onDataTabClick,
removeDataTab,
showFindDialog,
confirmFindDialog,
findCommand,
showInsertDocDialog,
onInsertDoc,
onSaveDoc,
onDeleteDoc,
formatByteSize,
};
},
});
</script>
<style>
.mongo-doc-btns {
position: absolute;
z-index: 2;
right: 3px;
top: 2px;
max-width: 130px;
}
</style>

View File

@@ -0,0 +1,188 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="35%" :destroy-on-close="true">
<el-form :model="form" ref="mongoForm" :rules="rules" label-width="65px">
<el-form-item prop="projectId" label="项目" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="envId" label="环境" required>
<el-select @change="changeEnv" style="width: 100%" v-model="form.envId" placeholder="请选择环境">
<el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="uri" label="uri" required>
<el-input
type="textarea"
:rows="2"
v-model.trim="form.uri"
placeholder="形如 mongodb://username:password@host1:port1"
auto-complete="off"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
import { mongoApi } from './api';
import { projectApi } from '../project/api.ts';
import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'MongoEdit',
props: {
visible: {
type: Boolean,
},
projects: {
type: Array,
},
mongo: {
type: [Boolean, Object],
},
title: {
type: String,
},
},
setup(props: any, { emit }) {
const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
projects: [],
envs: [],
form: {
id: null,
name: null,
uri: null,
project: null,
projectId: null,
envId: null,
env: null,
},
btnLoading: false,
rules: {
projectId: [
{
required: true,
message: '请选择项目',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
trigger: ['change', 'blur'],
},
],
name: [
{
required: true,
message: '请输入名称',
trigger: ['change', 'blur'],
},
],
uri: [
{
required: true,
message: '请输入mongo uri',
trigger: ['change', 'blur'],
},
],
},
});
watch(props, async (newValue) => {
state.dialogVisible = newValue.visible;
state.projects = newValue.projects;
if (newValue.mongo) {
getEnvs(newValue.mongo.projectId);
state.form = { ...newValue.mongo };
} else {
state.envs = [];
state.form = { db: 0 } as any;
}
});
const getEnvs = async (projectId: any) => {
state.envs = await projectApi.projectEnvs.request({ projectId });
};
const changeProject = (projectId: number) => {
for (let p of state.projects as any) {
if (p.id == projectId) {
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};
const changeEnv = (envId: number) => {
for (let p of state.envs as any) {
if (p.id == envId) {
state.form.env = p.name;
}
}
};
const btnOk = async () => {
mongoForm.value.validate((valid: boolean) => {
if (valid) {
mongoApi.saveMongo.request(state.form).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
ElMessage.error('请正确填写信息');
return false;
}
});
};
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
mongoForm.value.resetFields();
// 重置对象属性为null
state.form = {} as any;
}, 200);
};
return {
...toRefs(state),
mongoForm,
changeProject,
changeEnv,
btnOk,
cancel,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,427 @@
<template>
<div>
<el-card>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除</el-button>
<div style="float: right">
<el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="list" style="width: 100%" @current-change="choose" stripe>
<el-table-column label="选择" width="60px">
<template #default="scope">
<el-radio v-model="currentId" :label="scope.row.id">
<i></i>
</el-radio>
</template>
</el-table-column>
<el-table-column prop="project" label="项目" width></el-table-column>
<el-table-column prop="env" label="环境" width></el-table-column>
<el-table-column prop="name" label="名称" width></el-table-column>
<el-table-column prop="uri" label="连接uri" min-width="150" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.uri.split('@')[1] }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人"></el-table-column>
<el-table-column label="操作" width>
<template #default="scope">
<el-link type="primary" @click="showDatabases(scope.row.id)" plain size="small" :underline="false">数据库</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row>
</el-card>
<el-dialog width="800px" :title="databaseDialog.title" v-model="databaseDialog.visible">
<el-table :data="databaseDialog.data" size="small">
<el-table-column min-width="130" property="Name" label="库名" />
<el-table-column min-width="90" property="SizeOnDisk" label="size">
<template #default="scope">
{{ formatByteSize(scope.row.SizeOnDisk) }}
</template>
</el-table-column>
<el-table-column min-width="80" property="Empty" label="是否为空" />
<el-table-column min-width="80" label="操作">
<template #default="scope">
<el-link type="success" @click="showDatabaseStats(scope.row.Name)" plain size="small" :underline="false">stats</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small" :underline="false">集合</el-link>
</template>
</el-table-column>
</el-table>
<el-dialog width="700px" :title="databaseDialog.statsDialog.title" v-model="databaseDialog.statsDialog.visible">
<el-descriptions title="库状态信息" :column="3" border size="small">
<el-descriptions-item label="db" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.db }}
</el-descriptions-item>
<el-descriptions-item label="collections" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.collections }}
</el-descriptions-item>
<el-descriptions-item label="objects" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.objects }}
</el-descriptions-item>
<el-descriptions-item label="indexes" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.indexes }}
</el-descriptions-item>
<el-descriptions-item label="avgObjSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.avgObjSize) }}
</el-descriptions-item>
<el-descriptions-item label="dataSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.dataSize) }}
</el-descriptions-item>
<el-descriptions-item label="totalSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.totalSize) }}
</el-descriptions-item>
<el-descriptions-item label="storageSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.storageSize) }}
</el-descriptions-item>
<el-descriptions-item label="fsTotalSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.fsTotalSize) }}
</el-descriptions-item>
<el-descriptions-item label="fsUsedSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.fsUsedSize) }}
</el-descriptions-item>
<el-descriptions-item label="indexSize" label-align="right" align="center">
{{ formatByteSize(databaseDialog.statsDialog.data.indexSize) }}
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</el-dialog>
<el-dialog width="600px" :title="collectionsDialog.title" v-model="collectionsDialog.visible">
<div>
<el-button @click="showCreateCollectionDialog" type="primary" icon="plus" size="small">新建</el-button>
</div>
<el-table border stripe :data="collectionsDialog.data" size="small">
<el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
<el-table-column min-width="80" label="操作">
<template #default="scope">
<el-link type="success" @click="showCollectionStats(scope.row.name)" plain size="small" :underline="false">stats</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteCollection(scope.row.name)" title="确定删除该集合?">
<template #reference>
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-dialog width="700px" :title="collectionsDialog.statsDialog.title" v-model="collectionsDialog.statsDialog.visible">
<el-descriptions title="集合状态信息" :column="3" border size="small">
<el-descriptions-item label="ns" label-align="right" :span="2" align="center">
{{ collectionsDialog.statsDialog.data.ns }}
</el-descriptions-item>
<el-descriptions-item label="count" label-align="right" align="center">
{{ collectionsDialog.statsDialog.data.count }}
</el-descriptions-item>
<el-descriptions-item label="avgObjSize" label-align="right" align="center">
{{ collectionsDialog.statsDialog.data.avgObjSize }}
</el-descriptions-item>
<el-descriptions-item label="nindexes" label-align="right" align="center">
{{ collectionsDialog.statsDialog.data.nindexes }}
</el-descriptions-item>
<el-descriptions-item label="size" label-align="right" align="center">
{{ formatByteSize(collectionsDialog.statsDialog.data.size) }}
</el-descriptions-item>
<el-descriptions-item label="totalSize" label-align="right" align="center">
{{ formatByteSize(collectionsDialog.statsDialog.data.totalSize) }}
</el-descriptions-item>
<el-descriptions-item label="storageSize" label-align="right" align="center">
{{ formatByteSize(collectionsDialog.statsDialog.data.storageSize) }}
</el-descriptions-item>
<el-descriptions-item label="freeStorageSize" label-align="right" align="center">
{{ formatByteSize(collectionsDialog.statsDialog.data.freeStorageSize) }}
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</el-dialog>
<el-dialog width="400px" title="新建集合" v-model="createCollectionDialog.visible" :destroy-on-close="true">
<el-form :model="createCollectionDialog.form" label-width="70px">
<el-form-item prop="name" label="集合名" required>
<el-input v-model="createCollectionDialog.form.name" clearable></el-input>
</el-form-item>
<!-- <el-form-item label="描述:">
<el-input v-model="showEnvDialog.envForm.remark" auto-complete="off"></el-input>
</el-form-item> -->
</el-form>
<template #footer>
<div>
<el-button @click="createCollectionDialog.visible = false"> </el-button>
<el-button @click="onCreateCollection" type="primary"> </el-button>
</div>
</template>
</el-dialog>
<mongo-edit
@val-change="valChange"
:projects="projects"
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
></mongo-edit>
</div>
</template>
<script lang="ts">
import { mongoApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { projectApi } from '../project/api.ts';
import MongoEdit from './MongoEdit.vue';
import { formatByteSize } from '@/common/utils/format';
export default defineComponent({
name: 'MongoList',
components: {
MongoEdit,
},
setup() {
const state = reactive({
projects: [],
list: [],
total: 0,
currentId: null,
currentData: null,
query: {
pageNum: 1,
pageSize: 10,
prjectId: null,
clusterId: null,
},
mongoEditDialog: {
visible: false,
data: null,
title: '新增mongo',
},
databaseDialog: {
visible: false,
data: [],
title: '',
statsDialog: {
visible: false,
data: {},
title: '',
},
},
collectionsDialog: {
database: '',
visible: false,
data: [],
title: '',
statsDialog: {
visible: false,
data: {},
title: '',
},
},
createCollectionDialog: {
visible: false,
form: {
name: '',
},
},
});
onMounted(async () => {
search();
state.projects = (await projectApi.projects.request({ pageNum: 1, pageSize: 100 })).list;
});
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
// connect() {
// Req.post('/open/redis/connect', this.form, res => {
// this.redisInfo = res
// })
// }
const showDatabases = async (id: number) => {
state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
state.databaseDialog.title = `数据库列表`;
state.databaseDialog.visible = true;
};
const showDatabaseStats = async (dbName: string) => {
state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
database: dbName,
command: {
dbStats: 1,
},
});
state.databaseDialog.statsDialog.title = `'${dbName}' stats`;
state.databaseDialog.statsDialog.visible = true;
};
const showCollections = async (database: string) => {
state.collectionsDialog.database = database;
state.collectionsDialog.data = [];
setCollections(database);
state.collectionsDialog.title = `'${database}' 集合`;
state.collectionsDialog.visible = true;
};
const setCollections = async (database: string) => {
const res = await mongoApi.collections.request({ id: state.currentId, database });
const collections = [] as any;
for (let r of res) {
collections.push({ name: r });
}
state.collectionsDialog.data = collections;
};
/**
* 显示集合状态
*/
const showCollectionStats = async (collection: string) => {
state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
database: state.collectionsDialog.database,
command: {
collStats: collection,
},
});
state.collectionsDialog.statsDialog.title = `'${collection}' stats`;
state.collectionsDialog.statsDialog.visible = true;
};
/**
* 删除集合
*/
const onDeleteCollection = async (collection: string) => {
await mongoApi.runCommand.request({
id: state.currentId,
database: state.collectionsDialog.database,
command: {
drop: collection,
},
});
ElMessage.success('集合删除成功');
setCollections(state.collectionsDialog.database);
};
const showCreateCollectionDialog = () => {
state.createCollectionDialog.visible = true;
};
const onCreateCollection = async () => {
const form = state.createCollectionDialog.form;
await mongoApi.runCommand.request({
id: state.currentId,
database: state.collectionsDialog.database,
command: {
create: form.name,
},
});
ElMessage.success('集合创建成功');
state.createCollectionDialog.visible = false;
state.createCollectionDialog.form = {} as any;
setCollections(state.collectionsDialog.database);
};
const deleteMongo = async () => {
try {
await ElMessageBox.confirm(`确定删除该mongo?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await mongoApi.deleteMongo.request({ id: state.currentId });
ElMessage.success('删除成功');
state.currentData = null;
state.currentId = null;
search();
} catch (err) {}
};
// const info = (redis: any) => {
// redisApi.redisInfo.request({ id: redis.id }).then((res: any) => {
// state.infoDialog.info = res;
// state.infoDialog.title = `'${redis.host}' info`;
// state.infoDialog.visible = true;
// });
// };
const search = async () => {
const res = await mongoApi.mongoList.request(state.query);
state.list = res.list;
state.total = res.total;
};
const editMongo = (isAdd = false) => {
if (isAdd) {
state.mongoEditDialog.data = null;
state.mongoEditDialog.title = '新增mongo';
} else {
state.mongoEditDialog.data = state.currentData;
state.mongoEditDialog.title = '修改mongo';
}
state.mongoEditDialog.visible = true;
};
const valChange = () => {
state.currentId = null;
state.currentData = null;
search();
};
return {
...toRefs(state),
search,
handlePageChange,
choose,
showDatabases,
showDatabaseStats,
showCollections,
showCollectionStats,
onDeleteCollection,
showCreateCollectionDialog,
onCreateCollection,
formatByteSize,
deleteMongo,
editMongo,
valChange,
};
},
});
</script>
<style>
</style>

View File

@@ -0,0 +1,14 @@
import Api from '@/common/Api';
export const mongoApi = {
mongoList : Api.create("/mongos", 'get'),
saveMongo : Api.create("/mongos", 'post'),
deleteMongo : Api.create("/mongos/{id}", 'delete'),
databases: Api.create("/mongos/{id}/databases", 'get'),
collections: Api.create("/mongos/{id}/collections", 'get'),
runCommand: Api.create("/mongos/{id}/run-command", 'post'),
findCommand: Api.create("/mongos/{id}/command/find", 'post'),
updateByIdCommand: Api.create("/mongos/{id}/command/update-by-id", 'post'),
deleteByIdCommand: Api.create("/mongos/{id}/command/delete-by-id", 'post'),
insertCommand: Api.create("/mongos/{id}/command/insert", 'post'),
}

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="35%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="35%">
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>