Compare commits

..

62 Commits

Author SHA1 Message Date
agapple
9c75e25da7 optimizer pom dependency 2019-10-09 14:18:37 +08:00
agapple
7d91850be6 fixed issue #2257 , time 100 parse bugfix 2019-10-09 13:39:22 +08:00
agapple
4af3b24b69 fixed issue #2281 , null reject 2019-10-08 18:50:37 +08:00
craigwu9
e4e66b143c make canal.admin.port canal.admin.user canal.admin.passwd can be override by system env properties (#2274)
* Update SimpleAdminConnectors.java

add more information about which server execute failed

* Update CanalStarter.java

make canal.admin.port canal.admin.user canal.admin.passwd can be override by system env
2019-09-30 20:45:00 +08:00
agapple
dd93e2b0c4 fixed issue #2258 , optimizer rabbitmq send perf & matchTopics only match one Topic 2019-09-30 20:42:52 +08:00
agapple
d505b5ab9d fixed issue #2258 , optimizer rocketmq send perf 2019-09-30 20:19:25 +08:00
agapple
0fb717a97f fixed issue #2258 , optimizer kafka send perf 2019-09-30 14:24:59 +08:00
一杯Hash
c634f98fca bugfix mysql >= 8.0, 缓存层移除,EbeanServer 运行 Fail,Unknown system variable query_cache_size error! (#2264)
threw exception; nested exception is javax.persistence.PersistenceException: java.sql.SQLException: Unknown system variable 'query_cache_size'
2019-09-29 10:51:17 +08:00
rewerma
460479390a bug fix (#2249) 2019-09-26 10:58:28 +08:00
Li Qiang
26013e239d Update README.md 2019-09-25 19:49:22 +08:00
agapple
46fe40ed47 use es6x/es7x instead of elaticsearch project 2019-09-25 19:31:52 +08:00
Koshoooooo
0c27bcf73e Fix issue #2221 (#2242)
fastjson修复了拒绝服务安全问题,升级到当前1.2.58的兼容安全版本1.2.58.sec06
2019-09-25 19:28:13 +08:00
agapple
d86b0e6e67 fixed testcase 2019-09-25 19:16:33 +08:00
EdgarScott
fff623acb1 Add unit tests (#2235)
* Add Unit Tests for the following classes:

com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtilTest
com.alibaba.otter.canal.client.adapter.support.UtilTest
com.taobao.tddl.dbsync.binlog.JsonConversion_Json_ValueTest
com.taobao.tddl.dbsync.binlog.JsonDiffConversionTest
com.taobao.tddl.dbsync.binlog.LogBufferTest
com.taobao.tddl.dbsync.binlog.LogEventTest
com.taobao.tddl.dbsync.binlog.event.LogHeaderTest
com.taobao.tddl.dbsync.binlog.event.RowsLogBufferTest
com.alibaba.otter.canal.parse.driver.mysql.packets.client.RegisterSlaveCommandPacketTest
com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelperTest
com.alibaba.otter.canal.parse.inbound.mysql.ddl.DdlResultTest
com.alibaba.otter.canal.parse.inbound.mysql.ddl.DruidDdlParserTest

These tests were written using Diffblue Cover

* Add dependencies and refactor
2019-09-25 18:53:24 +08:00
rewerma
f45c0206da Add elasticsearch 7.x version adapter support (#2246)
* init es data sync upgrade

* Add elasticsearch 7.x version adapter support
2019-09-25 18:51:34 +08:00
gaecfov
61726c7e05 fix: ES配置文件监控问题 (#2202)
* fix:
ES监控配置文件时如果第一次因为文件错误新增失败后,再次修改不会新增的问题
ES监控配置文件添加的Config缓存没有带Instance导致匹配不到的问题

* fix:ES配置文件监控不生效
2019-09-17 09:22:55 +08:00
耐小心
79552e0a37 rabbitmq(amqp)MQ支持 (#2156) 2019-09-16 12:43:02 +08:00
agapple
5ec2087257 fixed issue #2197 , startup.bat fix 2019-09-16 09:58:27 +08:00
rewerma
5214257cd4 ES子表含group的where条件拼接 (#2190)
* ES子表含group的where条件拼接

* modify
2019-09-16 09:47:02 +08:00
gaecfov
365ca4d01f fix:当查询列包含常量拼接时解析主键字段进行条件拼接时会多出 where null=null (#2188) 2019-09-16 09:44:59 +08:00
agapple
419a73b3de fixed issue #2168, ignore create database 2019-09-12 09:08:30 +08:00
Jony
c093fbb8f6 动态刷新同步锁状态 (#2181)
* 解决修改数据库远程配置,canal-adapter 一直循环destroy、init。

* 解决动态添加instance和对应的adapter无锁状态问题。
2019-09-12 08:52:33 +08:00
rewerma
31bb85bea3 fix bug #2023 and code optimize (#2167)
* 减少等待刷新状态时间

* fix bug #2023
2019-09-12 08:47:22 +08:00
rewerma
803565456f fix modified_time null value error (#2157)
* template config load

* compile vue frontend for product

* fix modified_time null value error

* modify
2019-09-07 21:53:11 +08:00
Li Qiang
520f5771d8 Update README.md 2019-09-06 15:02:34 +08:00
Li Qiang
5fbd6f6606 Update README.md 2019-09-06 14:57:47 +08:00
Li Qiang
bc9c7f0f1d Update README.md 2019-09-06 14:54:22 +08:00
Li Qiang
08a2f0aa44 Update README.md 2019-09-06 14:45:33 +08:00
Li Qiang
a0387faa67 Update README.md 2019-09-06 14:45:14 +08:00
Li Qiang
1dd4674ccb Update README.md 2019-09-06 13:36:04 +08:00
Jony
60e26d1ddd 解决修改数据库远程配置,canal-adapter 一直循环destroy、init。 (#2137) 2019-09-04 12:25:54 +08:00
agapple
ecdab76705 fixed issue #2129 , Negative seek offset 2019-09-03 08:51:21 +08:00
agapple
403fdf997c Update README.md 2019-09-02 19:28:49 +08:00
agapple
a9e83463e0 Update README.md 2019-09-02 19:27:50 +08:00
agapple
2bde4327c7 [maven-release-plugin] prepare for next development iteration 2019-09-02 14:24:16 +08:00
agapple
d53bfd7ee7 [maven-release-plugin] prepare release canal-1.1.4 2019-09-02 14:24:03 +08:00
agapple
ccc45b672c fixed issue #2127 , remove logback stdout 2019-09-02 14:10:29 +08:00
agapple
0d42bdafe2 fixed stdout 2019-09-02 14:04:19 +08:00
agapple
95050ff382 fixed test example 2019-09-02 13:46:35 +08:00
agapple
5addd246ac fixed unused error 2019-09-01 11:20:00 +08:00
agapple
cfc95e2b7a fixed docker config 2019-09-01 10:41:00 +08:00
agapple
44187d9e3a fixed docker config 2019-09-01 10:15:58 +08:00
agapple
c76e8ba0e4 fixed canal-admin 2019-08-31 15:23:58 +08:00
rewerma
1bcae9dc49 template config load (#2123)
* template config load

* compile vue frontend for product
2019-08-31 14:56:28 +08:00
agapple
3a388308d9 fixed compiler error 2019-08-31 13:01:52 +08:00
agapple
a60ec6fa42 fixed tescase failed 2019-08-31 13:00:31 +08:00
agapple
456945fe3a add autoRegister and cluster 2019-08-31 12:59:12 +08:00
agapple
ba24c15833 add conflict file 2019-08-31 12:47:27 +08:00
agapple
4e0b8a0278 fixed admin 2019-08-31 12:24:41 +08:00
rewerma
274e91f8aa bug fix (#2122)
* admin端口调整完成

* server端适配调整

* 调整部分功能

* 单机/集群admin调试完成

* 重新发布前端页面

* bug fix

* 解决server端admin配置会被远程覆盖的问题
解决default-instance.xml无法加载远程zkServers配置的问题

* 集群模式下取活动实例的问题
2019-08-31 12:22:16 +08:00
rewerma
89055f7405 canal-admin完善 (#2121)
* admin端口调整完成

* server端适配调整

* 调整部分功能

* 单机/集群admin调试完成

* 重新发布前端页面
2019-08-30 18:09:22 +08:00
rewerma
e8dba41216 code optimize (#2108)
* code optimize
2019-08-27 22:52:14 +08:00
agapple
f52b2cccd5 fixed testcase 2019-08-27 22:45:27 +08:00
rewerma
7386327af9 code optimize (#2107)
* admin assembly打包

* 修改npm  profile

* code optimize

* code optimize
2019-08-27 22:24:53 +08:00
rewerma
c48407b449 修改npm profile (#2106)
* admin assembly打包

* 修改npm  profile
2019-08-27 20:55:15 +08:00
rewerma
d92badeb45 admin assembly打包 (#2104) 2019-08-27 19:48:22 +08:00
agapple
7093179cc8 fixed issue #2099 , rename poll to polling 2019-08-27 16:25:42 +08:00
agapple
bf89974f7c fixed issue #2099 , support remote config poll 2019-08-27 16:11:20 +08:00
rewerma
2e1b7a5093 通过http从canal-admin中拉配置 (#2093)
* modify

* http拉取配置监听

* revert main pom

* 整理代码
2019-08-26 19:01:08 +08:00
agapple
3d4dbc9a8c fixed issue #2099 , support user/passwd acl and canal admin operator 2019-08-26 18:16:33 +08:00
agapple
e376a129ed fixed canal-admin vue 2019-08-24 16:54:27 +08:00
wangwengeek
983ff5fc73 查询数据过长变成byte数组转化为json解决 (#2090)
Caused by: com.alibaba.fastjson.JSONException: syntax error, pos 2, json : [B@406b7f2 #2089
2019-08-23 17:38:22 +08:00
300 changed files with 19352 additions and 19563 deletions

9
.gitignore vendored
View File

@@ -18,7 +18,8 @@ jtester.properties
*.rpm
client-adapter/example/
*.dat
canal-admin/canal-admin-ui/dist
canal-admin/canal-admin-ui/node
canal-admin/canal-admin-ui/node_modules
conf/
canal-admin/canal-admin-ui/dist/
canal-admin/canal-admin-ui/node/
canal-admin/canal-admin-ui/node_modules/
canal-admin/canal-admin-server/src/main/resources/conf/

View File

@@ -5,11 +5,13 @@
[![average time to resolve an issue](http://isitmaintained.com/badge/resolution/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
[![percentage of issues still open](http://isitmaintained.com/badge/open/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
## 背景
## 简介
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更。从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变更进行同步,由此衍生出了增量订阅和消费业务,从此开启一段新纪元。
![](https://github.com/bucketli/simpletools/blob/master/main_func_v2.png?raw=true)
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
**canal [kə'næl]**,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费
早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。
基于日志增量订阅和消费的业务包括
- 数据库镜像
@@ -18,12 +20,7 @@
- 业务 cache 刷新
- 带业务逻辑的增量数据处理
## 项目介绍
#### 名称:canal [kə'næl]
#### 译意: 水道/管道/沟渠
#### 产品定位: 基于数据库增量日志解析,提供增量数据订阅和消费
#### 关键词: MySQL binlog parser / real-time / queue&topic / index build
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
## 工作原理
@@ -35,7 +32,6 @@
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
#### canal 工作原理
![](http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg)
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
@@ -43,7 +39,7 @@
## 重要版本更新说明
canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
1. canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
- 整体性能测试&优化,提升了150%. #726 参考: [Performance](https://github.com/alibaba/canal/wiki/Performance)
- 原生支持prometheus监控 #765 [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
@@ -51,6 +47,8 @@ canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)
- 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: [Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)
- 原生支持docker镜像 #801 参考: [Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)
2. canal 1.1.4版本,迎来最重要的WebUI能力,引入canal-admin工程,支持面向WebUI的canal动态管理能力,支持配置、任务、日志等在线白屏运维能力,具体文档:[Canal Admin Guide](https://github.com/alibaba/canal/wiki/Canal-Admin-Guide)
## 文档
- [Home](https://github.com/alibaba/canal/wiki/Home)
@@ -60,6 +58,11 @@ canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)
- [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart")
- [Aliyun RDS for MySQL QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)
- [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
- Canal Admin
- [Canal Admin QuickStart](https://github.com/alibaba/canal/wiki/Canal-Admin-QuickStart)
- [Canal Admin Guide](https://github.com/alibaba/canal/wiki/Canal-Admin-Guide)
- [Canal Admin ServerGuide](https://github.com/alibaba/canal/wiki/Canal-Admin-ServerGuide)
- [Canal Admin Docker](https://github.com/alibaba/canal/wiki/Canal-Admin-Docker)
- [AdminGuide](https://github.com/alibaba/canal/wiki/AdminGuide)
- [ClientExample](https://github.com/alibaba/canal/wiki/ClientExample)
- [ClientAPI](https://github.com/alibaba/canal/wiki/ClientAPI)
@@ -92,9 +95,4 @@ canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递
- [阿里巴巴去 Oracle 数据迁移同步工具: yugong](http://github.com/alibaba/yugong)
## 问题反馈
- 邮件: jianghang115@gmail.com
- 报告 issue: [github issues](https://github.com/alibaba/canal/issues)
## 其他资料
- [ ADC 阿里技术嘉年华分享 ppt ( google docs)](https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing)

View File

@@ -1,17 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal-admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
<version>1.1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal-admin-server</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.protocol</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -48,24 +50,17 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>canal-admin-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.1.RELEASE</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
@@ -77,16 +72,56 @@
</tiles>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<attach>false</attach>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.alibaba.otter.canal.admin.CanalAdminApplication</mainClass>
</manifest>
</archive>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.yml</exclude>
<exclude>static/**</exclude>
<exclude>public/**</exclude>
<exclude>templates/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<id>npm</id>
<activation>
<property>
<name>env</name>
<value>release</value>
<value>npm</value>
</property>
</activation>
<build>
@@ -133,6 +168,58 @@
</plugins>
</build>
</profile>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>!release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
</descriptors>
<finalName>canal-admin</finalName>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release</id>
<activation>
<property>
<name>env</name>
<value>release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
</descriptors>
<finalName>canal.admin-${project.version}</finalName>
<outputDirectory>${project.basedir}/../../target</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,52 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>application.yml</include>
<include>logback.xml</include>
<include>public/**</include>
<include>canal_manager.sql</include>
<include>canal-template.properties</include>
<include>instance-template.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,53 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>application.yml</include>
<include>logback.xml</include>
<include>public/**</include>
<include>canal_manager.sql</include>
<include>canal-template.properties</include>
<include>instance-template.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,5 @@
#!/bin/bash
sh stop.sh
sh startup.sh

View File

@@ -0,0 +1,22 @@
@echo off
@if not "%ECHO%" == "" echo %ECHO%
@if "%OS%" == "Windows_NT" setlocal
set ENV_PATH=.\
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
set conf_dir=%ENV_PATH%\..\conf
set CLASSPATH=%conf_dir%
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
set JAVA_MEM_OPTS= -Xms128m -Xmx512m
set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8
set ADAPTER_OPTS= -DappName=canal-admin
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %ADAPTER_OPTS%
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.admin.CanalAdminApplication
echo start cmd : %CMD_STR%
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.admin.CanalAdminApplication

View File

@@ -0,0 +1,84 @@
#!/bin/bash
current_path=`pwd`
case "`uname`" in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
base=${bin_abs_path}/..
export LANG=en_US.UTF-8
export BASE=$base
if [ -f $base/bin/adapter.pid ] ; then
echo "found adapter.pid , Please run stop.sh first ,then startup.sh" 2>&2
exit 1
fi
if [ ! -d $base/logs ] ; then
mkdir -p $base/logs
fi
## set java path
if [ -z "$JAVA" ] ; then
JAVA=$(which java)
fi
ALIBABA_JAVA="/usr/alibaba/java/bin/java"
TAOBAO_JAVA="/opt/taobao/java/bin/java"
if [ -z "$JAVA" ]; then
if [ -f $ALIBABA_JAVA ] ; then
JAVA=$ALIBABA_JAVA
elif [ -f $TAOBAO_JAVA ] ; then
JAVA=$TAOBAO_JAVA
else
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
exit 1
fi
fi
case "$#"
in
0 )
;;
2 )
if [ "$1" = "debug" ]; then
DEBUG_PORT=$2
DEBUG_SUSPEND="n"
JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND"
fi
;;
* )
echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN."
exit;;
esac
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m"
fi
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
CANAL_OPTS="-DappName=canal-admin"
for i in $base/lib/*;
do CLASSPATH=$i:"$CLASSPATH";
done
CLASSPATH="$base/conf:$CLASSPATH";
echo "cd to $bin_abs_path for workaround relative path"
cd $bin_abs_path
echo CLASSPATH :$CLASSPATH
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.admin.CanalAdminApplication 1>>/dev/null 2>&1 &
echo $! > $base/bin/admin.pid
echo "cd to $current_path for continue"
cd $current_path

View File

@@ -0,0 +1,65 @@
#!/bin/bash
cygwin=false;
linux=false;
case "`uname`" in
CYGWIN*)
cygwin=true
;;
Linux*)
linux=true
;;
esac
get_pid() {
STR=$1
PID=$2
if $cygwin; then
JAVA_CMD="$JAVA_HOME\bin\java"
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
else
if $linux; then
if [ ! -z "$PID" ]; then
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
fi
else
if [ ! -z "$PID" ]; then
JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'`
fi
fi
fi
echo $JAVA_PID;
}
base=`dirname $0`/..
pidfile=$base/bin/admin.pid
if [ ! -f "$pidfile" ];then
echo "canal-admin is not running. exists"
exit
fi
pid=`cat $pidfile`
if [ "$pid" == "" ] ; then
pid=`get_pid "appName=canal-admin"`
fi
echo -e "`hostname`: stopping canal $pid ... "
kill $pid
LOOPS=0
while (true);
do
gpid=`get_pid "appName=canal-admin" "$pid"`
if [ "$gpid" == "" ] ; then
echo "Oook! cost:$LOOPS"
`rm $pidfile`
break;
fi
let LOOPS=LOOPS+1
sleep 1
done

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

@@ -0,0 +1,50 @@
package com.alibaba.otter.canal.admin.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TemplateConfigLoader {
private static final Logger logger = LoggerFactory.getLogger(TemplateConfigLoader.class);
public static final String CONF_DIR = "conf";
public static final String CANAL_CONFIG_TMP = "canal-template.properties";
public static final String INSTANCE_CONFIG_TMP = "instance-template.properties";
public static String loadCanalConfig() {
return loadFile(CANAL_CONFIG_TMP);
}
public static String loadInstanceConfig() {
return loadFile(INSTANCE_CONFIG_TMP);
}
private static String loadFile(String fileName) {
File configFile = new File(".." + File.separator + CONF_DIR + File.separator + fileName);
if (!configFile.exists()) {
URL url = TemplateConfigLoader.class.getClassLoader().getResource("");
if (url != null) {
configFile = new File(url.getPath() + fileName + File.separator);
}
}
if (!configFile.exists()) {
return null;
}
try (InputStream in = new FileInputStream(configFile)) {
byte[] bytes = new byte[in.available()];
in.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.error("Read " + fileName + " error", e);
}
return null;
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.otter.canal.admin.common;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Threads {
public static int pool = 60;
public static final ExecutorService executorService = new ThreadPoolExecutor(pool,
pool,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(pool * 20),
DaemonThreadFactory.daemonThreadFactory);
}

View File

@@ -25,23 +25,25 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public class ServiceException extends RuntimeException {
public ServiceException() {
}
private static final long serialVersionUID = -663217666968123330L;
public ServiceException(String message) {
super(message);
}
public ServiceException(){
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(String message){
super(message);
}
public ServiceException(Throwable cause) {
super(cause);
}
public ServiceException(String message, Throwable cause){
super(message, cause);
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ServiceException(Throwable cause){
super(cause);
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace){
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.otter.canal.admin.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* spring util配置类
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
/*
* 注入ApplicationContext
*/
public void setApplicationContext(final ApplicationContext context) throws BeansException {
// 在加载Spring时自动获得context
SpringContext.context = context;
}
public static Object getBean(final String beanName) {
return SpringContext.context.getBean(beanName);
}
public static Object getBean(final Class<?> clz) {
return context.getBean(clz);
}
}

View File

@@ -74,8 +74,8 @@ public class WebConfig implements WebMvcConfigurer {
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(json);
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable e) {
throw new RuntimeException(e);
}
return false;
}
@@ -84,6 +84,7 @@ public class WebConfig implements WebMvcConfigurer {
}
})
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/config/**")
.excludePathPatterns("/api/**/user/login")
.excludePathPatterns("/api/**/user/logout")
.excludePathPatterns("/api/**/user/info");

View File

@@ -0,0 +1,131 @@
package com.alibaba.otter.canal.admin.connector;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
/**
* canal admin操作客户端
*
* @author agapple 2019年8月31日 下午12:46:29
* @since 1.1.4
*/
public interface AdminConnector {
/**
* 链接对应的canal server
*
* @throws CanalClientException
*/
void connect() throws ServiceException;
/**
* 释放链接
*
* @throws CanalClientException
*/
void disconnect() throws ServiceException;
/**
* 获取Canal Server状态
*
* @return 状态代码
*/
boolean check();
/**
* 启动Canal Server
*
* @return 是否成功
*/
boolean start();
/**
* 停止Canal Server
*
* @return 是否成功
*/
boolean stop();
/**
* 重启Canal Server
*
* @return 是否成功
*/
boolean restart();
/**
* 获取所有当前节点下运行中的实例
*
* @return 实例信息
*/
String getRunningInstances();
/**
* 通过实例名检查
*
* @param destination
* @return
*/
boolean checkInstance(String destination);
/**
* 通过实例名启动实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean startInstance(String destination);
/**
* 通过实例名关闭实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean stopInstance(String destination);
/**
* 通过实例名释放,主要针对cluster模式有效(通知当前主机释放instance运行交给其他人来抢占)
*
* @param destination 实例名
* @return 是否成功
*/
boolean releaseInstance(String destination);
/**
* 通过实例名重启实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean restartInstance(String destination);
/**
* 获取Canal Server日志列表
*
* @return 日志信息
*/
String listCanalLog();
/**
* 获取Canal Server日志
*
* @return 日志信息
*/
String canalLog(int lines);
/**
* 获取Instance的机器日志列表
*
* @param destination
*/
String listInstanceLog(String destination);
/**
* 通过实例名获取实例日志
*
* @return 日志信息
*/
String instanceLog(String destination, String fileName, int lines);
}

View File

@@ -0,0 +1,323 @@
package com.alibaba.otter.canal.admin.connector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.protocol.AdminPacket.Ack;
import com.alibaba.otter.canal.protocol.AdminPacket.ClientAuth;
import com.alibaba.otter.canal.protocol.AdminPacket.Handshake;
import com.alibaba.otter.canal.protocol.AdminPacket.InstanceAdmin;
import com.alibaba.otter.canal.protocol.AdminPacket.LogAdmin;
import com.alibaba.otter.canal.protocol.AdminPacket.Packet;
import com.alibaba.otter.canal.protocol.AdminPacket.PacketType;
import com.alibaba.otter.canal.protocol.AdminPacket.ServerAdmin;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.protobuf.ByteString;
/**
* 基于netty实现的admin控制
*
* @author agapple 2019年8月26日 上午10:23:44
* @since 1.1.4
*/
public class SimpleAdminConnector implements AdminConnector {
private static final Logger logger = LoggerFactory.getLogger(SimpleAdminConnector.class);
private String user;
private String passwd;
private SocketAddress address;
private int soTimeout = 60000; // milliseconds
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private SocketChannel channel;
private ReadableByteChannel readableChannel;
private WritableByteChannel writableChannel;
private volatile boolean connected = false;
public SimpleAdminConnector(String ip, int port, String user, String passwd){
this.address = new InetSocketAddress(ip, port);
this.user = user;
this.passwd = passwd;
}
@Override
public void connect() throws ServiceException {
try {
if (connected) {
return;
}
channel = SocketChannel.open();
channel.socket().setSoTimeout(soTimeout);
channel.connect(address);
readableChannel = Channels.newChannel(channel.socket().getInputStream());
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
Packet p = Packet.parseFrom(readNextPacket());
if (p.getVersion() != 1) {
throw new CanalClientException("unsupported version at this client.");
}
if (p.getType() != PacketType.HANDSHAKE) {
throw new CanalClientException("expect handshake but found other type.");
}
Handshake handshake = Handshake.parseFrom(p.getBody());
ByteString seed = handshake.getSeeds(); // seed for auth
String newPasswd = passwd;
if (passwd != null) {
// encode passwd
newPasswd = SecurityUtil.byte2HexStr(SecurityUtil.scramble411(passwd.getBytes(), seed.toByteArray()));
}
ClientAuth ca = ClientAuth.newBuilder()
.setUsername(user != null ? user : "")
.setPassword(ByteString.copyFromUtf8(newPasswd != null ? newPasswd : ""))
.setNetReadTimeout(idleTimeout)
.setNetWriteTimeout(idleTimeout)
.build();
writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTAUTHENTICATION)
.setBody(ca.toByteString())
.build()
.toByteArray());
//
Packet ack = Packet.parseFrom(readNextPacket());
if (ack.getType() != PacketType.ACK) {
throw new CanalClientException("unexpected packet type when ack is expected");
}
Ack ackBody = Ack.parseFrom(ack.getBody());
if (ackBody.getCode() > 0) {
throw new ServiceException("something goes wrong when doing authentication: " + ackBody.getMessage());
}
connected = true;
} catch (IOException e) {
throw new ServiceException(e);
} catch (NoSuchAlgorithmException e) {
throw new ServiceException(e);
}
}
@Override
public void disconnect() throws ServiceException {
if (!connected) {
return;
}
connected = false;
if (readableChannel != null) {
quietlyClose(readableChannel);
readableChannel = null;
}
if (writableChannel != null) {
quietlyClose(writableChannel);
writableChannel = null;
}
if (channel != null) {
quietlyClose(channel);
channel = null;
}
}
@Override
public boolean check() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("check")));
}
@Override
public boolean start() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("start")));
}
@Override
public boolean stop() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("stop")));
}
@Override
public boolean restart() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("restart")));
}
@Override
public String getRunningInstances() {
return doServerAdmin("list");
}
@Override
public boolean checkInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "check")));
}
@Override
public boolean startInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "start")));
}
@Override
public boolean stopInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "stop")));
}
@Override
public boolean releaseInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "release")));
}
@Override
public boolean restartInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "restart")));
}
@Override
public String listCanalLog() {
return doLogAdmin("server", "list", null, null, 0);
}
@Override
public String canalLog(int lines) {
return doLogAdmin("server", "file", null, null, lines);
}
@Override
public String listInstanceLog(String destination) {
return doLogAdmin("instance", "list", destination, null, 0);
}
@Override
public String instanceLog(String destination, String fileName, int lines) {
return doLogAdmin("instance", "file", destination, fileName, lines);
}
// ==================== helper method ====================
private String doServerAdmin(String action) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.SERVER)
.setBody(ServerAdmin.newBuilder().setAction(action).build().toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private String doInstanceAdmin(String destination, String action) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.INSTANCE)
.setBody(InstanceAdmin.newBuilder()
.setDestination(destination)
.setAction(action)
.build()
.toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private String doLogAdmin(String type, String action, String destination, String file, int count) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.LOG)
.setBody(LogAdmin.newBuilder()
.setType(type)
.setAction(action)
.setDestination(destination == null ? "" : destination)
.setFile(file == null ? "" : file)
.setCount(count)
.build()
.toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private void writeWithHeader(byte[] body) throws IOException {
writeWithHeader(writableChannel, body);
}
private byte[] readNextPacket() throws IOException {
return readNextPacket(readableChannel);
}
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
writeHeader.clear();
writeHeader.putInt(body.length);
writeHeader.flip();
channel.write(writeHeader);
channel.write(ByteBuffer.wrap(body));
}
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
readHeader.clear();
read(channel, readHeader);
int bodyLen = readHeader.getInt(0);
ByteBuffer bodyBuf = ByteBuffer.allocate(bodyLen).order(ByteOrder.BIG_ENDIAN);
read(channel, bodyBuf);
return bodyBuf.array();
}
private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) {
int r = channel.read(buffer);
if (r == -1) {
throw new IOException("end of stream when reading header");
}
}
}
private void quietlyClose(Channel channel) {
try {
channel.close();
} catch (IOException e) {
logger.warn("exception on closing channel:{} \n {}", channel, e);
}
}
}

View File

@@ -0,0 +1,36 @@
package com.alibaba.otter.canal.admin.connector;
import java.util.function.Function;
import com.alibaba.otter.canal.admin.config.SpringContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
public class SimpleAdminConnectors {
private static final Logger logger = LoggerFactory.getLogger(SimpleAdminConnectors.class);
public static <R> R execute(String ip, int port, Function<AdminConnector, R> function) {
Environment env = (Environment) SpringContext.getBean(Environment.class);
String defaultUser = env.getProperty("canal.adminUser", "admin");
String defaultPasswd = env.getProperty("canal.adminPasswd", "admin");
return execute(ip, port, defaultUser, defaultPasswd, function);
}
public static <R> R execute(String ip, int port, String user, String passwd, Function<AdminConnector, R> function) {
SimpleAdminConnector connector = new SimpleAdminConnector(ip, port, user, passwd);
try {
connector.connect();
return function.apply(connector);
} catch (Exception e) {
logger.error("connect to ip:{},port:{},user:{},password:{}, failed", ip, port, user,passwd);
logger.error(e.getMessage());
} finally {
connector.disconnect();
}
return null;
}
}

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.CanalClusterService;
import com.alibaba.otter.canal.admin.service.NodeServerService;
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalClusterController {
@Autowired
CanalClusterService 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

@@ -1,9 +1,15 @@
package com.alibaba.otter.canal.admin.controller;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
@@ -26,9 +32,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));
}
/**
@@ -43,4 +50,9 @@ public class CanalConfigController {
canalConfigService.updateContent(canalConfig);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/config/template")
public BaseModel<String> template(@PathVariable String env) {
return BaseModel.getInstance(TemplateConfigLoader.loadCanalConfig());
}
}

View File

@@ -3,11 +3,21 @@ package com.alibaba.otter.canal.admin.controller;
import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
/**
@@ -31,9 +41,9 @@ public class CanalInstanceController {
* @return 实例列表
*/
@GetMapping(value = "/instances")
public BaseModel<List<CanalInstanceConfig>> nodeServers(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));
}
/**
@@ -49,6 +59,18 @@ public class CanalInstanceController {
return BaseModel.getInstance("success");
}
/**
* 实例详情信息
*
* @param id 实例配置id
* @param env 环境变量
* @return 实例信息
*/
@GetMapping(value = "/instance")
public BaseModel<CanalInstanceConfig> config(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.detail(id));
}
/**
* 实例详情信息
*
@@ -112,6 +134,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));
}
/**
* 获取远程实例运行日志
*
@@ -125,4 +161,21 @@ 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.findActiveInstanceByServerId(serverId));
}
@GetMapping(value = "/instance/template")
public BaseModel<String> template(@PathVariable String env) {
return BaseModel.getInstance(TemplateConfigLoader.loadInstanceConfig());
}
}

View File

@@ -1,12 +1,19 @@
package com.alibaba.otter.canal.admin.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
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 +37,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

@@ -0,0 +1,114 @@
package com.alibaba.otter.canal.admin.controller;
import java.security.NoSuchAlgorithmException;
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 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.PollingConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* Canal Instance配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/config")
public class PollingConfigController {
private static final byte[] seeds = "canal is best!".getBytes();
@Value(value = "${canal.adminUser}")
String user;
@Value(value = "${canal.adminPasswd}")
String passwd;
@Autowired
PollingConfigService pollingConfigService;
/**
* 获取server全局配置
*/
@GetMapping(value = "/server_polling")
public BaseModel<CanalConfig> canalConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @RequestParam boolean register,
@RequestParam String cluster, @PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
if (StringUtils.isEmpty(md5) && register) {
// do something
pollingConfigService.autoRegister(ip, port, cluster);
}
CanalConfig canalConfig = pollingConfigService.getChangedConfig(ip, port, md5);
return BaseModel.getInstance(canalConfig);
}
/**
* 获取单个instance的配置
*/
@GetMapping(value = "/instance_polling/{destination}")
public BaseModel<CanalInstanceConfig> instanceConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@PathVariable String env,
@PathVariable String destination, @RequestParam String md5) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalInstanceConfig canalInstanceConfig = pollingConfigService.getInstanceConfig(destination, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
/**
* 获取对应server(ip+port)所需要运行的instance列表
*/
@GetMapping(value = "/instances_polling")
public BaseModel<CanalInstanceConfig> instancesPoll(@RequestHeader String user, @RequestHeader String passwd,
@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 = pollingConfigService.getInstancesConfig(ip, port, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
private boolean auth(String user, String passwd) {
// 如果user/passwd密码为空,则任何用户账户都能登录
if ((StringUtils.isEmpty(this.user) || StringUtils.equals(this.user, user))) {
if (StringUtils.isEmpty(this.passwd)) {
return true;
} else if (StringUtils.isEmpty(passwd)) {
// 如果server密码有配置,客户端密码为空,则拒绝
return false;
}
try {
// manager这里保存了原始密码,反过来和canal发送过来的进行校验
byte[] passForClient = SecurityUtil.scramble411(this.passwd.getBytes(), seeds);
return SecurityUtil.scrambleServerAuth(passForClient, SecurityUtil.hexStr2Bytes(passwd), seeds);
} catch (NoSuchAlgorithmException e) {
return false;
}
}
return false;
}
}

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

@@ -1,90 +0,0 @@
package com.alibaba.otter.canal.admin.jmx;
/**
* Canal Server JMX MBean
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalServerMXBean {
/**
* 获取Canal Server状态
*
* @return 状态代码
*/
int getStatus();
/**
* 启动Canal Server
*
* @return 是否成功
*/
boolean start();
/**
* 停止Canal Server
*
* @return 是否成功
*/
boolean stop();
/**
* 重启Canal Server
*
* @return 是否成功
*/
boolean restart();
/**
* 退出Canal Server(关闭进程)
*
* @return 是否成功
*/
boolean exit();
/**
* 通过实例名启动实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean startInstance(String destination);
/**
* 通过实例名关闭实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean stopInstance(String destination);
/**
* 通过实例名重启实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean reloadInstance(String destination);
/**
* 获取所有当前节点下运行中的实例
*
* @return 实例信息
*/
String getRunningInstances();
/**
* 获取Canal Server日志(末尾100行)
*
* @return 日志信息
*/
String canalLog();
/**
* 通过实例名获取实例日志(末尾100行)
*
* @return 日志信息
*/
String instanceLog(String destination);
}

View File

@@ -1,164 +0,0 @@
package com.alibaba.otter.canal.admin.jmx;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.util.concurrent.*;
import java.util.function.Function;
/**
* Canal Server JMX 控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class JMXConnection {
private static final Logger logger = LoggerFactory.getLogger(JMXConnection.class);
private String ip;
private Integer port;
private JMXConnector jmxc;
private CanalServerMXBean canalServerMXBean;
public JMXConnection(String ip, Integer port){
this.ip = ip;
this.port = port;
}
/**
* 执行相关操作
*
* @param ip jmx ip
* @param port jmx port
* @param function 执行方法
* @param <R> 返回泛型
* @return 执行结果
*/
public static <R> R execute(String ip, int port, Function<CanalServerMXBean, R> function) {
JMXConnection jmxConnection = new JMXConnection(ip, port);
try {
CanalServerMXBean canalServerMXBean = jmxConnection.getCanalServerMXBean();
return function.apply(canalServerMXBean);
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
jmxConnection.close();
}
return null;
}
/**
* 连接远程jmx
*/
public void connect() {
try {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + ip + ":" + port + "/jmxrmi");
// jmxc = JMXConnectorFactory.connect(url, null);
jmxc = connectWithTimeout(url, 3, TimeUnit.SECONDS);
MBeanServerConnection mBeanServerConnection = jmxc.getMBeanServerConnection();
ObjectName name = new ObjectName("CanalServerAgent:type=CanalServerStatus");
mBeanServerConnection.addNotificationListener(name, (notification, handback) -> {
}, null, null);
canalServerMXBean = MBeanServerInvocationHandler
.newProxyInstance(mBeanServerConnection, name, CanalServerMXBean.class, false);
} catch (Exception e) {
throw new ServiceException(e.getMessage(), e);
}
}
/**
* 带超时的jmx连接器
*
* @param url jmx url
* @param timeout 超时时间
* @param unit 超时时间单位
* @return JMXConnector
* @throws IOException 连接异常
*/
private static JMXConnector connectWithTimeout(final JMXServiceURL url, long timeout,
TimeUnit unit) throws IOException {
final BlockingQueue<Object> mailbox = new ArrayBlockingQueue<>(1);
ExecutorService executor = Executors.newSingleThreadExecutor(daemonThreadFactory);
executor.submit(() -> {
try {
JMXConnector connector = JMXConnectorFactory.connect(url);
if (!mailbox.offer(connector)) connector.close();
} catch (Throwable t) {
mailbox.offer(t);
}
});
Object result;
try {
result = mailbox.poll(timeout, unit);
if (result == null) {
if (!mailbox.offer("")) result = mailbox.take();
}
} catch (InterruptedException e) {
throw initCause(new InterruptedIOException(e.getMessage()), e);
} finally {
executor.shutdown();
}
if (result == null) throw new SocketTimeoutException("Connect timed out: " + url);
if (result instanceof JMXConnector) return (JMXConnector) result;
try {
throw (Throwable) result;
} catch (IOException | RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
// In principle this can't happen but we wrap it anyway
throw new IOException(e.toString(), e);
}
}
private static <T extends Throwable> T initCause(T wrapper, Throwable wrapped) {
wrapper.initCause(wrapped);
return wrapper;
}
private static class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
}
private static final ThreadFactory daemonThreadFactory = new DaemonThreadFactory();
/**
* 获取MBean
*
* @return canalServerMXBean
*/
public CanalServerMXBean getCanalServerMXBean() {
if (jmxc == null) {
connect();
}
return canalServerMXBean;
}
/**
* 关闭jmx连接
*/
public void close() {
try {
if (jmxc != null) {
jmxc.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
jmxc = null;
}
}
}

View File

@@ -0,0 +1,72 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import io.ebean.annotation.WhenModified;
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;
@WhenModified
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

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import io.ebean.annotation.WhenModified;
import java.util.Date;
@@ -8,7 +9,7 @@ import javax.persistence.Entity;
import javax.persistence.Id;
/**
* Canal住配置实体类
* Canal主配置实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
@@ -31,10 +32,19 @@ 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;
@WhenModified
private Date modifiedTime;
public void init() {
this.name = "canal.properties";
}
public Long getId() {
return id;
}
@@ -43,6 +53,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;
}
@@ -59,6 +85,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;
}
@@ -66,4 +100,13 @@ public class CanalConfig extends Model {
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -1,12 +1,12 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.Date;
import javax.persistence.*;
import io.ebean.Finder;
import io.ebean.annotation.WhenModified;
/**
* Canal实例配置信息实体类
*
@@ -30,15 +30,32 @@ public class CanalInstanceConfig extends Model {
}
@Id
private Long id;
private String name;
private String content;
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: 停止
@WhenModified
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;
@@ -48,6 +65,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;
}
@@ -64,6 +113,22 @@ 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;
}
public void setStatus(String status) {
this.status = status;
}
public Date getModifiedTime() {
return modifiedTime;
}
@@ -72,19 +137,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

@@ -1,13 +1,12 @@
package com.alibaba.otter.canal.admin.model;
import javax.persistence.MappedSuperclass;
import javax.persistence.OptimisticLockException;
import org.apache.commons.beanutils.PropertyUtils;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import org.apache.commons.beanutils.PropertyUtils;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.OptimisticLockException;
import java.lang.reflect.Field;
/**
* EBean Model扩展类
@@ -33,21 +32,10 @@ public abstract class Model extends io.ebean.Model {
public void saveOrUpdate() {
try {
Field idField = null;
// find id field
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
Id idAnn = field.getAnnotation(Id.class);
if (idAnn != null) {
idField = field;
break;
}
}
if (idField == null) {
return;
}
Object idVal = PropertyUtils.getProperty(this, idField.getName());
if (idVal == null) {
EbeanServer ebeanServer = Ebean.getDefaultServer();
Object id = ebeanServer.getBeanId(this);
if (id == null) {
init();
this.save();
} else {
this.update();
@@ -61,7 +49,13 @@ public abstract class Model extends io.ebean.Model {
try {
EbeanServer ebeanServer = Ebean.getDefaultServer();
Object id = ebeanServer.getBeanId(this);
if (id == null) {
return;
}
Object model = ebeanServer.createQuery(this.getClass()).where().idEq(id).findOne();
if (model == null) {
return;
}
for (String propertyName : propertiesNames) {
if (propertyName.startsWith("nn:")) { // not null
propertyName = propertyName.substring(3);

View File

@@ -1,12 +1,11 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import io.ebean.annotation.WhenModified;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import io.ebean.Finder;
import javax.persistence.*;
/**
* 节点信息实体类
@@ -32,16 +31,23 @@ public class NodeServer extends Model {
}
@Id
private Long id;
private String name;
private String ip;
private Integer port;
private Integer port2;
private Integer 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;
@WhenModified
private Date modifiedTime;
public void init() {
status = -1;
status = "-1";
}
public Long getId() {
@@ -52,6 +58,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;
}
@@ -68,27 +90,35 @@ public class NodeServer extends Model {
this.ip = ip;
}
public Integer getPort() {
return port;
public Integer getAdminPort() {
return adminPort;
}
public void setPort(Integer port) {
this.port = port;
public void setAdminPort(Integer adminPort) {
this.adminPort = adminPort;
}
public Integer getPort2() {
return port2;
public Integer getMetricPort() {
return metricPort;
}
public void setPort2(Integer port2) {
this.port2 = port2;
public void setMetricPort(Integer metricPort) {
this.metricPort = metricPort;
}
public Integer getStatus() {
public Integer getTcpPort() {
return tcpPort;
}
public void setTcpPort(Integer tcpPort) {
this.tcpPort = tcpPort;
}
public String getStatus() {
return status;
}
public void setStatus(Integer status) {
public void setStatus(String status) {
this.status = status;
}

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

@@ -1,6 +1,7 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import io.ebean.annotation.WhenCreated;
import javax.persistence.Entity;
import javax.persistence.Id;
@@ -36,6 +37,7 @@ public class User extends Model {
private String introduction;
private String avatar;
private String name;
@WhenCreated
private Date creationDate;
@Transient

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 CanalClusterService {
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,9 @@ import com.alibaba.otter.canal.admin.model.CanalConfig;
public interface CanalConfigService {
CanalConfig getCanalConfig();
CanalConfig getCanalConfig(Long clusterId, Long serverId);
CanalConfig getCanalConfigSummary();
CanalConfig getAdapterConfig();

View File

@@ -1,10 +1,11 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
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 java.util.Map;
*/
public interface CanalInstanceService {
List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig);
Pager<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig, Pager<CanalInstanceConfig> pager);
void save(CanalInstanceConfig canalInstanceConfig);
@@ -26,4 +27,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> findActiveInstanceByServerId(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,15 @@
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 PollingConfigService {
public boolean autoRegister(String ip, Integer adminPort, String cluster);
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,54 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.util.List;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalClusterService;
@Service
public class CanalClusterServiceImpl implements CanalClusterService {
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("当前集群下存在Server, 无法删除");
}
// 判断集群下是否存在instance信息
int instanceCnt = CanalInstanceConfig.find.query().where().eq("clusterId", id).findCount();
if (instanceCnt > 0) {
throw new ServiceException("当前集群下存在Instance配置,无法删除");
}
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

@@ -1,9 +1,21 @@
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 org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* Canal配置信息业务层
@@ -14,15 +26,101 @@ import com.alibaba.otter.canal.admin.service.CanalConfigService;
@Service
public class CanalConfigServiceImpl implements CanalConfigService {
public CanalConfig getCanalConfig() {
return CanalConfig.find.byId(1L);
private Logger logger = LoggerFactory.getLogger(CanalConfigServiceImpl.class);
private static final String CANAL_GLOBAL_CONFIG = "canal.properties";
private static final String CANAL_ADAPTER_CONFIG = "application.yml";
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.setName(CANAL_GLOBAL_CONFIG);
return config;
}
return config;
}
public CanalConfig getCanalConfigSummary() {
return CanalConfig.find.query()
.setDisableLazyLoading(true)
.select("name, modifiedTime")
.where()
.eq("id", 1L)
.findOne();
}
public CanalConfig getAdapterConfig() {
return CanalConfig.find.byId(2L);
long id = 2L;
CanalConfig config = CanalConfig.find.byId(id);
if (config == null) {
String context = loadDefaultConf(CANAL_ADAPTER_CONFIG);
if (context == null) {
return null;
}
config = new CanalConfig();
config.setId(id);
config.setName(CANAL_ADAPTER_CONFIG);
config.setModifiedTime(new Date());
config.setContent(context);
return config;
}
return config;
}
public void updateContent(CanalConfig canalConfig) {
canalConfig.update("content");
try {
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();
}
}
private String loadDefaultConf(String confFileName) {
InputStream input = null;
try {
input = Thread.currentThread().getContextClassLoader().getResourceAsStream("conf/" + confFileName);
if (input == null) {
return null;
}
return StringUtils.join(IOUtils.readLines(input), "\n");
} catch (IOException e) {
logger.error("find " + confFileName + " is error!", e);
return null;
} finally {
if (input != null) {
IOUtils.closeQuietly(input);
}
}
}
}

View File

@@ -1,19 +1,32 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.jmx.CanalServerMXBean;
import com.alibaba.otter.canal.admin.jmx.JMXConnection;
import com.alibaba.otter.canal.admin.common.Threads;
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.model.Pager;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import io.ebean.Query;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.collect.Lists;
/**
* Canal实例配置信息业务层
@@ -24,51 +37,195 @@ import io.ebean.Query;
@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 = JMXConnection
.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::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
List<Future<Void>> futures = new ArrayList<>(canalInstanceConfigs.size());
for (CanalInstanceConfig canalInstanceConfig1 : canalInstanceConfigs) {
futures.add(Threads.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())) {
// 集群模式下server对象为空
if (canalInstanceConfig1.getNodeServer() == null) {
canalInstanceConfig1.setNodeServer(nodeServer);
}
canalInstanceConfig1.setRunningStatus("1");
break;
}
}
}
return null;
}));
}
for (Future<Void> f :futures){
try {
f.get(3, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// ignore
} catch (TimeoutException e){
break;
}
}
return pager;
}
/**
* 通过Server id获取当前Server下所有运行的Instance
*
* @param serverId server id
*/
public List<CanalInstanceConfig> findActiveInstanceByServerId(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(",");
Object obj[] = Lists.newArrayList(instances).toArray();
// 单机模式和集群模式区分处理
if (nodeServer.getClusterId() != null) { // 集群模式
List<CanalInstanceConfig> list = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.where()
// 暂停的实例也显示 .eq("status", "1")
.in("name", obj)
.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) {
@@ -90,9 +247,9 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
return result;
}
String log = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.instanceLog(canalInstanceConfig.getName()));
String log = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.instanceLog(canalInstanceConfig.getName(), null, 100));
result.put("instance", canalInstanceConfig.getName());
result.put("log", log);
@@ -120,22 +277,46 @@ public class CanalInstanceServiceImpl implements CanalInstanceService {
if (canalInstanceConfig == null) {
return false;
}
Boolean resutl = null;
Boolean result = null;
if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.startInstance(canalInstanceConfig.getName()));
if (nodeServer.getClusterId() == null) { // 非集群模式
return instanceOperation(id, "start");
} else {
throw new ServiceException("集群模式不允许指定server启动");
}
} else if ("stop".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.stopInstance(canalInstanceConfig.getName()));
if (nodeServer.getClusterId() != null) {
// 集群模式,通知主动释放
result = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.releaseInstance(canalInstanceConfig.getName()));
} else { // 非集群模式下直接将状态置为0
return instanceOperation(id, "stop");
}
} else {
return false;
}
if (resutl == null) {
resutl = false;
if (result == null) {
result = false;
}
return resutl;
return result;
}
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,24 +1,29 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.security.NoSuchAlgorithmException;
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.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import com.alibaba.otter.canal.admin.common.Threads;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.jmx.CanalServerMXBean;
import com.alibaba.otter.canal.admin.jmx.JMXConnection;
import com.alibaba.otter.canal.admin.connector.AdminConnector;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnectors;
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.model.Pager;
import com.alibaba.otter.canal.admin.service.NodeServerService;
import io.ebean.Query;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* 节点信息业务层
@@ -29,19 +34,30 @@ import io.ebean.Query;
@Service
public class NodeServerServiceImpl implements NodeServerService {
private static final Logger logger = LoggerFactory.getLogger(NodeServerServiceImpl.class);
public void save(NodeServer nodeServer) {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("port", nodeServer.getPort())
.eq("adminPort", nodeServer.getAdminPort())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.save();
if (nodeServer.getClusterId() == null) { // 单机模式
CanalConfig canalConfig = new CanalConfig();
canalConfig.setServerId(nodeServer.getId());
String configTmp = TemplateConfigLoader.loadCanalConfig();
canalConfig.setContent(configTmp);
try {
String contentMd5 = SecurityUtil.md5String(canalConfig.getContent());
canalConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
}
canalConfig.save();
}
}
public NodeServer detail(Long id) {
@@ -52,25 +68,39 @@ public class NodeServerServiceImpl implements NodeServerService {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("port", nodeServer.getPort())
.eq("adminPort", nodeServer.getAdminPort())
.ne("id", nodeServer.getId())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.update("name", "ip", "port", "port2");
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() + "%");
@@ -78,51 +108,68 @@ 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());
}
}
}
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());
List<Future<Boolean>> futures = new ArrayList<>(nodeServers.size());
// get all nodes status
for (NodeServer ns : nodeServers) {
futures.add(executorService.submit(() -> {
int status = -1;
JMXConnection jmxConnection = new JMXConnection(ns.getIp(), ns.getPort());
try {
CanalServerMXBean canalServerMXBean = jmxConnection.getCanalServerMXBean();
status = canalServerMXBean.getStatus();
} catch (Exception e) {
logger.warn(e.getMessage());
} finally {
jmxConnection.close();
}
ns.setStatus(status);
return status != -1;
futures.add(Threads.executorService.submit(() -> {
boolean status = SimpleAdminConnectors.execute(ns.getIp(), ns.getAdminPort(), AdminConnector::check);
ns.setStatus(status ? "1" : "0");
return !status;
}));
}
futures.forEach(f -> {
for (Future<Boolean> f :futures){
try {
f.get();
f.get(3, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// ignore
} catch (TimeoutException e){
break;
}
});
}
executorService.shutdownNow();
return nodeServers;
return pager;
}
public int remoteNodeStatus(String ip, Integer port) {
Integer resutl = JMXConnection.execute(ip, port, CanalServerMXBean::getStatus);
if (resutl == null) {
resutl = -1;
}
return resutl;
boolean result = SimpleAdminConnectors.execute(ip, port, AdminConnector::check);
return result ? 1 : 0;
}
public String remoteCanalLog(Long id) {
@@ -130,7 +177,9 @@ public class NodeServerServiceImpl implements NodeServerService {
if (nodeServer == null) {
return "";
}
return JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::canalLog);
return SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.canalLog(100));
}
public boolean remoteOperation(Long id, String option) {
@@ -138,18 +187,18 @@ public class NodeServerServiceImpl implements NodeServerService {
if (nodeServer == null) {
return false;
}
Boolean resutl = null;
Boolean result = null;
if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::start);
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::start);
} else if ("stop".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::stop);
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::stop);
} else {
return false;
}
if (resutl == null) {
resutl = false;
if (result == null) {
result = false;
}
return resutl;
return result;
}
}

View File

@@ -0,0 +1,134 @@
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalCluster;
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.CanalClusterService;
import com.alibaba.otter.canal.admin.service.NodeServerService;
import com.alibaba.otter.canal.admin.service.PollingConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.base.Joiner;
@Service
public class PollingConfigServiceImpl implements PollingConfigService {
@Autowired
NodeServerService nodeServerService;
@Autowired
CanalClusterService canalClusterService;
public boolean autoRegister(String ip, Integer adminPort, String cluster) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", adminPort).findOne();
if (server == null) {
server = new NodeServer();
server.setName(ip);
server.setIp(ip);
server.setAdminPort(adminPort);
server.setTcpPort(adminPort + 1);
server.setMetricPort(adminPort + 2);
if (StringUtils.isNotEmpty(cluster)) {
CanalCluster clusterConfig = CanalCluster.find.query().where().eq("name", cluster).findOne();
if (clusterConfig == null) {
throw new ServiceException("auto cluster : " + cluster + " is not found.");
}
server.setClusterId(clusterConfig.getId());
}
nodeServerService.save(server);
}
return true;
}
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) {
throw new ServiceException("canal.properties config is empty");
}
if (!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

@@ -1,10 +1,14 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.User;
import com.alibaba.otter.canal.admin.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* 用户信息业务层
@@ -15,26 +19,47 @@ import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private static byte[] seeds = "canal is best!".getBytes();
public User find4Login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return null;
}
User user = User.find.query().where().eq("username", username).eq("password", password).findOne();
if (user != null) {
user.setPassword("");
User user = User.find.query().where().eq("username", username).findOne();
if (user == null) {
throw new ServiceException("user:" + username + " auth failed!");
}
try {
byte[] pass = SecurityUtil.scramble411(password.getBytes(), seeds);
if (!SecurityUtil.scrambleServerAuth(pass, SecurityUtil.hexStr2Bytes(user.getPassword()), seeds)) {
throw new ServiceException("user:" + user.getName() + " passwd incorrect!");
}
} catch (NoSuchAlgorithmException e) {
throw new ServiceException("user:" + user.getName() + " auth failed!");
}
user.setPassword("");
return user;
}
public void update(User user) {
User userTmp = User.find.byId(1L);
User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne();
if (userTmp == null) {
throw new ServiceException();
}
if (!userTmp.getPassword().equals(user.getOldPassword())) {
throw new ServiceException("错误的旧密码");
try {
byte[] pass = SecurityUtil.scramble411(user.getOldPassword().getBytes(), seeds);
if (!SecurityUtil.scrambleServerAuth(pass, SecurityUtil.hexStr2Bytes(userTmp.getPassword()), seeds)) {
throw new ServiceException("old passwd is unmatch");
}
user.setId(userTmp.getId());
user.setPassword(SecurityUtil.scrambleGenPass(user.getPassword().getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new ServiceException("passwd process failed");
}
user.setId(1L);
user.update("username", "nn:password");
}
}

View File

@@ -6,10 +6,16 @@ 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
address: 127.0.0.1:3306
database: canal_manager
username: canal
password: canal
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
hikari:
maximum-pool-size: 30
minimum-idle: 1
canal:
adminUser: admin
adminPasswd: admin

View File

@@ -0,0 +1,139 @@
#################################################
######### common argument #############
#################################################
# tcp bind ip
canal.ip =
# register ip to zookeeper
canal.register.ip =
canal.port = 11111
canal.metrics.pull.port = 11112
# canal instance user/passwd
canal.user = canal
canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458
# canal admin config
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
canal.zkServers =
# flush data to zk
canal.zookeeper.flush.period = 1000
canal.withoutNetty = false
# tcp, kafka, RocketMQ
canal.serverMode = tcp
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir}
canal.file.flush.period = 1000
## memory store RingBuffer size, should be Math.pow(2,n)
canal.instance.memory.buffer.size = 16384
## memory store RingBuffer used memory unit size , default 1kb
canal.instance.memory.buffer.memunit = 1024
## meory store gets mode used MEMSIZE or ITEMSIZE
canal.instance.memory.batch.mode = MEMSIZE
canal.instance.memory.rawEntry = true
## detecing config
canal.instance.detecting.enable = false
#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
canal.instance.detecting.sql = select 1
canal.instance.detecting.interval.time = 3
canal.instance.detecting.retry.threshold = 3
canal.instance.detecting.heartbeatHaEnable = false
# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
canal.instance.transaction.size = 1024
# mysql fallback connected to new master should fallback times
canal.instance.fallbackIntervalInSeconds = 60
# network config
canal.instance.network.receiveBufferSize = 16384
canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30
# binlog filter config
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false
canal.instance.filter.rows = false
canal.instance.filter.transaction.entry = false
# binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
# binlog ddl isolation
canal.instance.get.ddl.isolation = false
# parallel parser config
canal.instance.parser.parallel = true
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
#canal.instance.parser.parallelThreadSize = 16
## disruptor ringbuffer size, must be power of 2
canal.instance.parser.parallelBufferSize = 256
# table meta tsdb info
canal.instance.tsdb.enable = true
canal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername = canal
canal.instance.tsdb.dbPassword = canal
# dump snapshot interval, default 24 hour
canal.instance.tsdb.snapshot.interval = 24
# purge snapshot expire , default 360 hour(15 days)
canal.instance.tsdb.snapshot.expire = 360
# aliyun ak/sk , support rds/mq
canal.aliyun.accessKey =
canal.aliyun.secretKey =
#################################################
######### destinations #############
#################################################
canal.destinations =
# conf root dir
canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = true
canal.auto.scan.interval = 5
canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = manager
canal.instance.global.lazy = false
canal.instance.global.manager.address = ${canal.admin.manager}
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
##################################################
######### MQ #############
##################################################
canal.mq.servers = 127.0.0.1:6667
canal.mq.retries = 0
canal.mq.batchSize = 16384
canal.mq.maxRequestSize = 1048576
canal.mq.lingerMs = 100
canal.mq.bufferMemory = 33554432
canal.mq.canalBatchSize = 50
canal.mq.canalGetTimeout = 100
canal.mq.flatMessage = true
canal.mq.compressionType = none
canal.mq.acks = all
#canal.mq.properties. =
canal.mq.producerGroup = test
# Set this value to "cloud", if you want open message trace feature in aliyun.
canal.mq.accessChannel = local
# aliyun mq namespace
#canal.mq.namespace =
##################################################
######### Kafka Kerberos Info #############
##################################################
canal.mq.kafka.kerberos.enable = false
canal.mq.kafka.kerberos.krb5FilePath = "../conf/kerberos/krb5.conf"
canal.mq.kafka.kerberos.jaasFilePath = "../conf/kerberos/jaas.conf"

View File

@@ -0,0 +1,110 @@
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `canal_manager` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;
USE `canal_manager`;
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for canal_adapter_config
-- ----------------------------
DROP TABLE IF EXISTS `canal_adapter_config`;
CREATE TABLE `canal_adapter_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`category` varchar(45) NOT NULL,
`name` varchar(45) NOT NULL,
`status` varchar(45) DEFAULT NULL,
`content` text NOT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- 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=utf8;
-- ----------------------------
-- Table structure for canal_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 `sid_UNIQUE` (`server_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for canal_instance_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 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for canal_node_server
-- ----------------------------
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,
`tcp_port` int(11) DEFAULT NULL,
`metric_port` int(11) DEFAULT NULL,
`status` varchar(45) DEFAULT NULL,
`modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for canal_user
-- ----------------------------
DROP TABLE IF EXISTS `canal_user`;
CREATE TABLE `canal_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(31) NOT NULL,
`password` varchar(128) NOT NULL,
`name` varchar(31) NOT NULL,
`roles` varchar(31) NOT NULL,
`introduction` varchar(255) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
-- ----------------------------
-- Records of canal_user
-- ----------------------------
BEGIN;
INSERT INTO `canal_user` VALUES (1, 'admin', '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9', 'Canal Manager', 'admin', NULL, NULL, '2019-07-14 00:05:28');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,57 @@
#################################################
## mysql serverId , v1.0.26+ will autoGen
# canal.instance.mysql.slaveId=0
# enable gtid use true/false
canal.instance.gtidon=false
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=
# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=
# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==
# table regex
canal.instance.filter.regex=.*\\..*
# table black regex
canal.instance.filter.black.regex=
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch
# mq config
canal.mq.topic=example
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\..*,.*\\..*
canal.mq.partition=0
# hash partition config
#canal.mq.partitionsNum=3
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#################################################

View File

@@ -0,0 +1,47 @@
<configuration scan="true" scanPeriod=" 5 seconds">
<jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<Key>admin</Key>
<DefaultValue>admin</DefaultValue>
</discriminator>
<sift>
<appender name="FILE-admin" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>../logs/admin.log</File>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>../logs/admin/%d{yyyy-MM-dd}/admin-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
</sift>
</appender>
<root level="INFO">
<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,a=n[0],f=n[1],i=n[2],h=0,d=[];h<a.length;h++)c=a[h],u[c]&&d.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);l&&l(n);while(d.length)d.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 a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-101fc062":"bc898027","chunk-137eaf06":"867259ad","chunk-238a81e9":"273bda76","chunk-37c49cbf":"64d26540","chunk-3fcdf643":"4b7133b8","chunk-555c32e2":"6c39a877","chunk-63c8061b":"7ccc9231","chunk-7279d7fc":"22670fea","chunk-4f09fed2":"ff28d88d","chunk-69386cf0":"76d77f5c","chunk-7ec889b7":"6ba68ef0","chunk-e1a839e4":"c870a02b"}[e]+".js"}function f(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,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-101fc062":1,"chunk-238a81e9":1,"chunk-37c49cbf":1,"chunk-555c32e2":1,"chunk-63c8061b":1,"chunk-7279d7fc":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-555c32e2":"9d3c5014","chunk-63c8061b":"acff1abf","chunk-7279d7fc":"84a25dbe","chunk-4f09fed2":"70ec0b86","chunk-69386cf0":"741ff14e","chunk-7ec889b7":"c0585512","chunk-e1a839e4":"31d6cfe0"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],h=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(h===r||h===u))return n()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){i=d[a],h=i.getAttribute("data-href");if(h===r||h===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,h=document.createElement("script");h.charset="utf-8",h.timeout=120,f.nc&&h.setAttribute("nonce",f.nc),h.src=a(e),i=function(n){h.onerror=h.onload=null,clearTimeout(d);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 d=setTimeout(function(){i({type:"timeout",target:h})},12e4);h.onerror=h.onload=i,document.head.appendChild(h)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="/",f.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 d=0;d<i.length;d++)n(i[d]);var l=h;t()})([]);</script><script src=/static/js/app.b8d01331.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 c,r,o=n[0],f=n[1],i=n[2],d=0,h=[];d<o.length;d++)r=o[d],u[r]&&h.push(u[r][0]),u[r]=0;for(c in f)Object.prototype.hasOwnProperty.call(f,c)&&(e[c]=f[c]);l&&l(n);while(h.length)h.shift()();return a.push.apply(a,i||[]),t()}function t(){for(var e,n=0;n<a.length;n++){for(var t=a[n],c=!0,r=1;r<t.length;r++){var o=t[r];0!==u[o]&&(c=!1)}c&&(a.splice(n--,1),e=f(f.s=t[0]))}return e}var c={},r={runtime:0},u={runtime:0},a=[];function o(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0dca2f22":"a2bc28b8","chunk-5afa45f5":"79ddcc04","chunk-5b373aad":"90a2d8e7","chunk-bd1d44ee":"8e2c4851","chunk-101fc062":"bc898027","chunk-2b9b6c5c":"cc2181b9","chunk-37c49cbf":"64d26540","chunk-49959c8b":"6d226f70","chunk-4f09fed2":"ff28d88d","chunk-55380ff2":"681c71c9","chunk-69386cf0":"76d77f5c","chunk-7ec889b7":"bd1ca803","chunk-da289616":"7def925e"}[e]+".js"}function f(n){if(c[n])return c[n].exports;var t=c[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-5afa45f5":1,"chunk-5b373aad":1,"chunk-bd1d44ee":1,"chunk-101fc062":1,"chunk-2b9b6c5c":1,"chunk-37c49cbf":1,"chunk-49959c8b":1,"chunk-4f09fed2":1,"chunk-69386cf0":1,"chunk-7ec889b7":1,"chunk-da289616":1};r[e]?n.push(r[e]):0!==r[e]&&t[e]&&n.push(r[e]=new Promise(function(n,t){for(var c="static/css/"+({}[e]||e)+"."+{"chunk-0dca2f22":"31d6cfe0","chunk-5afa45f5":"a34bb177","chunk-5b373aad":"b966d36b","chunk-bd1d44ee":"1528199a","chunk-101fc062":"fad9926f","chunk-2b9b6c5c":"c89f8844","chunk-37c49cbf":"efc21a9c","chunk-49959c8b":"e8e2beee","chunk-4f09fed2":"70ec0b86","chunk-55380ff2":"31d6cfe0","chunk-69386cf0":"741ff14e","chunk-7ec889b7":"c0585512","chunk-da289616":"c89f8844"}[e]+".css",u=f.p+c,a=document.getElementsByTagName("link"),o=0;o<a.length;o++){var i=a[o],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===c||d===u))return n()}var h=document.getElementsByTagName("style");for(o=0;o<h.length;o++){i=h[o],d=i.getAttribute("data-href");if(d===c||d===u)return n()}var l=document.createElement("link");l.rel="stylesheet",l.type="text/css",l.onload=n,l.onerror=function(n){var c=n&&n.target&&n.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+c+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=c,delete r[e],l.parentNode.removeChild(l),t(a)},l.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(l)}).then(function(){r[e]=0}));var c=u[e];if(0!==c)if(c)n.push(c[2]);else{var a=new Promise(function(n,t){c=u[e]=[n,t]});n.push(c[2]=a);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=o(e),i=function(n){d.onerror=d.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var c=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src,a=new Error("Loading chunk "+e+" failed.\n("+c+": "+r+")");a.type=c,a.request=r,t[1](a)}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)},f.m=e,f.c=c,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var c in e)f.d(t,c,function(n){return e[n]}.bind(null,c));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="/",f.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.6845b228.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 @@
.pagination-container[data-v-38ef71f0]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-38ef71f0]{display:none}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-101fc062"],{2017:function(e,t,s){"use strict";var n=s("3b76"),o=s.n(n);o.a},"3b76":function(e,t,s){},"562b":function(e,t,s){},"9ed6":function(e,t,s){"use strict";s.r(t);var n=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"login-container"},[s("el-form",{ref:"loginForm",staticClass:"login-form",attrs:{model:e.loginForm,rules:e.loginRules,"auto-complete":"on","label-position":"left"}},[s("div",{staticClass:"title-container"},[s("h3",{staticClass:"title"},[e._v("Canal Admin Login")])]),e._v(" "),s("el-form-item",{attrs:{prop:"username"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"user"}})],1),e._v(" "),s("el-input",{ref:"username",attrs:{placeholder:"Username",name:"username",type:"text",tabindex:"1","auto-complete":"on"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),e._v(" "),s("el-form-item",{attrs:{prop:"password"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"password"}})],1),e._v(" "),s("el-input",{key:e.passwordType,ref:"password",attrs:{type:e.passwordType,placeholder:"Password",name:"password",tabindex:"2","auto-complete":"on"},nativeOn:{keyup:function(t){return!t.type.indexOf("key")&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleLogin(t)}},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),e._v(" "),s("span",{staticClass:"show-pwd",on:{click:e.showPwd}},[s("svg-icon",{attrs:{"icon-class":"password"===e.passwordType?"eye":"eye-open"}})],1)],1),e._v(" "),s("el-button",{staticStyle:{width:"100%","margin-bottom":"30px"},attrs:{loading:e.loading,type:"primary"},nativeOn:{click:function(t){return t.preventDefault(),e.handleLogin(t)}}},[e._v("Login")]),e._v(" "),s("div",{staticClass:"tips"})],1)],1)},o=[],r=s("61f7"),a={name:"Login",data:function(){var e=function(e,t,s){Object(r["b"])(t)?s():s(new Error("Please enter the correct user name"))},t=function(e,t,s){t.length<6?s(new Error("The password can not be less than 6 digits")):s()};return{loginForm:{username:"",password:""},loginRules:{username:[{required:!0,trigger:"blur",validator:e}],password:[{required:!0,trigger:"blur",validator:t}]},loading:!1,passwordType:"password",redirect:void 0}},watch:{$route:{handler:function(e){this.redirect=e.query&&e.query.redirect},immediate:!0}},methods:{showPwd:function(){var e=this;"password"===this.passwordType?this.passwordType="":this.passwordType="password",this.$nextTick(function(){e.$refs.password.focus()})},handleLogin:function(){var e=this;this.$refs.loginForm.validate(function(t){if(!t)return console.log("error submit!!"),!1;e.loading=!0,e.$store.dispatch("user/login",e.loginForm).then(function(){e.$router.push({path:e.redirect||"/"}),e.loading=!1}).catch(function(){e.loading=!1})})}}},i=a,l=(s("2017"),s("ec2d"),s("2877")),c=Object(l["a"])(i,n,o,!1,null,"31c14ebf",null);t["default"]=c.exports},ec2d:function(e,t,s){"use strict";var n=s("562b"),o=s.n(n);o.a}}]);

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"})}}}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-37c49cbf"],{3671:function(t,e,n){"use strict";var a=n("afe4"),c=n.n(a);c.a},9406:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},c=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"dashboard-container"},[n("div",{staticClass:"dashboard-text"},[t._v(" ")])])}],s=n("db72"),r=n("2f62"),u={name:"Dashboard",computed:Object(s["a"])({},Object(r["b"])(["name"])),mounted:function(){this.$router.push("/canalServer")}},i=u,o=(n("3671"),n("2877")),d=Object(o["a"])(i,a,c,!1,null,"42037c2b",null);e["default"]=d.exports},afe4:function(t,e,n){}}]);

View File

@@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-238a81e9"],{"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){}}]);
(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){}}]);

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){}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4f09fed2"],{"68d6":function(t,e,n){"use strict";var r=n("9d07"),o=n.n(r);o.a},"9d07":function(t,e,n){},c6ed:function(t,e,n){"use strict";n.d(e,"c",function(){return o}),n.d(e,"a",function(){return c}),n.d(e,"g",function(){return u}),n.d(e,"b",function(){return a}),n.d(e,"e",function(){return d}),n.d(e,"f",function(){return i}),n.d(e,"d",function(){return f});var r=n("b775");function o(t){return Object(r["a"])({url:"/nodeServers",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/nodeServer",method:"post",data:t})}function u(t){return Object(r["a"])({url:"/nodeServer",method:"put",data:t})}function a(t){return Object(r["a"])({url:"/nodeServer/"+t,method:"delete"})}function d(t){return Object(r["a"])({url:"/nodeServer/start/"+t,method:"put"})}function i(t){return Object(r["a"])({url:"/nodeServer/stop/"+t,method:"put"})}function f(t){return Object(r["a"])({url:"/nodeServer/log/"+t,method:"get"})}},caf8: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",{staticStyle:{"padding-left":"10px","padding-right":"10px","padding-top":"20px"}},[n("el-form-item",[t._v("\n canal.log    \n "),n("el-button",{attrs:{type:"primary"},on:{click:t.onRefresh}},[t._v("刷新")]),t._v(" "),n("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("el-input",{attrs:{rows:35,readonly:"readonly",type:"textarea"},model:{value:t.form.desc,callback:function(e){t.$set(t.form,"desc",e)},expression:"form.desc"}})],1)])],1)},o=[],c=n("c6ed"),u={data:function(){return{form:{desc:""}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(c["d"])(this.$route.query.id).then(function(e){t.form.desc=e.data})},onRefresh:function(){this.fetchData()},onBack:function(){history.go(-1)}}},a=u,d=(n("68d6"),n("2877")),i=Object(d["a"])(a,r,o,!1,null,"64c3fae5",null);e["default"]=i.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-555c32e2"],{1248:function(t,n,e){"use strict";e.r(n);var a=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",{staticClass:"filter-container",staticStyle:{"padding-left":"10px","padding-top":"20px"}},[e("el-input",{staticClass:"filter-item",staticStyle:{width:"200px"},attrs:{placeholder:"实例名称"},model:{value:t.form.name,callback:function(n){t.$set(t.form,"name",n)},expression:"form.name"}}),t._v("\n  \n "),e("el-button",{staticClass:"filter-item",attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("新建")]),t._v(" "),e("el-button",{staticClass:"filter-item",attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],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)},r=[],c=(e("7f7f"),e("f546")),o={components:{editor:e("7c9e")},data:function(){return{form:{name:"",content:""}}},created:function(){},methods:{editorInit:function(){e("2099"),e("be9d"),e("2968"),e("e0e5"),e("bb36"),e("0329"),e("95b8"),e("6a21")},onSubmit:function(){var t=this;""!==this.form.name?this.$confirm("确定新建","确定新建",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){Object(c["a"])(t.form).then(function(n){"success"===n.data?(t.$message({message:"新建成功",type:"success"}),t.$router.push("/canalServer/canalInstances")):t.$message({message:"新建失败",type:"error"})})}):this.$message({message:"请输入实例名称",type:"error"})},onBack:function(){history.go(-1)}}},i=o,u=(e("e943"),e("2877")),s=Object(u["a"])(i,a,r,!1,null,"44b63e9d",null);n["default"]=s.exports},"18ae":function(t,n,e){},e943:function(t,n,e){"use strict";var a=e("18ae"),r=e.n(a);r.a},f546:function(t,n,e){"use strict";e.d(n,"d",function(){return r}),e.d(n,"b",function(){return c}),e.d(n,"h",function(){return o}),e.d(n,"a",function(){return i}),e.d(n,"c",function(){return u}),e.d(n,"f",function(){return s}),e.d(n,"g",function(){return f}),e.d(n,"e",function(){return l});var a=e("b775");function r(t){return Object(a["a"])({url:"/canal/instances",method:"get",params:t})}function c(t){return Object(a["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(a["a"])({url:"/canal/instance",method:"put",data:t})}function i(t){return Object(a["a"])({url:"/canal/instance",method:"post",data:t})}function u(t){return Object(a["a"])({url:"/canal/instance/"+t,method:"delete"})}function s(t,n){return Object(a["a"])({url:"/canal/instance/start/"+t+"/"+n,method:"put"})}function f(t,n){return Object(a["a"])({url:"/canal/instance/stop/"+t+"/"+n,method:"put"})}function l(t,n){return Object(a["a"])({url:"/canal/instance/log/"+t+"/"+n,method:"get"})}}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5afa45f5"],{"0984":function(t,e,n){},"0f47":function(t,e,n){"use strict";var o=n("0984"),r=n.n(o);r.a},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:"success"},on:{click:t.onLoadTemplate}},[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 c(t,e){return Object(a["a"])({url:"/canal/config/"+t+"/"+e,method:"get"})}function i(t){return Object(a["a"])({url:"/canal/config",method:"put",data:t})}function s(){return Object(a["a"])({url:"/canal/config/template",method:"get"})}var u={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),c(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(){i(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)},onLoadTemplate:function(){var t=this;s().then(function(e){t.form.content=e.data})}}},f=u,l=(n("0f47"),n("2877")),m=Object(l["a"])(f,o,r,!1,null,"2e6a6604",null);e["default"]=m.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5b373aad"],{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:"success"},on:{click:t.onLoadTemplate}},[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)},onLoadTemplate:function(){var t=this;Object(c["f"])().then(function(e){t.form.content=e.data})}}},i=u,s=(n("890e"),n("2877")),l=Object(s["a"])(i,r,a,!1,null,"e884438a",null);e["default"]=l.exports},"41be":function(t,e,n){},"890e":function(t,e,n){"use strict";var r=n("41be"),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,"k",function(){return o}),n.d(e,"a",function(){return u}),n.d(e,"c",function(){return i}),n.d(e,"i",function(){return s}),n.d(e,"j",function(){return l}),n.d(e,"g",function(){return f}),n.d(e,"h",function(){return d}),n.d(e,"d",function(){return m}),n.d(e,"f",function(){return p});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"})}function p(){return Object(r["a"])({url:"/canal/instance/template",method:"get"})}}}]);

View File

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

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-69386cf0"],{2829:function(e,t,s){},"646d":function(e,t,s){"use strict";var r=s("2829"),o=s.n(r);o.a},"9fb7":function(e,t,s){"use strict";s.r(t);var r=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"app-container",staticStyle:{width:"600px"}},[s("el-form",{ref:"form",attrs:{rules:e.rules,model:e.form,"label-width":"120px"}},[s("el-form-item",{attrs:{label:"用户名",prop:"username"}},[s("el-input",{staticStyle:{width:"200px"},model:{value:e.form.username,callback:function(t){e.$set(e.form,"username",t)},expression:"form.username"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"旧密码",prop:"oldPassword"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{type:"password"},model:{value:e.form.oldPassword,callback:function(t){e.$set(e.form,"oldPassword",t)},expression:"form.oldPassword"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"密码",prop:"password"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{placeholder:"空为不修改密码",type:"password"},model:{value:e.form.password,callback:function(t){e.$set(e.form,"password",t)},expression:"form.password"}})],1),e._v(" "),s("el-form-item",[s("el-button",{attrs:{type:"primary"},on:{click:e.onSubmit}},[e._v("修改")]),e._v(" "),s("el-button",{on:{click:e.onCancel}},[e._v("取消")])],1)],1)],1)},o=[],a=s("c24f"),n=s("5f87"),l={data:function(){return{form:{username:"",oldPassword:"",password:null},rules:{username:[{required:!0,message:"用户名能为空",trigger:"change"}],oldPassword:[{required:!0,message:"旧密码不能为空",trigger:"change"}]}}},created:function(){this.fetchUserInfo()},methods:{fetchUserInfo:function(){var e=this;Object(a["a"])(Object(n["a"])()).then(function(t){e.form.username=t.data.username})},onSubmit:function(){var e=this;this.$refs["form"].validate(function(t){t&&Object(a["d"])(e.form).then(function(t){"success"===t.data?(e.form.oldPassword="",e.form.password=null,e.$nextTick(function(){e.$refs["form"].clearValidate()}),e.$message({message:"修改用户信息成功",type:"success"})):e.$message({message:"修改用户信息成功",type:"error"})})})},onCancel:function(){history.go(-1)}}},i=l,c=(s("646d"),s("2877")),f=Object(c["a"])(i,r,o,!1,null,"756ebb70",null);t["default"]=f.exports}}]);

View File

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

View File

@@ -1 +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["e"])(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,"d",function(){return r}),e.d(n,"b",function(){return a}),e.d(n,"h",function(){return o}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"f",function(){return f}),e.d(n,"g",function(){return s}),e.d(n,"e",function(){return d});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"})}}}]);
(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-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["g"])(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,"k",function(){return o}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"i",function(){return f}),e.d(n,"j",function(){return s}),e.d(n,"g",function(){return d}),e.d(n,"h",function(){return l}),e.d(n,"d",function(){return m}),e.d(n,"f",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 m(t){return Object(c["a"])({url:"/canal/active/instances/"+t,method:"get"})}function h(){return Object(c["a"])({url:"/canal/instance/template",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 @@
(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)},c=[],o=(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(o["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(o["k"])(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,c,!1,null,"5c332416",null);n["default"]=s.exports},d631:function(t,n,e){"use strict";e.d(n,"c",function(){return c}),e.d(n,"a",function(){return o}),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 c(t){return Object(r["a"])({url:"/canal/clusters",method:"get",params:t})}function o(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"),c=e.n(r);c.a},f546:function(t,n,e){"use strict";e.d(n,"e",function(){return c}),e.d(n,"b",function(){return o}),e.d(n,"k",function(){return a}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"i",function(){return l}),e.d(n,"j",function(){return s}),e.d(n,"g",function(){return d}),e.d(n,"h",function(){return f}),e.d(n,"d",function(){return m}),e.d(n,"f",function(){return b});var r=e("b775");function c(t){return Object(r["a"])({url:"/canal/instances",method:"get",params:t})}function o(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"})}function b(){return Object(r["a"])({url:"/canal/instance/template",method:"get"})}}}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,42 @@
package com.alibaba.otter.canal.admin;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnector;
@Ignore
public class SimpleAdminConnectorTest {
@Test
public void testSimple() {
SimpleAdminConnector connector = new SimpleAdminConnector("127.0.0.1", 11110, "admin", "admin");
connector.connect();
System.out.println("check 1 : " + connector.check());
System.out.println("getRunning Before stop 1 : " + connector.getRunningInstances());
System.out.println("stop 1: " + connector.stop());
System.out.println("getRunning After stop 1 : " + connector.getRunningInstances());
System.out.println("check 1 : " + connector.check());
System.out.println("listFile 1 : " + connector.listCanalLog());
System.out.println("getFile 1 : " + connector.canalLog(10));
connector.disconnect();
connector.connect();
System.out.println("check 2 : " + connector.check());
System.out.println("get Running Before start : " + connector.getRunningInstances());
System.out.println("start 2 : " + connector.start());
System.out.println("get Running After start : " + connector.getRunningInstances());
System.out.println("check 2 : " + connector.check());
System.out.println("check after before example 2 : " + connector.checkInstance("example"));
System.out.println("stop example 2 : " + connector.stopInstance("example"));
System.out.println("check example 2 : " + connector.checkInstance("example"));
System.out.println("start example 2 : " + connector.startInstance("example"));
System.out.println("check after start example 2 : " + connector.checkInstance("example"));
System.out.println("listFile 2 : " + connector.listCanalLog());
System.out.println("getFile 2 : " + connector.canalLog(10));
System.out.println("listFile 3 : " + connector.listInstanceLog("example"));
System.out.println("getFile 3 : " + connector.instanceLog("example", "example.log", 10));
connector.disconnect();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>canal-admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
<version>1.1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -20,11 +18,11 @@
<profiles>
<profile>
<id>release</id>
<id>npm</id>
<activation>
<property>
<name>env</name>
<value>release</value>
<value>npm</value>
</property>
</activation>
<build>

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'
})
}
@@ -14,3 +14,10 @@ export function updateCanalConfig(data) {
data
})
}
export function getTemplateConfig() {
return request({
url: '/canal/config/template',
method: 'get'
})
}

View File

@@ -58,3 +58,24 @@ 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'
})
}
export function getTemplateInstance() {
return request({
url: '/canal/instance/template',
method: 'get'
})
}

View File

@@ -0,0 +1,101 @@
<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

@@ -77,49 +77,56 @@ export const constantRoutes = [
meta: { title: 'Canal Server', icon: 'example' },
children: [
{
path: 'nodeServers',
name: '节点状态',
component: () => import('@/views/canalServer/NodeServer'),
meta: { title: '节点管理', icon: 'tree' }
path: 'canalClusters',
name: 'Canal 集群管理',
component: () => import('@/views/canalServer/CanalCluster'),
meta: { title: '集群管理', icon: 'tree' }
},
{
path: 'config',
name: 'Canal主配置',
component: () => import('@/views/canalServer/Config'),
meta: { title: 'Canal主配置', icon: 'form' }
path: 'nodeServers',
name: 'Server 状态',
component: () => import('@/views/canalServer/NodeServer'),
meta: { title: 'Server 管理', icon: 'form' }
},
{
path: 'nodeServer/config',
name: 'Server 配置',
component: () => import('@/views/canalServer/CanalConfig'),
meta: { title: 'Server 配置' },
hidden: true
},
{
path: 'canalInstances',
name: '实例管理',
name: 'Instance 管理',
component: () => import('@/views/canalServer/CanalInstance'),
meta: { title: '实例管理', icon: 'nested' }
meta: { title: 'Instance 管理', icon: 'nested' }
},
{
path: 'canalInstance/add',
name: '新建实例配置',
name: '新建Instance配置',
component: () => import('@/views/canalServer/CanalInstanceAdd'),
meta: { title: '新建实例配置' },
meta: { title: '新建Instance配置' },
hidden: true
},
{
path: 'canalInstance/modify',
name: '修改实例配置',
name: '修改Instance配置',
component: () => import('@/views/canalServer/CanalInstanceUpdate'),
meta: { title: '修改实例配置' },
meta: { title: '修改Instance配置' },
hidden: true
},
{
path: 'nodeServer/log',
name: 'Canal日志',
name: 'Server 日志',
component: () => import('@/views/canalServer/CanalLogDetail'),
meta: { title: 'Canal日志' },
meta: { title: 'Server 日志' },
hidden: true
},
{
path: 'canalInstance/log',
name: 'Canal Instance日志',
name: 'Instance 日志',
component: () => import('@/views/canalServer/CanalInstanceLogDetail'),
meta: { title: 'Canal Instance日志' },
meta: { title: 'Instance 日志' },
hidden: true
}
]

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()
}

Some files were not shown because too many files have changed in this diff Show More