feat: 机器新增备注字段,其他优化

This commit is contained in:
meilin.huang
2022-05-12 10:34:16 +08:00
parent 9db3db31be
commit ffb91e9169
20 changed files with 147 additions and 135 deletions

View File

@@ -12,8 +12,8 @@
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?jump_from=webapi">119699946</a>
### 系统相关资料
- 在线文档: https://objs.gitee.io/mayfly-go-docs
- 系统操作视频: https://space.bilibili.com/484091081/channel/seriesdetail?sid=2064467
- 项目文档: https://objs.gitee.io/mayfly-go-docs
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
### 系统功能

View File

@@ -150,70 +150,6 @@
margin-right: 5px;
}
/* Link 文字链接
------------------------------- */
// default
.el-link.el-link--default:hover {
color: set-color(primary-light-3);
}
// primary
.el-link.el-link--primary {
color: set-color(primary);
&:hover {
color: set-color(primary-light-3);
}
}
.el-link.el-link--default::after,
.el-link.is-underline:hover::after,
.el-link.el-link--primary.is-underline:hover::after,
.el-link.el-link--primary::after {
border-color: set-color(primary);
}
// success
.el-link.el-link--success {
color: set-color(success);
&:hover {
color: set-color(success-light-3);
}
}
.el-link.el-link--success.is-underline:hover::after,
.el-link.el-link--success::after {
border-color: set-color(success);
}
// info
.el-link.el-link--info {
color: set-color(info);
&:hover {
color: set-color(info-light-3);
}
}
.el-link.el-link--info.is-underline:hover::after,
.el-link.el-link--info::after {
border-color: set-color(info);
}
// warning
.el-link.el-link--warning {
color: set-color(warning);
&:hover {
color: set-color(warning-light-3);
}
}
.el-link.el-link--warning.is-underline:hover::after,
.el-link.el-link--warning::after {
border-color: set-color(warning);
}
// danger
.el-link.el-link--danger {
color: set-color(danger);
&:hover {
color: set-color(danger-light-3);
}
}
.el-link.el-link--danger.is-underline:hover::after,
.el-link.el-link--danger::after {
border-color: set-color(danger);
}
/* Radio 单选框
------------------------------- */
.el-radio__input.is-checked + .el-radio__label,

View File

@@ -35,7 +35,7 @@
type="password"
show-password
v-model.trim="form.password"
placeholder="请输入密码,新增为必填项"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
></el-input>
</el-form-item>
@@ -81,6 +81,7 @@ import { dbApi } from './api';
import { projectApi } from '../project/api.ts';
import { ElMessage } from 'element-plus';
import type { ElInput } from 'element-plus';
import { notBlank } from '@/common/assert';
export default defineComponent({
name: 'DbEdit',
@@ -189,7 +190,7 @@ export default defineComponent({
},
});
watch(props, async (newValue) => {
watch(props, (newValue) => {
state.projects = newValue.projects;
if (newValue.db) {
getEnvs(newValue.db.projectId);
@@ -199,6 +200,7 @@ export default defineComponent({
} else {
state.envs = [];
state.form = { port: 3306 } as any;
state.databaseList = [];
}
state.dialogVisible = newValue.visible;
});
@@ -241,6 +243,8 @@ export default defineComponent({
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};
@@ -254,6 +258,9 @@ export default defineComponent({
};
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
}
dbForm.value.validate((valid: boolean) => {
if (valid) {
state.form.port = Number.parseInt(state.form.port as any);

View File

@@ -19,7 +19,7 @@
</el-form-item>
</el-form>
</div>
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip>
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
<el-table-column label="选择" width="60px">
<template #default="scope">
<el-radio v-model="chooseId" :label="scope.row.id">
@@ -36,7 +36,7 @@
</template>
</el-table-column>
<el-table-column prop="type" label="类型" min-width="80"></el-table-column>
<el-table-column prop="database" label="数据库" min-width="120">
<el-table-column prop="database" label="数据库" min-width="160">
<template #default="scope">
<el-tag
@click="showTableInfo(scope.row, db)"
@@ -45,7 +45,7 @@
size="small"
v-for="db in scope.row.dbs"
:key="db"
style="cursor: pointer"
style="cursor: pointer; margin-left: 3px"
>{{ db }}</el-tag
>
</template>
@@ -78,7 +78,7 @@
<el-row class="mb10">
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
</el-row>
<el-table border :data="tableInfoDialog.infos" size="small">
<el-table border stripe :data="tableInfoDialog.infos" size="small">
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column
@@ -125,7 +125,7 @@
</el-dialog>
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
<el-table border :data="columnDialog.columns" size="small">
<el-table border stripe :data="columnDialog.columns" size="small">
<el-table-column prop="columnName" label="名称" show-overflow-tooltip> </el-table-column>
<el-table-column width="120" prop="columnType" label="类型" show-overflow-tooltip> </el-table-column>
<el-table-column width="80" prop="nullable" label="是否可为空" show-overflow-tooltip> </el-table-column>
@@ -134,7 +134,7 @@
</el-dialog>
<el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
<el-table border :data="indexDialog.indexs" size="small">
<el-table border stripe :data="indexDialog.indexs" size="small">
<el-table-column prop="indexName" label="索引名" show-overflow-tooltip> </el-table-column>
<el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
<el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
@@ -264,6 +264,8 @@ export default defineComponent({
};
const valChange = () => {
state.chooseData = null;
state.chooseId = null;
search();
};

View File

@@ -85,6 +85,7 @@
filterable
allow-create
default-first-option
size="small"
class="mr10"
>
<el-option v-for="item in sqlNames" :key="item" :label="item.database" :value="item">

View File

@@ -4,8 +4,8 @@
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
<template #footer>
<span class="dialog-footer">
<el-button @click="runSql" type="primary" :loading="btnLoading"> </el-button>
<el-button @click="cancel"> </el-button>
<el-button @click="runSql" type="primary" :loading="btnLoading"> </el-button>
</span>
</template>
</el-dialog>

View File

@@ -6,9 +6,6 @@
<el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" size="small" plain>添加</el-button>
</div>
</div>
<!-- <div style="float: right;">
</div> -->
<el-table :data="fileTable" stripe style="width: 100%">
<el-table-column prop="name" label="名称" width>
<template #default="scope">
@@ -47,6 +44,17 @@
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination
small
style="text-align: center"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
@current-change="handlePageChange"
></el-pagination>
</el-row>
</el-dialog>
<el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="680px">
@@ -157,8 +165,8 @@
<template #footer>
<div class="dialog-footer">
<el-button v-auth="'machine:file:write'" type="primary" @click="updateContent"> </el-button>
<el-button @click="fileContent.contentVisible = false"> </el-button>
<el-button v-auth="'machine:file:write'" type="primary" @click="updateContent"> </el-button>
</div>
</template>
</el-dialog>
@@ -217,12 +225,18 @@ export default defineComponent({
const state = reactive({
dialogVisible: false,
query: {
id: 0,
pageNum: 1,
pageSize: 8,
},
form: {
id: null,
type: null,
name: '',
remark: '',
},
total: 0,
fileTable: [] as any,
btnLoading: false,
fileContent: {
@@ -265,8 +279,15 @@ export default defineComponent({
});
const getFiles = async () => {
const res = await files.request({ id: props.machineId });
state.query.id = props.machineId;
const res = await files.request(state.query);
state.fileTable = res.list;
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
getFiles();
};
/**
@@ -307,7 +328,8 @@ export default defineComponent({
id: row.id,
})
.then(() => {
state.fileTable.splice(idx, 1);
getFiles();
// state.fileTable.splice(idx, 1);
});
});
} else {
@@ -571,6 +593,7 @@ export default defineComponent({
cmOptions,
add,
getFiles,
handlePageChange,
addFiles,
deleteRow,
getConf,

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="35%">
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px" >
<el-form :model="form" ref="machineForm" :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>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
@@ -19,19 +19,22 @@
<el-form-item prop="username" label="用户名:" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:" required>
<el-form-item prop="password" label="密码:">
<el-input
type="password"
show-password
v-model.trim="form.password"
placeholder="请输入密码"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
></el-input>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input type="textarea" v-model="form.remark"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<div>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div>
@@ -44,6 +47,7 @@
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
import { machineApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
export default defineComponent({
name: 'MachineEdit',
@@ -74,6 +78,7 @@ export default defineComponent({
port: 22,
username: null,
password: null,
remark: '',
},
btnLoading: false,
rules: {
@@ -119,13 +124,6 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['change', 'blur'],
},
],
},
});
@@ -148,6 +146,9 @@ export default defineComponent({
};
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
}
machineForm.value.validate((valid: boolean) => {
if (valid) {
machineApi.saveMachine.request(state.form).then(() => {

View File

@@ -15,15 +15,6 @@
<el-button v-auth="'machine:del'" :disabled="currentId == null" @click="deleteMachine(currentId)" type="danger" icon="delete"
>删除</el-button
>
<el-button
v-auth="'machine:file'"
type="success"
icon="files"
:disabled="currentId == null || currentData.status == -1"
@click="fileManage(currentData)"
plain
>文件</el-button
>
<div style="float: right">
<el-select v-model="params.projectId" placeholder="请选择项目" @clear="search" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
@@ -77,6 +68,7 @@
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="120"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="hasCli" width="70">
<template #default="scope">
{{ `${scope.row.hasCli ? '是' : '否'}` }}
@@ -90,26 +82,50 @@
<el-table-column prop="creator" label="创建者" min-width="80"></el-table-column>
<el-table-column label="操作" min-width="280" fixed="right">
<template #default="scope">
<el-button :disabled="scope.row.status == -1" type="success" @click="serviceManager(scope.row)" plain size="small"
>脚本</el-button
>
<el-button
<el-link
v-auth="'machine:terminal'"
:disabled="scope.row.status == -1"
type="primary"
@click="showTerminal(scope.row)"
plain
size="small"
>终端</el-button
:underline="false"
>终端</el-link
>
<el-button @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain size="small">进程</el-button>
<el-button
<el-divider v-auth="'machine:terminal'" direction="vertical" border-style="dashed" />
<el-link
v-auth="'machine:file'"
type="success"
:disabled="scope.row.status == -1"
@click="fileManage(scope.row)"
plain
size="small"
:underline="false"
>文件</el-link
>
<el-divider v-auth="'machine:file'" direction="vertical" border-style="dashed" />
<el-link
:disabled="scope.row.status == -1"
type="warning"
@click="serviceManager(scope.row)"
plain
size="small"
:underline="false"
>脚本</el-link
>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain :underline="false" size="small"
>进程</el-link
>
<el-divider direction="vertical" border-style="dashed" />
<el-link
:disabled="!scope.row.hasCli || scope.row.status == -1"
type="danger"
@click="closeCli(scope.row)"
plain
size="small"
>关闭连接</el-button
:underline="false"
>关闭连接</el-link
>
</template>
</el-table-column>

View File

@@ -12,9 +12,7 @@
<el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary" icon="tickets" size="small" plain
>查看</el-button
>
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus" size="small" plain
>添加</el-button
>
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus" size="small" plain>添加</el-button>
<el-button
v-auth="'machine:script:del'"
:disabled="currentId == null"
@@ -62,6 +60,17 @@
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination
small
style="text-align: center"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
@current-change="handlePageChange"
></el-pagination>
</el-row>
</el-dialog>
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
@@ -133,12 +142,18 @@ export default defineComponent({
type: 0,
currentId: null,
currentData: null,
query: {
machineId: 0,
pageNum: 1,
pageSize: 8,
},
editDialog: {
visible: false,
data: null,
title: '',
machineId: 9999999,
},
total: 0,
scriptTable: [],
scriptParamsDialog: {
visible: false,
@@ -166,9 +181,15 @@ export default defineComponent({
const getScripts = async () => {
state.currentId = null;
state.currentData = null;
const machineId = state.type == 0 ? props.machineId : 9999999;
const res = await machineApi.scripts.request({ machineId: machineId });
state.query.machineId = state.type == 0 ? props.machineId : 9999999;
const res = await machineApi.scripts.request(state.query);
state.scriptTable = res.list;
state.total = res.total;
};
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
getScripts();
};
const runScript = async (script: any) => {
@@ -219,9 +240,9 @@ export default defineComponent({
}
if (script.type == enums.scriptTypeEnum['REAL_TIME'].value) {
script = script.script
script = script.script;
if (state.scriptParamsDialog.params) {
script = templateResolve(script, state.scriptParamsDialog.params)
script = templateResolve(script, state.scriptParamsDialog.params);
}
state.terminalDialog.cmd = script;
state.terminalDialog.visible = true;
@@ -312,6 +333,7 @@ export default defineComponent({
paramsForm,
enums,
getScripts,
handlePageChange,
runScript,
hasParamsRun,
closeTermnial,

View File

@@ -76,8 +76,8 @@
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="saveValue" type="primary" v-auth="'redis:data:save'"> </el-button>
<el-button @click="cancel()"> </el-button>
<el-button @click="saveValue" type="primary" v-auth="'redis:data:save'"> </el-button>
</div>
</template>
</el-dialog>

View File

@@ -133,6 +133,8 @@ export default defineComponent({
state.form.project = p.name;
}
}
state.form.envId = null;
state.form.env = null;
state.envs = [];
getEnvs(projectId);
};

View File

@@ -14,7 +14,7 @@
</el-select>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="redisTable" style="width: 100%" @current-change="choose">
<el-table :data="redisTable" 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">
@@ -181,6 +181,8 @@ export default defineComponent({
};
const valChange = () => {
state.currentId = null;
state.currentData = null;
search();
};

View File

@@ -9,9 +9,10 @@ type MachineForm struct {
Ip string `json:"ip" binding:"required"`
// 用户名
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Password string `json:"password"`
// 端口号
Port int `json:"port" binding:"required"`
Remark string `json:"remark"`
}
type MachineRunForm struct {

View File

@@ -30,6 +30,7 @@ type MachineVO struct {
Modifier *string `json:"modifier"`
ModifierId *int64 `json:"modifierId"`
HasCli bool `json:"hasCli" gorm:"-"`
Remark *string `json:"remark"`
}
type MachineScriptVO struct {

View File

@@ -162,7 +162,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
}
// 最大连接周期超过时间的连接就close
DB.SetConnMaxLifetime(100 * time.Second)
// DB.SetConnMaxLifetime(100 * time.Second)
// 设置最大连接数
DB.SetMaxOpenConns(2)
// 设置闲置连接数
@@ -182,7 +182,7 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
var dbCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info(fmt.Sprintf("删除db连接缓存 id: %s", key))
global.Log.Info(fmt.Sprintf("删除db连接缓存 id = %s", key))
value.(*DbInstance).Close()
})
@@ -307,13 +307,9 @@ func getDsn(d *entity.Db) string {
return ""
}
// 关闭该数据库所有连接
// 删除db缓存并关闭该数据库所有连接
func CloseDb(dbId uint64, db string) {
id := GetDbCacheKey(dbId, db)
if di := GetDbInstanceByCache(id); di != nil {
di.Close()
dbCache.Delete(id)
}
dbCache.Delete(GetDbCacheKey(dbId, db))
}
//-----------------------------------元数据-------------------------------------------

View File

@@ -51,7 +51,10 @@ func (m *machineAppImpl) Count(condition *entity.Machine) int64 {
// 根据条件获取机器信息
func (m *machineAppImpl) Save(me *entity.Machine) {
// ’修改机器信息且密码不为空‘ or ‘新增’需要测试是否可连接
if (me.Id != 0 && me.Password != "") || me.Id == 0 {
biz.ErrIsNilAppendErr(machine.TestConn(me), "该机器无法连接: %s")
}
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
err := m.GetMachine(oldMachine)

View File

@@ -126,7 +126,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
var redisCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id: %d", key))
global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %d", key))
value.(*RedisInstance).Cli.Close()
})
@@ -137,12 +137,9 @@ type RedisInstance struct {
Cli *redis.Client
}
// 关闭redis连接
// 移除redis连接缓存并关闭redis连接
func CloseRedis(id uint64) {
if load, ok := redisCache.Get(id); ok {
load.(*RedisInstance).Cli.Close()
redisCache.Delete(id)
}
}
func TestRedisConnection(re *entity.Redis) {

View File

@@ -14,6 +14,7 @@ type Machine struct {
Password string `json:"-"`
Port int `json:"port"` // 端口号
Status int8 `json:"status"` // 状态 1:启用2:停用
Remark string `json:"remark"` // 备注
}
const (

View File

@@ -88,6 +88,7 @@ CREATE TABLE `t_machine` (
`username` varchar(12) COLLATE utf8mb4_bin NOT NULL,
`password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
`remark` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`need_monitor` tinyint(2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(16) COLLATE utf8mb4_bin DEFAULT NULL,