Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a74f6124a | ||
|
|
bd974e3da8 | ||
|
|
86cd805a64 | ||
|
|
8b54b9e596 | ||
|
|
2d3baeab7d | ||
|
|
7b245c5a64 | ||
|
|
78af6b1130 | ||
|
|
7547cac3c5 | ||
|
|
066743cfd6 | ||
|
|
fdd0763567 | ||
|
|
bcdbf75dc5 | ||
|
|
db23f15b61 | ||
|
|
44722fe01c | ||
|
|
be9813142f | ||
|
|
c48e80a163 | ||
|
|
c1bfad4f2d | ||
|
|
10f4ef6acd | ||
|
|
4c0d82fb98 | ||
|
|
7cec0994e9 | ||
|
|
483044916d | ||
|
|
c8bb6e200c | ||
|
|
9cc6b67426 | ||
|
|
62072ea9ab | ||
|
|
f3daceea6b | ||
|
|
4efc0f6078 | ||
|
|
40d2a8c6cc | ||
|
|
28da536ec8 | ||
|
|
6f129bf524 | ||
|
|
31ae308760 | ||
|
|
c2ce662f9a | ||
|
|
7044e172f5 | ||
|
|
c070757da0 | ||
|
|
92dd2bb189 | ||
|
|
8639b45fe9 | ||
|
|
6b9487a0a0 | ||
|
|
5741f5a347 | ||
|
|
4c3ef6772c | ||
|
|
64824bc5aa | ||
|
|
23209f24ed | ||
|
|
015026ea91 | ||
|
|
c7967e989c | ||
|
|
e4b37f33de | ||
|
|
598f15a404 | ||
|
|
ec5f1eb10d | ||
|
|
28c0aac98d | ||
|
|
50ea6870dc | ||
|
|
317439670c | ||
|
|
64ff140304 | ||
|
|
a93af74539 | ||
|
|
fc39b2af68 | ||
|
|
e87a0c3b63 | ||
|
|
b43043f4e4 | ||
|
|
60a46ae941 | ||
|
|
b5c05c2fd2 | ||
|
|
0cf18976d9 | ||
|
|
803c0607d3 | ||
|
|
f04e742f4f | ||
|
|
c81034dda3 | ||
|
|
a847660a45 | ||
|
|
4957083493 | ||
|
|
24f0eece42 | ||
|
|
adccc0e912 | ||
|
|
8728beb4b6 | ||
|
|
5bea876ec9 | ||
|
|
6c494f8892 | ||
|
|
74489d1bba | ||
|
|
5ac0f7b710 | ||
|
|
75b4911caf | ||
|
|
95602f4784 | ||
|
|
058e1fc546 | ||
|
|
e808c6ec70 | ||
|
|
ce7948d6e7 | ||
|
|
8df6d6fed8 | ||
|
|
f9989e0856 | ||
|
|
4075a0623f | ||
|
|
37fc248ebf | ||
|
|
d0a83aad27 | ||
|
|
3b9ff26d1a | ||
|
|
4f7842c843 | ||
|
|
b870e7ecd0 | ||
|
|
331dd63c5f | ||
|
|
72c4daba5f | ||
|
|
7427a74612 | ||
|
|
24fae6fa52 | ||
|
|
4c690fb5a3 | ||
|
|
8f088cddc0 | ||
|
|
e94ee64316 | ||
|
|
3165cb9640 | ||
|
|
2b738f4e04 | ||
|
|
16f335c381 | ||
|
|
a8bf2b641f | ||
|
|
67faf0e03e | ||
|
|
8cb85753bb | ||
|
|
df86f94409 | ||
|
|
80594444f7 | ||
|
|
d8996432b9 | ||
|
|
69df50fe17 | ||
|
|
c5736bd5f3 | ||
|
|
55030476ee | ||
|
|
cd007088db | ||
|
|
1fc880f255 | ||
|
|
8c68cf833a | ||
|
|
0de8f4691e | ||
|
|
5a2efbf9cb | ||
|
|
dfab98fce1 | ||
|
|
e5c7bbffbf | ||
|
|
e139698f6e | ||
|
|
2b8120b6ed | ||
|
|
68ebb3a957 | ||
|
|
21309cb065 | ||
|
|
386567a633 | ||
|
|
95c69bb854 | ||
|
|
6febcf1a0f | ||
|
|
fadfe1d6ca | ||
|
|
a7b12c7098 | ||
|
|
2c320e8618 | ||
|
|
d76d25df6c | ||
|
|
b95c0d03cf | ||
|
|
08af40221f | ||
|
|
d13c1eb17c | ||
|
|
82ed9db8ae | ||
|
|
d3195ac973 | ||
|
|
15e29d276a | ||
|
|
d715ff64b4 | ||
|
|
f63ce4ffe6 | ||
|
|
a7f9a779ad | ||
|
|
39aeeb8c58 | ||
|
|
9826948ebb | ||
|
|
d6ef66a281 | ||
|
|
97640b717b | ||
|
|
20e7becfe9 | ||
|
|
99b34ff632 | ||
|
|
739963acd0 | ||
|
|
0ec4699149 | ||
|
|
c101ae2280 | ||
|
|
a137deeaff | ||
|
|
c5be5b7a8a | ||
|
|
95bed6bcf6 | ||
|
|
661d119265 | ||
|
|
08167c95c7 | ||
|
|
37c5168a7d | ||
|
|
579fa72ae2 | ||
|
|
844c019433 | ||
|
|
c94e3a0024 | ||
|
|
f6ca489984 | ||
|
|
b2b69307f9 | ||
|
|
531b535228 | ||
|
|
99cccd2113 | ||
|
|
38d8c3d462 | ||
|
|
024ffc2aa2 | ||
|
|
a81d2710ff | ||
|
|
4bad0e88f8 | ||
|
|
0181dfaa42 | ||
|
|
0411d99e7b | ||
|
|
8fcdd7f798 | ||
|
|
b0f4e4ee78 | ||
|
|
d448455ebf | ||
|
|
e07fdb6426 | ||
|
|
38336b249e | ||
|
|
e7b81cd83a | ||
|
|
2810e7e815 | ||
|
|
024986a195 | ||
|
|
9b2ad0eafd | ||
|
|
03d46f150f | ||
|
|
b488ca02d8 | ||
|
|
17a6ba9c9c | ||
|
|
3b618740d4 | ||
|
|
06bfcda275 | ||
|
|
5821c1f606 | ||
|
|
a2360efff9 | ||
|
|
51c469aa6f | ||
|
|
6b3bf31404 | ||
|
|
941ed7f60d | ||
|
|
9db2bd291b | ||
|
|
95b26c4d1d | ||
|
|
b114e0cb15 | ||
|
|
5c96bf282e | ||
|
|
6d280d1048 | ||
|
|
cf4db1625d | ||
|
|
df98fec945 | ||
|
|
e03705e211 | ||
|
|
4df60a0fb5 | ||
|
|
078673c6a9 | ||
|
|
4bd91a1079 | ||
|
|
05ad671cc1 | ||
|
|
43045bc672 | ||
|
|
971c9a88c2 | ||
|
|
7b6e482827 | ||
|
|
ad39559f8c | ||
|
|
2707c12570 | ||
|
|
7dab5ac6c2 | ||
|
|
9d4b61f083 | ||
|
|
a34f9ae97b | ||
|
|
64ca62b986 | ||
|
|
14c1af4c80 | ||
|
|
7436d9f06f | ||
|
|
c4599df1c2 | ||
|
|
7abf50f233 | ||
|
|
1781381187 | ||
|
|
5d4210bd22 | ||
|
|
9888fae559 | ||
|
|
81ab7e9783 | ||
|
|
b891afa50e | ||
|
|
8187d7c5df | ||
|
|
e7f069850f | ||
|
|
d377a92a5d | ||
|
|
f664af1af4 | ||
|
|
5e2eabb3c2 | ||
|
|
f2368e5469 | ||
|
|
fd4c8db92b | ||
|
|
f21abc13af | ||
|
|
ae474d67ad | ||
|
|
0c4711ab62 | ||
|
|
072420c32b | ||
|
|
1473a4a6b5 | ||
|
|
0fff514876 | ||
|
|
4017de5102 | ||
|
|
fcc1fc78ae | ||
|
|
17efc2ada0 | ||
|
|
a68bd28852 | ||
|
|
1de8b27da1 | ||
|
|
01dc8ce2b8 | ||
|
|
f7e490f7a0 | ||
|
|
8cadd585c6 | ||
|
|
57c3226e56 | ||
|
|
79a3961da1 | ||
|
|
62338a945e | ||
|
|
e07db7b178 | ||
|
|
05cc627b5d | ||
|
|
44beaf1916 | ||
|
|
476244b26d | ||
|
|
cfdf509f9a | ||
|
|
c14ed724ff | ||
|
|
5a4cfeab46 | ||
|
|
947cf130ab | ||
|
|
0ef21e4155 | ||
|
|
83b760494d | ||
|
|
aba8df699c | ||
|
|
0c5f5c128f | ||
|
|
ad62275cd7 | ||
|
|
f6d03bcb37 | ||
|
|
5284aa9793 | ||
|
|
10276e254a | ||
|
|
597a08ac37 | ||
|
|
8211ef35e6 | ||
|
|
a20960834b | ||
|
|
7b6b332463 | ||
|
|
43f869599d | ||
|
|
e3d05f6201 | ||
|
|
032301102c | ||
|
|
bb64d84298 | ||
|
|
ccd39e9d62 | ||
|
|
32031da9db | ||
|
|
9a2e890f57 | ||
|
|
8f76b33214 | ||
|
|
a7467539c7 | ||
|
|
db5495d158 | ||
|
|
b04d948064 | ||
|
|
5f8eb4d261 | ||
|
|
197ca67c8b | ||
|
|
851637611f | ||
|
|
4ebf17f57b | ||
|
|
2f214ce3a6 | ||
|
|
8d4fbb395b | ||
|
|
d935c39648 | ||
|
|
6e0e3fbef1 | ||
|
|
cbe88af2e3 | ||
|
|
ba9d9dea6d | ||
|
|
2d88d803ee | ||
|
|
ff1fef1eb6 | ||
|
|
4beb1e6855 | ||
|
|
d2bbe9b061 | ||
|
|
171bedf488 | ||
|
|
e387cfdee5 | ||
|
|
47d7914304 | ||
|
|
e4177ab0ce | ||
|
|
739f3b4fa1 | ||
|
|
60441d8b31 | ||
|
|
ca41be8412 | ||
|
|
18b304f99d | ||
|
|
7f9dce4b77 | ||
|
|
f26adff7c7 | ||
|
|
77646c0185 | ||
|
|
7e4912cd2e | ||
|
|
70a4f0363d | ||
|
|
725960d46e | ||
|
|
b00d347eb0 | ||
|
|
92666b41e8 | ||
|
|
f1753e79f9 | ||
|
|
f1a998b2d5 | ||
|
|
03e6662ce5 | ||
|
|
b845a67666 | ||
|
|
690222bde8 | ||
|
|
63ad80c8c4 | ||
|
|
de501c85a1 | ||
|
|
670b7830a5 | ||
|
|
fdf150bd24 | ||
|
|
c66b6a0f0f | ||
|
|
a91aebafc2 | ||
|
|
35fcd56822 | ||
|
|
a63e982115 | ||
|
|
4bdf3d766f | ||
|
|
779ebad890 | ||
|
|
b348260288 | ||
|
|
3011c3292b | ||
|
|
94b8fdbbf4 | ||
|
|
1dd9934f9c | ||
|
|
3dec7c50ce | ||
|
|
ecd9ababa1 | ||
|
|
b0eaa156cc | ||
|
|
a70178afef | ||
|
|
114c3bd020 | ||
|
|
f9e19d46c4 | ||
|
|
661752096c | ||
|
|
100b43a5db | ||
|
|
5e116de264 | ||
|
|
a1091368fb | ||
|
|
71b7c0b868 | ||
|
|
7c79714528 | ||
|
|
8d84521767 | ||
|
|
14ba358525 | ||
|
|
7ea1468a8d | ||
|
|
f72ac9a3bb | ||
|
|
f100aeda39 | ||
|
|
953a0d0583 | ||
|
|
2afe7debda | ||
|
|
d8737a5228 | ||
|
|
150a13824b | ||
|
|
830e149d0e | ||
|
|
d9e259dde4 | ||
|
|
9e6adff942 | ||
|
|
82a78546ab | ||
|
|
19c35c0b08 | ||
|
|
ade2a54369 | ||
|
|
dffbc437a6 | ||
|
|
b5697a366a | ||
|
|
0469d28c05 | ||
|
|
d5f44dd76f | ||
|
|
354d64069f | ||
|
|
3465866278 | ||
|
|
840148fbf6 | ||
|
|
34b5f9c927 | ||
|
|
75ee65f9e8 | ||
|
|
035ef26542 | ||
|
|
4ef45b9a1d | ||
|
|
990fa9498a | ||
|
|
276a2b988a | ||
|
|
ef72cddfe8 | ||
|
|
d8d3246e30 | ||
|
|
bba800eb96 | ||
|
|
ff71bf515a | ||
|
|
6ef48591a1 | ||
|
|
a7526dbf4a | ||
|
|
51d94efc9c | ||
|
|
833cc55e26 | ||
|
|
689366248d | ||
|
|
3acdffb297 | ||
|
|
fbf249580f | ||
|
|
d2e3e19831 | ||
|
|
0523b68c26 | ||
|
|
995a33a645 | ||
|
|
5c4b1f6e63 | ||
|
|
ba5865683f | ||
|
|
89a4943349 | ||
|
|
35e0e882c1 | ||
|
|
0197482618 | ||
|
|
72bad4d54c | ||
|
|
48de904ee1 | ||
|
|
f6a931d8c6 | ||
|
|
277a273ebc | ||
|
|
bd5110b951 | ||
|
|
aeecf87490 | ||
|
|
eac3f5ce5f | ||
|
|
706ee88914 | ||
|
|
7c63e81e01 | ||
|
|
99a1123e01 | ||
|
|
24d898d87a | ||
|
|
5650b56bed | ||
|
|
547cbf82c8 | ||
|
|
03ee9c7ecf | ||
|
|
ac738dae05 | ||
|
|
dda8081984 | ||
|
|
f0fc37c43b | ||
|
|
5a99de1fa9 | ||
|
|
ce956c980d | ||
|
|
c045dc244e | ||
|
|
1b11aa1d21 | ||
|
|
dac642f744 | ||
|
|
19e164b599 | ||
|
|
339ecdd817 | ||
|
|
5fe7ae1613 | ||
|
|
65f2695963 | ||
|
|
cf5e42442b | ||
|
|
e7b678d5e0 | ||
|
|
f04447837c | ||
|
|
b7d2ad2e18 | ||
|
|
f1710f1c7f | ||
|
|
37094447b1 | ||
|
|
b97d377d83 | ||
|
|
46b6528273 | ||
|
|
6c3b3acde0 | ||
|
|
91b036a4fc | ||
|
|
67cd0e5fca | ||
|
|
9dfa5da7bc | ||
|
|
9bda60bf89 | ||
|
|
9aaf20fb5e | ||
|
|
8496ceaf15 | ||
|
|
cf23e0ffea | ||
|
|
4e9e3f5abe | ||
|
|
cbab3fb561 | ||
|
|
f7bf4ac6ff | ||
|
|
df4c66b9e2 | ||
|
|
a6ee87281a | ||
|
|
90dab6831b | ||
|
|
839d5415fc | ||
|
|
29f03bf398 | ||
|
|
ec235f3efd | ||
|
|
3b25b3b342 | ||
|
|
aa89453bf6 | ||
|
|
cc97f3b331 | ||
|
|
89ec3037d4 | ||
|
|
e711f1a091 | ||
|
|
ad18e0335d | ||
|
|
67cc73b8e2 | ||
|
|
638952fad1 | ||
|
|
56ec66f90e | ||
|
|
c0bbe530f1 | ||
|
|
4bfc476e08 | ||
|
|
5d71d50d4e | ||
|
|
26700f3005 | ||
|
|
a7be9c75f9 | ||
|
|
b6f1e0d13c | ||
|
|
50630cc934 | ||
|
|
43db5bec97 | ||
|
|
affd598470 | ||
|
|
178bea07a8 | ||
|
|
fcdf95b5ec | ||
|
|
991418ce6e | ||
|
|
d34bee3359 | ||
|
|
0a3c1645e8 | ||
|
|
9dc4d78b3b | ||
|
|
bc056288d4 | ||
|
|
8aea31232b | ||
|
|
fc6c871382 | ||
|
|
14c0e4535a | ||
|
|
7ee7ba0e05 | ||
|
|
7992b883d2 | ||
|
|
5bc0c3f06f | ||
|
|
b457ecd387 | ||
|
|
997646b2db | ||
|
|
915849b83a | ||
|
|
129118b483 | ||
|
|
aa68408c53 | ||
|
|
d396a0b65c | ||
|
|
827b872cf8 | ||
|
|
6f3cae5ddf | ||
|
|
31bf92ec8e | ||
|
|
68d8aee6c3 | ||
|
|
c376f0afd2 | ||
|
|
60e5554125 | ||
|
|
241c43a894 | ||
|
|
fecc112196 | ||
|
|
dd04577994 | ||
|
|
51a299259b | ||
|
|
0a0a39880c | ||
|
|
5ad9cf3f16 | ||
|
|
d29fe5b04e | ||
|
|
20062d861d | ||
|
|
73de8b32a9 | ||
|
|
8af1301062 | ||
|
|
8c3ade2d50 | ||
|
|
752a3cd66a | ||
|
|
154ba422f2 | ||
|
|
03a9522191 | ||
|
|
c4947cad3c | ||
|
|
3f8cdafea3 | ||
|
|
fcaf8e88f8 | ||
|
|
e7ab25f819 | ||
|
|
b45a27e231 | ||
|
|
2e5b7b89af | ||
|
|
66912d3d05 | ||
|
|
ec1ac6d11c | ||
|
|
7cca8857ff | ||
|
|
e7086415f8 | ||
|
|
4a27d81da1 | ||
|
|
739425e99c | ||
|
|
cc1574d913 | ||
|
|
d7ab6e2dc1 | ||
|
|
808bedf984 | ||
|
|
357eb885b2 | ||
|
|
417e04f655 | ||
|
|
2d41bb9960 | ||
|
|
7d7c82ce91 | ||
|
|
0c3f7a806e | ||
|
|
2a38bca34b | ||
|
|
c5bd42bbf1 | ||
|
|
8c1f15a49e | ||
|
|
f9778a0aff | ||
|
|
2ac84b4320 | ||
|
|
0677443ccf | ||
|
|
2c1f3e649a | ||
|
|
4faf5428b1 | ||
|
|
b71472661b | ||
|
|
6fa111866f | ||
|
|
f7ea8df89e | ||
|
|
041225c3f2 | ||
|
|
a8d708ea1b | ||
|
|
5ae7112192 | ||
|
|
2278340c7b | ||
|
|
87abaee8a7 | ||
|
|
8f2e2b9ccd | ||
|
|
d2ecb0aad9 | ||
|
|
1126d26574 | ||
|
|
b71e299161 | ||
|
|
572ea9f27a | ||
|
|
bc298bedd5 | ||
|
|
202557630c | ||
|
|
c27d5fa162 | ||
|
|
3090557eeb | ||
|
|
6de9f7efa7 | ||
|
|
f2b8d000f7 | ||
|
|
8ee546d34d | ||
|
|
5b94c69d10 | ||
|
|
8ae192f0a9 | ||
|
|
2e1a1c31e6 | ||
|
|
238da873b2 | ||
|
|
edb5f294ef | ||
|
|
5a76e2aa58 | ||
|
|
1e20cac0da | ||
|
|
d8e411462e | ||
|
|
59e65ed4a6 | ||
|
|
4319b5e888 | ||
|
|
0d9695c3a5 | ||
|
|
359aa1a074 | ||
|
|
e608f09b53 | ||
|
|
beed10901d | ||
|
|
dab6fa5aa1 | ||
|
|
30d20f824b | ||
|
|
80cf533b05 | ||
|
|
6ff57f53f8 | ||
|
|
650446de5b | ||
|
|
347601b9fe | ||
|
|
5c8a5998d9 | ||
|
|
391e040c6a | ||
|
|
8597d9e926 | ||
|
|
a895ce666e | ||
|
|
9d113dbc13 | ||
|
|
5cc2f8f9f7 | ||
|
|
5b9ada1f8c | ||
|
|
369059fa39 | ||
|
|
9473f4e8fd | ||
|
|
685053599f | ||
|
|
a9727cf267 | ||
|
|
bc399ba9f9 | ||
|
|
81f403a9f2 | ||
|
|
332f759f96 | ||
|
|
5d5aafb7cd | ||
|
|
f42f7cfe72 | ||
|
|
96b9484f70 | ||
|
|
3bb408194d | ||
|
|
865ba94605 | ||
|
|
bb883cd228 | ||
|
|
d3e1ddd234 | ||
|
|
b9c4884af1 | ||
|
|
a6b16eedfa | ||
|
|
9d04a82c42 | ||
|
|
c9ad18894d | ||
|
|
b2b44555ca | ||
|
|
14e094b522 | ||
|
|
0dea4b7944 | ||
|
|
94a3cfa9de | ||
|
|
ed1feb9502 | ||
|
|
a4856b9a73 | ||
|
|
ff3ccde62a | ||
|
|
2a2977eca7 | ||
|
|
1d9e2ccedd | ||
|
|
dd8243a56d | ||
|
|
2c9acb655d | ||
|
|
e1191b665e | ||
|
|
e4e23c9ecd | ||
|
|
4ce2b94241 | ||
|
|
11ac253c46 | ||
|
|
1d2e85672c | ||
|
|
82b8b56aa3 | ||
|
|
4e18d7800a | ||
|
|
7efd067bd5 | ||
|
|
4c7129115c | ||
|
|
0f14ed926b | ||
|
|
be5f1546ca | ||
|
|
f3160bf6cb | ||
|
|
fa79db65b7 | ||
|
|
d134aa0c20 | ||
|
|
b9f40f326f | ||
|
|
774aeeea68 | ||
|
|
f28efce11e | ||
|
|
d03e7e2b15 | ||
|
|
56ed158814 | ||
|
|
166a9a7f8f | ||
|
|
b131e8cfa4 | ||
|
|
2e1d0e36c5 | ||
|
|
ddcf619deb | ||
|
|
8f1b66ab5a | ||
|
|
167858cd95 | ||
|
|
04e56fb94c | ||
|
|
00fce30072 | ||
|
|
423392a18a | ||
|
|
cdb34571ab | ||
|
|
3b4d28eec3 | ||
|
|
297122f47f | ||
|
|
f9ff05f16c | ||
|
|
071545987b | ||
|
|
c36a51b8e3 | ||
|
|
232bbaf6d4 | ||
|
|
a0b38a9cac | ||
|
|
d76367584c | ||
|
|
86fd77f9c4 | ||
|
|
ebaf78f5aa | ||
|
|
b484593869 | ||
|
|
a7bf6258f7 | ||
|
|
2c6bd3ee1b | ||
|
|
87ac3daecc | ||
|
|
fd7b9d766a | ||
|
|
f822761ab2 | ||
|
|
1e2cf9d476 | ||
|
|
2d930894ff | ||
|
|
30b7de7233 | ||
|
|
0d22dbc982 | ||
|
|
8446e17703 | ||
|
|
fb936f896e | ||
|
|
0d0fcbbc0b | ||
|
|
5cfbc6b91d | ||
|
|
3b1f6ce4fb | ||
|
|
fd3927ce3e | ||
|
|
bbfd4b4996 | ||
|
|
848cbe11dd | ||
|
|
f7185a618b | ||
|
|
d70dc8b88c | ||
|
|
b5f5d3f0f0 | ||
|
|
ddfc2464f3 | ||
|
|
c3402a97f3 | ||
|
|
6ee6b3c398 | ||
|
|
42a4abe4c7 | ||
|
|
0f1334c92c | ||
|
|
346d091d00 | ||
|
|
27dba9ec1b | ||
|
|
8dbf63e436 | ||
|
|
30a11c75cb | ||
|
|
49caff5759 | ||
|
|
0a7251add0 | ||
|
|
b5dac60cbb | ||
|
|
9270834631 | ||
|
|
575dd59c78 | ||
|
|
15e79c1400 | ||
|
|
c75c248086 | ||
|
|
741f6310b3 | ||
|
|
2202ec451a | ||
|
|
1830458b6b | ||
|
|
1b88430f71 | ||
|
|
bbb4c4347e | ||
|
|
0ee46ad70e | ||
|
|
243e129710 | ||
|
|
a91f204682 | ||
|
|
5bc5eec85e | ||
|
|
80d3781d81 | ||
|
|
b1e2ab4448 | ||
|
|
3e49e33f9f | ||
|
|
986840b6bf | ||
|
|
61aea888f6 | ||
|
|
c16037122b | ||
|
|
a823b909ad | ||
|
|
5d55c7ac00 | ||
|
|
75f380252e | ||
|
|
5eb67990ca | ||
|
|
017fe87e4b | ||
|
|
fc050c3ce9 | ||
|
|
b893aafae7 | ||
|
|
9c24b26d02 | ||
|
|
22d1fc98ee | ||
|
|
d88f204a15 | ||
|
|
5d0f79932a | ||
|
|
7c44e5933d | ||
|
|
4d36c60738 | ||
|
|
53c2a02124 | ||
|
|
1701a4c97c | ||
|
|
e4b6385dcc | ||
|
|
2e92ec3e4d | ||
|
|
d20485027f | ||
|
|
eb2a83b975 | ||
|
|
9344ed121f | ||
|
|
c3327e567e | ||
|
|
3ed8987779 | ||
|
|
fa8588f766 | ||
|
|
fa8ffb8e66 | ||
|
|
9da8f9cc5a | ||
|
|
af03f07cc7 | ||
|
|
5ae16b1c7c | ||
|
|
a37a7ff142 | ||
|
|
ff592758cf | ||
|
|
b04446d24d | ||
|
|
40f8c931c7 | ||
|
|
a22517adc2 | ||
|
|
821f3eced5 | ||
|
|
00ec70d425 | ||
|
|
9d48d23787 | ||
|
|
9535e82da6 | ||
|
|
fd80f953e9 | ||
|
|
05718a9f20 | ||
|
|
19b7f730b6 | ||
|
|
4f2b4cea53 | ||
|
|
e566160f1e | ||
|
|
74890050d0 | ||
|
|
0cda90cdea | ||
|
|
1a11153cd2 | ||
|
|
d5aa324ce4 | ||
|
|
09df602f3b | ||
|
|
b026c44ea8 | ||
|
|
19f765df77 | ||
|
|
4e1675033a | ||
|
|
e9439125bd | ||
|
|
a11f9e4b34 | ||
|
|
10b0ade627 | ||
|
|
1a8d6e65cf | ||
|
|
9748fceda8 | ||
|
|
7005c11ac2 | ||
|
|
88bf2ae64d | ||
|
|
cae6df1457 | ||
|
|
6682c56b9f | ||
|
|
5394613ba6 | ||
|
|
6e84b98651 | ||
|
|
af1a6a851f | ||
|
|
1516cf779d | ||
|
|
db56effd91 | ||
|
|
f3d1c8b754 | ||
|
|
abf6a37f3a | ||
|
|
e8e0032404 | ||
|
|
52734eac7e | ||
|
|
ca61be5f5a | ||
|
|
b28e3d6f18 | ||
|
|
1ab1f119b8 | ||
|
|
a332f23b11 | ||
|
|
a6798c61df | ||
|
|
8ae8f526a6 | ||
|
|
847634a0f2 | ||
|
|
bc8f201b21 | ||
|
|
e450c3f83f | ||
|
|
32910b5983 | ||
|
|
a497962153 | ||
|
|
82f8a9fc98 | ||
|
|
f5bf1667a9 | ||
|
|
a9cccb3017 | ||
|
|
47ac10ca18 | ||
|
|
d4a70d5eea | ||
|
|
e78de8568e | ||
|
|
728550cac3 | ||
|
|
81c216f1c1 | ||
|
|
17f345bfe5 | ||
|
|
ebf9139762 | ||
|
|
7de40091a3 | ||
|
|
ee5775ce05 | ||
|
|
21ef2e5dd4 | ||
|
|
283956ee93 | ||
|
|
06275a46ed | ||
|
|
ea81b1dc92 | ||
|
|
fd5cfe23db | ||
|
|
bcf30a9a05 | ||
|
|
d5122dcf50 | ||
|
|
b5e05c8c77 | ||
|
|
9434ed513e | ||
|
|
6fe3ef43d8 | ||
|
|
c1f92858a2 | ||
|
|
00b5ade787 | ||
|
|
6b94f396ff | ||
|
|
b7de1e5070 | ||
|
|
4fe6c36c49 | ||
|
|
899592ce2a | ||
|
|
8585761f42 | ||
|
|
1aa2e1a409 | ||
|
|
9e3d4a7ffe | ||
|
|
fc3de4b7fd | ||
|
|
e7f07d7d3e | ||
|
|
6e07021223 | ||
|
|
3c80b6c1f7 | ||
|
|
20b0b39b14 | ||
|
|
de59d22429 | ||
|
|
1bef1b72bc | ||
|
|
47348e2e96 | ||
|
|
0f3c427e28 | ||
|
|
9bde69b6d6 | ||
|
|
dd8b1ce955 | ||
|
|
cb884094d5 | ||
|
|
f567abdaee | ||
|
|
02757693b4 | ||
|
|
35797ca6ff | ||
|
|
5588da59b9 | ||
|
|
fbb3b814c0 | ||
|
|
2784f33b52 | ||
|
|
2656d1d2af | ||
|
|
b6359d014f | ||
|
|
a4445043c1 | ||
|
|
59169876eb | ||
|
|
d06ecdef20 | ||
|
|
4a10986b5f | ||
|
|
e81b4279b8 | ||
|
|
a6abe7fa82 | ||
|
|
10a80b2752 | ||
|
|
0131c3ad13 | ||
|
|
62f975deeb | ||
|
|
2accfbf5db | ||
|
|
7ed6fcdf04 | ||
|
|
156fc0edbe | ||
|
|
c421ff718b | ||
|
|
cc31e2db7c | ||
|
|
10114f4a9f | ||
|
|
e879216fa4 | ||
|
|
f0ea893bf4 | ||
|
|
3053b9635a | ||
|
|
a1588e5f98 | ||
|
|
1fd623311e | ||
|
|
a480ef60ea | ||
|
|
4776552605 | ||
|
|
efb7adc967 | ||
|
|
feb2af060f | ||
|
|
15526aab8c | ||
|
|
679fd464f4 | ||
|
|
c8a32b578c | ||
|
|
84f3d92790 | ||
|
|
13291c8d7b | ||
|
|
8a74504fe3 | ||
|
|
11d4761bcb | ||
|
|
72213bb80d | ||
|
|
e8aee3a276 | ||
|
|
a3bf1d59e6 | ||
|
|
d3f52c6d3d | ||
|
|
fb1126cf39 | ||
|
|
ec00792475 | ||
|
|
d7bbb0d1a7 | ||
|
|
dfd58f4aa1 | ||
|
|
179afb0340 | ||
|
|
fec9803aae | ||
|
|
75de57c102 | ||
|
|
2084b1fc37 | ||
|
|
07d4d2023a | ||
|
|
fac278150e | ||
|
|
da5fb30191 | ||
|
|
01e74c73e2 | ||
|
|
782ef7fe14 | ||
|
|
bd14848600 | ||
|
|
1c245280c7 | ||
|
|
d7f48f534b | ||
|
|
8ce320148c | ||
|
|
3eef7b583d | ||
|
|
89a32aaabd | ||
|
|
b53131553e | ||
|
|
68d8afae3e | ||
|
|
92f24bd02e | ||
|
|
61c7f7738d | ||
|
|
9e816bc48f | ||
|
|
377a72e1c0 | ||
|
|
b1dc5bd59f | ||
|
|
23f7b3b5d8 | ||
|
|
09339333bb | ||
|
|
f36ce647e6 | ||
|
|
6d3584b6fb | ||
|
|
4006cedaad | ||
|
|
cd0bf60824 | ||
|
|
c4c45d9e0b | ||
|
|
02868d8fec | ||
|
|
7d9e2dc69b | ||
|
|
7c7ce43455 | ||
|
|
119c5a6971 | ||
|
|
8a952d3a5f | ||
|
|
764e1b6dc0 | ||
|
|
4e5e2f84e0 | ||
|
|
3c716e2a43 | ||
|
|
872dad8ce8 | ||
|
|
bd6ab1cc80 | ||
|
|
7fd958a490 | ||
|
|
da1ce51a0f | ||
|
|
26edbeaa28 | ||
|
|
93ebc7b973 | ||
|
|
50d1ec583b | ||
|
|
3d5f13fec8 | ||
|
|
fe12a8481f | ||
|
|
1eaf198acf | ||
|
|
c0a40499b0 | ||
|
|
8cfb70e080 | ||
|
|
d624358cb5 | ||
|
|
72b907a75f | ||
|
|
698573b14a | ||
|
|
129051a56e | ||
|
|
e608e1d601 | ||
|
|
22a0d88fe9 | ||
|
|
ccd1e52411 | ||
|
|
aaf18ccd7e | ||
|
|
62b1c1ea93 | ||
|
|
20807e58c8 | ||
|
|
89eb3d6078 | ||
|
|
7b52568575 | ||
|
|
d520260fe8 | ||
|
|
77c1c65b83 | ||
|
|
fe38bc16d2 | ||
|
|
b4c6490c94 | ||
|
|
67f2e55d38 | ||
|
|
b9d7f0b6d0 | ||
|
|
b880d96a86 | ||
|
|
b9156936ae | ||
|
|
617f9bbc62 | ||
|
|
b19eaeb89a | ||
|
|
5582a38930 | ||
|
|
c6eee47af6 | ||
|
|
10bb800f85 | ||
|
|
89726a6365 | ||
|
|
8e856acac5 | ||
|
|
df8fe4a9eb | ||
|
|
33a2a6121a | ||
|
|
441d2d2449 | ||
|
|
bef6a66eaf | ||
|
|
c1805a4831 | ||
|
|
38eecd9aef | ||
|
|
cef431c8af | ||
|
|
3efd589cb1 | ||
|
|
92c570e967 | ||
|
|
cf7af17aec | ||
|
|
3308bf0662 | ||
|
|
be605617c3 | ||
|
|
bb8fec963b | ||
|
|
da8bf59d5b | ||
|
|
54168af790 | ||
|
|
f0bed4c762 | ||
|
|
4d4d97419c | ||
|
|
41eef9a090 | ||
|
|
9ae6c0c863 | ||
|
|
5f47809c8e | ||
|
|
f2e3ff2dae | ||
|
|
92778be5be | ||
|
|
5da8d391f8 | ||
|
|
f867b183ab | ||
|
|
ea555df315 | ||
|
|
4af1e5fb84 | ||
|
|
bf02db4cbc | ||
|
|
967f8d6b0f | ||
|
|
4f4b016da3 | ||
|
|
a4255a6377 | ||
|
|
dca1b20043 | ||
|
|
025d36604b | ||
|
|
e23a19cd85 | ||
|
|
593902d349 | ||
|
|
d014167226 | ||
|
|
df668a6f72 | ||
|
|
cc5eb1075b | ||
|
|
19f4c5ae1b | ||
|
|
4e41ebf746 | ||
|
|
31cbdb1824 | ||
|
|
1b6efdf463 | ||
|
|
cb6c7ef3e8 | ||
|
|
4bd2a79905 | ||
|
|
345e22bd2c | ||
|
|
9964bb75b1 | ||
|
|
ec072a45d8 | ||
|
|
efb7b32694 | ||
|
|
74bc6368f2 | ||
|
|
b96e4dd23a | ||
|
|
d991e6be60 | ||
|
|
e7846085fc | ||
|
|
6faa3fd586 | ||
|
|
fa34eb0e59 | ||
|
|
3e97e6b771 | ||
|
|
330da50a5e | ||
|
|
e35a06743c | ||
|
|
0d6757c257 | ||
|
|
e4cde2f6a1 | ||
|
|
9b7d62717e | ||
|
|
b306bde3ec | ||
|
|
31f72e1aac | ||
|
|
ad201660a6 | ||
|
|
77f1dfd324 | ||
|
|
11467fca2a | ||
|
|
cc8d5b4263 | ||
|
|
4022aa8b5a | ||
|
|
187b98fbda | ||
|
|
0f33c8c89c | ||
|
|
b6967dc0dc | ||
|
|
b734a32ae6 | ||
|
|
1f16310b7d | ||
|
|
5349a5c49b | ||
|
|
a2241e2521 | ||
|
|
fedc07352b | ||
|
|
e251de79f3 | ||
|
|
7076e3124d | ||
|
|
fca7da6bba | ||
|
|
57c2cc4c4e | ||
|
|
3c913d1e22 | ||
|
|
4141049bb9 | ||
|
|
912a7a12be | ||
|
|
5c2ed38d2f | ||
|
|
c8f1d54fa1 | ||
|
|
e0fa8e0f72 | ||
|
|
7eb6fb3e8a | ||
|
|
debfdb8873 | ||
|
|
eda7e4ca2b | ||
|
|
70e6bdde02 | ||
|
|
21f3f8c973 | ||
|
|
f46133d116 | ||
|
|
9b7de00efb | ||
|
|
c340bd6565 | ||
|
|
511734c3d8 | ||
|
|
af002f4e24 | ||
|
|
28ad12d0ea | ||
|
|
6229a8593f | ||
|
|
256056e14f | ||
|
|
499be8bd8c | ||
|
|
cd87ef00cc | ||
|
|
6b7109c66b | ||
|
|
b90c9fac41 | ||
|
|
88c79544f0 | ||
|
|
fddaff1bd9 | ||
|
|
847ebf9bf3 | ||
|
|
3e7e7b7f6e | ||
|
|
06e7f44114 | ||
|
|
8ad603fea4 | ||
|
|
863a61d626 | ||
|
|
7b38542183 | ||
|
|
0af789b1c7 | ||
|
|
2d49b709ca | ||
|
|
826a45c105 | ||
|
|
03452dcdda | ||
|
|
514876f60f | ||
|
|
89994c52f9 | ||
|
|
edc2fbe1dc | ||
|
|
cfa90af33c | ||
|
|
32d4174083 | ||
|
|
bb0f3b3a7e | ||
|
|
dc0010823f | ||
|
|
b1431f8827 | ||
|
|
b9e3629459 | ||
|
|
31ed4efe3a | ||
|
|
6da1507405 | ||
|
|
0b82ee8d10 | ||
|
|
00890f87e5 | ||
|
|
91ac0e30aa | ||
|
|
9316a3df6d | ||
|
|
2d31e6bf91 | ||
|
|
a965d2cd04 | ||
|
|
93be9d1909 | ||
|
|
7cf9375ac6 | ||
|
|
af289cd9fb | ||
|
|
d1f1c0716b | ||
|
|
0c7aed197a | ||
|
|
325d16bc66 | ||
|
|
3104b4cc24 | ||
|
|
9595e6abbd | ||
|
|
c6f4de9f88 | ||
|
|
d66839c965 | ||
|
|
ac773009f8 | ||
|
|
8b03f172ed | ||
|
|
5e206e4f7e | ||
|
|
448fc8bcde | ||
|
|
c60b04424b | ||
|
|
5aed505429 | ||
|
|
912f5db2c7 | ||
|
|
8b57b9a0a5 | ||
|
|
d1c83ea7bb | ||
|
|
7d485efd71 | ||
|
|
20942533e6 | ||
|
|
6b2d80a145 | ||
|
|
01ef15223c | ||
|
|
da3290c381 | ||
|
|
a9284b1b39 | ||
|
|
3409472eb9 | ||
|
|
5348994b4a | ||
|
|
a3b9f6f1eb | ||
|
|
2dc019709b | ||
|
|
c56f58deae | ||
|
|
fa059804ce |
@@ -0,0 +1,6 @@
|
||||
coverage:
|
||||
status:
|
||||
# pull-requests only
|
||||
patch:
|
||||
default:
|
||||
threshold: 0.1%
|
||||
@@ -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 -->
|
||||
@@ -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!
|
||||
```
|
||||
@@ -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.-->
|
||||
@@ -14,3 +14,11 @@ jtester.properties
|
||||
.idea/
|
||||
*.iml
|
||||
.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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
|
||||
@@ -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
|
||||
@@ -1,89 +1,100 @@
|
||||
<div class="blog_content">
|
||||
<div class="iteye-blog-content-contain">
|
||||
[](https://travis-ci.com/alibaba/canal)
|
||||
[](https://codecov.io/gh/alibaba/canal)
|
||||

|
||||

|
||||
[](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
|
||||
[](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
|
||||
|
||||
<h3>最新更新</h3>
|
||||
<ol>
|
||||
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
|
||||
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
|
||||
</ol>
|
||||
## 背景
|
||||
|
||||
<h1>背景</h1>
|
||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</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;">基于日志增量订阅&消费支持的业务:</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;"> 定位: 基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了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="https://camo.githubusercontent.com/46c626b4cde399db43b2634a7911a04aecf273a0/687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333130372f63383762363762612d333934632d333038362d393537372d3964623035626530346339352e6a7067" 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>
|
||||
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更。从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变更进行同步,由此衍生出了增量订阅和消费业务,从此开启一段新纪元。
|
||||
|
||||
<h1>相关文档</h1>
|
||||
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
|
||||
|
||||
See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文档</a>
|
||||
基于日志增量订阅和消费的业务包括
|
||||
- 数据库镜像
|
||||
- 数据库实时备份
|
||||
- 索引构建和实时维护(拆分异构索引、倒排索引等)
|
||||
- 业务 cache 刷新
|
||||
- 带业务逻辑的增量数据处理
|
||||
|
||||
<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></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/AdminGuide">AdminGuide</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/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 href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
|
||||
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
||||
</ul>
|
||||
## 项目介绍
|
||||
|
||||
<h1>相关资料</h1>
|
||||
#### 名称:canal [kə'næl]
|
||||
#### 译意: 水道/管道/沟渠
|
||||
#### 产品定位: 基于数据库增量日志解析,提供增量数据订阅和消费
|
||||
#### 关键词: MySQL binlog parser / real-time / queue&topic / index build
|
||||
|
||||
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
|
||||
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
|
||||
## 工作原理
|
||||
|
||||
<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>
|
||||
#### MySQL主备复制原理
|
||||

|
||||
|
||||
<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>
|
||||
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
|
||||
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
|
||||
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
|
||||
|
||||
<pre>
|
||||
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||
</pre>
|
||||
#### canal 工作原理
|
||||

|
||||
|
||||
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
|
||||
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
|
||||
- canal 解析 binary log 对象(原始为 byte 流)
|
||||
|
||||
## 重要版本更新说明
|
||||
|
||||
canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
|
||||
|
||||
- 整体性能测试&优化,提升了150%. #726 参考: [Performance](https://github.com/alibaba/canal/wiki/Performance)
|
||||
- 原生支持prometheus监控 #765 [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
|
||||
- 原生支持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)
|
||||
|
||||
## 文档
|
||||
|
||||
- [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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -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}
|
||||
@@ -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}}
|
||||
@@ -0,0 +1 @@
|
||||
.dashboard-container[data-v-42037c2b]{margin:30px}.dashboard-text[data-v-42037c2b]{font-size:30px;line-height:46px}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-64c3fae5]{text-align:center}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-44b63e9d]{text-align:center}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-5feb688e]{text-align:center}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-756ebb70]{text-align:center}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-11496239]{text-align:center}
|
||||
@@ -0,0 +1 @@
|
||||
.line[data-v-0b80198c]{text-align:center}
|
||||
@@ -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)}}
|
||||
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -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}}]);
|
||||
@@ -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){}}]);
|
||||
@@ -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){}}]);
|
||||
@@ -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}}]);
|
||||
@@ -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"})}}}]);
|
||||
@@ -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}}]);
|
||||
@@ -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}}]);
|
||||
@@ -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){}}]);
|
||||
@@ -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"})}}}]);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
# just a flag
|
||||
ENV = 'production'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = '/api/v1'
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
NODE_ENV = production
|
||||
|
||||
# just a flag
|
||||
ENV = 'staging'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = '/stage-api'
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
build/*.js
|
||||
src/assets
|
||||
public
|
||||
dist
|
||||
@@ -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']
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js: 10
|
||||
script: npm run test
|
||||
notifications:
|
||||
email: false
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
@@ -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}`)
|
||||
}
|
||||
@@ -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/'
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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': {}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 66 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getList(params) {
|
||||
return request({
|
||||
url: '/table/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/user/login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getInfo(token) {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
method: 'get',
|
||||
params: { token }
|
||||
})
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/user/logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUser(data) {
|
||||
return request({
|
||||
url: '/user',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||