canal-admin完善 (#2121)

* admin端口调整完成

* server端适配调整

* 调整部分功能

* 单机/集群admin调试完成

* 重新发布前端页面
This commit is contained in:
rewerma
2019-08-30 18:09:22 +08:00
committed by agapple
parent e8dba41216
commit 89055f7405
60 changed files with 1882 additions and 382 deletions

View File

@@ -0,0 +1,15 @@
package com.alibaba.otter.canal.admin.common;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class DaemonThreadFactory implements ThreadFactory {
public static final ThreadFactory daemonThreadFactory = new DaemonThreadFactory();
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
}

View File

@@ -31,7 +31,7 @@ import com.google.protobuf.ByteString;
/**
* 基于netty实现的admin控制
*
*
* @author agapple 2019年8月26日 上午10:23:44
* @since 1.1.4
*/

View File

@@ -0,0 +1,89 @@
package com.alibaba.otter.canal.admin.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalClusterServic;
import com.alibaba.otter.canal.admin.service.NodeServerService;
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalClusterController {
@Autowired
CanalClusterServic canalClusterServic;
@Autowired
NodeServerService nodeServerService;
@GetMapping(value = "/clusters")
public BaseModel<List<CanalCluster>> clusters(CanalCluster canalCluster, @PathVariable String env) {
return BaseModel.getInstance(canalClusterServic.findList(canalCluster));
}
@PostMapping(value = "/cluster")
public BaseModel<String> save(@RequestBody CanalCluster canalCluster, @PathVariable String env) {
canalClusterServic.save(canalCluster);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/cluster/{id}")
public BaseModel<CanalCluster> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalClusterServic.detail(id));
}
@PutMapping(value = "/cluster")
public BaseModel<String> update(@RequestBody CanalCluster canalCluster, @PathVariable String env) {
canalClusterServic.update(canalCluster);
return BaseModel.getInstance("success");
}
@DeleteMapping(value = "/cluster/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
canalClusterServic.delete(id);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/clustersAndServers")
public BaseModel<List<?>> clustersAndServers(@PathVariable String env) {
List<CanalCluster> clusters = canalClusterServic.findList(new CanalCluster());
JSONObject group = new JSONObject();
group.put("label", "集群");
JSONArray jsonArray = new JSONArray();
clusters.forEach(cluster -> {
JSONObject item = new JSONObject();
item.put("label", cluster.getName());
item.put("value", "cluster:" + cluster.getId());
jsonArray.add(item);
});
group.put("options", jsonArray);
NodeServer param = new NodeServer();
param.setClusterId(-1L);
List<NodeServer> servers = nodeServerService.findAll(param); // 取所有standalone的节点
JSONObject group2 = new JSONObject();
group2.put("label", "单机主机");
JSONArray jsonArray2 = new JSONArray();
servers.forEach(server -> {
JSONObject item = new JSONObject();
item.put("label", server.getName());
item.put("value", "server:" + server.getId());
jsonArray2.add(item);
});
group2.put("options", jsonArray2);
List<JSONObject> result = new ArrayList<>();
result.add(group);
result.add(group2);
return BaseModel.getInstance(result);
}
}

View File

@@ -31,9 +31,10 @@ public class CanalConfigController {
* @param env 环境变量
* @return 配置信息
*/
@GetMapping(value = "/config")
public BaseModel<CanalConfig> canalConfig(@PathVariable String env) {
return BaseModel.getInstance(canalConfigService.getCanalConfig());
@GetMapping(value = "/config/{clusterId}/{serverId}")
public BaseModel<CanalConfig> canalConfig(@PathVariable Long clusterId, @PathVariable Long serverId,
@PathVariable String env) {
return BaseModel.getInstance(canalConfigService.getCanalConfig(clusterId, serverId));
}
/**

View File

@@ -3,15 +3,9 @@ package com.alibaba.otter.canal.admin.controller;
import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.admin.model.Pager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
@@ -38,8 +32,9 @@ public class CanalInstanceController {
* @return 实例列表
*/
@GetMapping(value = "/instances")
public BaseModel<List<CanalInstanceConfig>> list(CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig));
public BaseModel<Pager<CanalInstanceConfig>> list(CanalInstanceConfig canalInstanceConfig,
Pager<CanalInstanceConfig> pager, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig, pager));
}
/**
@@ -130,6 +125,20 @@ public class CanalInstanceController {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "stop"));
}
/**
* 通过操作instance状态启动/停止远程instance
*
* @param id 实例配置id
* @param option 操作类型: start/stop
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/status/{id}")
public BaseModel<Boolean> instanceStart(@PathVariable Long id, @RequestParam String option,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.instanceOperation(id, option));
}
/**
* 获取远程实例运行日志
*
@@ -143,4 +152,16 @@ public class CanalInstanceController {
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteInstanceLog(id, nodeId));
}
/**
* 通过Server id获取所有活动的Instance
*
* @param serverId 节点id
* @param env 环境变量
* @return 实例列表
*/
@GetMapping(value = "/active/instances/{serverId}")
public BaseModel<List<CanalInstanceConfig>> activeInstances(@PathVariable Long serverId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findActiveInstaceByServerId(serverId));
}
}

View File

@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.NodeServerService;
/**
@@ -30,8 +31,9 @@ public class NodeServerController {
* @return 节点信息列表
*/
@GetMapping(value = "/nodeServers")
public BaseModel<List<NodeServer>> nodeServers(NodeServer nodeServer, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.findList(nodeServer));
public BaseModel<Pager<NodeServer>> nodeServers(NodeServer nodeServer, Pager<NodeServer> pager,
@PathVariable String env) {
return BaseModel.getInstance(nodeServerService.findList(nodeServer, pager));
}
/**

View File

@@ -1,26 +1,17 @@
package com.alibaba.otter.canal.admin.controller;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import com.alibaba.otter.canal.admin.service.PollingConfigServer;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.base.Joiner;
/**
* Canal Instance配置管理控制层
@@ -34,43 +25,28 @@ public class PollingConfigController {
private static final byte[] seeds = "canal is best!".getBytes();
@Autowired
CanalInstanceService canalInstanceConfigService;
@Autowired
CanalConfigService canalConfigService;
@Value(value = "${canal.adminUser}")
String user;
@Value(value = "${canal.adminPasswd}")
String passwd;
@Autowired
PollingConfigServer pollingConfigServer;
/**
* 获取server全局配置
*/
@GetMapping(value = "/server_polling")
public BaseModel<CanalConfig> canalConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@PathVariable String env, @RequestParam String md5) {
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalConfig config = canalConfigService.getCanalConfig();
if (StringUtils.isEmpty(md5)) {
return BaseModel.getInstance(config);
} else {
try {
String newMd5 = SecurityUtil.md5String(config.getContent());
if (StringUtils.equals(md5, newMd5)) {
config.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
}
return BaseModel.getInstance(config);
}
CanalConfig canalConfig = pollingConfigServer.getChangedConfig(ip, port, md5);
return BaseModel.getInstance(canalConfig);
}
/**
@@ -78,26 +54,14 @@ public class PollingConfigController {
*/
@GetMapping(value = "/instance_polling/{destination}")
public BaseModel<CanalInstanceConfig> instanceConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@PathVariable String env,
@PathVariable String destination, @RequestParam String md5) {
@PathVariable String env, @PathVariable String destination,
@RequestParam String md5) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalInstanceConfig config = canalInstanceConfigService.findOne(destination);
if (StringUtils.isEmpty(md5)) {
return BaseModel.getInstance(config);
} else {
try {
String newMd5 = SecurityUtil.md5String(config.getContent());
if (StringUtils.equals(md5, newMd5)) {
config.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
}
return BaseModel.getInstance(config);
}
CanalInstanceConfig canalInstanceConfig = pollingConfigServer.getInstanceConfig(destination, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
/**
@@ -105,30 +69,14 @@ public class PollingConfigController {
*/
@GetMapping(value = "/instances_polling")
public BaseModel<CanalInstanceConfig> instancesPoll(@RequestHeader String user, @RequestHeader String passwd,
@PathVariable String env, @RequestParam String ip,
@RequestParam String port, @RequestParam String md5) {
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalInstanceConfig canalInstanceConfig = new CanalInstanceConfig();
List<CanalInstanceConfig> configs = canalInstanceConfigService.findList(canalInstanceConfig);
List<String> instances = configs.stream().map(CanalInstanceConfig::getName).collect(Collectors.toList());
String data = Joiner.on(',').join(instances);
canalInstanceConfig.setContent(data);
if (StringUtils.isEmpty(md5)) {
return BaseModel.getInstance(canalInstanceConfig);
} else {
try {
String newMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
if (StringUtils.equals(md5, newMd5)) {
canalInstanceConfig.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
}
return BaseModel.getInstance(canalInstanceConfig);
}
CanalInstanceConfig canalInstanceConfig = pollingConfigServer.getInstancesConfig(ip, port, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
private boolean auth(String user, String passwd) {

View File

@@ -27,7 +27,7 @@ public class UserController {
public static final LoadingCache<String, User> loginUsers = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build(key -> null); // 用户登录信息缓存
@Autowired

View File

@@ -0,0 +1,70 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* Canal集群信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
@Table(name = "canal_cluster")
public class CanalCluster extends Model {
public static final CanalClusterFinder find = new CanalClusterFinder();
public static class CanalClusterFinder extends Finder<Long, CanalCluster> {
/**
* Construct using the default EbeanServer.
*/
public CanalClusterFinder(){
super(CanalCluster.class);
}
}
@Id
private Long id;
private String name;
private String zkHosts;
private Date modifiedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getZkHosts() {
return zkHosts;
}
public void setZkHosts(String zkHosts) {
this.zkHosts = zkHosts;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}

View File

@@ -8,7 +8,7 @@ import javax.persistence.Entity;
import javax.persistence.Id;
/**
* Canal住配置实体类
* Canal主配置实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
@@ -31,8 +31,11 @@ public class CanalConfig extends Model {
@Id
private Long id;
private Long clusterId;
private Long serverId;
private String name;
private String content;
private String contentMd5;
private String status;
private Date modifiedTime;
@@ -44,6 +47,22 @@ public class CanalConfig extends Model {
this.id = id;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
public String getName() {
return name;
}
@@ -60,6 +79,14 @@ public class CanalConfig extends Model {
this.content = content;
}
public String getContentMd5() {
return contentMd5;
}
public void setContentMd5(String contentMd5) {
this.contentMd5 = contentMd5;
}
public Date getModifiedTime() {
return modifiedTime;
}

View File

@@ -1,12 +1,10 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import javax.persistence.*;
import io.ebean.Finder;
/**
* Canal实例配置信息实体类
@@ -31,16 +29,31 @@ public class CanalInstanceConfig extends Model {
}
@Id
private Long id;
private String name;
private String content;
private String status;
private Date modifiedTime;
private Long id;
@Column(name = "cluster_id")
private Long clusterId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cluster_id", updatable = false, insertable = false)
private CanalCluster canalCluster;
@Column(name = "server_id")
private Long serverId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", updatable = false, insertable = false)
private NodeServer nodeServer;
private String name;
private String content;
private String contentMd5;
private String status; // 1: 正常 0: 停止
private Date modifiedTime;
@Transient
private Long nodeId;
private String clusterServerId;
@Transient
private String nodeIp;
private String runningStatus = "0"; // 1: 运行中 0: 停止
public void init() {
status = "1";
}
public Long getId() {
return id;
@@ -50,6 +63,38 @@ public class CanalInstanceConfig extends Model {
this.id = id;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public CanalCluster getCanalCluster() {
return canalCluster;
}
public void setCanalCluster(CanalCluster canalCluster) {
this.canalCluster = canalCluster;
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
public NodeServer getNodeServer() {
return nodeServer;
}
public void setNodeServer(NodeServer nodeServer) {
this.nodeServer = nodeServer;
}
public String getName() {
return name;
}
@@ -66,6 +111,14 @@ public class CanalInstanceConfig extends Model {
this.content = content;
}
public String getContentMd5() {
return contentMd5;
}
public void setContentMd5(String contentMd5) {
this.contentMd5 = contentMd5;
}
public String getStatus() {
return status;
}
@@ -82,19 +135,19 @@ public class CanalInstanceConfig extends Model {
this.modifiedTime = modifiedTime;
}
public Long getNodeId() {
return nodeId;
public String getClusterServerId() {
return clusterServerId;
}
public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
public void setClusterServerId(String clusterServerId) {
this.clusterServerId = clusterServerId;
}
public String getNodeIp() {
return nodeIp;
public String getRunningStatus() {
return runningStatus;
}
public void setNodeIp(String nodeIp) {
this.nodeIp = nodeIp;
public void setRunningStatus(String runningStatus) {
this.runningStatus = runningStatus;
}
}

View File

@@ -4,9 +4,7 @@ import io.ebean.Finder;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.*;
/**
* 节点信息实体类
@@ -32,14 +30,19 @@ public class NodeServer extends Model {
}
@Id
private Long id;
private String name;
private String ip;
private Integer adminPort;
private Integer metricPort;
private Integer tcpPort;
private String status;
private Date modifiedTime;
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cluster_id", updatable = false, insertable = false)
private CanalCluster canalCluster;
@Column(name = "cluster_id")
private Long clusterId;
private String name;
private String ip;
private Integer adminPort;
private Integer metricPort;
private Integer tcpPort;
private String status;
private Date modifiedTime;
public void init() {
status = "-1";
@@ -53,6 +56,22 @@ public class NodeServer extends Model {
this.id = id;
}
public CanalCluster getCanalCluster() {
return canalCluster;
}
public void setCanalCluster(CanalCluster canalCluster) {
this.canalCluster = canalCluster;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public String getName() {
return name;
}

View File

@@ -0,0 +1,81 @@
package com.alibaba.otter.canal.admin.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Pager<T> implements Serializable {
private static final long serialVersionUID = -986577815091763517L;
private Long count = 0L;
private List<T> items = new ArrayList<T>();
private Integer page = 1;
private Integer size = 20;
private Long offset = 0L;
public Pager(){
}
public Pager(Integer page, Integer size){
this.page = page;
this.size = size;
}
public Pager(Long count, List<T> items){
this.count = count;
this.items = items;
}
public String toString() {
return "PageResult[count=" + this.count + ", items=" + this.items + "]";
}
public Long getCount() {
return this.count;
}
public void setCount(Long count) {
this.count = count;
}
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
public Integer getPage() {
if (page == null) {
page = 1;
}
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
if (size == null) {
size = 20;
}
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Long getOffset() {
offset = (long) (getPage() - 1) * (long) getSize();
return offset;
}
public void setOffset(Long offset) {
this.offset = offset;
}
}

View File

@@ -0,0 +1,18 @@
package com.alibaba.otter.canal.admin.service;
import java.util.List;
import com.alibaba.otter.canal.admin.model.CanalCluster;
public interface CanalClusterServic {
void save(CanalCluster canalCluster);
CanalCluster detail(Long id);
void update(CanalCluster canalCluster);
void delete(Long id);
List<CanalCluster> findList(CanalCluster canalCluster);
}

View File

@@ -4,7 +4,7 @@ import com.alibaba.otter.canal.admin.model.CanalConfig;
public interface CanalConfigService {
CanalConfig getCanalConfig();
CanalConfig getCanalConfig(Long clusterId, Long serverId);
CanalConfig getCanalConfigSummary();

View File

@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.Pager;
/**
* Canal实例配置信息业务层接口
@@ -13,7 +14,7 @@ import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
*/
public interface CanalInstanceService {
List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig);
Pager<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig, Pager<CanalInstanceConfig> pager);
void save(CanalInstanceConfig canalInstanceConfig);
@@ -28,4 +29,8 @@ public interface CanalInstanceService {
Map<String, String> remoteInstanceLog(Long id, Long nodeId);
boolean remoteOperation(Long id, Long nodeId, String option);
boolean instanceOperation(Long id, String option);
List<CanalInstanceConfig> findActiveInstaceByServerId(Long serverId);
}

View File

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import java.util.List;
@@ -14,7 +15,9 @@ public interface NodeServerService {
void delete(Long id);
List<NodeServer> findList(NodeServer nodeServer);
List<NodeServer> findAll(NodeServer nodeServer);
Pager<NodeServer> findList(NodeServer nodeServer, Pager<NodeServer> pager);
int remoteNodeStatus(String ip, Integer port);

View File

@@ -0,0 +1,13 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
public interface PollingConfigServer {
CanalConfig getChangedConfig(String ip, Integer port, String md5);
CanalInstanceConfig getInstancesConfig(String ip, Integer port, String md5);
CanalInstanceConfig getInstanceConfig(String destination, String md5);
}

View File

@@ -0,0 +1,47 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.util.List;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.NodeServer;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.service.CanalClusterServic;
import io.ebean.Query;
@Service
public class CanalClusterServiceImpl implements CanalClusterServic {
public void save(CanalCluster canalCluster) {
canalCluster.save();
}
public CanalCluster detail(Long id) {
return CanalCluster.find.byId(id);
}
public void update(CanalCluster canalCluster) {
canalCluster.update("name", "zkHosts");
}
public void delete(Long id) {
// 判断集群下是否存在server信息
int serverCnt = NodeServer.find.query().where().eq("clusterId", id).findCount();
if (serverCnt > 0) {
throw new ServiceException("Servers exist, delete failed");
}
CanalCluster canalCluster = CanalCluster.find.byId(id);
if (canalCluster != null) {
canalCluster.delete();
}
}
public List<CanalCluster> findList(CanalCluster canalCluster) {
Query<CanalCluster> query = CanalCluster.find.query();
query.order().asc("id");
return query.findList();
}
}

View File

@@ -2,8 +2,14 @@ package com.alibaba.otter.canal.admin.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import io.ebean.Query;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
@@ -27,20 +33,27 @@ public class CanalConfigServiceImpl implements CanalConfigService {
private static final String CANAL_GLOBAL_CONFIG = "canal.properties";
private static final String CANAL_ADAPTER_CONFIG = "application.yml";
public CanalConfig getCanalConfig() {
long id = 1L;
CanalConfig config = CanalConfig.find.byId(id);
if (config == null) {
String context = loadDefaultConf(CANAL_GLOBAL_CONFIG);
if (context == null) {
return null;
public CanalConfig getCanalConfig(Long clusterId, Long serverId) {
CanalConfig config = null;
if (clusterId != null && clusterId != 0) {
config = CanalConfig.find.query().where().eq("clusterId", clusterId).findOne();
} else if (serverId != null && serverId != 0) {
config = CanalConfig.find.query().where().eq("serverId", serverId).findOne();
if (config == null) {
NodeServer nodeServer = NodeServer.find.byId(serverId);
if (nodeServer != null) {
Long cid = nodeServer.getClusterId();
if (cid != null) {
config = CanalConfig.find.query().where().eq("clusterId", cid).findOne();
}
}
}
} else {
throw new ServiceException("clusterId and serverId are all empty");
}
if (config == null) {
config = new CanalConfig();
config.setId(id);
config.setName(CANAL_GLOBAL_CONFIG);
config.setModifiedTime(new Date());
config.setContent(context);
return config;
}
@@ -78,9 +91,19 @@ public class CanalConfigServiceImpl implements CanalConfigService {
public void updateContent(CanalConfig canalConfig) {
try {
canalConfig.insert();
} catch (Throwable e) {
canalConfig.update();
String contentMd5 = SecurityUtil.md5String(canalConfig.getContent());
canalConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
if (canalConfig.getId() != null) {
CanalConfig canalConfigTmp = CanalConfig.find.byId(canalConfig.getId());
if (canalConfigTmp != null && canalConfigTmp.getClusterId() != null) {
canalConfig.setServerId(null);
}
canalConfig.update("serverId", "content", "contentMd5");
} else {
canalConfig.save();
}
}

View File

@@ -1,19 +1,23 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import com.alibaba.otter.canal.admin.model.Pager;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.DaemonThreadFactory;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.connector.AdminConnector;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnectors;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import io.ebean.Query;
/**
* Canal实例配置信息业务层
@@ -24,52 +28,196 @@ import com.alibaba.otter.canal.admin.service.CanalInstanceService;
@Service
public class CanalInstanceServiceImpl implements CanalInstanceService {
public List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig) {
public Pager<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig,
Pager<CanalInstanceConfig> pager) {
Query<CanalInstanceConfig> query = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("name, modifiedTime");
.select("clusterId, serverId, name, modifiedTime")
.fetch("canalCluster", "name")
.fetch("nodeServer", "name,ip,adminPort");
if (canalInstanceConfig != null) {
if (StringUtils.isNotEmpty(canalInstanceConfig.getName())) {
query.where().like("name", "%" + canalInstanceConfig.getName() + "%");
}
}
query.order().asc("id");
List<CanalInstanceConfig> canalInstanceConfigs = query.findList();
// check all canal instances running status
List<NodeServer> nodeServers = NodeServer.find.query().findList();
for (NodeServer nodeServer : nodeServers) {
String runningInstances = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
AdminConnector::getRunningInstances);
if (runningInstances == null) {
continue;
}
String[] instances = runningInstances.split(",");
for (String instance : instances) {
for (CanalInstanceConfig cig : canalInstanceConfigs) {
if (instance.equals(cig.getName())) {
cig.setNodeId(nodeServer.getId());
cig.setNodeIp(nodeServer.getIp());
break;
}
if (StringUtils.isNotEmpty(canalInstanceConfig.getClusterServerId())) {
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
query.where()
.eq("clusterId", Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8)));
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
query.where().eq("serverId", Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7)));
}
}
}
return canalInstanceConfigs;
Query<CanalInstanceConfig> queryCnt = query.copy();
int count = queryCnt.findCount();
pager.setCount((long) count);
query.setFirstRow(pager.getOffset().intValue()).setMaxRows(pager.getSize()).order().asc("id");
List<CanalInstanceConfig> canalInstanceConfigs = query.findList();
pager.setItems(canalInstanceConfigs);
if (canalInstanceConfigs.isEmpty()) {
return pager;
}
// check all canal instances running status
ExecutorService executorService = Executors.newFixedThreadPool(canalInstanceConfigs.size(),
DaemonThreadFactory.daemonThreadFactory);
List<Future<Void>> futures = new ArrayList<>(canalInstanceConfigs.size());
for (CanalInstanceConfig canalInstanceConfig1 : canalInstanceConfigs) {
futures.add(executorService.submit(() -> {
List<NodeServer> nodeServers;
if (canalInstanceConfig1.getClusterId() != null) { // 集群模式
nodeServers = NodeServer.find.query()
.where()
.eq("clusterId", canalInstanceConfig1.getClusterId())
.findList();
} else if (canalInstanceConfig1.getServerId() != null) { // 单机模式
nodeServers = Collections.singletonList(canalInstanceConfig1.getNodeServer());
} else {
return null;
}
for (NodeServer nodeServer : nodeServers) {
String runningInstances = SimpleAdminConnectors
.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::getRunningInstances);
if (runningInstances == null) {
continue;
}
String[] instances = runningInstances.split(",");
for (String instance : instances) {
if (instance.equals(canalInstanceConfig1.getName())) {
if (canalInstanceConfig1.getNodeServer() == null) { // 集群模式下 server 对象为空
canalInstanceConfig1.setNodeServer(nodeServer);
}
canalInstanceConfig1.setRunningStatus("1");
break;
}
}
}
return null;
}));
}
futures.forEach(f -> {
try {
f.get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
// ignore
}
});
executorService.shutdownNow();
return pager;
}
/**
* 通过Server id获取当前Server下所有运行的Instance
*
* @param serverId server id
*/
public List<CanalInstanceConfig> findActiveInstaceByServerId(Long serverId) {
NodeServer nodeServer = NodeServer.find.byId(serverId);
if (nodeServer == null) {
return null;
}
String runningInstances = SimpleAdminConnectors
.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::getRunningInstances);
if (runningInstances == null) {
return null;
}
String[] instances = runningInstances.split(",");
// 单机模式和集群模式区分处理
if (nodeServer.getClusterId() != null) { // 集群模式
List<CanalInstanceConfig> list = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.where()
// 暂停的实例也显示 .eq("status", "1")
.in("name", instances)
.findList();
list.forEach(config -> config.setRunningStatus("1"));
return list; // 集群模式直接返回当前运行的Instances
} else { // 单机模式
// 当前Server所配置的所有Instance
List<CanalInstanceConfig> list = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.where()
// 暂停的实例也显示 .eq("status", "1")
.eq("serverId", serverId)
.findList();
List<String> instanceList = Arrays.asList(instances);
list.forEach(config -> {
if (instanceList.contains(config.getName())) {
config.setRunningStatus("1");
}
});
return list;
}
}
public void save(CanalInstanceConfig canalInstanceConfig) {
if (StringUtils.isEmpty(canalInstanceConfig.getClusterServerId())) {
throw new ServiceException("empty cluster or server id");
}
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
Long clusterId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8));
canalInstanceConfig.setClusterId(clusterId);
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
Long serverId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7));
canalInstanceConfig.setServerId(serverId);
}
try {
String contentMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
canalInstanceConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
canalInstanceConfig.insert();
}
public CanalInstanceConfig detail(Long id) {
return CanalInstanceConfig.find.byId(id);
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig != null) {
if (canalInstanceConfig.getClusterId() != null) {
canalInstanceConfig.setClusterServerId("cluster:" + canalInstanceConfig.getClusterId());
} else if (canalInstanceConfig.getServerId() != null) {
canalInstanceConfig.setClusterServerId("server:" + canalInstanceConfig.getServerId());
}
}
return canalInstanceConfig;
}
public void updateContent(CanalInstanceConfig canalInstanceConfig) {
canalInstanceConfig.update("content");
if (StringUtils.isEmpty(canalInstanceConfig.getClusterServerId())) {
throw new ServiceException("empty cluster or server id");
}
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
Long clusterId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8));
canalInstanceConfig.setClusterId(clusterId);
canalInstanceConfig.setServerId(null);
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
Long serverId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7));
canalInstanceConfig.setServerId(serverId);
canalInstanceConfig.setClusterId(null);
}
try {
String contentMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
canalInstanceConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
canalInstanceConfig.update("content", "contentMd5", "clusterId", "serverId");
}
public void delete(Long id) {
@@ -133,13 +281,34 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
}
Boolean resutl = null;
if ("start".equals(option)) {
resutl = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.startInstance(canalInstanceConfig.getName()));
if (nodeServer.getClusterId() == null) { // 非集群模式
return instanceOperation(id, "start");
// resutl = SimpleAdminConnectors.execute(nodeServer.getIp(),
// nodeServer.getAdminPort(),
// adminConnector ->
// adminConnector.startInstance(canalInstanceConfig.getName()));
}
} else if ("stop".equals(option)) {
resutl = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.stopInstance(canalInstanceConfig.getName()));
if (nodeServer.getClusterId() != null) {
resutl = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.stopInstance(canalInstanceConfig.getName()));
NodeServer nodeServerTmp = nodeServer;
// 集群模式下停止实例后过五秒钟再次启动进行启动抢占
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
SimpleAdminConnectors.execute(nodeServerTmp.getIp(),
nodeServerTmp.getAdminPort(),
adminConnector -> adminConnector.startInstance(canalInstanceConfig.getName()));
} catch (Throwable e) {
}
});
thread.start();
} else { // 非集群模式下直接将状态置为0
return instanceOperation(id, "stop");
}
} else {
return false;
}
@@ -150,4 +319,20 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
return resutl;
}
public boolean instanceOperation(Long id, String option) {
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return false;
}
if ("stop".equals(option)) {
canalInstanceConfig.setStatus("0");
canalInstanceConfig.update("status");
} else if ("start".equals(option)) {
canalInstanceConfig.setStatus("1");
canalInstanceConfig.update("status");
} else {
return false;
}
return true;
}
}

View File

@@ -1,13 +1,12 @@
package com.alibaba.otter.canal.admin.service.impl;
import com.alibaba.otter.canal.admin.common.DaemonThreadFactory;
import com.alibaba.otter.canal.admin.model.*;
import io.ebean.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
@@ -15,7 +14,6 @@ import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.connector.AdminConnector;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnectors;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.NodeServerService;
/**
@@ -31,7 +29,7 @@ public class NodeServerServiceImpl implements NodeServerService {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("admin_port", nodeServer.getAdminPort())
.eq("adminPort", nodeServer.getAdminPort())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
@@ -48,25 +46,39 @@ public class NodeServerServiceImpl implements NodeServerService {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("admin_port", nodeServer.getAdminPort())
.eq("adminPort", nodeServer.getAdminPort())
.ne("id", nodeServer.getId())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.update("name", "ip", "admin_port", "tcp_port", "metric_port");
nodeServer.update("name", "ip", "adminPort", "tcpPort", "metricPort", "clusterId");
}
public void delete(Long id) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer != null) {
// 判断是否存在实例
int cnt = CanalInstanceConfig.find.query().where().eq("serverId", nodeServer.getId()).findCount();
if (cnt > 0) {
throw new ServiceException("当前Server下存在Instance配置, 无法删除");
}
// 同时删除配置
CanalConfig canalConfig = CanalConfig.find.query().where().eq("serverId", id).findOne();
if (canalConfig != null) {
canalConfig.delete();
}
nodeServer.delete();
}
}
public List<NodeServer> findList(NodeServer nodeServer) {
private Query<NodeServer> getBaseQuery(NodeServer nodeServer) {
Query<NodeServer> query = NodeServer.find.query();
query.fetch("canalCluster", "name").setDisableLazyLoading(true);
if (nodeServer != null) {
if (StringUtils.isNotEmpty(nodeServer.getName())) {
query.where().like("name", "%" + nodeServer.getName() + "%");
@@ -74,14 +86,45 @@ public class NodeServerServiceImpl implements NodeServerService {
if (StringUtils.isNotEmpty(nodeServer.getIp())) {
query.where().eq("ip", nodeServer.getIp());
}
}
query.order().asc("id");
List<NodeServer> nodeServers = query.findList();
if (nodeServers.isEmpty()) {
return nodeServers;
if (nodeServer.getClusterId() != null) {
if (nodeServer.getClusterId() == -1) {
query.where().isNull("clusterId");
} else {
query.where().eq("clusterId", nodeServer.getClusterId());
}
}
}
ExecutorService executorService = Executors.newFixedThreadPool(nodeServers.size());
return query;
}
public List<NodeServer> findAll(NodeServer nodeServer) {
Query<NodeServer> query = getBaseQuery(nodeServer);
query.order().asc("id");
return query.findList();
}
public Pager<NodeServer> findList(NodeServer nodeServer, Pager<NodeServer> pager) {
Query<NodeServer> query = getBaseQuery(nodeServer);
Query<NodeServer> queryCnt = query.copy();
int count = queryCnt.findCount();
pager.setCount((long) count);
List<NodeServer> nodeServers = query.order()
.asc("id")
.setFirstRow(pager.getOffset().intValue())
.setMaxRows(pager.getSize())
.findList();
pager.setItems(nodeServers);
if (nodeServers.isEmpty()) {
return pager;
}
ExecutorService executorService = Executors.newFixedThreadPool(nodeServers.size(),
DaemonThreadFactory.daemonThreadFactory);
List<Future<Boolean>> futures = new ArrayList<>(nodeServers.size());
// get all nodes status
for (NodeServer ns : nodeServers) {
@@ -93,15 +136,15 @@ public class NodeServerServiceImpl implements NodeServerService {
}
futures.forEach(f -> {
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
f.get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
// ignore
}
});
executorService.shutdownNow();
return nodeServers;
return pager;
}
public int remoteNodeStatus(String ip, Integer port) {
@@ -114,9 +157,8 @@ public class NodeServerServiceImpl implements NodeServerService {
if (nodeServer == null) {
return "";
}
return SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.canalLog(100));
return SimpleAdminConnectors
.execute(nodeServer.getIp(), nodeServer.getAdminPort(), adminConnector -> adminConnector.canalLog(100));
}
public boolean remoteOperation(Long id, String option) {
@@ -126,7 +168,8 @@ public class NodeServerServiceImpl implements NodeServerService {
}
Boolean result = null;
if ("start".equals(option)) {
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::start);
result = SimpleAdminConnectors
.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::start);
} else if ("stop".equals(option)) {
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::stop);
} else {

View File

@@ -0,0 +1,96 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.PollingConfigServer;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.base.Joiner;
@Service
public class PollingConfigServiceImpl implements PollingConfigServer {
public CanalConfig getChangedConfig(String ip, Integer port, String md5) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", port).findOne();
if (server == null) {
return null;
}
CanalConfig canalConfig;
if (server.getClusterId() != null) { // 集群模式
canalConfig = CanalConfig.find.query().where().eq("clusterId", server.getClusterId()).findOne();
} else { // 单机模式
canalConfig = CanalConfig.find.query().where().eq("serverId", server.getId()).findOne();
}
if (canalConfig != null && !canalConfig.getContentMd5().equals(md5)) { // 内容发生变化
return canalConfig;
}
return null;
}
public CanalInstanceConfig getInstancesConfig(String ip, Integer port, String md5) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", port).findOne();
if (server == null) {
return null;
}
List<CanalInstanceConfig> canalInstanceConfigs;
if (server.getClusterId() != null) { // 集群模式
canalInstanceConfigs = CanalInstanceConfig.find.query()
.where()
.eq("status", "1")
.eq("clusterId", server.getClusterId())
.findList(); // 取属于该集群的所有instance config
} else { // 单机模式
canalInstanceConfigs = CanalInstanceConfig.find.query()
.where()
.eq("status", "1")
.eq("serverId", server.getId())
.findList();
}
CanalInstanceConfig canalInstanceConfig = new CanalInstanceConfig();
List<String> instances = canalInstanceConfigs.stream()
.map(CanalInstanceConfig::getName)
.collect(Collectors.toList());
String data = Joiner.on(',').join(instances);
canalInstanceConfig.setContent(data);
if (!StringUtils.isEmpty(md5)) {
try {
String newMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
if (StringUtils.equals(md5, newMd5)) {
canalInstanceConfig.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
// ignore
}
}
return canalInstanceConfig;
}
public CanalInstanceConfig getInstanceConfig(String destination, String md5) {
CanalInstanceConfig instanceConfig = CanalInstanceConfig.find.query().where().eq("name", destination).findOne();
if (instanceConfig == null) {
return null;
}
if (StringUtils.isEmpty(md5)) {
return instanceConfig;
} else {
try {
String newMd5 = SecurityUtil.md5String(instanceConfig.getContent());
if (StringUtils.equals(md5, newMd5)) {
instanceConfig.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
// ignore
}
return instanceConfig;
}
}
}

View File

@@ -6,14 +6,14 @@ spring:
time-zone: GMT+8
spring.datasource:
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 121212
driver-class-name: com.mysql.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 1
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 121212
driver-class-name: com.mysql.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 1
canal:
adminUser: admin
adminPasswd: admin
adminUser: admin
adminPasswd: admin

View File

@@ -17,7 +17,19 @@ CREATE TABLE `canal_adapter_config` (
`content` text NOT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for canal_cluster
-- ----------------------------
DROP TABLE IF EXISTS `canal_cluster`;
CREATE TABLE `canal_cluster` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(63) NOT NULL,
`zk_hosts` varchar(255) NOT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for canal_config
@@ -25,13 +37,16 @@ CREATE TABLE `canal_adapter_config` (
DROP TABLE IF EXISTS `canal_config`;
CREATE TABLE `canal_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`cluster_id` bigint(20) DEFAULT NULL,
`server_id` bigint(20) DEFAULT NULL,
`name` varchar(45) NOT NULL,
`status` varchar(45) DEFAULT NULL,
`content` text NOT NULL,
`content_md5` varchar(128) NOT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
UNIQUE KEY `sid_UNIQUE` (`server_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for canal_instance_config
@@ -39,13 +54,16 @@ CREATE TABLE `canal_config` (
DROP TABLE IF EXISTS `canal_instance_config`;
CREATE TABLE `canal_instance_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`cluster_id` bigint(20) DEFAULT NULL,
`server_id` bigint(20) DEFAULT NULL,
`name` varchar(45) NOT NULL,
`status` varchar(45) DEFAULT NULL,
`content` text NOT NULL,
`content_md5` varchar(128) DEFAULT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for canal_node_server
@@ -53,6 +71,7 @@ CREATE TABLE `canal_instance_config` (
DROP TABLE IF EXISTS `canal_node_server`;
CREATE TABLE `canal_node_server` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`cluster_id` bigint(20) DEFAULT NULL,
`name` varchar(63) NOT NULL,
`ip` varchar(63) NOT NULL,
`admin_port` int(11) DEFAULT NULL,
@@ -61,7 +80,7 @@ CREATE TABLE `canal_node_server` (
`status` varchar(45) DEFAULT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for canal_user
@@ -77,7 +96,9 @@ CREATE TABLE `canal_user` (
`avatar` varchar(255) DEFAULT NULL,
`creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
-- ----------------------------
-- Records of canal_user

View File

@@ -38,4 +38,10 @@
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</root>
<logger name="io.ebean.SQL" additivity="false">
<level value="INFO" />
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</logger>
</configuration>

View File

@@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=/favicon.ico><title>Canal Admin</title><link href=/static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=/static/css/chunk-libs.5cf311f0.css rel=stylesheet><link href=/static/css/app.bb951cb3.css rel=stylesheet></head><body><noscript><strong>We're sorry but Canal Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/static/js/chunk-elementUI.667f4c87.js></script><script src=/static/js/chunk-libs.c04beefc.js></script><script>(function(e){function n(n){for(var r,c,o=n[0],a=n[1],i=n[2],h=0,l=[];h<o.length;h++)c=o[h],u[c]&&l.push(u[c][0]),u[c]=0;for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(e[r]=a[r]);d&&d(n);while(l.length)l.shift()();return f.push.apply(f,i||[]),t()}function t(){for(var e,n=0;n<f.length;n++){for(var t=f[n],r=!0,c=1;c<t.length;c++){var o=t[c];0!==u[o]&&(r=!1)}r&&(f.splice(n--,1),e=a(a.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},f=[];function o(e){return a.p+"static/js/"+({}[e]||e)+"."+{"chunk-101fc062":"bc898027","chunk-137eaf06":"4b6e5771","chunk-238a81e9":"273bda76","chunk-37c49cbf":"64d26540","chunk-3fcdf643":"4b7133b8","chunk-1ef583f0":"9f611126","chunk-5560c396":"55ba7544","chunk-6f6d6145":"8b7e1719","chunk-4f09fed2":"ff28d88d","chunk-69386cf0":"76d77f5c","chunk-7ec889b7":"6ba68ef0","chunk-e1a839e4":"4357a0af"}[e]+".js"}function a(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var n=[],t={"chunk-101fc062":1,"chunk-238a81e9":1,"chunk-37c49cbf":1,"chunk-1ef583f0":1,"chunk-5560c396":1,"chunk-6f6d6145":1,"chunk-4f09fed2":1,"chunk-69386cf0":1,"chunk-7ec889b7":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-101fc062":"fad9926f","chunk-137eaf06":"31d6cfe0","chunk-238a81e9":"e8e2beee","chunk-37c49cbf":"efc21a9c","chunk-3fcdf643":"31d6cfe0","chunk-1ef583f0":"ec3a43f5","chunk-5560c396":"b660603c","chunk-6f6d6145":"bf63041a","chunk-4f09fed2":"70ec0b86","chunk-69386cf0":"741ff14e","chunk-7ec889b7":"c0585512","chunk-e1a839e4":"31d6cfe0"}[e]+".css",u=a.p+r,f=document.getElementsByTagName("link"),o=0;o<f.length;o++){var i=f[o],h=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(h===r||h===u))return n()}var l=document.getElementsByTagName("style");for(o=0;o<l.length;o++){i=l[o],h=i.getAttribute("data-href");if(h===r||h===u)return n()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=n,d.onerror=function(n){var r=n&&n.target&&n.target.src||u,f=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");f.code="CSS_CHUNK_LOAD_FAILED",f.request=r,delete c[e],d.parentNode.removeChild(d),t(f)},d.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(d)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var f=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=f);var i,h=document.createElement("script");h.charset="utf-8",h.timeout=120,a.nc&&h.setAttribute("nonce",a.nc),h.src=o(e),i=function(n){h.onerror=h.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,f=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");f.type=r,f.request=c,t[1](f)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:h})},12e4);h.onerror=h.onload=i,document.head.appendChild(h)}return Promise.all(n)},a.m=e,a.c=r,a.d=function(e,n,t){a.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},a.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,n){if(1&n&&(e=a(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)a.d(t,r,function(n){return e[n]}.bind(null,r));return t},a.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return a.d(n,"a",n),n},a.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},a.p="/",a.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],h=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var d=h;t()})([]);</script><script src=/static/js/app.a89b2d02.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=/favicon.ico><title>Canal Admin</title><link href=/static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=/static/css/chunk-libs.5cf311f0.css rel=stylesheet><link href=/static/css/app.bb951cb3.css rel=stylesheet></head><body><noscript><strong>We're sorry but Canal Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/static/js/chunk-elementUI.667f4c87.js></script><script src=/static/js/chunk-libs.c04beefc.js></script><script>(function(e){function n(n){for(var r,c,f=n[0],a=n[1],i=n[2],d=0,h=[];d<f.length;d++)c=f[d],u[c]&&h.push(u[c][0]),u[c]=0;for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(e[r]=a[r]);l&&l(n);while(h.length)h.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var f=t[c];0!==u[f]&&(r=!1)}r&&(o.splice(n--,1),e=a(a.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function f(e){return a.p+"static/js/"+({}[e]||e)+"."+{"chunk-0dca2f22":"a2bc28b8","chunk-2301924a":"b0e97fa6","chunk-98f505d0":"aade2e3b","chunk-bd1d44ee":"c2f6b1a9","chunk-101fc062":"bc898027","chunk-14b5f7a4":"d0531bb5","chunk-22553be3":"21abed4f","chunk-37c49cbf":"64d26540","chunk-49959c8b":"6d226f70","chunk-4f09fed2":"ff28d88d","chunk-55380ff2":"681c71c9","chunk-69386cf0":"76d77f5c","chunk-7ec889b7":"295b8aad"}[e]+".js"}function a(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var n=[],t={"chunk-2301924a":1,"chunk-98f505d0":1,"chunk-bd1d44ee":1,"chunk-101fc062":1,"chunk-14b5f7a4":1,"chunk-22553be3":1,"chunk-37c49cbf":1,"chunk-49959c8b":1,"chunk-4f09fed2":1,"chunk-69386cf0":1,"chunk-7ec889b7":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-0dca2f22":"31d6cfe0","chunk-2301924a":"160e7b4a","chunk-98f505d0":"5280f88f","chunk-bd1d44ee":"1528199a","chunk-101fc062":"fad9926f","chunk-14b5f7a4":"f3e06673","chunk-22553be3":"f3e06673","chunk-37c49cbf":"efc21a9c","chunk-49959c8b":"e8e2beee","chunk-4f09fed2":"70ec0b86","chunk-55380ff2":"31d6cfe0","chunk-69386cf0":"741ff14e","chunk-7ec889b7":"c0585512"}[e]+".css",u=a.p+r,o=document.getElementsByTagName("link"),f=0;f<o.length;f++){var i=o[f],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var h=document.getElementsByTagName("style");for(f=0;f<h.length;f++){i=h[f],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var l=document.createElement("link");l.rel="stylesheet",l.type="text/css",l.onload=n,l.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],l.parentNode.removeChild(l),t(o)},l.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(l)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,a.nc&&d.setAttribute("nonce",a.nc),d.src=f(e),i=function(n){d.onerror=d.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var h=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},a.m=e,a.c=r,a.d=function(e,n,t){a.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},a.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,n){if(1&n&&(e=a(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)a.d(t,r,function(n){return e[n]}.bind(null,r));return t},a.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return a.d(n,"a",n),n},a.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},a.p="/",a.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var h=0;h<i.length;h++)n(i[h]);var l=d;t()})([]);</script><script src=/static/js/app.cae6e777.js></script></body></html>

View File

@@ -0,0 +1 @@
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}

View File

@@ -0,0 +1 @@
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}

View File

@@ -0,0 +1 @@
.line[data-v-28f0cd0f]{text-align:center}

View File

@@ -0,0 +1 @@
.wscn-http404-container[data-v-c095f994]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-c095f994]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-c095f994]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-c095f994]{width:100%}.wscn-http404 .pic-404__child[data-v-c095f994]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-c095f994]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-c095f994;animation-name:cloudLeft-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-c095f994]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-c095f994;animation-name:cloudMid-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-c095f994]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-c095f994;animation-name:cloudRight-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-c095f994]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-c095f994]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-c095f994],.wscn-http404 .bullshit__oops[data-v-c095f994]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-c095f994]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994],.wscn-http404 .bullshit__return-home[data-v-c095f994]{opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-c095f994]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}

View File

@@ -0,0 +1 @@
.line[data-v-35af5ff9]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-5c332416]{text-align:center}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2301924a"],{"10df":function(t,e,n){},1248:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticClass:"filter-container",staticStyle:{"padding-left":"10px","padding-top":"20px"}},[n("el-input",{staticClass:"filter-item",staticStyle:{width:"200px"},attrs:{placeholder:"Instance名称"},model:{value:t.form.name,callback:function(e){t.$set(t.form,"name",e)},expression:"form.name"}}),t._v(" "),n("el-select",{staticClass:"filter-item",attrs:{placeholder:"所属集群/主机"},model:{value:t.form.clusterServerId,callback:function(e){t.$set(t.form,"clusterServerId",e)},expression:"form.clusterServerId"}},t._l(t.options,function(e){return n("el-option-group",{key:e.label,attrs:{label:e.label}},t._l(e.options,function(t){return n("el-option",{key:t.value,attrs:{label:t.label,value:t.value}})}),1)}),1),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("保存")]),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(e){t.$set(t.form,"content",e)},expression:"form.content"}})],1)],1)},a=[],c=(n("7f7f"),n("f546")),o=n("d631"),u={components:{editor:n("7c9e")},data:function(){return{options:[],form:{name:"",content:"",clusterServerId:""}}},created:function(){var t=this;Object(o["d"])().then(function(e){t.options=e.data})},methods:{editorInit:function(){n("2099"),n("be9d"),n("2968"),n("e0e5"),n("bb36"),n("0329"),n("95b8"),n("6a21")},onSubmit:function(){var t=this;""!==this.form.name?""!==this.form.clusterServerId?null!==this.form.content&&""!==this.form.content?this.$confirm("确定新建","确定新建",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){Object(c["a"])(t.form).then(function(e){"success"===e.data?(t.$message({message:"新建成功",type:"success"}),t.$router.push("/canalServer/canalInstances")):t.$message({message:"新建失败",type:"error"})})}):this.$message({message:"请输入配置内容",type:"error"}):this.$message({message:"请选择所属集群/主机",type:"error"}):this.$message({message:"请输入Instance名称",type:"error"})},onBack:function(){history.go(-1)}}},i=u,s=(n("2eb8"),n("2877")),l=Object(s["a"])(i,r,a,!1,null,"28f0cd0f",null);e["default"]=l.exports},"2eb8":function(t,e,n){"use strict";var r=n("10df"),a=n.n(r);a.a},d631:function(t,e,n){"use strict";n.d(e,"c",function(){return a}),n.d(e,"a",function(){return c}),n.d(e,"e",function(){return o}),n.d(e,"b",function(){return u}),n.d(e,"d",function(){return i});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/clusters",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/cluster",method:"post",data:t})}function o(t){return Object(r["a"])({url:"/canal/cluster",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/cluster/"+t,method:"delete"})}function i(){return Object(r["a"])({url:"/canal/clustersAndServers",method:"get"})}},f546:function(t,e,n){"use strict";n.d(e,"e",function(){return a}),n.d(e,"b",function(){return c}),n.d(e,"j",function(){return o}),n.d(e,"a",function(){return u}),n.d(e,"c",function(){return i}),n.d(e,"h",function(){return s}),n.d(e,"i",function(){return l}),n.d(e,"f",function(){return f}),n.d(e,"g",function(){return d}),n.d(e,"d",function(){return m});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/instances",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(r["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"delete"})}function s(t,e){return Object(r["a"])({url:"/canal/instance/start/"+t+"/"+e,method:"put"})}function l(t,e){return Object(r["a"])({url:"/canal/instance/stop/"+t+"/"+e,method:"put"})}function f(t,e){return Object(r["a"])({url:"/canal/instance/log/"+t+"/"+e,method:"get"})}function d(t,e){return Object(r["a"])({url:"/canal/instance/status/"+t+"?option="+e,method:"put"})}function m(t){return Object(r["a"])({url:"/canal/active/instances/"+t,method:"get"})}}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-49959c8b"],{"26fc":function(t,s,a){t.exports=a.p+"static/img/404_cloud.0f4bc32b.png"},"8cdb":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"wscn-http404-container"},[a("div",{staticClass:"wscn-http404"},[t._m(0),t._v(" "),a("div",{staticClass:"bullshit"},[a("div",{staticClass:"bullshit__oops"},[t._v("OOPS!")]),t._v(" "),t._m(1),t._v(" "),a("div",{staticClass:"bullshit__headline"},[t._v(t._s(t.message))]),t._v(" "),a("div",{staticClass:"bullshit__info"},[t._v("Please check that the URL you entered is correct, or click the button below to return to the homepage.")]),t._v(" "),a("a",{staticClass:"bullshit__return-home",attrs:{href:""}},[t._v("Back to home")])])])])},c=[function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("div",{staticClass:"pic-404"},[e("img",{staticClass:"pic-404__parent",attrs:{src:a("a36b"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child left",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child mid",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child right",attrs:{src:a("26fc"),alt:"404"}})])},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"bullshit__info"},[t._v("All rights reserved\n "),a("a",{staticStyle:{color:"#20a0ff"},attrs:{href:"https://wallstreetcn.com",target:"_blank"}},[t._v("wallstreetcn")])])}],i={name:"Page404",computed:{message:function(){return"The webmaster said that you can not enter this page..."}}},l=i,n=(a("97ef"),a("2877")),r=Object(n["a"])(l,e,c,!1,null,"c095f994",null);s["default"]=r.exports},"97ef":function(t,s,a){"use strict";var e=a("b51e"),c=a.n(e);c.a},a36b:function(t,s,a){t.exports=a.p+"static/img/404.a57b6f31.png"},b51e:function(t,s,a){}}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-7ec889b7"],{"11ec":function(t,n,e){},"7f19":function(t,n,e){"use strict";var c=e("11ec"),r=e.n(c);r.a},"7f84":function(t,n,e){"use strict";e.r(n);var c=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",[e("el-form",{ref:"form",attrs:{model:t.form}},[e("div",{staticStyle:{"padding-left":"10px","padding-right":"10px","padding-top":"20px"}},[e("el-form-item",[t._v("\n "+t._s(t.form.instance)+"    \n "),e("el-button",{attrs:{type:"primary"},on:{click:t.onRefresh}},[t._v("刷新")]),t._v(" "),e("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),e("el-input",{attrs:{rows:35,readonly:"readonly",type:"textarea"},model:{value:t.form.desc,callback:function(n){t.$set(t.form,"desc",n)},expression:"form.desc"}})],1)])],1)},r=[],a=e("f546"),o={data:function(){return{form:{instance:"",desc:""}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(a["f"])(this.$route.query.id,this.$route.query.nodeId).then(function(n){t.form.instance=n.data.instance+".log",t.form.desc=n.data.log})},onRefresh:function(){this.fetchData()},onBack:function(){history.go(-1)}}},u=o,i=(e("7f19"),e("2877")),f=Object(i["a"])(u,c,r,!1,null,"0b80198c",null);n["default"]=f.exports},f546:function(t,n,e){"use strict";e.d(n,"e",function(){return r}),e.d(n,"b",function(){return a}),e.d(n,"j",function(){return o}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"h",function(){return f}),e.d(n,"i",function(){return s}),e.d(n,"f",function(){return d}),e.d(n,"g",function(){return l}),e.d(n,"d",function(){return h});var c=e("b775");function r(t){return Object(c["a"])({url:"/canal/instances",method:"get",params:t})}function a(t){return Object(c["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(c["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(c["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(c["a"])({url:"/canal/instance/"+t,method:"delete"})}function f(t,n){return Object(c["a"])({url:"/canal/instance/start/"+t+"/"+n,method:"put"})}function s(t,n){return Object(c["a"])({url:"/canal/instance/stop/"+t+"/"+n,method:"put"})}function d(t,n){return Object(c["a"])({url:"/canal/instance/log/"+t+"/"+n,method:"get"})}function l(t,n){return Object(c["a"])({url:"/canal/instance/status/"+t+"?option="+n,method:"put"})}function h(t){return Object(c["a"])({url:"/canal/active/instances/"+t,method:"get"})}}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-98f505d0"],{"4e8a":function(t,e,n){"use strict";var o=n("727e"),r=n.n(o);r.a},"727e":function(t,e,n){},aa99:function(t,e,n){"use strict";n.r(e);var o=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticStyle:{"padding-left":"10px","padding-top":"20px"}},[n("el-form-item",[t._v("\n "+t._s(t.form.name)+"    \n "),n("el-button",{attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("保存")]),t._v(" "),n("el-button",{attrs:{type:"warning"},on:{click:t.onCancel}},[t._v("重置")]),t._v(" "),n("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1)],1),t._v(" "),n("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(e){t.$set(t.form,"content",e)},expression:"form.content"}})],1)],1)},r=[],a=(n("7f7f"),n("b775"));function i(t,e){return Object(a["a"])({url:"/canal/config/"+t+"/"+e,method:"get"})}function c(t){return Object(a["a"])({url:"/canal/config",method:"put",data:t})}var s={components:{editor:n("7c9e")},data:function(){return{form:{id:null,name:"",content:"",serverId:null,clusterId:null}}},created:function(){this.loadCanalConfig()},methods:{editorInit:function(){n("2099"),n("be9d"),n("2968"),n("e0e5"),n("bb36"),n("0329"),n("95b8"),n("6a21")},loadCanalConfig:function(){var t=this,e=0,n=0;this.$route.query.clusterId?e=this.$route.query.clusterId:this.$route.query.serverId&&(n=this.$route.query.serverId),i(e,n).then(function(e){var n=e.data;t.form.id=n.id,t.form.name=n.name,t.form.content=n.content,t.form.serverId=t.$route.query.serverId,t.form.clusterId=t.$route.query.clusterId})},onSubmit:function(){var t=this;null!==this.form.content&&""!==this.form.content?this.$confirm("修改主配置可能会导致Server重启,是否继续?","确定修改",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){c(t.form).then(function(e){"success"===e.data?(t.$message({message:"保存成功",type:"success"}),t.loadCanalConfig()):t.$message({message:"保存失败",type:"error"})})}):this.$message({message:"配置内容不能为空",type:"error"})},onCancel:function(){this.loadCanalConfig()},onBack:function(){history.go(-1)}}},u=s,f=(n("4e8a"),n("2877")),l=Object(f["a"])(u,o,r,!1,null,"35af5ff9",null);e["default"]=l.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-bd1d44ee"],{"878d":function(t,n,e){},b0a2:function(t,n,e){"use strict";e.r(n);var r=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",[e("el-form",{ref:"form",attrs:{model:t.form}},[e("div",{staticStyle:{"padding-left":"10px","padding-top":"20px"}},[e("el-form-item",[t._v("\n "+t._s(t.form.name)+"    \n "),e("el-select",{staticClass:"filter-item",attrs:{placeholder:"所属集群/主机"},model:{value:t.form.clusterServerId,callback:function(n){t.$set(t.form,"clusterServerId",n)},expression:"form.clusterServerId"}},t._l(t.options,function(n){return e("el-option-group",{key:n.label,attrs:{label:n.label}},t._l(n.options,function(t){return e("el-option",{key:t.value,attrs:{label:t.label,value:t.value}})}),1)}),1),t._v(" "),e("el-button",{attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("修改")]),t._v(" "),e("el-button",{attrs:{type:"warning"},on:{click:t.onCancel}},[t._v("重置")]),t._v(" "),e("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1)],1),t._v(" "),e("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(n){t.$set(t.form,"content",n)},expression:"form.content"}})],1)],1)},o=[],c=(e("7f7f"),e("f546")),a=e("d631"),u={components:{editor:e("7c9e")},data:function(){return{options:[],form:{id:null,name:"",content:"",clusterServerId:""}}},created:function(){var t=this;this.loadCanalConfig(),Object(a["d"])().then(function(n){t.options=n.data})},methods:{editorInit:function(){e("2099"),e("be9d"),e("2968"),e("e0e5"),e("bb36"),e("0329"),e("95b8"),e("6a21")},loadCanalConfig:function(){var t=this;Object(c["b"])(this.$route.query.id).then(function(n){var e=n.data;t.form.id=e.id,t.form.name=e.name+"/instance.propertios",t.form.content=e.content,t.form.clusterServerId=e.clusterServerId})},onSubmit:function(){var t=this;this.$confirm("修改Instance配置可能会导致重启,是否继续?","确定修改",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){Object(c["j"])(t.form).then(function(n){"success"===n.data?(t.$message({message:"修改成功",type:"success"}),t.loadCanalConfig()):t.$message({message:"修改失败",type:"error"})})})},onCancel:function(){this.loadCanalConfig()},onBack:function(){history.go(-1)}}},i=u,l=(e("de9c"),e("2877")),s=Object(l["a"])(i,r,o,!1,null,"5c332416",null);n["default"]=s.exports},d631:function(t,n,e){"use strict";e.d(n,"c",function(){return o}),e.d(n,"a",function(){return c}),e.d(n,"e",function(){return a}),e.d(n,"b",function(){return u}),e.d(n,"d",function(){return i});var r=e("b775");function o(t){return Object(r["a"])({url:"/canal/clusters",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/cluster",method:"post",data:t})}function a(t){return Object(r["a"])({url:"/canal/cluster",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/cluster/"+t,method:"delete"})}function i(){return Object(r["a"])({url:"/canal/clustersAndServers",method:"get"})}},de9c:function(t,n,e){"use strict";var r=e("878d"),o=e.n(r);o.a},f546:function(t,n,e){"use strict";e.d(n,"e",function(){return o}),e.d(n,"b",function(){return c}),e.d(n,"j",function(){return a}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"h",function(){return l}),e.d(n,"i",function(){return s}),e.d(n,"f",function(){return d}),e.d(n,"g",function(){return f}),e.d(n,"d",function(){return m});var r=e("b775");function o(t){return Object(r["a"])({url:"/canal/instances",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"get"})}function a(t){return Object(r["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"delete"})}function l(t,n){return Object(r["a"])({url:"/canal/instance/start/"+t+"/"+n,method:"put"})}function s(t,n){return Object(r["a"])({url:"/canal/instance/stop/"+t+"/"+n,method:"put"})}function d(t,n){return Object(r["a"])({url:"/canal/instance/log/"+t+"/"+n,method:"get"})}function f(t,n){return Object(r["a"])({url:"/canal/instance/status/"+t+"?option="+n,method:"put"})}function m(t){return Object(r["a"])({url:"/canal/active/instances/"+t,method:"get"})}}}]);

View File

@@ -0,0 +1,46 @@
import request from '@/utils/request'
export function getCanalClusters(params) {
return request({
url: '/canal/clusters',
method: 'get',
params: params
})
}
export function addCanalCluster(data) {
return request({
url: '/canal/cluster',
method: 'post',
data
})
}
export function canalClusterDetail(id) {
return request({
url: '/canal/cluster/' + id,
method: 'get'
})
}
export function updateCanalCluster(data) {
return request({
url: '/canal/cluster',
method: 'put',
data
})
}
export function deleteCanalCluster(id) {
return request({
url: '/canal/cluster/' + id,
method: 'delete'
})
}
export function getClustersAndServers() {
return request({
url: '/canal/clustersAndServers',
method: 'get'
})
}

View File

@@ -1,8 +1,8 @@
import request from '@/utils/request'
export function getCanalConfig() {
export function getCanalConfig(clusterId, serverId) {
return request({
url: '/canal/config',
url: '/canal/config/' + clusterId + '/' + serverId,
method: 'get'
})
}

View File

@@ -58,3 +58,17 @@ export function instanceLog(id, nodeId) {
method: 'get'
})
}
export function instanceStatus(id, option) {
return request({
url: '/canal/instance/status/' + id + '?option=' + option,
method: 'put'
})
}
export function getActiveInstances(serverId) {
return request({
url: '/canal/active/instances/' + serverId,
method: 'get'
})
}

View File

@@ -0,0 +1,100 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scrollTo'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>

View File

@@ -76,17 +76,24 @@ export const constantRoutes = [
name: 'Canal Server',
meta: { title: 'Canal Server', icon: 'example' },
children: [
{
path: 'canalClusters',
name: 'Canal 集群管理',
component: () => import('@/views/canalServer/CanalCluster'),
meta: { title: '集群管理', icon: 'tree' }
},
{
path: 'nodeServers',
name: 'Server 状态',
component: () => import('@/views/canalServer/NodeServer'),
meta: { title: 'Server 管理', icon: 'tree' }
meta: { title: 'Server 管理', icon: 'form' }
},
{
path: 'config',
name: 'Server 全局配置',
component: () => import('@/views/canalServer/Config'),
meta: { title: 'Server 全局配置', icon: 'form' }
path: 'nodeServer/config',
name: 'Server 配置',
component: () => import('@/views/canalServer/CanalConfig'),
meta: { title: 'Server 配置' },
hidden: true
},
{
path: 'canalInstances',

View File

@@ -0,0 +1,50 @@
Math.easeInOutQuad = function(t, b, c, d) {
t /= d / 2
if (t < 1) {
return c / 2 * t * t + b
}
t--
return -c / 2 * (t * (t - 2) - 1) + b
}
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
})()
// because it's so fucking difficult to detect the scrolling element, just move them all
function move(amount) {
document.documentElement.scrollTop = amount
document.body.parentNode.scrollTop = amount
document.body.scrollTop = amount
}
function position() {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}
export function scrollTo(to, duration, callback) {
const start = position()
const change = to - start
const increment = 20
let currentTime = 0
duration = (typeof (duration) === 'undefined') ? 500 : duration
var animateScroll = function() {
// increment the time
currentTime += increment
// find the value with the quadratic in-out easing function
var val = Math.easeInOutQuad(currentTime, start, change, duration)
// move the document.body
move(val)
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll)
} else {
if (callback && typeof (callback) === 'function') {
// the animation is done so lets callback
callback()
}
}
}
animateScroll()
}

View File

@@ -0,0 +1,204 @@
<template>
<div class="app-container">
<div class="filter-container">
<!-- <el-input v-model="listQuery.name" placeholder="Server 名称" style="width: 200px;" class="filter-item" />
<el-input v-model="listQuery.ip" placeholder="Server IP" style="width: 200px;" class="filter-item" />
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="fetchData()">查询</el-button> -->
<el-button class="filter-item" type="primary" @click="handleCreate()">新建集群</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column label="集群名称" min-width="200" align="center">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="ZK地址" min-width="300" align="center">
<template slot-scope="scope">
<span>{{ scope.row.zkHosts }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="created_at" label="操作" min-width="150">
<template slot-scope="scope">
<el-dropdown trigger="click">
<el-button type="primary" size="mini">
操作<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleConfig(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="handleView(scope.row)">查看Server</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<el-dialog :visible.sync="dialogFormVisible" :title="textMap[dialogStatus]" width="600px">
<el-form ref="dataForm" :rules="rules" :model="canalCluster" label-position="left" label-width="120px" style="width: 400px; margin-left:30px;">
<el-form-item label="集群名称" prop="name">
<el-input v-model="canalCluster.name" />
</el-form-item>
<el-form-item label="ZK地址" prop="zkHosts">
<el-input v-model="canalCluster.zkHosts" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="dataOperation()">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { addCanalCluster, getCanalClusters, updateCanalCluster, deleteCanalCluster } from '@/api/canalCluster'
export default {
filters: {
statusFilter(status) {
const statusMap = {
'1': 'success',
'0': 'gray',
'-1': 'danger'
}
return statusMap[status]
},
statusLabel(status) {
const statusMap = {
'1': '启动',
'0': '停止',
'-1': '断开'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
listQuery: {
name: '',
ip: ''
},
dialogFormVisible: false,
textMap: {
create: '新建集群信息',
update: '修改集群信息'
},
canalCluster: {
id: null,
name: null,
zkHosts: null
},
rules: {
name: [{ required: true, message: '集群名称不能为空', trigger: 'change' }],
zkHosts: [{ required: true, message: 'zk地址不能为空', trigger: 'change' }]
},
dialogStatus: 'create'
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
getCanalClusters(this.listQuery).then(res => {
this.list = res.data
}).finally(() => {
this.listLoading = false
})
},
resetModel() {
this.canalCluster = {
id: null,
name: null,
zkHosts: null
}
},
handleCreate() {
this.resetModel()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
dataOperation() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
if (this.dialogStatus === 'create') {
addCanalCluster(this.canalCluster).then(res => {
this.operationRes(res)
})
}
if (this.dialogStatus === 'update') {
updateCanalCluster(this.canalCluster).then(res => {
this.operationRes(res)
})
}
}
})
},
operationRes(res) {
if (res.data === 'success') {
this.fetchData()
this.dialogFormVisible = false
this.$message({
message: this.textMap[this.dialogStatus] + '成功',
type: 'success'
})
} else {
this.$message({
message: this.textMap[this.dialogStatus] + '失败',
type: 'error'
})
}
},
handleView(row) {
this.$router.push('/canalServer/nodeServers?clusterId=' + row.id)
},
handleConfig(row) {
this.$router.push('/canalServer/nodeServer/config?clusterId=' + row.id)
},
handleUpdate(row) {
this.resetModel()
this.canalCluster = Object.assign({}, row)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
handleDelete(row) {
this.$confirm('删除集群信息会导致服务停止', '确定删除集群信息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteCanalCluster(row.id).then((res) => {
if (res.data === 'success') {
this.fetchData()
this.$message({
message: '删除集群信息成功',
type: 'success'
})
} else {
this.$message({
message: '删除集群信息失败',
type: 'error'
})
}
})
})
}
}
}
</script>

View File

@@ -4,8 +4,9 @@
<div style="padding-left: 10px;padding-top: 20px;">
<el-form-item>
{{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp;
<el-button type="primary" @click="onSubmit">修改</el-button>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button type="warning" @click="onCancel">重置</el-button>
<el-button type="info" @click="onBack">返回</el-button>
</el-form-item>
</div>
<editor v-model="form.content" lang="properties" theme="chrome" width="100%" :height="800" @init="editorInit" />
@@ -25,7 +26,9 @@ export default {
form: {
id: null,
name: '',
content: ''
content: '',
serverId: null,
clusterId: null
}
}
},
@@ -44,16 +47,32 @@ export default {
require('brace/snippets/javascript')
},
loadCanalConfig() {
getCanalConfig().then(response => {
let clusterId = 0
let serverId = 0
if (this.$route.query.clusterId) {
clusterId = this.$route.query.clusterId
} else if (this.$route.query.serverId) {
serverId = this.$route.query.serverId
}
getCanalConfig(clusterId, serverId).then(response => {
const data = response.data
this.form.id = data.id
this.form.name = data.name
this.form.content = data.content
this.form.serverId = this.$route.query.serverId
this.form.clusterId = this.$route.query.clusterId
})
},
onSubmit() {
if (this.form.content === null || this.form.content === '') {
this.$message({
message: '配置内容不能为空',
type: 'error'
})
return
}
this.$confirm(
'修改Server主配置可能会导致Server重启,是否继续?',
'修改主配置可能会导致Server重启,是否继续?',
'确定修改',
{
confirmButtonText: '确定',
@@ -64,13 +83,13 @@ export default {
updateCanalConfig(this.form).then(response => {
if (response.data === 'success') {
this.$message({
message: '修改成功',
message: '保存成功',
type: 'success'
})
this.loadCanalConfig()
} else {
this.$message({
message: '修改失败',
message: '保存失败',
type: 'error'
})
}
@@ -79,6 +98,9 @@ export default {
},
onCancel() {
this.loadCanalConfig()
},
onBack() {
history.go(-1)
}
}
}

View File

@@ -2,7 +2,13 @@
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.name" placeholder="Instance 名称" style="width: 200px;" class="filter-item" />
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="fetchData()">查询</el-button>
<el-select v-model="listQuery.clusterServerId" placeholder="所属集群/主机" class="filter-item">
<el-option key="" label="所属集群/主机" value="" />
<el-option-group v-for="group in options" :key="group.label" :label="group.label">
<el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
</el-option-group>
</el-select>
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="queryData()">查询</el-button>
&nbsp;&nbsp;
<el-button class="filter-item" type="primary" @click="handleCreate()">新建 Instance</el-button>
<el-button class="filter-item" type="info" @click="fetchData()">刷新列表</el-button>
@@ -20,9 +26,25 @@
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="运行Server" min-width="200" align="center">
<el-table-column label="所属集群" min-width="200" align="center">
<template slot-scope="scope">
<span>{{ scope.row.nodeIp }}</span>
<span v-if="scope.row.canalCluster !== null">
{{ scope.row.canalCluster.name }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="所属主机" min-width="200" align="center">
<template slot-scope="scope">
<span v-if="scope.row.nodeServer !== null">
{{ scope.row.nodeServer.name }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="状态" min-width="150" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.runningStatus | statusFilter">{{ scope.row.runningStatus | statusLabel }}</el-tag>
</template>
</el-table-column>
<el-table-column label="修改时间" min-width="200" align="center">
@@ -47,33 +69,29 @@
</template>
</el-table-column>
</el-table>
<el-dialog :visible.sync="dialogFormVisible" title="确定启动Instance" width="500px">
<el-form ref="dataForm" :rules="rules" :model="nodeModel" label-position="left" label-width="120px" style="width: 350px; margin-left:30px;">
<el-form-item label="选择运行Server" prop="nodeId">
<el-select v-model="nodeModel.id" placeholder="选择运行Server">
<el-option v-for="item in nodeServices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="doStartInstance()">确定</el-button>
</div>
</el-dialog>
<pagination v-show="count>0" :total="count" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="fetchData()" />
</div>
</template>
<script>
import { getCanalInstances, deleteCanalInstance, startInstance, stopInstance } from '@/api/canalInstance'
import { getNodeServers } from '@/api/nodeServer'
import { getCanalInstances, deleteCanalInstance, instanceStatus } from '@/api/canalInstance'
import Pagination from '@/components/Pagination'
import { getClustersAndServers } from '@/api/canalCluster'
export default {
components: { Pagination },
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
'1': 'success',
'0': 'gray'
}
return statusMap[status]
},
statusLabel(status) {
const statusMap = {
'1': '启动',
'0': '停止'
}
return statusMap[status]
}
@@ -84,26 +102,37 @@ export default {
listLoading: true,
dialogFormVisible: false,
nodeServices: [],
count: 0,
options: [],
listQuery: {
name: ''
name: '',
clusterServerId: '',
page: 1,
size: 20
},
currentId: null,
nodeModel: {
id: null
},
rules: {
id: [{ required: true, message: '请选择运行Server', trigger: 'change' }]
}
}
},
created() {
getClustersAndServers().then((res) => {
this.options = res.data
})
this.fetchData()
},
methods: {
queryData() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getCanalInstances(this.listQuery).then(res => {
this.list = res.data
this.list = res.data.items
this.count = res.data.count
}).finally(() => {
this.listLoading = false
})
},
@@ -136,55 +165,46 @@ export default {
})
},
handleStart(row) {
if (row.nodeId !== null) {
this.$message({ message: '当前Instance已处于启动状态!', type: 'error' })
return
}
this.currentId = row.id
this.nodeModel.id = null
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
getNodeServers().then((res) => {
this.nodeServices = res.data
this.dialogFormVisible = true
})
},
doStartInstance() {
startInstance(this.currentId, this.nodeModel.id).then((res) => {
if (res.data) {
this.fetchData()
this.$message({
message: '启动成功',
type: 'success'
})
this.dialogFormVisible = false
} else {
this.$message({
message: '启动Instance出现异常',
type: 'error'
})
}
// if (row.runningStatus === '1') {
// this.$message({ message: '当前Instance已处于启动状态!', type: 'error' })
// return
// }
this.$confirm('启动Instance: ' + row.name, '确定启动Instance服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
instanceStatus(row.id, 'start').then((res) => {
if (res.data) {
this.fetchData()
this.$message({
message: '启动成功, 稍后请刷新列表查看状态',
type: 'success'
})
} else {
this.$message({
message: '启动Instance出现异常',
type: 'error'
})
}
})
})
},
handleStop(row) {
if (row.nodeId === null) {
this.$message({ message: '当前Instance已处于停止状态!', type: 'error' })
return
}
// if (row.runningStatus === '0') {
// this.$message({ message: '当前Instance已处于停止状态!', type: 'error' })
// return
// }
this.$confirm('停止Instance: ' + row.name, '确定停止Instance服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
stopInstance(row.id, row.nodeId).then((res) => {
instanceStatus(row.id, 'stop').then((res) => {
if (res.data) {
this.fetchData()
this.$message({
message: '停止成功',
message: '停止成功, 稍后请刷新列表查看状态',
type: 'success'
})
} else {
@@ -201,7 +221,7 @@ export default {
this.$message({ message: '当前Instance不是启动状态,无法查看日志', type: 'warning' })
return
}
this.$router.push('canalInstance/log?id=' + row.id + '&nodeId=' + row.nodeId)
this.$router.push('canalInstance/log?id=' + row.id + '&nodeId=' + row.nodeServer.id)
}
}
}

View File

@@ -3,7 +3,11 @@
<el-form ref="form" :model="form">
<div class="filter-container" style="padding-left: 10px;padding-top: 20px;">
<el-input v-model="form.name" placeholder="Instance名称" style="width: 200px;" class="filter-item" />
&nbsp;
<el-select v-model="form.clusterServerId" placeholder="所属集群/主机" class="filter-item">
<el-option-group v-for="group in options" :key="group.label" :label="group.label">
<el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
</el-option-group>
</el-select>
<el-button class="filter-item" type="primary" @click="onSubmit">保存</el-button>
<el-button class="filter-item" type="info" @click="onBack">返回</el-button>
</div>
@@ -14,6 +18,7 @@
<script>
import { addCanalInstance } from '@/api/canalInstance'
import { getClustersAndServers } from '@/api/canalCluster'
export default {
components: {
@@ -21,13 +26,18 @@ export default {
},
data() {
return {
options: [],
form: {
name: '',
content: ''
content: '',
clusterServerId: ''
}
}
},
created() {
getClustersAndServers().then((res) => {
this.options = res.data
})
},
methods: {
editorInit() {
@@ -48,6 +58,20 @@ export default {
})
return
}
if (this.form.clusterServerId === '') {
this.$message({
message: '请选择所属集群/主机',
type: 'error'
})
return
}
if (this.form.content === null || this.form.content === '') {
this.$message({
message: '请输入配置内容',
type: 'error'
})
return
}
this.$confirm(
'确定新建',
'确定新建',

View File

@@ -4,6 +4,11 @@
<div style="padding-left: 10px;padding-top: 20px;">
<el-form-item>
{{ form.name }}&nbsp;&nbsp;&nbsp;&nbsp;
<el-select v-model="form.clusterServerId" placeholder="所属集群/主机" class="filter-item">
<el-option-group v-for="group in options" :key="group.label" :label="group.label">
<el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
</el-option-group>
</el-select>
<el-button type="primary" @click="onSubmit">修改</el-button>
<el-button type="warning" @click="onCancel">重置</el-button>
<el-button type="info" @click="onBack">返回</el-button>
@@ -16,6 +21,7 @@
<script>
import { canalInstanceDetail, updateCanalInstance } from '@/api/canalInstance'
import { getClustersAndServers } from '@/api/canalCluster'
export default {
components: {
@@ -23,15 +29,20 @@ export default {
},
data() {
return {
options: [],
form: {
id: null,
name: '',
content: ''
content: '',
clusterServerId: ''
}
}
},
created() {
this.loadCanalConfig()
getClustersAndServers().then((res) => {
this.options = res.data
})
},
methods: {
editorInit() {
@@ -50,6 +61,7 @@ export default {
this.form.id = data.id
this.form.name = data.name + '/instance.propertios'
this.form.content = data.content
this.form.clusterServerId = data.clusterServerId
})
},
onSubmit() {

View File

@@ -1,9 +1,14 @@
<template>
<div class="app-container">
<div class="filter-container">
<!-- <el-input v-model="listQuery.name" placeholder="Server 名称" style="width: 200px;" class="filter-item" />
<!--<el-input v-model="listQuery.name" placeholder="Server 名称" style="width: 200px;" class="filter-item" />-->
<el-select v-model="listQuery.clusterId" placeholder="所属集群" class="filter-item">
<el-option key="" label="所属集群" value="" />
<el-option key="-1" label="单机" value="-1" />
<el-option v-for="item in canalClusters" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-model="listQuery.ip" placeholder="Server IP" style="width: 200px;" class="filter-item" />
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="fetchData()">查询</el-button> -->
<el-button class="filter-item" type="primary" icon="el-icon-search" plain @click="queryData()">查询</el-button>
<el-button class="filter-item" type="primary" @click="handleCreate()">新建Server</el-button>
<el-button class="filter-item" type="info" @click="fetchData()">刷新列表</el-button>
</div>
@@ -15,6 +20,16 @@
fit
highlight-current-row
>
<el-table-column label="所属集群" min-width="200" align="center">
<template slot-scope="scope">
<span v-if="scope.row.canalCluster !== null">
{{ scope.row.canalCluster.name }}
</span>
<span v-else>
-
</span>
</template>
</el-table-column>
<el-table-column label="Server 名称" min-width="200" align="center">
<template slot-scope="scope">
{{ scope.row.name }}
@@ -52,18 +67,31 @@
操作<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleConfig(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="handleStart(scope.row)">启动</el-dropdown-item>
<el-dropdown-item @click.native="handleStop(scope.row)">停止</el-dropdown-item>
<el-dropdown-item @click.native="handleInstances(scope.row)">实例</el-dropdown-item>
<el-dropdown-item @click.native="handleLog(scope.row)">日志</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-show="count>0" :total="count" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="fetchData()" />
<el-dialog :visible.sync="dialogFormVisible" :title="textMap[dialogStatus]" width="600px">
<el-form ref="dataForm" :rules="rules" :model="nodeModel" label-position="left" label-width="120px" style="width: 400px; margin-left:30px;">
<el-form-item label="所属集群" prop="clusterId">
<el-select v-if="dialogStatus === 'create'" v-model="nodeModel.clusterId" placeholder="选择所属集群">
<el-option key="" label="单机" value="" />
<el-option v-for="item in canalClusters" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-else v-model="nodeModel.clusterId" placeholder="选择所属集群" disabled="disabled">
<el-option key="" label="单机" value="" />
<el-option v-for="item in canalClusters" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="Server 名称" prop="name">
<el-input v-model="nodeModel.name" />
</el-form-item>
@@ -85,13 +113,54 @@
<el-button type="primary" @click="dataOperation()">确定</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="dialogInstances" title="实例列表" width="800px">
<div class="filter-container">
<el-button class="filter-item" type="info" @click="activeInstances()">刷新列表</el-button>
</div>
<el-table
v-loading="listLoading2"
:data="instanceList"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column label="Instance 名称" min-width="200" align="center">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="状态" min-width="200" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.runningStatus | statusFilter">{{ scope.row.runningStatus | statusLabel }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="200" align="center">
<template slot-scope="scope">
<el-dropdown trigger="click">
<el-button type="primary" size="mini">
操作<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleStartInstance(scope.row)">启动</el-dropdown-item>
<el-dropdown-item @click.native="handleStopInstance(scope.row)">停止</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import { addNodeServer, getNodeServers, updateNodeServer, deleteNodeServer, startNodeServer, stopNodeServer } from '@/api/nodeServer'
import { getActiveInstances, stopInstance, startInstance } from '@/api/canalInstance'
import { getCanalClusters } from '@/api/canalCluster'
import Pagination from '@/components/Pagination'
export default {
components: { Pagination },
filters: {
statusFilter(status) {
const statusMap = {
@@ -113,18 +182,28 @@ export default {
data() {
return {
list: null,
instanceList: null,
listLoading: true,
listLoading2: true,
serverIdTmp: null,
canalClusters: [],
count: 0,
listQuery: {
name: '',
ip: ''
ip: '',
clusterId: null,
page: 1,
size: 20
},
dialogFormVisible: false,
dialogInstances: false,
textMap: {
create: '新建Server信息',
update: '修改Server信息'
},
nodeModel: {
id: undefined,
clusterId: null,
name: null,
ip: null,
adminPort: 11110,
@@ -134,26 +213,43 @@ export default {
rules: {
name: [{ required: true, message: 'Server 名称不能为空', trigger: 'change' }],
ip: [{ required: true, message: 'Server IP不能为空', trigger: 'change' }],
port: [{ required: true, message: 'Server admin端口不能为空', trigger: 'change' }]
adminPort: [{ required: true, message: 'Server admin端口不能为空', trigger: 'change' }]
},
dialogStatus: 'create'
}
},
// { min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'change' }
created() {
getCanalClusters().then((res) => {
this.canalClusters = res.data
})
if (this.$route.query.clusterId) {
try {
this.listQuery.clusterId = Number(this.$route.query.clusterId)
} catch (e) {
console.log(e)
}
}
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
getNodeServers(this.listQuery).then(res => {
this.list = res.data
this.list = res.data.items
this.count = res.data.count
}).finally(() => {
this.listLoading = false
})
},
queryData() {
this.listQuery.page = 1
this.fetchData()
},
resetModel() {
this.nodeModel = {
id: undefined,
clusterId: null,
name: null,
ip: null,
adminPort: null,
@@ -169,6 +265,19 @@ export default {
this.$refs['dataForm'].clearValidate()
})
},
handleInstances(row) {
this.serverIdTmp = row.id
this.activeInstances()
},
activeInstances() {
this.listLoading2 = true
this.dialogInstances = true
getActiveInstances(this.serverIdTmp).then(res => {
this.instanceList = res.data
}).finally(() => {
this.listLoading2 = false
})
},
dataOperation() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
@@ -200,6 +309,9 @@ export default {
})
}
},
handleConfig(row) {
this.$router.push('/canalServer/nodeServer/config?serverId=' + row.id)
},
handleUpdate(row) {
this.resetModel()
this.nodeModel = Object.assign({}, row)
@@ -210,7 +322,7 @@ export default {
})
},
handleDelete(row) {
this.$confirm('删除Server信息并不会导致节点服务停止', '确定删除Server信息', {
this.$confirm('删除Server信息会导致节点服务停止', '确定删除Server信息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -285,6 +397,58 @@ export default {
},
handleLog(row) {
this.$router.push('nodeServer/log?id=' + row.id)
},
handleStartInstance(row) {
if (row.runningStatus !== '0') {
this.$message({ message: '当前Instance不是停止状态,无法启动', type: 'error' })
return
}
this.$confirm('启动Instance服务', '确定启动Instance服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
startInstance(row.id, this.serverIdTmp).then((res) => {
if (res.data) {
this.activeInstances()
this.$message({
message: '启动成功, 稍后请刷新列表查看状态',
type: 'success'
})
} else {
this.$message({
message: '启动Instance服务出现异常',
type: 'error'
})
}
})
})
},
handleStopInstance(row) {
if (row.runningStatus !== '1') {
this.$message({ message: '当前Instance不是运行状态,无法停止', type: 'error' })
return
}
this.$confirm('集群模式下停止实例其它主机将会抢占执行该实例', '停止 Instance 服务', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
stopInstance(row.id, this.serverIdTmp).then((res) => {
if (res.data) {
this.activeInstances()
this.$message({
message: '停止成功, 稍后请刷新列表查看状态',
type: 'success'
})
} else {
this.$message({
message: '停止Instance服务出现异常',
type: 'error'
})
}
})
})
}
}
}

View File

@@ -154,86 +154,87 @@ public class CanalController {
final ServerRunningData serverData = new ServerRunningData(cid, registerIp + ":" + port);
ServerRunningMonitors.setServerData(serverData);
ServerRunningMonitors.setRunningMonitors(MigrateMap.makeComputingMap(new Function<String, ServerRunningMonitor>() {
ServerRunningMonitors
.setRunningMonitors(MigrateMap.makeComputingMap(new Function<String, ServerRunningMonitor>() {
public ServerRunningMonitor apply(final String destination) {
ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData);
runningMonitor.setDestination(destination);
runningMonitor.setListener(new ServerRunningListener() {
public ServerRunningMonitor apply(final String destination) {
ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData);
runningMonitor.setDestination(destination);
runningMonitor.setListener(new ServerRunningListener() {
public void processActiveEnter() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
embededCanalServer.start(destination);
if (canalMQStarter != null) {
canalMQStarter.startDestination(destination);
public void processActiveEnter() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
embededCanalServer.start(destination);
if (canalMQStarter != null) {
canalMQStarter.startDestination(destination);
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
}
public void processActiveExit() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
if (canalMQStarter != null) {
canalMQStarter.stopDestination(destination);
public void processActiveExit() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
if (canalMQStarter != null) {
canalMQStarter.stopDestination(destination);
}
embededCanalServer.stop(destination);
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
embededCanalServer.stop(destination);
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
}
public void processStart() {
try {
if (zkclientx != null) {
final String path = ZookeeperPathUtils.getDestinationClusterNode(destination,
registerIp + ":" + port);
initCid(path);
zkclientx.subscribeStateChanges(new IZkStateListener() {
public void processStart() {
try {
if (zkclientx != null) {
final String path = ZookeeperPathUtils.getDestinationClusterNode(destination,
registerIp + ":" + port);
initCid(path);
zkclientx.subscribeStateChanges(new IZkStateListener() {
public void handleStateChanged(KeeperState state) throws Exception {
public void handleStateChanged(KeeperState state) throws Exception {
}
}
public void handleNewSession() throws Exception {
initCid(path);
}
public void handleNewSession() throws Exception {
initCid(path);
}
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
});
@Override
public void handleSessionEstablishmentError(Throwable error) throws Exception {
logger.error("failed to connect to zookeeper", error);
}
});
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
}
public void processStop() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
if (zkclientx != null) {
final String path = ZookeeperPathUtils.getDestinationClusterNode(destination,
registerIp + ":" + port);
releaseCid(path);
public void processStop() {
try {
MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination));
if (zkclientx != null) {
final String path = ZookeeperPathUtils.getDestinationClusterNode(destination,
registerIp + ":" + port);
releaseCid(path);
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
} finally {
MDC.remove(CanalConstants.MDC_DESTINATION);
}
}
});
if (zkclientx != null) {
runningMonitor.setZkClient(zkclientx);
});
if (zkclientx != null) {
runningMonitor.setZkClient(zkclientx);
}
// 触发创建一下cid节点
runningMonitor.init();
return runningMonitor;
}
// 触发创建一下cid节点
runningMonitor.init();
return runningMonitor;
}
}));
}));
// 初始化monitor机制
autoScan = BooleanUtils.toBoolean(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN));
@@ -285,7 +286,8 @@ public class CanalController {
instanceConfigMonitors = MigrateMap.makeComputingMap(new Function<InstanceMode, InstanceConfigMonitor>() {
public InstanceConfigMonitor apply(InstanceMode mode) {
int scanInterval = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL));
int scanInterval = Integer
.valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL));
if (mode.isSpring()) {
SpringInstanceConfigMonitor monitor = new SpringInstanceConfigMonitor();
@@ -381,7 +383,7 @@ public class CanalController {
}
private PlainCanalConfigClient getManagerClient(String managerAddress) {
return new PlainCanalConfigClient(managerAddress, this.adminUser, this.adminPasswd);
return new PlainCanalConfigClient(managerAddress, this.adminUser, this.adminPasswd, null, adminPort);
}
private void initInstanceConfig(Properties properties) {
@@ -393,7 +395,8 @@ public class CanalController {
InstanceConfig oldConfig = instanceConfigs.put(destination, config);
if (oldConfig != null) {
logger.warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config);
logger
.warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config);
}
}
}

View File

@@ -27,7 +27,7 @@ public class CanalLauncher {
private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class);
public static final CountDownLatch runningLatch = new CountDownLatch(1);
private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("canal-server-scan"));
new NamedThreadFactory("canal-server-scan"));
public static void main(String[] args) {
try {
@@ -49,11 +49,19 @@ public class CanalLauncher {
if (StringUtils.isNotEmpty(managerAddress)) {
String user = properties.getProperty(CanalConstants.CANAL_ADMIN_USER);
String passwd = properties.getProperty(CanalConstants.CANAL_ADMIN_PASSWD);
final PlainCanalConfigClient configClient = new PlainCanalConfigClient(managerAddress, user, passwd);
String adminPort = properties.getProperty(CanalConstants.CANAL_ADMIN_PORT);
if (StringUtils.isEmpty(adminPort)) {
adminPort = "11110";
}
final PlainCanalConfigClient configClient = new PlainCanalConfigClient(managerAddress,
user,
passwd,
"",
Integer.parseInt(adminPort));
PlainCanal canalConfig = configClient.findServer(null);
properties = canalConfig.getProperties();
int scanIntervalInSecond = Integer.valueOf(properties.getProperty(CanalConstants.CANAL_AUTO_SCAN_INTERVAL,
"5"));
int scanIntervalInSecond = Integer
.valueOf(properties.getProperty(CanalConstants.CANAL_AUTO_SCAN_INTERVAL, "5"));
executor.scheduleWithFixedDelay(new Runnable() {
private PlainCanal lastCanalConfig;

View File

@@ -22,24 +22,26 @@ import com.google.common.collect.MigrateMap;
/**
* 基于manager配置的实现
*
*
* @author agapple 2019年8月26日 下午10:00:20
* @since 1.1.4
*/
public class ManagerInstanceConfigMonitor extends AbstractCanalLifeCycle implements InstanceConfigMonitor, CanalLifeCycle {
private static final Logger logger = LoggerFactory.getLogger(ManagerInstanceConfigMonitor.class);
private static final Logger logger = LoggerFactory
.getLogger(ManagerInstanceConfigMonitor.class);
private long scanIntervalInSecond = 5;
private InstanceAction defaultAction = null;
private Map<String, InstanceAction> actions = new MapMaker().makeMap();
private Map<String, PlainCanal> configs = MigrateMap.makeComputingMap(new Function<String, PlainCanal>() {
private Map<String, PlainCanal> configs = MigrateMap
.makeComputingMap(new Function<String, PlainCanal>() {
public PlainCanal apply(String destination) {
return new PlainCanal();
}
});
public PlainCanal apply(String destination) {
return new PlainCanal();
}
});
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("canal-instance-scan"));
new NamedThreadFactory("canal-instance-scan"));
private volatile boolean isFirst = true;
private PlainCanalConfigClient configClient;
@@ -84,7 +86,7 @@ public class ManagerInstanceConfigMonitor extends AbstractCanalLifeCycle impleme
}
private void scan() {
String instances = configClient.findInstances(ip, String.valueOf(port), lastInstanceMD5);
String instances = configClient.findInstances(lastInstanceMD5);
final List<String> is = Lists.newArrayList(StringUtils.split(instances, ','));
List<String> start = Lists.newArrayList();
List<String> stop = Lists.newArrayList();

View File

@@ -2,6 +2,8 @@ package com.alibaba.otter.canal.instance.manager.plain;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
@@ -19,7 +21,7 @@ import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* 远程配置获取
*
*
* @author rewerma 2019-01-25 下午05:20:16
* @author agapple 2019年8月26日 下午7:52:06
* @since 1.1.4
@@ -31,8 +33,10 @@ public class PlainCanalConfigClient extends AbstractCanalLifeCycle implements Ca
private String user;
private String passwd;
private HttpHelper httpHelper;
private String localIp;
private int adminPort;
public PlainCanalConfigClient(String configURL, String user, String passwd){
public PlainCanalConfigClient(String configURL, String user, String passwd, String localIp, int adminPort){
this.configURL = configURL;
if (!StringUtils.startsWithIgnoreCase(configURL, "http")) {
this.configURL = "http://" + configURL;
@@ -42,6 +46,16 @@ public class PlainCanalConfigClient extends AbstractCanalLifeCycle implements Ca
this.user = user;
this.passwd = passwd;
this.httpHelper = new HttpHelper();
if (StringUtils.isNotEmpty(localIp)) {
this.localIp = localIp;
} else {
try {
this.localIp = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
this.adminPort = adminPort;
}
/**
@@ -53,7 +67,7 @@ public class PlainCanalConfigClient extends AbstractCanalLifeCycle implements Ca
if (StringUtils.isEmpty(md5)) {
md5 = "";
}
String url = configURL + "/api/v1/config/server_polling?md5=" + md5;
String url = configURL + "/api/v1/config/server_polling?ip=" + localIp + "&port=" + adminPort + "&md5=" + md5;
return queryConfig(url);
}
@@ -71,11 +85,12 @@ public class PlainCanalConfigClient extends AbstractCanalLifeCycle implements Ca
/**
* 返回需要运行的instance列表
*/
public String findInstances(String ip, String port, String md5) {
public String findInstances(String md5) {
if (StringUtils.isEmpty(md5)) {
md5 = "";
}
String url = configURL + "/api/v1/config/instances_polling?md5=" + md5 + "&ip=" + ip + "&port=" + port;
String url = configURL + "/api/v1/config/instances_polling?md5=" + md5 + "&ip=" + localIp + "&port="
+ adminPort;
ResponseModel<CanalConfig> config = doQuery(url);
if (config.data != null) {
return config.data.content;

View File

@@ -1,18 +1,22 @@
package com.alibaba.otter.canal.instance.manager;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.util.Assert;
import com.alibaba.otter.canal.instance.manager.plain.PlainCanal;
import com.alibaba.otter.canal.instance.manager.plain.PlainCanalConfigClient;
@Ignore
public class PlainCanalConfigClientIntegration {
@Test
public void testSimple() {
PlainCanalConfigClient client = new PlainCanalConfigClient("http://127.0.0.1:8089",
"admin",
"4ACFE3202A5FF5CF467898FC58AAB1D615029441");
"4ACFE3202A5FF5CF467898FC58AAB1D615029441",
"127.0.0.1",
11110);
PlainCanal plain = client.findServer(null);
Assert.notNull(plain);
@@ -20,8 +24,11 @@ public class PlainCanalConfigClientIntegration {
plain = client.findServer(plain.getMd5());
Assert.isNull(plain);
plain = client.findInstance("example", null);
Assert.notNull(plain);
String instances = client.findInstances(null);
Assert.notNull(instances);
plain = client.findInstance("example", null);
Assert.notNull(plain);
plain = client.findInstance("example", plain.getMd5());
Assert.isNull(plain);