canal instance manager module finished

This commit is contained in:
mcy
2019-07-13 21:34:44 +08:00
parent 4efc0f6078
commit f3daceea6b
12 changed files with 304 additions and 20 deletions

View File

@@ -22,7 +22,7 @@ public class CanalInstanceController {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig)); return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig));
} }
@PostMapping(value = "/iInstance") @PostMapping(value = "/instance")
public BaseModel<String> save(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) { public BaseModel<String> save(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.save(canalInstanceConfig); canalInstanceConfigService.save(canalInstanceConfig);
return BaseModel.getInstance("success"); return BaseModel.getInstance("success");
@@ -34,7 +34,7 @@ public class CanalInstanceController {
} }
@PutMapping(value = "/instance") @PutMapping(value = "/instance")
public BaseModel<String> update(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) { public BaseModel<String> update(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.updateContent(canalInstanceConfig); canalInstanceConfigService.updateContent(canalInstanceConfig);
return BaseModel.getInstance("success"); return BaseModel.getInstance("success");
} }
@@ -44,4 +44,14 @@ public class CanalInstanceController {
canalInstanceConfigService.delete(id); canalInstanceConfigService.delete(id);
return BaseModel.getInstance("success"); return BaseModel.getInstance("success");
} }
@PutMapping(value = "/instance/start/{id}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, null, "start"));
}
@PutMapping(value = "/instance/stop/{id}/{nodeId}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable Long nodeId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "stop"));
}
} }

View File

@@ -29,6 +29,8 @@ public class CanalInstanceConfig extends Model {
private String content; private String content;
private Date modifiedTime; private Date modifiedTime;
@Transient
private Long nodeId;
@Transient @Transient
private String nodeIp; private String nodeIp;
@@ -64,6 +66,14 @@ public class CanalInstanceConfig extends Model {
this.modifiedTime = modifiedTime; this.modifiedTime = modifiedTime;
} }
public Long getNodeId() {
return nodeId;
}
public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
public String getNodeIp() { public String getNodeIp() {
return nodeIp; return nodeIp;
} }

View File

@@ -15,4 +15,6 @@ public interface CanalInstanceService {
void updateContent(CanalInstanceConfig canalInstanceConfig); void updateContent(CanalInstanceConfig canalInstanceConfig);
void delete(Long id); void delete(Long id);
boolean remoteOperation(Long id, Long nodeId, String option);
} }

View File

@@ -40,6 +40,7 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
for (String instance : instances) { for (String instance : instances) {
for (CanalInstanceConfig cig : canalInstanceConfigs) { for (CanalInstanceConfig cig : canalInstanceConfigs) {
if (instance.equals(cig.getName())) { if (instance.equals(cig.getName())) {
cig.setNodeId(nodeServer.getId());
cig.setNodeIp(nodeServer.getIp()); cig.setNodeIp(nodeServer.getIp());
break; break;
} }
@@ -69,4 +70,40 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
} }
} }
public boolean remoteOperation(Long id, Long nodeId, String option) {
NodeServer nodeServer = null;
if ("start".equals(option)) {
// select the first node server
nodeServer = NodeServer.find.query().findOne();
} else {
if (nodeId == null) {
return false;
}
nodeServer = NodeServer.find.byId(nodeId);
}
if (nodeServer == null) {
return false;
}
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return false;
}
Boolean resutl = null;
if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.startInstance(canalInstanceConfig.getName()));
} else if ("stop".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.stopInstance(canalInstanceConfig.getName()));
} else {
return false;
}
if (resutl == null) {
resutl = false;
}
return resutl;
}
} }

View File

@@ -116,7 +116,7 @@ public class NodeServerServiceImpl implements NodeServerService {
if ("start".equals(option)) { if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::start); resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::start);
} else if ("stop".equals(option)) { } else if ("stop".equals(option)) {
JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::stop); resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::stop);
} else { } else {
return false; return false;
} }

View File

@@ -22,3 +22,32 @@ export function updateCanalInstance(data) {
data data
}) })
} }
export function addCanalInstance(data) {
return request({
url: '/canal/instance',
method: 'post',
data
})
}
export function deleteCanalInstance(id) {
return request({
url: '/canal/instance/' + id,
method: 'delete'
})
}
export function startInstance(id) {
return request({
url: '/canal/instance/start/' + id,
method: 'put'
})
}
export function stopNodeServer(id, nodeId) {
return request({
url: '/canal/instance/stop/' + id + '/' + nodeId,
method: 'put'
})
}

View File

@@ -75,10 +75,24 @@ export const constantRoutes = [
meta: { title: 'Canal主配置', icon: 'form' } meta: { title: 'Canal主配置', icon: 'form' }
}, },
{ {
path: 'canalInstance', path: 'canalInstances',
name: '实例管理', name: '实例管理',
component: () => import('@/views/canalServer/canalInstance'), component: () => import('@/views/canalServer/canalInstance'),
meta: { title: '实例管理', icon: 'nested' } meta: { title: '实例管理', icon: 'nested' }
},
{
path: 'canalInstance/add',
name: '新建实例配置',
component: () => import('@/views/canalServer/canalInstanceAdd'),
meta: { title: '新建实例配置' },
hidden: true
},
{
path: 'canalInstance/modify',
name: '修改实例配置',
component: () => import('@/views/canalServer/canalInstanceUpdate'),
meta: { title: '修改实例配置' },
hidden: true
} }
] ]
}, },

View File

@@ -1,5 +1,12 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.name" placeholder="实例名称" style="width: 200px;" class="filter-item" />
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="fetchData()">查询</el-button>
&nbsp;&nbsp;
<el-button class="filter-item" type="primary" @click="handleCreate()">新建实例</el-button>
<el-button class="filter-item" type="info" @click="fetchData()">刷新列表</el-button>
</div>
<el-table <el-table
v-loading="listLoading" v-loading="listLoading"
:data="list" :data="list"
@@ -30,8 +37,8 @@
操作<i class="el-icon-arrow-down el-icon--right" /> 操作<i class="el-icon-arrow-down el-icon--right" />
</el-button> </el-button>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleUpdate(scope.row)">修改节点</el-dropdown-item> <el-dropdown-item @click.native="handleUpdate(scope.row)">修改实例</el-dropdown-item>
<el-dropdown-item @click.native="handleDelete(scope.row)">删除节点</el-dropdown-item> <el-dropdown-item @click.native="handleDelete(scope.row)">删除实例</el-dropdown-item>
<el-dropdown-item @click.native="handleStart(scope.row)">启动服务</el-dropdown-item> <el-dropdown-item @click.native="handleStart(scope.row)">启动服务</el-dropdown-item>
<el-dropdown-item @click.native="handleStop(scope.row)">停止服务</el-dropdown-item> <el-dropdown-item @click.native="handleStop(scope.row)">停止服务</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
@@ -43,7 +50,7 @@
</template> </template>
<script> <script>
import { getCanalInstances } from '@/api/canalInstance' import { getCanalInstances, deleteCanalInstance, startInstance, stopNodeServer } from '@/api/canalInstance'
export default { export default {
filters: { filters: {
@@ -59,7 +66,10 @@ export default {
data() { data() {
return { return {
list: null, list: null,
listLoading: true listLoading: true,
listQuery: {
name: ''
}
} }
}, },
created() { created() {
@@ -68,10 +78,90 @@ export default {
methods: { methods: {
fetchData() { fetchData() {
this.listLoading = true this.listLoading = true
getCanalInstances().then(res => { getCanalInstances(this.listQuery).then(res => {
this.list = res.data this.list = res.data
this.listLoading = false this.listLoading = false
}) })
},
handleCreate() {
this.$router.push('/canalServer/canalInstance/add')
},
handleUpdate(row) {
this.$router.push('/canalServer/canalInstance/modify?id=' + row.id)
},
handleDelete(row) {
this.$confirm('删除实例配置会导致Canal实例停止', '确定删除实例信息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteCanalInstance(row.id).then((res) => {
if (res.data === 'success') {
this.fetchData()
this.$message({
message: '删除实例信息成功',
type: 'success'
})
} else {
this.$message({
message: '删除实例点信息失败',
type: 'error'
})
}
})
})
},
handleStart(row) {
if (row.nodeId !== null) {
this.$message({ message: '当前实例不是停止状态,无法启动', type: 'error' })
return
}
this.$confirm('启动Canal Instance: ' + row.name, '确定启动实例服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
startInstance(row.id).then((res) => {
if (res.data) {
this.fetchData()
this.$message({
message: '启动成功',
type: 'success'
})
} else {
this.$message({
message: '启动实例服务出现异常',
type: 'error'
})
}
})
})
},
handleStop(row) {
if (row.nodeId === null) {
this.$message({ message: '当前实例不是启动状态,无法停止', type: 'error' })
return
}
this.$confirm('停止Canal Instance: ' + row.name, '确定停止实例服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
stopNodeServer(row.id, row.nodeId).then((res) => {
if (res.data) {
this.fetchData()
this.$message({
message: '停止成功',
type: 'success'
})
} else {
this.$message({
message: '停止实例服务出现异常',
type: 'error'
})
}
})
})
} }
} }
} }

View File

@@ -0,0 +1,88 @@
<template>
<div>
<el-form ref="form" :model="form">
<div class="filter-container" style="padding-left: 10px;padding-top: 20px;">
<el-input v-model="form.name" placeholder="实例名称" style="width: 200px;" class="filter-item" />
&nbsp;
<el-button class="filter-item" type="primary" @click="onSubmit">新建</el-button>
<el-button class="filter-item" type="info" @click="onBack">返回</el-button>
</div>
<editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" />
</el-form>
</div>
</template>
<script>
import { addCanalInstance } from '@/api/canalInstance'
export default {
components: {
editor: require('vue2-ace-editor')
},
data() {
return {
form: {
name: '',
content: ''
}
}
},
created() {
},
methods: {
editorInit() {
require('brace/ext/language_tools')
require('brace/mode/html')
require('brace/mode/yaml')
require('brace/mode/properties')
require('brace/mode/javascript')
require('brace/mode/less')
require('brace/theme/chrome')
require('brace/snippets/javascript')
},
onSubmit() {
if (this.form.name === '') {
this.$message({
message: '请输入实例名称',
type: 'error'
})
return
}
this.$confirm(
'确定新建',
'确定新建',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
addCanalInstance(this.form).then(response => {
if (response.data === 'success') {
this.$message({
message: '新建成功',
type: 'success'
})
this.$router.push('/canalServer/canalInstances')
} else {
this.$message({
message: '新建失败',
type: 'error'
})
}
})
})
},
onBack() {
history.go(-1)
}
}
}
</script>
<style scoped>
.line{
text-align: center;
}
</style>

View File

@@ -5,7 +5,8 @@
<el-form-item> <el-form-item>
{{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp; {{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp;
<el-button type="primary" @click="onSubmit">修改</el-button> <el-button type="primary" @click="onSubmit">修改</el-button>
<el-button @click="onCancel">重置</el-button> <el-button type="warning" @click="onCancel">重置</el-button>
<el-button type="info" @click="onBack">返回</el-button>
</el-form-item> </el-form-item>
</div> </div>
<editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" /> <editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" />
@@ -14,7 +15,7 @@
</template> </template>
<script> <script>
import { getCanalConfig, updateCanalConfig } from '@/api/canalConfig' import { canalInstanceDetail, updateCanalInstance } from '@/api/canalInstance'
export default { export default {
components: { components: {
@@ -44,16 +45,16 @@ export default {
require('brace/snippets/javascript') require('brace/snippets/javascript')
}, },
loadCanalConfig() { loadCanalConfig() {
getCanalConfig().then(response => { canalInstanceDetail(this.$route.query.id).then(response => {
const data = response.data const data = response.data
this.form.id = data.id this.form.id = data.id
this.form.name = data.name this.form.name = data.name + '/instance.propertios'
this.form.content = data.content this.form.content = data.content
}) })
}, },
onSubmit() { onSubmit() {
this.$confirm( this.$confirm(
'修改Canal主配置可能会导致Server重启,是否继续?', '修改Canal实例配置可能会导致实例重启,是否继续?',
'确定修改', '确定修改',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -61,7 +62,7 @@ export default {
type: 'warning' type: 'warning'
} }
).then(() => { ).then(() => {
updateCanalConfig(this.form).then(response => { updateCanalInstance(this.form).then(response => {
if (response.data === 'success') { if (response.data === 'success') {
this.$message({ this.$message({
message: '修改成功', message: '修改成功',
@@ -79,6 +80,9 @@ export default {
}, },
onCancel() { onCancel() {
this.loadCanalConfig() this.loadCanalConfig()
},
onBack() {
history.go(-1)
} }
} }
} }

View File

@@ -5,7 +5,7 @@
<el-form-item> <el-form-item>
{{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp; {{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp;
<el-button type="primary" @click="onSubmit">修改</el-button> <el-button type="primary" @click="onSubmit">修改</el-button>
<el-button @click="onCancel">重置</el-button> <el-button type="warning" @click="onCancel">重置</el-button>
</el-form-item> </el-form-item>
</div> </div>
<editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" /> <editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" />

View File

@@ -100,7 +100,7 @@
</template> </template>
<script> <script>
import { addNodeServer, getNodeServers, updateNodeServer, deleteNodeServer, startNodeServer } from '@/api/nodeServer' import { addNodeServer, getNodeServers, updateNodeServer, deleteNodeServer, startNodeServer, stopNodeServer } from '@/api/nodeServer'
export default { export default {
filters: { filters: {
@@ -227,12 +227,12 @@ export default {
if (res.data === 'success') { if (res.data === 'success') {
this.fetchData() this.fetchData()
this.$message({ this.$message({
message: '删除点信息成功', message: '删除节点信息成功',
type: 'success' type: 'success'
}) })
} else { } else {
this.$message({ this.$message({
message: '删除点信息失败', message: '删除节点信息失败',
type: 'error' type: 'error'
}) })
} }
@@ -275,7 +275,7 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
startNodeServer(row.id).then((res) => { stopNodeServer(row.id).then((res) => {
if (res.data) { if (res.data) {
this.fetchData() this.fetchData()
this.$message({ this.$message({