Compare commits

...

350 Commits

Author SHA1 Message Date
agapple
3a74f6124a fixed issue #1940 #2088 , add canal.register.ip support docker 2019-08-22 23:48:19 +08:00
agapple
bd974e3da8 upgrade fastjson 1.2.58 2019-08-22 23:03:22 +08:00
agapple
86cd805a64 upgrade fastsql 973 2019-08-22 22:02:58 +08:00
agapple
8b54b9e596 add python client info 2019-08-22 15:17:33 +08:00
agapple
2d3baeab7d fastsql tsdb testcase 2019-08-22 15:08:36 +08:00
黄大海
7b245c5a64 fix #1762 (#1959) 2019-08-21 14:33:31 +08:00
agapple
78af6b1130 fixed issue #2067 , add example 2019-08-21 13:52:34 +08:00
ever
7547cac3c5 fix compiler problem: upgrade rocketmq to 4.5.2 (#2074) 2019-08-21 12:48:46 +08:00
soocold
066743cfd6 always check whether ringbuffer has an exception in event processor (#2053) 2019-08-21 12:46:17 +08:00
rewerma
fdd0763567 新增es adapter rest client和认证机制 (#2046)
* 新增es adapter rest client和认证机制

* 修改getMapping位置

* 代码调整

* 代码调整

* 整理代码

* fix #2048
2019-08-21 12:44:57 +08:00
ThomasPerkins1123
bcdbf75dc5 Add unit tests for com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper (#2039)
These tests were written using Diffblue Cover.
2019-08-21 12:44:24 +08:00
jason-liew
db23f15b61 fix: hbase etl sql syntax error in some case (#2034) 2019-08-21 12:44:04 +08:00
rewerma
44722fe01c canal-admin-ui 指定profile编译 (#2019)
* canal-admin-ui 指定profile编译

* 启动实例时选择指定节点

* modify

* modify

* 静态页面修改
2019-07-22 09:15:14 +08:00
bucketli
be9813142f remove openjdk12 2019-07-20 19:05:49 +08:00
bucketli
c48e80a163 remove oraclejdk8 CI config,add oraclejdk12 and openjdk12 2019-07-20 18:34:01 +08:00
bucketli
c1bfad4f2d 1.not compile canal-admin module,unless you need it, to be optimize.#1998,2.support JDK 11 & 12 ,3.fix the CI block test 2019-07-20 18:12:07 +08:00
rewerma
10f4ef6acd canal admin (#1998)
* 增加注解

* modify

* 文件大小写问题
2019-07-16 21:47:53 +08:00
Li Qiang
4c0d82fb98 Update README.md 2019-07-16 20:35:37 +08:00
rewerma
7cec0994e9 Merge branch 'master' into master 2019-07-14 19:24:42 +08:00
mcy
483044916d canal admin 2019-07-14 19:18:15 +08:00
mcy
c8bb6e200c modify 2019-07-14 09:59:58 +08:00
mcy
9cc6b67426 ddl sql 2019-07-14 09:59:35 +08:00
mcy
62072ea9ab modify 2019-07-14 00:06:25 +08:00
mcy
f3daceea6b canal instance manager module finished 2019-07-13 21:34:44 +08:00
rewerma
4efc0f6078 modify 2019-07-13 18:03:28 +08:00
rewerma
40d2a8c6cc modify 2019-07-13 14:33:42 +08:00
rewerma
28da536ec8 modify 2019-07-13 11:47:55 +08:00
mcy
6f129bf524 modify 2019-07-12 21:00:33 +08:00
mcy
31ae308760 增减jmx状态控制 2019-07-12 20:59:20 +08:00
mcy
c2ce662f9a modify 2019-07-12 20:08:16 +08:00
mcy
7044e172f5 jmx 客户端 2019-07-12 20:06:58 +08:00
Menng
c070757da0 修正文档链接&优化格式 #1965 (#1967) 2019-07-12 10:13:29 +08:00
xingwenge
92dd2bb189 Update README.md (#1981) 2019-07-12 09:53:06 +08:00
rewerma
8639b45fe9 fix #1761 (#1973)
* Message转Dml对null值的判断

* Message转Dml对null值的判断

* Message转Dml对null值的判断

* fix #1761
2019-07-12 09:52:36 +08:00
mcy
6b9487a0a0 modify 2019-07-12 09:16:10 +08:00
rewerma
5741f5a347 modify 2019-07-12 00:42:18 +08:00
rewerma
4c3ef6772c modify 2019-07-12 00:01:37 +08:00
rewerma
64824bc5aa modify 2019-07-11 23:29:49 +08:00
mcy
23209f24ed 增减jmx状态控制 2019-07-11 21:34:12 +08:00
mcy
015026ea91 modify 2019-07-11 21:31:38 +08:00
mcy
c7967e989c modify 2019-07-11 20:46:02 +08:00
mcy
e4b37f33de modify 2019-07-11 20:44:39 +08:00
mcy
598f15a404 modify 2019-07-11 16:30:50 +08:00
mcy
ec5f1eb10d modify 2019-07-11 16:16:32 +08:00
mcy
28c0aac98d modify 2019-07-11 15:11:29 +08:00
mcy
50ea6870dc modify 2019-07-11 15:07:57 +08:00
mcy
317439670c modify 2019-07-11 10:16:12 +08:00
rewerma
64ff140304 modify 2019-07-11 01:05:11 +08:00
rewerma
a93af74539 modify 2019-07-11 00:20:56 +08:00
rewerma
fc39b2af68 modify 2019-07-10 21:46:38 +08:00
mcy
e87a0c3b63 canal-admin init 2019-07-10 16:22:38 +08:00
mcy
b43043f4e4 Merge remote-tracking branch 'origin/master' 2019-07-09 19:09:24 +08:00
mcy
60a46ae941 fix #1761 2019-07-09 19:08:35 +08:00
Braavos
b5c05c2fd2 Add unit tests for com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter (#1937)
These tests were written using Diffblue Cover.
2019-06-28 14:57:18 +08:00
靳光宇
0cf18976d9 Update HbaseSyncService.java (#1936)
Fix batch update or delete with only affects the first data issue
2019-06-28 14:56:51 +08:00
Li Qiang
803c0607d3 Update README.md 2019-06-27 19:45:26 +08:00
Li Qiang
f04e742f4f #1897 , fix DummyEventStore implements new method (#1932) 2019-06-27 09:58:24 +08:00
cjk87927
c81034dda3 增加表字段过滤功能 (#1914)
* 增加表字段过滤功能

* 增加表字段过滤功能

* 更换fastsql版本通过编译

* 1.修改字段配置格式 2.增加字段黑名单配置(冲突时以白名单为准)
2019-06-26 17:45:14 +08:00
agapple
a847660a45 fixed issue #1826 , remove kafka transaction send 2019-06-26 16:02:19 +08:00
agapple
4957083493 fixed issue #1897 , memory event store ack null 2019-06-25 17:32:46 +08:00
rewerma
24f0eece42 Merge pull request #1 from alibaba/master
merge
2019-06-19 21:00:31 +08:00
agapple
adccc0e912 add logger info for query_log_event 2019-06-19 15:04:22 +08:00
agapple
8728beb4b6 fixed compile 2019-06-18 22:18:30 +08:00
agapple
5bea876ec9 fixed issue #1352, add pkNames for flatMessage partition 2019-06-18 21:44:52 +08:00
agapple
6c494f8892 add rocketmq-client pom version 2019-06-18 21:28:55 +08:00
BillowX
74489d1bba 当MQ配置为Kafka时,支持配置Kerberos认证 (#1895) 2019-06-18 15:02:14 +08:00
Z6N
5ac0f7b710 按主键散列,未设置主键时无限空指针异常 (#1889)
当按注解散列时,如果监控的表未显示设置主键,将会导致flatMessage.getPkNames()为空,导致无限空指针异常
2019-06-18 15:01:00 +08:00
lanxinxu
75b4911caf 线程安全的获取meta (#1866) 2019-06-18 14:59:30 +08:00
lanxinxu
95602f4784 并行解析模式下有丢数据风险 (#1857) 2019-06-18 14:57:59 +08:00
agapple
058e1fc546 fixed delete message send mq bugfix 2019-05-28 00:14:20 +08:00
lulu2panpan
e808c6ec70 AbstractCanalInstance的subscribeChange方法,增加对AbstractEventParser的类型判断;MysqlEventParser对Gtid的处理需要考虑兼容性 (#1824)
* AbstractCanalInstance的subscribeChange方法,增加对AbstractEventParser的类型判断,以支持用户的自定义CanalEventParser

* MysqlEventParser对Gtid模式的处理需要考虑兼容性,支持系统平滑的从非gtid模式升级为gtid模式
2019-05-28 00:05:13 +08:00
rewerma
ce7948d6e7 fix Message转Dml对null值的bug (#1808)
* Message转Dml对null值的判断

* Message转Dml对null值的判断

* Message转Dml对null值的判断
2019-05-28 00:03:55 +08:00
soso
8df6d6fed8 Fix: 主备切换后没有往Kafka发送数据 (#1810)
发生主备切换时,调用canalMQStarter启停对应的destination。
2019-05-28 00:03:28 +08:00
nieyuan1991
f9989e0856 fix:未捕获kafka事务异常,造成canal ack异常 (#1814) 2019-05-28 00:02:25 +08:00
xiayuan
4075a0623f set the validationQuery while testWhileIdle is true (#1827) 2019-05-27 23:56:25 +08:00
Marsss___
37fc248ebf 删除操作计算hash值的情况没有考虑,导致删除时的分区和插入时的分区不一致 (#1848) 2019-05-27 23:54:25 +08:00
Heng Du
d0a83aad27 Support seamless docking with Alibaba Cloud RocketMQ commercial service (#1849) 2019-05-27 23:51:32 +08:00
agapple
3b9ff26d1a add logo 2019-05-15 10:20:08 +08:00
mcy
4f7842c843 Message转Dml对null值的判断 2019-05-14 17:38:01 +08:00
mcy
b870e7ecd0 Message转Dml对null值的判断 2019-05-14 17:35:34 +08:00
mcy
331dd63c5f Message转Dml对null值的判断 2019-05-14 17:31:51 +08:00
JKTerrific
72c4daba5f 1:添加对于查询结果中,MAX( CASE a.key WHEN 'WORD1' THEN a.value ELSE 0 END ) AS… (#1794)
* 1:添加对于查询结果中,MAX( CASE a.key WHEN 'WORD1' THEN a.value ELSE 0 END ) AS WORD1 的竖表转横表的支持
2:添加对于查询条件尾部,使用GROUP BY _id 等分组语句时出现的sql解析异常问题

* fix index out of range
2019-05-11 17:33:59 +08:00
Jet
7427a74612 improve es etl (#1758)
* fix typo in trace log

* improve es etl

* improve es etl log

* improve es etl by add threadCount

* improve es etl by add threadCount

* improve es etl query

* remove comment
2019-05-11 17:26:08 +08:00
paascloud
24fae6fa52 变量定义错误 (#1776)
* 变量定义错误

* 变量定义错误
2019-05-11 17:21:21 +08:00
Sweet
4c690fb5a3 Null point Exception during loading the checksum (#1788)
If you connect to the mysql with version 5.1, the result of command 'select @master_binlog_checksum' is an array with only one Null element .
2019-05-11 17:17:54 +08:00
Jet
8f088cddc0 Fix es bulk (#1743)
thanks
2019-04-25 09:36:09 +08:00
Jet
e94ee64316 fix typo in trace log (#1738) 2019-04-24 09:57:05 +08:00
dataccs
3165cb9640 支持用户自定义的CanalAlarmHandler (#1736)
* 支持用户自定义的CanalAlarmHandler

* RowsLogEvent增加对TableMapLogEvent判空检查,防止NPE异常
2019-04-24 09:56:32 +08:00
rewerma
2b738f4e04 fix bugs (#1712)
* kafka 模式默认开启事务异步发送

* fix adapter etl rest sql inject bug

* fix #1704
2019-04-18 20:33:15 +08:00
rewerma
16f335c381 修复rest etl 存在SQL注入风险 (#1685)
* kafka 模式默认开启事务异步发送

* fix adapter etl rest sql inject bug
2019-04-15 23:58:12 +08:00
王超
a8bf2b641f String.split(String regex) bug fix (#1696) 2019-04-15 23:55:03 +08:00
chenxyz707
67faf0e03e resovle config/metrics as a destination folder (#1678) 2019-04-09 21:05:42 +08:00
Xin Wang
8cb85753bb open the switch to get codecov test coverage (#1671)
* add codecov for test coverage
2019-04-09 19:11:48 +08:00
agapple
df86f94409 fixed issue template 2019-04-09 15:12:30 +08:00
agapple
80594444f7 fixed issue template 2019-04-09 15:11:14 +08:00
agapple
d8996432b9 add issue template 2019-04-09 15:08:43 +08:00
agapple
69df50fe17 fixed issue #1672 , CanalKafkaProducer send 2019-04-08 19:57:00 +08:00
agapple
c5736bd5f3 Merge pull request #1670 from lovepoem/update_compile_jdk
update jacoco lib
2019-04-08 12:28:27 +08:00
Xin Wang
55030476ee reverse jdk to 1.8 2019-04-07 19:14:46 +08:00
Xin Wang
cd007088db reverse jdk to 1.8 2019-04-07 18:52:35 +08:00
Xin Wang
1fc880f255 reverse jdk to 1.7 2019-04-07 18:45:54 +08:00
Xin Wang
8c68cf833a update jacoco lib 2019-04-07 18:38:50 +08:00
Xin Wang
0de8f4691e update jacoco lib 2019-04-07 18:25:05 +08:00
Xin Wang
5a2efbf9cb update jacoco lib 2019-04-07 18:23:53 +08:00
agapple
dfab98fce1 Merge pull request #1669 from lovepoem/add_test_coverage_and_jdk11
add test coverage and jdk11
2019-04-06 23:37:40 +08:00
Xin Wang
e5c7bbffbf update pom 2019-04-06 18:13:41 +08:00
Xin Wang
e139698f6e update pom 2019-04-06 17:50:05 +08:00
Xin Wang
2b8120b6ed update pom 2019-04-06 17:49:03 +08:00
Xin Wang
68ebb3a957 update skipTests=false 2019-04-06 17:35:16 +08:00
Xin Wang
21309cb065 resove Could not resolve dependencies for project com.alibaba.otter:client-adapter.hbase🫙1.1.4-SNAPSHOT: Could not find artifact jdk.tools:jdk.tools🫙1.7 at specified path 2019-04-06 17:23:48 +08:00
Xin Wang
386567a633 resove Could not resolve dependencies for project com.alibaba.otter:client-adapter.hbase🫙1.1.4-SNAPSHOT: Could not find artifact jdk.tools:jdk.tools🫙1.7 at specified path 2019-04-06 16:56:03 +08:00
Xin Wang
95c69bb854 add test coverage and jdk11 2019-04-06 16:45:04 +08:00
Xin Wang
6febcf1a0f add test coverage and jdk11 2019-04-06 16:42:45 +08:00
agapple
fadfe1d6ca [maven-release-plugin] prepare for next development iteration 2019-04-04 10:25:44 +08:00
agapple
a7b12c7098 [maven-release-plugin] prepare release canal-1.1.3 2019-04-04 10:25:29 +08:00
agapple
2c320e8618 Merge pull request #1661 from rewerma/master
flatMessage模式下默认canal.mq.lingerMs=100
2019-04-04 10:08:34 +08:00
machey
d76d25df6c flatMessage模式下默认canal.mq.lingerMs=100 2019-04-04 00:14:27 +08:00
agapple
b95c0d03cf fixed issue #1660 ,support mysql8.0 caching_sha2_password auth 2019-04-03 23:42:41 +08:00
agapple
08af40221f Merge pull request #1658 from rewerma/master
adapter 日志打印修改
2019-04-03 12:12:56 +08:00
rewerma
d13c1eb17c Merge branch 'master' into master 2019-04-03 11:34:10 +08:00
agapple
82ed9db8ae fixed rocketmq partition out of range 2019-04-03 11:26:31 +08:00
agapple
d3195ac973 fixed issue #1626 , support alter table id first 2019-04-03 10:58:43 +08:00
agapple
15e29d276a fixed default xml config 2019-03-28 22:05:56 +08:00
agapple
d715ff64b4 fixed packet write batch 2019-03-28 21:52:51 +08:00
agapple
f63ce4ffe6 fixed mysql auth 2019-03-28 20:45:16 +08:00
agapple
a7f9a779ad fixed issue #1640 , support schema partition 2019-03-28 17:01:36 +08:00
agapple
39aeeb8c58 Merge pull request #1641 from wangsaner/master
修改canal.mq.partitionHash散列规则,使其支持同库下的不同表hash至相同分区
2019-03-28 16:58:31 +08:00
wangsaner
9826948ebb 修改canal.mq.partitionHash散列规则,
当指明pk级做分区(schema.table:pk1^pk2)时,由table.hashCode()^column.getValue().hashCode()改为database.hashCode()^column.getValue().hashCode(),从而支持同库下不同表hash至相同分区
2019-03-28 16:01:02 +08:00
wangsaner
d6ef66a281 Merge pull request #1 from alibaba/master
merge
2019-03-28 10:37:24 +08:00
agapple
97640b717b fixed compiler 2019-03-28 10:23:33 +08:00
agapple
20e7becfe9 fixed format 2019-03-28 10:19:53 +08:00
agapple
99b34ff632 Merge pull request #1639 from wangsaner/master
fixed issue #1636
2019-03-28 10:17:42 +08:00
wangsaner
739963acd0 fixed issue #1636
非flat模式,当MQ主题为多分区时,数据拆分只取了for循环里面的最后一份数据,
导致消费端只能消费到一个分区的数据
2019-03-27 21:57:04 +08:00
agapple
0ec4699149 fixed format & support Mysql Auth Switch Protocol 2019-03-27 11:54:40 +08:00
mcy
c101ae2280 Merge branch 'master' of https://github.com/rewerma/canal 2019-03-26 11:39:41 +08:00
mcy
a137deeaff 使用统一log 2019-03-26 11:29:52 +08:00
rewerma
c5be5b7a8a Merge pull request #40 from alibaba/master
merge
2019-03-26 00:43:11 +08:00
agapple
95bed6bcf6 fixed fastsql 2019-03-25 19:50:02 +08:00
agapple
661d119265 fixed issue #1625, mq dymaic topic match 2019-03-25 17:54:35 +08:00
agapple
08167c95c7 Merge pull request #1622 from rewerma/master
fix bugs
2019-03-22 18:14:14 +08:00
mcy
37c5168a7d code optimization 2019-03-22 14:49:50 +08:00
mcy
579fa72ae2 kafka consumer connector引入事务隔离级别 fix #1512 2019-03-22 13:44:02 +08:00
rewerma
844c019433 Merge pull request #39 from alibaba/master
merge
2019-03-22 09:10:43 +08:00
agapple
c94e3a0024 fixed default properties 2019-03-21 20:59:09 +08:00
agapple
f6ca489984 Merge pull request #1620 from rewerma/master
fix bugs
2019-03-21 20:51:10 +08:00
agapple
b2b69307f9 fixed rocketmq 2019-03-21 20:50:52 +08:00
agapple
531b535228 fixed bug 2019-03-21 20:26:00 +08:00
mcy
99cccd2113 fix #1608 2019-03-21 20:07:06 +08:00
rewerma
38d8c3d462 Merge branch 'master' into master 2019-03-21 19:42:07 +08:00
agapple
024ffc2aa2 fixed mq partition num priority 2019-03-21 19:39:21 +08:00
agapple
a81d2710ff fixed MQ partition 2019-03-21 19:27:39 +08:00
mcy
4bad0e88f8 增加日志配置 fix #1570 2019-03-21 19:04:33 +08:00
mcy
0181dfaa42 sql解析对case when 的支持
fix #1598
2019-03-21 18:35:58 +08:00
rewerma
0411d99e7b Merge pull request #37 from alibaba/master
merge
2019-03-21 18:07:41 +08:00
agapple
8fcdd7f798 Merge pull request #1619 from rewerma/master
fix #1608
2019-03-21 17:58:57 +08:00
mcy
b0f4e4ee78 fix #1608 2019-03-21 17:55:35 +08:00
agapple
d448455ebf Merge pull request #1613 from KangZhiDong/master
set utf8mb4 to support emoji
2019-03-21 16:49:44 +08:00
agapple
e07fdb6426 Update README.md 2019-03-21 16:46:42 +08:00
agapple
38336b249e fixed travis 2019-03-21 16:36:41 +08:00
agapple
e7b81cd83a fixed mvn wrapper 2019-03-21 16:30:05 +08:00
agapple
2810e7e815 add mvnw 2019-03-21 16:20:13 +08:00
agapple
024986a195 fixed travlis.yml 2019-03-21 16:16:31 +08:00
kangzhidong
9b2ad0eafd set utf8mb4 to support emoji 2019-03-20 16:29:53 +08:00
康智冬
03d46f150f Merge pull request #2 from alibaba/master
update
2019-03-20 16:21:38 +08:00
agapple
b488ca02d8 fixed travlis 2019-03-20 13:55:29 +08:00
agapple
17a6ba9c9c fixed travlis 2019-03-20 13:44:29 +08:00
agapple
3b618740d4 Merge pull request #1607 from rewerma/master
ES同步新增父子文档索引适配
2019-03-20 13:35:25 +08:00
mcy
06bfcda275 code optimization 2019-03-20 10:41:51 +08:00
mcy
5821c1f606 modify 2019-03-20 09:10:15 +08:00
mcy
a2360efff9 增加对sql字段 `` 的支持
fix #1559
2019-03-19 19:13:15 +08:00
mcy
51c469aa6f es etl 主键类型bug fix #1559 2019-03-19 17:54:11 +08:00
mcy
6b3bf31404 adapter代码整理优化 2019-03-19 17:49:00 +08:00
mcy
941ed7f60d Merge branch 'feature/es2' 2019-03-18 09:06:58 +08:00
rewerma
9db2bd291b Merge pull request #36 from alibaba/master
merge
2019-03-18 09:05:47 +08:00
agapple
95b26c4d1d fixed #1433, optimzier code 2019-03-17 10:35:16 +08:00
agapple
b114e0cb15 Merge pull request #1581 from alexinea/master
CanalSharp has transferred to .NET Core Community
2019-03-17 10:30:03 +08:00
agapple
5c96bf282e Merge pull request #1590 from rewerma/master
fix #1585
2019-03-17 10:29:12 +08:00
agapple
6d280d1048 Merge pull request #1597 from KangZhiDong/master
fix the error log does not contain table name
2019-03-17 10:28:47 +08:00
agapple
cf4db1625d Merge pull request #1605 from lizhyocx/master
解决单核服务器无法订阅数据的bug
2019-03-17 10:27:50 +08:00
lizhyocx
df98fec945 解决单核服务器无法订阅数据的bug
解决单核服务器c中anal.instance.parser.parallel = true时无法订阅数据的bug
2019-03-15 17:11:14 +08:00
mcy
e03705e211 es父子文档索引ETL支持 2019-03-13 17:19:08 +08:00
mcy
4df60a0fb5 es增加父子文档索引同步适配 2019-03-13 16:57:23 +08:00
kangzhidong
078673c6a9 fix the error log does not contain table name 2019-03-13 16:55:38 +08:00
mcy
4bd91a1079 fix bug 2019-03-13 14:36:27 +08:00
mcy
05ad671cc1 es增加relations配置
配置monitor yml加载使用通用加载类
2019-03-13 14:34:31 +08:00
mcy
43045bc672 use system time zone for es datetime field formatter 2019-03-13 10:01:16 +08:00
mcy
971c9a88c2 modify 2019-03-12 17:07:33 +08:00
rewerma
7b6e482827 Merge pull request #35 from alibaba/master
merge
2019-03-12 16:58:05 +08:00
mcy
ad39559f8c fix #1585 2019-03-12 16:56:59 +08:00
AlexLEWIS
2707c12570 CanalSharp has transferred to .NET Core Community
CanalSharp has transferred to .NET Core Community(NCC) and became a member project of NCC.
2019-03-11 14:47:14 +08:00
康智冬
7dab5ac6c2 Merge pull request #1 from alibaba/master
update
2019-03-08 10:05:20 +08:00
agapple
9d4b61f083 Merge pull request #1567 from dragontree101/miss_producergroup_properties
producerGroup配置没有读取
2019-03-06 11:07:15 +08:00
dragonlong1986
a34f9ae97b Merge branch 'master' into miss_producergroup_properties 2019-03-05 17:44:35 +08:00
dragonlong1986
64ca62b986 update 2019-03-05 17:39:56 +08:00
agapple
14c1af4c80 Merge pull request #1542 from rewerma/master
fixed #1538
2019-03-04 14:13:14 +08:00
agapple
7436d9f06f Merge pull request #1550 from lee123lee123/master
Issues #1460  修改tsdb中同步数据带表情字符错误问题
2019-03-04 14:12:33 +08:00
agapple
c4599df1c2 Merge pull request #1560 from dragontree101/doc_mistake_key
key改成大写,小写不会生效
2019-03-04 14:07:22 +08:00
dragonlong1986
7abf50f233 key改成大写,小写不会生效 2019-03-02 10:25:19 +08:00
machey
1781381187 modify 2019-03-01 00:19:47 +08:00
machey
5d4210bd22 modify 2019-02-28 23:31:07 +08:00
machey
9888fae559 负时区的bug 2019-02-28 23:22:02 +08:00
rewerma
81ab7e9783 Merge branch 'master' into master 2019-02-28 19:37:27 +08:00
mcy
b891afa50e 增加时区判断 2019-02-28 19:28:17 +08:00
lee123lee123
8187d7c5df Issues #1460 修改utf8数据带表情字符错误 2019-02-28 19:04:14 +08:00
mcy
e7f069850f fixed #1538 2019-02-28 17:29:06 +08:00
agapple
d377a92a5d fixed issue #1548, NPE 2019-02-28 10:52:28 +08:00
agapple
f664af1af4 fixed issue #1546 , support drop database 2019-02-28 10:47:42 +08:00
agapple
5e2eabb3c2 Merge pull request #1544 from finefuture/MQ-fix-pr
fix issue 1532
2019-02-28 10:32:41 +08:00
agapple
f2368e5469 Merge pull request #1540 from wurongjun/master
修复当适配器mapping文件修改时,无法生效的问题
2019-02-28 10:32:07 +08:00
TomlongTK
fd4c8db92b fix issue 1532
因为binlog日志同表名大小写的原因导致kafka的日志恢复并resize,报错之后引起集群崩溃
现在修改为投递消息的topic不区分表名大小写
2019-02-26 14:23:29 +08:00
mcy
f21abc13af fixed #1538 2019-02-26 10:23:38 +08:00
wurongjun@duiba.com.cn
ae474d67ad 修复当适配器mapping文件修改时,无法生效的问题 2019-02-25 17:53:30 +08:00
bigbro
0c4711ab62 Merge pull request #2 from alibaba/master
合并原项目最新代码
2019-02-25 17:51:19 +08:00
agapple
072420c32b fixed 2019-02-25 15:18:04 +08:00
agapple
1473a4a6b5 Merge pull request #1523 from rewerma/master
es adapter新增upsert属性
2019-02-25 14:50:58 +08:00
agapple
0fff514876 Merge pull request #1533 from DaiZZZZZ/master
CanalParameter添加parallel参数,用来配置canalinstance
2019-02-25 14:45:54 +08:00
agapple
4017de5102 Merge pull request #1526 from jiuchengspotorg/master
adapter rdb无法同步ddl更新问题修正
2019-02-25 14:15:47 +08:00
mcy
fcc1fc78ae fix #1521 2019-02-22 17:09:50 +08:00
mcy
17efc2ada0 es 批量提交修改 2019-02-22 16:56:58 +08:00
zhikuodai
a68bd28852 CanalParameter类添加parallel参数 2019-02-21 22:04:06 +08:00
daizzzzz
1de8b27da1 Merge pull request #3 from alibaba/master
merge
2019-02-21 17:25:55 +08:00
mcy
01dc8ce2b8 YmlConfigBinder代码整理 2019-02-20 15:00:48 +08:00
jiucheng
f7e490f7a0 1、adapter/rdb,从库表字段(varchar,char,text,longtext)非空约束时,无法同步问题修正
2、主库存在0000-00-00 00:00:00时间时,从库表字段是非空约束时,无法同步问题修正
2019-02-20 10:10:14 +08:00
jiucheng
8cadd585c6 adapter rdb无法同步ddl更新问题修正
adapter/support/MessageUtil.java
2019-02-20 09:49:15 +08:00
mcy
57c3226e56 Merge branch 'feature/es2' 2019-02-19 15:05:59 +08:00
rewerma
79a3961da1 Merge pull request #34 from alibaba/master
merge
2019-02-19 15:05:34 +08:00
agapple
62338a945e Merge pull request #1496 from rewerma/master
远程配置代码整理
2019-02-18 21:08:02 +08:00
agapple
e07db7b178 Merge pull request #1507 from i404/master
修复 FlatMessage  Hash 到不正确的分区的Bug。
2019-02-18 21:05:03 +08:00
mcy
05cc627b5d fix rdb etl bug 2019-02-15 11:39:26 +08:00
i404
44beaf1916 修复当 canal.mq.flatMessage = true 时,MQMessageUtils::messagePartition 数据分区错误的 bug 2019-02-14 18:26:01 +08:00
mcy
476244b26d modify 2019-02-14 15:41:27 +08:00
mcy
cfdf509f9a 增加对pk的支持 2019-02-14 15:29:27 +08:00
mcy
c14ed724ff update中增加upsert判断 2019-02-13 15:59:46 +08:00
mcy
5a4cfeab46 Merge branch 'master' into feature/es2
# Conflicts:
#	client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
2019-02-13 15:51:24 +08:00
mcy
947cf130ab fixed #1504 2019-02-13 15:50:11 +08:00
mcy
0ef21e4155 代码整理 2019-02-13 15:47:48 +08:00
mcy
83b760494d ES adapter代码重新整理
新增upsert属性
2019-02-13 15:19:09 +08:00
mcy
aba8df699c Merge branch 'master' into feature/es2
# Conflicts:
#	client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java
2019-02-12 17:21:42 +08:00
rewer
0c5f5c128f 整理代码 2019-02-07 13:49:35 +08:00
mcy
ad62275cd7 增加kafka自定义properties 2019-02-05 19:20:36 +08:00
mcy
f6d03bcb37 fixed #1490 2019-02-05 17:48:08 +08:00
mcy
5284aa9793 fix bugs 2019-02-01 16:20:53 +08:00
mcy
10276e254a mq模式下adapter配置增加groupId属性对应 2019-02-01 15:18:16 +08:00
mcy
597a08ac37 通过groupId过滤对应的adapter配置 2019-02-01 13:51:49 +08:00
mcy
8211ef35e6 修改yml配置读取绑定,遵循spring yml加载方式,支持environment级占位符替换 2019-02-01 10:11:52 +08:00
mcy
a20960834b 远程配置代码整理 2019-01-31 09:45:52 +08:00
rewerma
7b6b332463 Merge pull request #33 from alibaba/master
merge
2019-01-31 09:03:37 +08:00
agapple
43f869599d Merge pull request #1484 from liulangwa/master
捕获"rs = query("select @@global.binlog_checksum")" 结果rs为null的异常
2019-01-30 11:45:33 +08:00
agapple
e3d05f6201 Merge pull request #1482 from wurongjun/master
修复从数据库加载配置写入文件时因目录不存在出现的问题
2019-01-30 11:43:47 +08:00
codebee
032301102c 捕获"rs = query("select @@global.binlog_checksum")" 结果rs为null的异常 2019-01-29 17:10:26 +08:00
wurongjun@duiba.com.cn
bb64d84298 修复从数据库加载配置写入文件时因目录不存在出现的问题 2019-01-29 16:10:26 +08:00
bigbro
ccd39e9d62 Merge pull request #1 from alibaba/master
update
2019-01-29 16:03:43 +08:00
agapple
32031da9db Merge pull request #1475 from rewerma/master
整理远程配置, 统一扩展接口
2019-01-29 10:13:03 +08:00
mcy
9a2e890f57 整理adapter端远程配置, 统一扩展接口 2019-01-25 18:50:30 +08:00
mcy
8f76b33214 解析配置修改 2019-01-25 17:51:38 +08:00
mcy
a7467539c7 整理server端远程配置, 统一扩展接口 2019-01-25 17:50:50 +08:00
agapple
db5495d158 Merge pull request #1464 from KangZhiDong/master
修改注释
2019-01-25 10:41:40 +08:00
kangzhidong
b04d948064 修改注释 2019-01-23 11:58:07 +08:00
agapple
5f8eb4d261 Merge pull request #1456 from rewerma/master
新增kafka消息投递事务
2019-01-21 23:45:13 +08:00
mcy
197ca67c8b 测试调整 2019-01-21 09:56:35 +08:00
machey
851637611f modify 2019-01-20 15:02:06 +08:00
machey
4ebf17f57b 代码调整 2019-01-20 14:30:20 +08:00
machey
2f214ce3a6 采用事务批量异步投递kafka flatMessage
fixed #1454
2019-01-20 14:20:06 +08:00
agapple
8d4fbb395b fixed issue #1451 , fixed NPE 2019-01-18 11:00:02 +08:00
agapple
d935c39648 Merge pull request #1449 from wdlily/master
fixed a word
2019-01-18 10:24:01 +08:00
wdlily
6e0e3fbef1 fixed a word
change '租' to '组'
2019-01-17 15:52:24 +08:00
rewerma
cbe88af2e3 Merge pull request #1442 from rewerma/master
fixed #1430
2019-01-17 15:18:40 +08:00
mcy
ba9d9dea6d fixed #1433 2019-01-17 11:27:12 +08:00
mcy
2d88d803ee merge #1440 and fixed #1430 2019-01-17 10:52:27 +08:00
rewerma
ff1fef1eb6 Merge pull request #32 from alibaba/master
merge
2019-01-17 10:48:23 +08:00
rewerma
4beb1e6855 Merge branch 'master' into master 2019-01-17 10:48:14 +08:00
rewerma
d2bbe9b061 Merge pull request #1440 from wurongjun/master
fix #1430
2019-01-17 10:46:30 +08:00
rewerma
171bedf488 Merge pull request #31 from alibaba/master
merge
2019-01-17 10:43:49 +08:00
mcy
e387cfdee5 fixed #1430 2019-01-17 10:22:08 +08:00
wurongjun@duiba.com.cn
47d7914304 停机时清空zk客户端信息,修复重启时报出的zk连接已关闭的异常 2019-01-16 17:28:51 +08:00
agapple
e4177ab0ce Merge pull request #1419 from rewerma/master
specified mq dynamic topic
2019-01-16 10:27:25 +08:00
mcy
739f3b4fa1 fixed #1433 2019-01-15 18:39:32 +08:00
mcy
60441d8b31 fixed #1396 2019-01-15 18:20:05 +08:00
mcy
ca41be8412 fixed #1420 2019-01-15 11:38:00 +08:00
mcy
18b304f99d specified mq dynamic topic 2019-01-14 14:25:47 +08:00
agapple
7f9dce4b77 Merge pull request #1418 from rewerma/master
fixed #1416
2019-01-14 13:10:32 +08:00
mcy
f26adff7c7 fixed #1416 2019-01-14 11:16:33 +08:00
mcy
77646c0185 拼写错误 2019-01-14 10:58:33 +08:00
agapple
7e4912cd2e Merge pull request #1413 from wingerx/master
获取TableMeta信息时,增加一次retry操作
2019-01-12 11:26:30 +08:00
agapple
70a4f0363d Merge pull request #1412 from rewerma/master
fixed #1408 fixed # 1403
2019-01-12 11:26:11 +08:00
winger
725960d46e 获取TableMeta信息时,增加一次retry操作 2019-01-11 14:00:15 +08:00
mcy
b00d347eb0 rdb adapter 增加 truncate dml适配 fixed #1408
fixed #1403
2019-01-11 13:40:37 +08:00
rewerma
92666b41e8 Merge pull request #30 from alibaba/master
merge
2019-01-11 12:59:01 +08:00
agapple
f1753e79f9 Merge pull request #1407 from lileibuaa/master
fix filter transaction entry logic in EntryEventSink
2019-01-11 10:29:06 +08:00
lileibuaa
f1a998b2d5 fix filter transaction entry logic in EntryEventSink 2019-01-10 21:16:25 +08:00
agapple
03e6662ce5 fixed mq 2019-01-08 23:57:16 +08:00
agapple
b845a67666 Merge pull request #1391 from rewerma/master
fixed 1390
2019-01-08 23:42:57 +08:00
mcy
690222bde8 dynamic topic 正则注释 2019-01-08 11:28:29 +08:00
mcy
63ad80c8c4 fixed 1390 2019-01-08 10:39:32 +08:00
agapple
de501c85a1 fixed issue #1374 , support queryLogEvent partition 2019-01-07 13:39:04 +08:00
agapple
670b7830a5 Merge pull request #1383 from rewerma/master
增加adapter远程配置
2019-01-07 13:31:01 +08:00
agapple
fdf150bd24 fixed issue #1341 2019-01-07 13:28:48 +08:00
agapple
c66b6a0f0f fixed charset 2019-01-07 12:59:33 +08:00
machey
a91aebafc2 modify 2019-01-06 21:08:17 +08:00
machey
35fcd56822 Merge branch 'feature/mgr'
# Conflicts:
#	client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java
2019-01-05 01:53:42 +08:00
machey
a63e982115 添加注释 2019-01-05 01:51:43 +08:00
machey
4bdf3d766f modify 2019-01-05 01:44:04 +08:00
machey
779ebad890 adapter 远程配置
修改rdb热加载配置的问题
2019-01-05 01:42:27 +08:00
agapple
b348260288 fixed issue #1363 , date format 2019-01-03 11:48:33 +08:00
agapple
3011c3292b Merge pull request #1362 from rewerma/master
fixed #1359
2019-01-02 22:42:22 +08:00
mcy
94b8fdbbf4 动态topic的xml配置 2019-01-02 13:28:27 +08:00
mcy
1dd9934f9c fixed #1359 2019-01-02 13:23:32 +08:00
mcy
3dec7c50ce commit 2019-01-02 11:05:46 +08:00
machey
ecd9ababa1 modify 2019-01-01 17:55:23 +08:00
machey
b0eaa156cc 增加adapter远程配置 2019-01-01 17:33:01 +08:00
agapple
a70178afef Merge pull request #1357 from rewerma/master
canal server配置统一数据库管理监控
2018-12-31 12:16:21 +08:00
machey
114c3bd020 modify 2018-12-30 17:16:47 +08:00
machey
f9e19d46c4 增加注释 2018-12-30 17:08:11 +08:00
machey
661752096c 增加注释 2018-12-30 17:04:15 +08:00
machey
100b43a5db Merge branch 'feature/mgr' 2018-12-30 16:50:32 +08:00
rewerma
5e116de264 Merge pull request #29 from alibaba/master
merge
2018-12-30 16:50:05 +08:00
machey
a1091368fb modify 2018-12-30 16:47:35 +08:00
machey
71b7c0b868 canal server增加数据库远程配置 2018-12-30 16:44:35 +08:00
mcy
7c79714528 增加远程db config配置 2018-12-29 20:21:03 +08:00
agapple
8d84521767 Merge pull request #1355 from rewerma/master
adapter 分组间线程统一抛异常
2018-12-28 23:06:08 +08:00
agapple
14ba358525 fixed dymaic topic 2018-12-28 18:35:50 +08:00
mcy
7ea1468a8d adapter 分组间线程统一抛异常 2018-12-28 17:30:04 +08:00
agapple
f72ac9a3bb fixed adapter throw exception 2018-12-28 16:55:44 +08:00
agapple
f100aeda39 Merge pull request #1345 from rewerma/master
fixed 1341
2018-12-28 15:22:20 +08:00
agapple
953a0d0583 Merge pull request #1349 from mujiansu/master
fix bat syntax error
2018-12-28 15:20:05 +08:00
agapple
2afe7debda Merge pull request #1305 from wingerx/master
修复同时开启gtid与tsdb 时gtid 属性丢失的问题
2018-12-28 15:13:47 +08:00
agapple
d8737a5228 fixed issue #1343, support charset 255 2018-12-28 13:46:50 +08:00
mcy
150a13824b 修改 kafka 异步发送 flat message 2018-12-27 17:52:33 +08:00
JasonMu
830e149d0e fix bat syntax error 2018-12-27 16:41:15 +08:00
mcy
d9e259dde4 增加支持按库名、表名动态发送topic支持 2018-12-27 15:40:04 +08:00
mcy
9e6adff942 修复adapter无法回退的问题,fixed 1341
修复HBase断连的问题
2018-12-26 15:24:30 +08:00
winger
82a78546ab Merge remote-tracking branch 'upstream/master' 2018-12-26 15:11:17 +08:00
winger
19c35c0b08 修复:当mysql 发生过purged gtid时,同时开启tsdb与gtid后,gtid属性丢失问题 2018-12-26 15:10:57 +08:00
agapple
ade2a54369 Merge pull request #1340 from rewerma/master
fixed #1338
2018-12-25 22:42:25 +08:00
machey
dffbc437a6 fixed #1338 2018-12-25 22:27:27 +08:00
mcy
b5697a366a merge 2018-12-23 18:24:04 +08:00
rewerma
0469d28c05 Merge pull request #28 from alibaba/master
merge
2018-12-23 18:20:43 +08:00
rewerma
d5f44dd76f Merge branch 'master' into master 2018-12-23 18:20:33 +08:00
mcy
354d64069f adapter增加同步异常重试 2018-12-23 18:16:47 +08:00
winger
3465866278 修复开启gtid时,同时开启tsdb后gtid 属性丢失的问题 2018-12-21 15:18:07 +08:00
agapple
840148fbf6 Merge pull request #1326 from rewerma/master
类型转换问题 fixed #1313
2018-12-21 15:10:25 +08:00
mcy
34b5f9c927 modify 2018-12-20 19:53:30 +08:00
mcy
75ee65f9e8 类型转换问题 fixed #1313 2018-12-20 19:45:57 +08:00
agapple
035ef26542 fixed issue #1306 , support MySqlOrderingExpr 2018-12-19 11:43:49 +08:00
agapple
4ef45b9a1d Merge pull request #1310 from panjianping/master
添加 KafkaOffsetCanalConnector 使用示例
2018-12-18 16:51:50 +08:00
panjianping
990fa9498a 添加 KafkaOffsetCanalConnector 使用示例 2018-12-18 16:46:19 +08:00
agapple
276a2b988a Merge pull request #1308 from panjianping/master
添加带kafka消息offset的Connector
2018-12-18 16:15:01 +08:00
panjianping
ef72cddfe8 添加带kafka消息offset的Connector 2018-12-18 11:54:41 +08:00
winger
d8d3246e30 Merge remote-tracking branch 'upstream/master' 2018-12-17 16:15:18 +08:00
winger
bba800eb96 修复开启gtid时,同时开启tsdb后gtid 属性丢失的问题 2018-12-17 16:14:55 +08:00
728 changed files with 42298 additions and 6239 deletions

6
.codecov.yml Normal file
View File

@@ -0,0 +1,6 @@
coverage:
status:
# pull-requests only
patch:
default:
threshold: 0.1%

View File

@@ -0,0 +1,8 @@
---
name: Any Questions on Canal
about: Ask whatever you want to know or confusion about Canal
---
## Question
<!-- You can ask any question about this project -->

View File

@@ -0,0 +1,29 @@
---
name: Bug Report for Canal
about: Create a report to help us improve
---
- [ ] I have searched the [issues](https://github.com/alibaba/canal/issues) of this repository and believe that this is not a duplicate.
- [ ] I have checked the [FAQ](https://github.com/alibaba/canal/wiki/FAQ) of this repository and believe that this is not a duplicate.
### environment
* canal version
* mysql version
### Issue Description
### Steps to reproduce
### Expected behaviour
### Actual behaviour
If there is an exception, please attach the exception trace:
```
Just put your stack trace here!
```

View File

@@ -0,0 +1,16 @@
---
name: Feature Request of Canal
about: Suggest an idea for this project
---
## Why you need it?
<!-- Is your feature request related to a problem? Please describe in details -->
## How it could be?
<!--A clear and concise description of what you want to happen. For a computer fan, you can explain more about input of the feature, and output of it.-->
## Other related information
<!-- Add any other context or screenshots about the feature request here.-->

View File

@@ -1,13 +0,0 @@
### environment
* canal version
* mysql version
### Issue Description
### Steps to reproduce
### Expected behaviour
### Actual behaviour

6
.gitignore vendored
View File

@@ -16,3 +16,9 @@ jtester.properties
.DS_Store
*.tar.gz
*.rpm
client-adapter/example/
*.dat
canal-admin/canal-admin-ui/dist
canal-admin/canal-admin-ui/node
canal-admin/canal-admin-ui/node_modules

BIN
.mvn/wrapper/maven-wrapper.jar vendored Executable file

Binary file not shown.

1
.mvn/wrapper/maven-wrapper.properties vendored Executable file
View File

@@ -0,0 +1 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip

23
.travis.yml Normal file
View File

@@ -0,0 +1,23 @@
language: java
sudo: false # faster builds
jdk:
- oraclejdk12
- openjdk11
- oraclejdk11
- openjdk8
cache:
directories:
- $HOME/.m2
install: true
script:
- travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true
after_success:
- bash <(curl -s https://codecov.io/bash)
after_failure:
- if [ -f canal.log ]; then echo "------TAIL of canal.log------"; tail -n 1000 canal.log; echo "------END of canal.log------"; fi

196
README.md
View File

@@ -1,126 +1,100 @@
<div class="blog_content">
<div class="iteye-blog-content-contain">
<h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p>
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
<p style="font-size: 14px;"> </p>
<p style="font-size: 14px;">基于日志增量订阅&amp;消费支持的业务:</p>
<ol style="font-size: 14px;">
<li>数据库镜像</li>
<li>数据库实时备份</li>
<li>多级索引 (卖家和买家各自分库索引)</li>
<li>search build</li>
<li>业务cache刷新</li>
<li>价格变化等重要业务消息</li>
</ol>
<h1>项目介绍</h1>
<p style="font-size: 14px;"> 名称:canal [kə'næl]</p>
<p style="font-size: 14px;"> 译意: 水道/管道/沟渠 </p>
<p style="font-size: 14px;"> 语言: 纯java开发</p>
<p style="font-size: 14px;"> 定位: 基于数据库增量日志解析,提供增量数据订阅&amp;消费,目前主要支持了mysql</p>
<p style="font-size: 14px;"> 关键词: mysql binlog parser / real-time / queue&topic </p>
<p style="font-size: 14px;"> </p>
<h2>工作原理</h2>
<h3 style="font-size: 14px;">mysql主备复制实现</h3>
<p><img src="http://dl.iteye.com/upload/attachment/0080/3086/468c1a14-e7ad-3290-9d3d-44ac501a7227.jpg" alt=""><br> 从上层来看,复制分成三步:
<ol>
<li>master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events,可以通过show binlog events进行查看);</li>
<li>slave将master的binary log events拷贝到它的中继日志(relay log);</li>
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
</ol>
<h3>canal的工作原理:</h3>
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273">
<p>原理相对比较简单:</p>
<ol>
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
<li>mysql master收到dump请求,开始推送binary log给slave(也就是canal)</li>
<li>canal解析binary log对象(原始为byte流)</li>
</ol>
[![build status](https://travis-ci.com/alibaba/canal.svg?branch=master)](https://travis-ci.com/alibaba/canal)
[![codecov](https://codecov.io/gh/alibaba/canal/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/canal)
![maven](https://img.shields.io/maven-central/v/com.alibaba.otter/canal.svg)
![license](https://img.shields.io/github/license/alibaba/canal.svg)
[![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")
<h1>重要版本更新说明</h1>
## 背景
canal 1.1.x系列,参考release文档:<a href="https://github.com/alibaba/canal/releases">版本发布信息</a>
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更。从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变更进行同步,由此衍生出了增量订阅和消费业务,从此开启一段新纪元。
1. 整体性能测试&优化,提升了150%. #726 参考: 【[Performance](https://github.com/alibaba/canal/wiki/Performance)】
2. 原生支持prometheus监控 #765 【[Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)】
3. 原生支持kafka消息投递 #695 【[Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)】
4. 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: 【[Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)】
5. 原生支持docker镜像 #801 参考: 【[Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)】
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
<h1>相关文档</h1>
基于日志增量订阅和消费的业务包括
- 数据库镜像
- 数据库实时备份
- 索引构建和实时维护(拆分异构索引、倒排索引等)
- 业务 cache 刷新
- 带业务逻辑的增量数据处理
See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文档</a>
## 项目介绍
<h3><a name="table-of-contents" class="anchor" href="#table-of-contents"><span class="mini-icon mini-icon-link"></span></a>wiki文档列表</h3>
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Home">Home</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Introduction">Introduction</a></li>
<li>
<a class="internal present" href="https://github.com/alibaba/canal/wiki/QuickStart">QuickStart</a>
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Docker-QuickStart">Docker QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart">Canal Kafka/RocketMQ QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart">Aliyun RDS QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Prometheus-QuickStart">Prometheus QuickStart</a></li>
</ul>
</li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/AdminGuide">AdminGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientExample">ClientExample</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientAPI">ClientAPI</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Performance">Performance</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/DevGuide">DevGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28mysql5.6%29">BinlogChange(Mysql5.6)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28MariaDB%29">BinlogChange(MariaDB)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/TableMetaTSDB">TableMetaTSDB</a></li>
<li><a href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/FAQ">FAQ</a></li>
</ul>
#### 名称:canal [kə'næl]
#### 译意: 水道/管道/沟渠
#### 产品定位: 基于数据库增量日志解析,提供增量数据订阅和消费
#### 关键词: MySQL binlog parser / real-time / queue&topic / index build
<h1>多语言业务</h1>
## 工作原理
1. canal整体交互协议设计上使用了protobuf3.0,理论上可以支持绝大部分的多语言场景,欢迎大家提交多客户端的PR
* canal java客户端: <a href="https://github.com/alibaba/canal/wiki/ClientExample"> https://github.com/alibaba/canal/wiki/ClientExample </a>
* canal c#客户端开源项目地址:<a href="https://github.com/CanalClient/CanalSharp"> https://github.com/CanalSharp/CanalSharp </a>
* canal go客户端开源项目地址:<a href="https://github.com/CanalClient/canal-go"> https://github.com/CanalClient/canal-go </a>
2. canal作为MySQL binlog的增量获取工具,可以将数据投递到MQ系统中,比如Kafka/RocketMQ,可以借助于MQ的多语言能力
* 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
#### MySQL主备复制原理
![](http://dl.iteye.com/upload/attachment/0080/3086/468c1a14-e7ad-3290-9d3d-44ac501a7227.jpg)
<h1>相关资料</h1>
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
* [与阿里巴巴的RocketMQ配合使用](https://github.com/apache/RocketMQ)
#### canal 工作原理
![](http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg)
<h1>相关开源</h1>
<ol>
<li>阿里巴巴分布式数据库同步系统(解决中美异地机房):<a href="http://github.com/alibaba/otter">http://github.com/alibaba/otter</a></li>
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
</ol>
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
- canal 解析 binary log 对象(原始为 byte 流)
<h1>相关产品</h1>
<ol>
<li><a href="https://www.aliyun.com/product/drds?spm=5176.55326.cloudEssentials.71.69fd227dRPZj9K">阿里云分布式数据库DRDS</a></li>
<li><a href="https://www.aliyun.com/product/dts?spm=5176.7947010.cloudEssentials.80.33f734f4JOAxSP">阿里云数据传输服务DTS</a></li>
<li><a href="https://www.aliyun.com/product/dbs?spm=5176.54487.cloudEssentials.83.34b851a8GmVZg6">阿里云数据库备份服务DBS</a></li>
<li><a href="https://www.aliyun.com/product/dms?spm=5176.169464.cloudEssentials.81.2e1066feC1sBBL">阿里云数据管理服务DMS</a></li>
</ol>
## 重要版本更新说明
<h1>问题反馈</h1>
<ol>
<li>qq交流群: 161559791 </li>
<li>邮件交流: jianghang115@gmail.com </li>
<li>新浪微博: agapple0002 </li>
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
</ol>
canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
<h3>最新更新</h3>
<ol>
<li>canal发布重大版本更新1.1.0,具体releaseNode参考:<a href="https://github.com/alibaba/canal/releases/tag/canal-1.1.0">https://github.com/alibaba/canal/releases/tag/canal-1.1.0</a></li>
<li>canal c#客户端开源项目地址:<a href="https://github.com/CanalClient/CanalSharp"> https://github.com/CanalClient/CanalSharp </a>,推荐! </li>
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
- 整体性能测试&优化,提升了150%. #726 参考: [Performance](https://github.com/alibaba/canal/wiki/Performance)
- 原生支持prometheus监控 #765 [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
- 原生支持kafka消息投递 #695 [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
- 原生支持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)
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
</ol>
## 文档
- [Home](https://github.com/alibaba/canal/wiki/Home)
- [Introduction](https://github.com/alibaba/canal/wiki/Introduction)
- [QuickStart](https://github.com/alibaba/canal/wiki/QuickStart)
- [Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)
- [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)
- [AdminGuide](https://github.com/alibaba/canal/wiki/AdminGuide)
- [ClientExample](https://github.com/alibaba/canal/wiki/ClientExample)
- [ClientAPI](https://github.com/alibaba/canal/wiki/ClientAPI)
- [Performance](https://github.com/alibaba/canal/wiki/Performance)
- [DevGuide](https://github.com/alibaba/canal/wiki/DevGuide)
- [BinlogChange(MySQL 5.6)](https://github.com/alibaba/canal/wiki/BinlogChange%28mysql5.6%29)
- [BinlogChange(MariaDB)](https://github.com/alibaba/canal/wiki/BinlogChange%28MariaDB%29)
- [TableMetaTSDB](https://github.com/alibaba/canal/wiki/TableMetaTSDB)
- [ReleaseNotes](http://alibaba.github.com/canal/release.html)
- [Download](https://github.com/alibaba/canal/releases)
- [FAQ](https://github.com/alibaba/canal/wiki/FAQ)
## 多语言
canal 特别设计了 client-server 模式,交互协议使用 protobuf 3.0 , client 端可采用不同语言实现不同的消费逻辑,欢迎大家提交 pull request
- canal java 客户端: [https://github.com/alibaba/canal/wiki/ClientExample](https://github.com/alibaba/canal/wiki/ClientExample)
- canal c# 客户端: [https://github.com/dotnetcore/CanalSharp](https://github.com/dotnetcore/CanalSharp)
- canal go客户端: [https://github.com/CanalClient/canal-go](https://github.com/CanalClient/canal-go)
- canal php客户端: [https://github.com/xingwenge/canal-php](https://github.com/xingwenge/canal-php)
- canal Python客户端:[https://github.com/haozi3156666/canal-python](https://github.com/haozi3156666/canal-python)
canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
## 相关开源
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)
- [阿里巴巴去 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

@@ -0,0 +1,138 @@
<?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">
<parent>
<artifactId>canal-admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal-admin-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.41.1</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</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>
<version>2.12</version>
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:11.41.1</tile>
</tiles>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<activation>
<property>
<name>env</name>
<value>release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<filesets>
<fileset>
<directory>src/main/resources/public</directory>
</fileset>
</filesets>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy Vue.js frontend content</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources/public</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/canal-admin-ui/target/dist</directory>
<includes>
<include>static/</include>
<include>index.html</include>
<include>avatar.gif</include>
<include>logo.png</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,21 @@
package com.alibaba.otter.canal.admin;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动入口
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
@SpringBootApplication
public class CanalAdminApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(CanalAdminApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.otter.canal.admin.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Service Logic Exception
*/
@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public class ServiceException extends RuntimeException {
public ServiceException() {
}
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
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,40 @@
package com.alibaba.otter.canal.admin.config;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.config.ServerConfig;
import io.ebean.config.UnderscoreNamingConvention;
/**
* Ebean 配置
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Configuration
public class EbeanConfig {
@Bean("ebeanServer")
public EbeanServer ebeanServer(DataSource dataSource) {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDefaultServer(true);
serverConfig.setNamingConvention(new UnderscoreNamingConvention());
List<String> packages = new ArrayList<>();
packages.add("com.alibaba.otter.canal.admin.model");
serverConfig.setPackages(packages);
serverConfig.setName("ebeanServer");
serverConfig.setDataSource(dataSource);
serverConfig.setDatabaseSequenceBatchSize(1);
serverConfig.setDdlGenerate(false);
serverConfig.setDdlRun(false);
return EbeanServerFactory.create(serverConfig);
}
}

View File

@@ -0,0 +1,91 @@
package com.alibaba.otter.canal.admin.config;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.alibaba.otter.canal.admin.controller.UserController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 相关MVC拦截器配置
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o) throws Exception {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Max-Age", String.valueOf(3600 * 24));
if (HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
}).addPathPatterns("/api/**");
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o) throws Exception {
String token = httpServletRequest.getHeader("X-Token");
boolean valid = false;
if (token != null) {
User user = UserController.loginUsers.getIfPresent(token);
if (user != null) {
valid = true;
httpServletRequest.setAttribute("user", user);
httpServletRequest.setAttribute("token", token);
}
}
if (!valid) {
BaseModel baseModel = BaseModel.getInstance(null);
baseModel.setCode(50014);
baseModel.setMessage("Expired token");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(baseModel);
try {
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(json);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
return true;
}
})
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/user/login")
.excludePathPatterns("/api/**/user/logout")
.excludePathPatterns("/api/**/user/info");
}
}

View File

@@ -0,0 +1,46 @@
package com.alibaba.otter.canal.admin.controller;
import com.alibaba.otter.canal.admin.model.BaseModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
/**
* Canal主配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalConfigController {
@Autowired
CanalConfigService canalConfigService;
/**
* 获取配置信息
*
* @param env 环境变量
* @return 配置信息
*/
@GetMapping(value = "/config")
public BaseModel<CanalConfig> canalConfig(@PathVariable String env) {
return BaseModel.getInstance(canalConfigService.getCanalConfig());
}
/**
* 修改配置
*
* @param canalConfig 配置信息对象
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/config")
public BaseModel<String> updateConfig(@RequestBody CanalConfig canalConfig, @PathVariable String env) {
canalConfigService.updateContent(canalConfig);
return BaseModel.getInstance("success");
}
}

View File

@@ -0,0 +1,128 @@
package com.alibaba.otter.canal.admin.controller;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
/**
* Canal Instance配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalInstanceController {
@Autowired
CanalInstanceService canalInstanceConfigService;
/**
* 实例配置列表
*
* @param canalInstanceConfig 查询对象
* @param env 环境变量
* @return 实例列表
*/
@GetMapping(value = "/instances")
public BaseModel<List<CanalInstanceConfig>> nodeServers(CanalInstanceConfig canalInstanceConfig,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig));
}
/**
* 保存实例配置
*
* @param canalInstanceConfig 实例配置对象
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/instance")
public BaseModel<String> save(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.save(canalInstanceConfig);
return BaseModel.getInstance("success");
}
/**
* 实例详情信息
*
* @param id 实例配置id
* @param env 环境变量
* @return 实例信息
*/
@GetMapping(value = "/instance/{id}")
public BaseModel<CanalInstanceConfig> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.detail(id));
}
/**
* 修改实例配置
*
* @param canalInstanceConfig 实例配置信息
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance")
public BaseModel<String> update(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.updateContent(canalInstanceConfig);
return BaseModel.getInstance("success");
}
/**
* 删除实例配置
*
* @param id 实例配置id
* @param env 环境变量
* @return 是否成功
*/
@DeleteMapping(value = "/instance/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
canalInstanceConfigService.delete(id);
return BaseModel.getInstance("success");
}
/**
* 启动远程实例
*
* @param id 实例配置id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/start/{id}/{nodeId}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable Long nodeId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "start"));
}
/**
* 关闭远程实例
*
* @param id 实例配置id
* @param nodeId 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/stop/{id}/{nodeId}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable Long nodeId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "stop"));
}
/**
* 获取远程实例运行日志
*
* @param id 实例配置id
* @param nodeId 节点id
* @param env 环境变量
* @return 实例日志信息
*/
@GetMapping(value = "/instance/log/{id}/{nodeId}")
public BaseModel<Map<String, String>> instanceLog(@PathVariable Long id, @PathVariable Long nodeId,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteInstanceLog(id, nodeId));
}
}

View File

@@ -0,0 +1,136 @@
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 com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.NodeServerService;
/**
* 节点信息控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}")
public class NodeServerController {
@Autowired
NodeServerService nodeServerService;
/**
* 获取所有节点信息列表
*
* @param nodeServer 筛选条件
* @param env 环境变量
* @return 节点信息列表
*/
@GetMapping(value = "/nodeServers")
public BaseModel<List<NodeServer>> nodeServers(NodeServer nodeServer, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.findList(nodeServer));
}
/**
* 保存节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/nodeServer")
public BaseModel<String> save(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.save(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 获取节点信息详情
*
* @param id 节点信息id
* @param env 环境变量
* @return 检点信息
*/
@GetMapping(value = "/nodeServer/{id}")
public BaseModel<NodeServer> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.detail(id));
}
/**
* 修改节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer")
public BaseModel<String> update(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.update(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 删除节点信息
*
* @param id 节点信息id
* @param env 环境变量
* @return 是否成功
*/
@DeleteMapping(value = "/nodeServer/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
nodeServerService.delete(id);
return BaseModel.getInstance("success");
}
/**
* 获取远程节点运行状态
*
* @param ip 节点ip
* @param port 节点端口
* @param env 环境变量
* @return 状态信息
*/
@GetMapping(value = "/nodeServer/status")
public BaseModel<Integer> status(@RequestParam String ip, @RequestParam Integer port, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteNodeStatus(ip, port));
}
/**
* 启动远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/start/{id}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "start"));
}
/**
* 获取远程节点日志
*
* @param id 节点id
* @param env 环境变量
* @return 节点日志
*/
@GetMapping(value = "/nodeServer/log/{id}")
public BaseModel<String> log(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteCanalLog(id));
}
/**
* 关闭远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/stop/{id}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "stop"));
}
}

View File

@@ -0,0 +1,107 @@
package com.alibaba.otter.canal.admin.controller;
import com.alibaba.otter.canal.admin.service.UserService;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.User;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 用户管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/user")
public class UserController {
public static final LoadingCache<String, User> loginUsers = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(key -> null); // 用户登录信息缓存
@Autowired
UserService userService;
/**
* 用户登录
*
* @param user 账号密码
* @param env 环境变量
* @return token
*/
@PostMapping(value = "/login")
public BaseModel<Map<String, String>> login(@RequestBody User user, @PathVariable String env) {
User loginUser = userService.find4Login(user.getUsername(), user.getPassword());
if (loginUser != null) {
Map<String, String> tokenResp = new HashMap<>();
String token = UUID.randomUUID().toString();
loginUsers.put(token, loginUser);
tokenResp.put("token", token);
return BaseModel.getInstance(tokenResp);
} else {
BaseModel<Map<String, String>> model = BaseModel.getInstance(null);
model.setCode(40001);
model.setMessage("Invalid username or password");
return model;
}
}
/**
* 获取用户信息
*
* @param token token
* @param env 环境变量
* @return 用户信息
*/
@GetMapping(value = "/info")
public BaseModel<User> info(@RequestParam String token, @PathVariable String env) {
User user = loginUsers.getIfPresent(token);
if (user != null) {
return BaseModel.getInstance(user);
} else {
BaseModel<User> model = BaseModel.getInstance(null);
model.setCode(50014);
model.setMessage("Invalid token");
return model;
}
}
/**
* 修改用户信息
*
* @param user 用户信息
* @param env 环境变量
* @param httpServletRequest httpServletRequest
* @return 是否成功
*/
@PutMapping(value = "")
public BaseModel<String> update(@RequestBody User user, @PathVariable String env,
HttpServletRequest httpServletRequest) {
userService.update(user);
String token = (String) httpServletRequest.getAttribute("token");
loginUsers.put(token, user);
return BaseModel.getInstance("success");
}
/**
* 用户退出
*
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/logout")
public BaseModel<String> logout(@PathVariable String env) {
return BaseModel.getInstance("success");
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.otter.canal.admin.handler;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.BaseModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* MVC异常拦截器
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@ControllerAdvice(annotations = ResponseBody.class)
public class CustomExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
/**
* 通用异常处理
*
* @param e 异常
* @return
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(value = Exception.class)
public BaseModel commonExceptionHandle(Exception e) {
if (e instanceof ServiceException) {
logger.error(e.getMessage());
} else {
logger.error(e.getMessage(), e);
}
BaseModel res = new BaseModel();
res.setCode(50000);
res.setMessage(e.getMessage());
return res;
}
}

View File

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

@@ -0,0 +1,164 @@
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,44 @@
package com.alibaba.otter.canal.admin.model;
/**
* 基础Model
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class BaseModel<T> {
private Integer code = 20000;
private String message;
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static <T> BaseModel<T> getInstance(T data) {
BaseModel<T> baseModel = new BaseModel<>();
baseModel.data = data;
return baseModel;
}
}

View File

@@ -0,0 +1,69 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* Canal住配置实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
public class CanalConfig extends Model {
public static final CanalConfigFinder find = new CanalConfigFinder();
public static class CanalConfigFinder extends Finder<Long, CanalConfig> {
/**
* Construct using the default EbeanServer.
*/
public CanalConfigFinder(){
super(CanalConfig.class);
}
}
@Id
private Long id;
private String name;
private String content;
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 getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}

View File

@@ -0,0 +1,90 @@
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;
/**
* Canal实例配置信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
public class CanalInstanceConfig extends Model {
public static final CanalInstanceConfigFinder find = new CanalInstanceConfigFinder();
public static class CanalInstanceConfigFinder extends Finder<Long, CanalInstanceConfig> {
/**
* Construct using the default EbeanServer.
*/
public CanalInstanceConfigFinder(){
super(CanalInstanceConfig.class);
}
}
@Id
private Long id;
private String name;
private String content;
private Date modifiedTime;
@Transient
private Long nodeId;
@Transient
private String nodeIp;
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 getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
public Long getNodeId() {
return nodeId;
}
public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
public String getNodeIp() {
return nodeIp;
}
public void setNodeIp(String nodeIp) {
this.nodeIp = nodeIp;
}
}

View File

@@ -0,0 +1,82 @@
package com.alibaba.otter.canal.admin.model;
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扩展类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@MappedSuperclass
public abstract class Model extends io.ebean.Model {
public void init() {
}
public void save() {
init();
super.save();
}
public void insert() {
init();
super.insert();
}
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) {
this.save();
} else {
this.update();
}
} catch (Exception e) {
throw new OptimisticLockException(e);
}
}
public void update(String... propertiesNames) {
try {
EbeanServer ebeanServer = Ebean.getDefaultServer();
Object id = ebeanServer.getBeanId(this);
Object model = ebeanServer.createQuery(this.getClass()).where().idEq(id).findOne();
for (String propertyName : propertiesNames) {
if (propertyName.startsWith("nn:")) { // not null
propertyName = propertyName.substring(3);
Object val = PropertyUtils.getProperty(this, propertyName);
if (val != null) {
PropertyUtils.setProperty(model, propertyName, val);
}
} else {
Object val = PropertyUtils.getProperty(this, propertyName);
PropertyUtils.setProperty(model, propertyName, val);
}
}
ebeanServer.update(model);
} catch (Exception e) {
throw new OptimisticLockException(e);
}
}
}

View File

@@ -0,0 +1,102 @@
package com.alibaba.otter.canal.admin.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import io.ebean.Finder;
/**
* 节点信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
@Table(name = "canal_node_server")
public class NodeServer extends Model {
public static final NodeServerFinder find = new NodeServerFinder();
public static class NodeServerFinder extends Finder<Long, NodeServer> {
/**
* Construct using the default EbeanServer.
*/
public NodeServerFinder(){
super(NodeServer.class);
}
}
@Id
private Long id;
private String name;
private String ip;
private Integer port;
private Integer port2;
private Integer status;
private Date modifiedTime;
public void init() {
status = -1;
}
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 getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getPort2() {
return port2;
}
public void setPort2(Integer port2) {
this.port2 = port2;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}

View File

@@ -0,0 +1,115 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
/**
* 用户信息实体类
*/
@Entity
@Table(name = "canal_user")
public class User extends Model {
public static final UserFinder find = new UserFinder();
public static class UserFinder extends Finder<Long, User> {
/**
* Construct using the default EbeanServer.
*/
public UserFinder(){
super(User.class);
}
}
@Id
private Long id;
private String username;
private String password;
private String roles;
private String introduction;
private String avatar;
private String name;
private Date creationDate;
@Transient
private String oldPassword;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
public interface CanalConfigService {
CanalConfig getCanalConfig();
CanalConfig getAdapterConfig();
void updateContent(CanalConfig canalConfig);
}

View File

@@ -0,0 +1,29 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import java.util.List;
import java.util.Map;
/**
* Canal实例配置信息业务层接口
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalInstanceService {
List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig);
void save(CanalInstanceConfig canalInstanceConfig);
CanalInstanceConfig detail(Long id);
void updateContent(CanalInstanceConfig canalInstanceConfig);
void delete(Long id);
Map<String, String> remoteInstanceLog(Long id, Long nodeId);
boolean remoteOperation(Long id, Long nodeId, String option);
}

View File

@@ -0,0 +1,24 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.NodeServer;
import java.util.List;
public interface NodeServerService {
void save(NodeServer nodeServer);
NodeServer detail(Long id);
void update(NodeServer nodeServer);
void delete(Long id);
List<NodeServer> findList(NodeServer nodeServer);
int remoteNodeStatus(String ip, Integer port);
String remoteCanalLog(Long id);
boolean remoteOperation(Long id, String option);
}

View File

@@ -0,0 +1,10 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.User;
public interface UserService {
User find4Login(String username, String password);
void update(User user);
}

View File

@@ -0,0 +1,28 @@
package com.alibaba.otter.canal.admin.service.impl;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
/**
* Canal配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class CanalConfigServiceImpl implements CanalConfigService {
public CanalConfig getCanalConfig() {
return CanalConfig.find.byId(1L);
}
public CanalConfig getAdapterConfig() {
return CanalConfig.find.byId(2L);
}
public void updateContent(CanalConfig canalConfig) {
canalConfig.update("content");
}
}

View File

@@ -0,0 +1,141 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import io.ebean.Query;
/**
* Canal实例配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class CanalInstanceServiceImpl implements CanalInstanceService {
public List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig) {
Query<CanalInstanceConfig> query = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("name, modifiedTime");
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;
}
}
}
}
return canalInstanceConfigs;
}
public void save(CanalInstanceConfig canalInstanceConfig) {
canalInstanceConfig.insert();
}
public CanalInstanceConfig detail(Long id) {
return CanalInstanceConfig.find.byId(id);
}
public void updateContent(CanalInstanceConfig canalInstanceConfig) {
canalInstanceConfig.update("content");
}
public void delete(Long id) {
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig != null) {
canalInstanceConfig.delete();
}
}
public Map<String, String> remoteInstanceLog(Long id, Long nodeId) {
Map<String, String> result = new HashMap<>();
NodeServer nodeServer = NodeServer.find.byId(nodeId);
if (nodeServer == null) {
return result;
}
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return result;
}
String log = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.instanceLog(canalInstanceConfig.getName()));
result.put("instance", canalInstanceConfig.getName());
result.put("log", log);
return result;
}
public boolean remoteOperation(Long id, Long nodeId, String option) {
NodeServer nodeServer = null;
if ("start".equals(option)) {
if (nodeId != null) {
nodeServer = NodeServer.find.byId(nodeId);
} else {
nodeServer = NodeServer.find.query().findOne();
}
} else {
if (nodeId == null) {
return false;
}
nodeServer = NodeServer.find.byId(nodeId);
}
if (nodeServer == null) {
return false;
}
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return false;
}
Boolean resutl = null;
if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.startInstance(canalInstanceConfig.getName()));
} else if ("stop".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(),
nodeServer.getPort(),
canalServerMXBean -> canalServerMXBean.stopInstance(canalInstanceConfig.getName()));
} else {
return false;
}
if (resutl == null) {
resutl = false;
}
return resutl;
}
}

View File

@@ -0,0 +1,155 @@
package com.alibaba.otter.canal.admin.service.impl;
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 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.jmx.CanalServerMXBean;
import com.alibaba.otter.canal.admin.jmx.JMXConnection;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.NodeServerService;
import io.ebean.Query;
/**
* 节点信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@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())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.save();
}
public NodeServer detail(Long id) {
return NodeServer.find.byId(id);
}
public void update(NodeServer nodeServer) {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("port", nodeServer.getPort())
.ne("id", nodeServer.getId())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.update("name", "ip", "port", "port2");
}
public void delete(Long id) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer != null) {
nodeServer.delete();
}
}
public List<NodeServer> findList(NodeServer nodeServer) {
Query<NodeServer> query = NodeServer.find.query();
if (nodeServer != null) {
if (StringUtils.isNotEmpty(nodeServer.getName())) {
query.where().like("name", "%" + nodeServer.getName() + "%");
}
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;
}
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.forEach(f -> {
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
// ignore
}
});
executorService.shutdownNow();
return nodeServers;
}
public int remoteNodeStatus(String ip, Integer port) {
Integer resutl = JMXConnection.execute(ip, port, CanalServerMXBean::getStatus);
if (resutl == null) {
resutl = -1;
}
return resutl;
}
public String remoteCanalLog(Long id) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer == null) {
return "";
}
return JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::canalLog);
}
public boolean remoteOperation(Long id, String option) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer == null) {
return false;
}
Boolean resutl = null;
if ("start".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::start);
} else if ("stop".equals(option)) {
resutl = JMXConnection.execute(nodeServer.getIp(), nodeServer.getPort(), CanalServerMXBean::stop);
} else {
return false;
}
if (resutl == null) {
resutl = false;
}
return resutl;
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.otter.canal.admin.service.impl;
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;
/**
* 用户信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class UserServiceImpl implements UserService {
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("");
}
return user;
}
public void update(User user) {
User userTmp = User.find.byId(1L);
if (userTmp == null) {
throw new ServiceException();
}
if (!userTmp.getPassword().equals(user.getOldPassword())) {
throw new ServiceException("错误的旧密码");
}
user.setId(1L);
user.update("username", "nn:password");
}
}

View File

@@ -0,0 +1,15 @@
server:
port: 8089
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#fff;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;box-shadow:inset 0 0 0 1000px #283443!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-31c14ebf]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-31c14ebf]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-31c14ebf]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-31c14ebf]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-31c14ebf]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-31c14ebf]{position:relative}.login-container .title-container .title[data-v-31c14ebf]{font-size:26px;color:#eee;margin:0 auto 40px auto;text-align:center;font-weight:700}.login-container .show-pwd[data-v-31c14ebf]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}

View File

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

View File

@@ -0,0 +1 @@
.dashboard-container[data-v-42037c2b]{margin:30px}.dashboard-text[data-v-42037c2b]{font-size:30px;line-height:46px}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;-webkit-box-shadow:0 0 10px #29d,0 0 5px #29d;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translateY(-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;-webkit-box-sizing:border-box;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

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

View File

@@ -0,0 +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){}}]);

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

File diff suppressed because one or more lines are too long

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

View File

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

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@@ -0,0 +1,14 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://127.0.0.1:8089/api/v1'
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -0,0 +1,6 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/api/v1'

View File

@@ -0,0 +1,8 @@
NODE_ENV = production
# just a flag
ENV = 'staging'
# base api
VUE_APP_BASE_API = '/stage-api'

View File

@@ -0,0 +1,4 @@
build/*.js
src/assets
public
dist

View File

@@ -0,0 +1,198 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

17
canal-admin/canal-admin-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
tests/**/coverage/
target/
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

View File

@@ -0,0 +1,5 @@
language: node_js
node_js: 10
script: npm run test
notifications:
email: false

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present PanJiaChen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
}

View File

@@ -0,0 +1,35 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./target/dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

View File

@@ -0,0 +1,24 @@
module.exports = {
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: {
'^.+\\.vue$': 'vue-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: ['jest-serializer-vue'],
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/tests/unit/coverage',
// 'collectCoverage': true,
'coverageReporters': [
'lcov',
'text-summary'
],
testURL: 'http://localhost/'
}

View File

@@ -0,0 +1,66 @@
import Mock from 'mockjs'
import { param2Obj } from '../src/utils'
import user from './user'
import table from './table'
const mocks = [
...user,
...table
]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
export function mockXHR() {
// mock patch
// https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
// https://expressjs.com/en/4x/api.html#req
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
// for mock server
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`/mock${url}`),
type: type || 'get',
response(req, res) {
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
export default mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})

View File

@@ -0,0 +1,68 @@
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) {
let mockLastIndex
const { default: mocks } = require('./index.js')
for (const mock of mocks) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocks).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
function unregisterRoutes() {
Object.keys(require.cache).forEach(i => {
if (i.includes(mockDir)) {
delete require.cache[require.resolve(i)]
}
})
}
module.exports = app => {
// es6 polyfill
require('@babel/register')
// parse app.body
// https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
const mockRoutes = registerRoutes(app)
var mockRoutesLength = mockRoutes.mockRoutesLength
var mockStartIndex = mockRoutes.mockStartIndex
// watch files, hot reload mock server
chokidar.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true
}).on('all', (event, path) => {
if (event === 'change' || event === 'add') {
try {
// remove mock routes stack
app._router.stack.splice(mockStartIndex, mockRoutesLength)
// clear routes cache
unregisterRoutes()
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
console.log(chalk.redBright(error))
}
}
})
}

View File

@@ -0,0 +1,29 @@
import Mock from 'mockjs'
const data = Mock.mock({
'items|30': [{
id: '@id',
title: '@sentence(10, 20)',
'status|1': ['published', 'draft', 'deleted'],
author: 'name',
display_time: '@datetime',
pageviews: '@integer(300, 5000)'
}]
})
export default [
{
url: '/table/list',
type: 'get',
response: config => {
const items = data.items
return {
code: 20000,
data: {
total: items.length,
items: items
}
}
}
}
]

View File

@@ -0,0 +1,84 @@
const tokens = {
admin: {
token: 'admin-token'
},
editor: {
token: 'editor-token'
}
}
const users = {
'admin-token': {
roles: ['admin'],
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Super Admin'
},
'editor-token': {
roles: ['editor'],
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Normal Editor'
}
}
export default [
// user login
{
url: '/user/login',
type: 'post',
response: config => {
const { username } = config.body
const token = tokens[username]
// mock error
if (!token) {
return {
code: 60204,
message: 'Account and password are incorrect.'
}
}
return {
code: 20000,
data: token
}
}
},
// get user info
{
url: '/user/info\.*',
type: 'get',
response: config => {
const { token } = config.query
const info = users[token]
// mock error
if (!info) {
return {
code: 50008,
message: 'Login failed, unable to get user details.'
}
}
return {
code: 20000,
data: info
}
}
},
// user logout
{
url: '/user/logout',
type: 'post',
response: _ => {
return {
code: 20000,
data: 'success'
}
}
}
]

16074
canal-admin/canal-admin-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
{
"name": "canal-admin-ui",
"version": "1.1.4",
"description": "A canal-admin ui with Element UI & axios & iconfont & permission control & lint",
"author": "Machengyuan <rewerma@163.com>",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
},
"dependencies": {
"axios": "0.18.1",
"element-ui": "2.7.2",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0"
},
"devDependencies": {
"@babel/core": "7.0.0",
"@babel/register": "7.0.0",
"@vue/cli-plugin-babel": "3.6.0",
"@vue/cli-plugin-eslint": "^3.9.1",
"@vue/cli-plugin-unit-jest": "3.6.3",
"@vue/cli-service": "3.6.0",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "^9.5.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-jest": "23.6.0",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint": "5.15.3",
"eslint-plugin-vue": "5.2.2",
"html-webpack-plugin": "3.2.0",
"mockjs": "1.0.1-beta3",
"node-sass": "^4.9.0",
"runjs": "^4.3.2",
"sass-loader": "^7.1.0",
"script-ext-html-webpack-plugin": "2.1.3",
"script-loader": "0.7.2",
"serve-static": "^1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.2",
"vue-template-compiler": "2.6.10",
"vue2-ace-editor": "^0.0.13"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

View File

@@ -0,0 +1,75 @@
<?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">
<parent>
<artifactId>canal-admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal-admin-ui</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
</properties>
<profiles>
<profile>
<id>release</id>
<activation>
<property>
<name>env</name>
<value>release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v9.11.1</nodeVersion>
</configuration>
</execution>
<!-- Install all project dependencies -->
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<!-- optional: default phase is "generate-resources" -->
<phase>generate-resources</phase>
<!-- Optional configuration which provides for running any npm command -->
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<!-- Build and minify static files -->
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
'plugins': {
// to edit target browsers: use "browserslist" field in package.json
'autoprefixer': {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,17 @@
<!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="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>

View File

@@ -0,0 +1,16 @@
import request from '@/utils/request'
export function getCanalConfig() {
return request({
url: '/canal/config',
method: 'get'
})
}
export function updateCanalConfig(data) {
return request({
url: '/canal/config',
method: 'put',
data
})
}

View File

@@ -0,0 +1,60 @@
import request from '@/utils/request'
export function getCanalInstances(params) {
return request({
url: '/canal/instances',
method: 'get',
params: params
})
}
export function canalInstanceDetail(id) {
return request({
url: '/canal/instance/' + id,
method: 'get'
})
}
export function updateCanalInstance(data) {
return request({
url: '/canal/instance',
method: 'put',
data
})
}
export function addCanalInstance(data) {
return request({
url: '/canal/instance',
method: 'post',
data
})
}
export function deleteCanalInstance(id) {
return request({
url: '/canal/instance/' + id,
method: 'delete'
})
}
export function startInstance(id, nodeId) {
return request({
url: '/canal/instance/start/' + id + '/' + nodeId,
method: 'put'
})
}
export function stopInstance(id, nodeId) {
return request({
url: '/canal/instance/stop/' + id + '/' + nodeId,
method: 'put'
})
}
export function instanceLog(id, nodeId) {
return request({
url: '/canal/instance/log/' + id + '/' + nodeId,
method: 'get'
})
}

View File

@@ -0,0 +1,60 @@
import request from '@/utils/request'
export function getNodeServers(params) {
return request({
url: '/nodeServers',
method: 'get',
params: params
})
}
export function addNodeServer(data) {
return request({
url: '/nodeServer',
method: 'post',
data
})
}
export function nodeServerDetail(id) {
return request({
url: '/nodeServer/' + id,
method: 'get'
})
}
export function updateNodeServer(data) {
return request({
url: '/nodeServer',
method: 'put',
data
})
}
export function deleteNodeServer(id) {
return request({
url: '/nodeServer/' + id,
method: 'delete'
})
}
export function startNodeServer(id) {
return request({
url: '/nodeServer/start/' + id,
method: 'put'
})
}
export function stopNodeServer(id) {
return request({
url: '/nodeServer/stop/' + id,
method: 'put'
})
}
export function nodeServerLog(id) {
return request({
url: '/nodeServer/log/' + id,
method: 'get'
})
}

View File

@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/table/list',
method: 'get',
params
})
}

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