mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-15 14:20:30 +09:00
Compare commits
537 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71bc29df2a | ||
|
|
59a8b39305 | ||
|
|
ab1c3b5606 | ||
|
|
1ae381e842 | ||
|
|
7677947447 | ||
|
|
0a62366fa4 | ||
|
|
cc433bb531 | ||
|
|
501ef6c062 | ||
|
|
b6529b453a | ||
|
|
40d8c952a0 | ||
|
|
c1511b54f8 | ||
|
|
9a50949fcc | ||
|
|
699e661d81 | ||
|
|
93990a5994 | ||
|
|
82e684f0a7 | ||
|
|
7d481247bc | ||
|
|
e24c5bc62a | ||
|
|
29fff2a6cd | ||
|
|
2a20b930f0 | ||
|
|
b0ba230abe | ||
|
|
1e48e1803f | ||
|
|
e90648c027 | ||
|
|
23417fa6a2 | ||
|
|
57d5ebd641 | ||
|
|
da8c9d5707 | ||
|
|
6b245c7eec | ||
|
|
d7653dc73e | ||
|
|
e6a7b35f6a | ||
|
|
f6e02ae181 | ||
|
|
f6d6cd16e7 | ||
|
|
87901fe6a7 | ||
|
|
3088a2fec1 | ||
|
|
bdd0caa026 | ||
|
|
068b0eed34 | ||
|
|
b2f9692815 | ||
|
|
5fc6c56409 | ||
|
|
d7a78c57ae | ||
|
|
31658416f5 | ||
|
|
1d81e88684 | ||
|
|
f55a58b100 | ||
|
|
902542e480 | ||
|
|
c198d32bd5 | ||
|
|
56d9f5d94f | ||
|
|
45f66d51fe | ||
|
|
95769248a3 | ||
|
|
dcbf3eb277 | ||
|
|
fab977fd7d | ||
|
|
9d2a01ebbe | ||
|
|
9a0789cab0 | ||
|
|
e5a6b8b32b | ||
|
|
4537c8e304 | ||
|
|
82ac821b34 | ||
|
|
0a4a31ae65 | ||
|
|
3b325536e8 | ||
|
|
307c6eab98 | ||
|
|
b1ff70265f | ||
|
|
226296737d | ||
|
|
a5750d1d0d | ||
|
|
f71c9b20ab | ||
|
|
9cc55492dc | ||
|
|
6d36e4620c | ||
|
|
4d5e8f62c3 | ||
|
|
8ba33f25b4 | ||
|
|
7a742d5aea | ||
|
|
abd25d6158 | ||
|
|
9f46e52564 | ||
|
|
e6927209e5 | ||
|
|
63fbedbdc8 | ||
|
|
6ec886686f | ||
|
|
f96e356c8a | ||
|
|
c646624e45 | ||
|
|
0148056b1f | ||
|
|
e9542352b5 | ||
|
|
7cef35517b | ||
|
|
bc0619a92a | ||
|
|
2debf6c63f | ||
|
|
baa01d10cb | ||
|
|
adb4864c85 | ||
|
|
c6f10e04eb | ||
|
|
ad6a61f3df | ||
|
|
5cb73019de | ||
|
|
8a8e878e47 | ||
|
|
fae82e1d4e | ||
|
|
e0011913c2 | ||
|
|
a59b6a6c96 | ||
|
|
68aaab6ac4 | ||
|
|
c75190513a | ||
|
|
1421c52c34 | ||
|
|
b2942737af | ||
|
|
a1d8b12e98 | ||
|
|
45d6a4c59a | ||
|
|
e6f6fb5f06 | ||
|
|
13c438d097 | ||
|
|
412e455907 | ||
|
|
75d630aad0 | ||
|
|
2105e289ac | ||
|
|
9eddc0b1fd | ||
|
|
fa4ede5179 | ||
|
|
3aa672031a | ||
|
|
82ca186bff | ||
|
|
727a11fcea | ||
|
|
7106dc0fbb | ||
|
|
a2c8ecb227 | ||
|
|
e4c771e364 | ||
|
|
261dd8b624 | ||
|
|
ac63576704 | ||
|
|
a3b92443c4 | ||
|
|
527ee1200a | ||
|
|
82a60eb651 | ||
|
|
7fd174b313 | ||
|
|
7c910196e1 | ||
|
|
554e7e5e25 | ||
|
|
8b70319436 | ||
|
|
053f196b6b | ||
|
|
86f8cc8347 | ||
|
|
e91d02ba9f | ||
|
|
109d2868d9 | ||
|
|
4673e41fc5 | ||
|
|
a2122351d6 | ||
|
|
375aaf71d3 | ||
|
|
b752af948d | ||
|
|
52ed016073 | ||
|
|
632288726b | ||
|
|
1dc9bee987 | ||
|
|
387c440e06 | ||
|
|
136afa0fca | ||
|
|
8ba3daa2f0 | ||
|
|
b3fe337203 | ||
|
|
da7375ea0d | ||
|
|
d9487d1106 | ||
|
|
3110496630 | ||
|
|
22e90dc7d1 | ||
|
|
5943243e62 | ||
|
|
1244a64d34 | ||
|
|
40a6ecd159 | ||
|
|
9df68dccac | ||
|
|
72bba6f80c | ||
|
|
cf803bb1dd | ||
|
|
13c78ed5d3 | ||
|
|
3bd878a978 | ||
|
|
c9d3528f4b | ||
|
|
3b2f8fc780 | ||
|
|
bc3da09d60 | ||
|
|
284f56302c | ||
|
|
cdd5047bbc | ||
|
|
5d3ba95bd9 | ||
|
|
ec384113eb | ||
|
|
8b2651876c | ||
|
|
a3852c421e | ||
|
|
e1e1bdbd01 | ||
|
|
6757511de3 | ||
|
|
f5b717ec36 | ||
|
|
6b491d9949 | ||
|
|
cf382db49f | ||
|
|
ee2f80e582 | ||
|
|
4453d62515 | ||
|
|
72068818d7 | ||
|
|
92bb94b9fa | ||
|
|
7a2098e9ce | ||
|
|
56b81f4f48 | ||
|
|
82c060c2a7 | ||
|
|
fba35b796d | ||
|
|
5f6fc1fa6f | ||
|
|
a9c227cd05 | ||
|
|
a8330e062f | ||
|
|
30d0cbf6de | ||
|
|
3063e08eb3 | ||
|
|
f7054b160f | ||
|
|
98b5d0e37a | ||
|
|
6348bac11a | ||
|
|
c2a26c97a8 | ||
|
|
cd6e01109b | ||
|
|
7553efdc3b | ||
|
|
6578dc312a | ||
|
|
c490a7ab28 | ||
|
|
0969a406c6 | ||
|
|
eed3021373 | ||
|
|
5b33bd4dca | ||
|
|
6d22207e27 | ||
|
|
249cf49873 | ||
|
|
b17d487c8b | ||
|
|
fedb2b50b6 | ||
|
|
4b18313e2d | ||
|
|
8387f587b3 | ||
|
|
c7b64dbdf3 | ||
|
|
085d61cf29 | ||
|
|
e6cf2e9390 | ||
|
|
4bcea96789 | ||
|
|
866062bdcb | ||
|
|
53a54ddbda | ||
|
|
8bdb85b878 | ||
|
|
9a26be41a8 | ||
|
|
5cf39b3861 | ||
|
|
99d87636d2 | ||
|
|
bccd50ac4a | ||
|
|
3bb30accfd | ||
|
|
5d2b71652c | ||
|
|
b70af4dafe | ||
|
|
a95cf20c6b | ||
|
|
b58a5dbe9f | ||
|
|
f7222d65ae | ||
|
|
ff43103f37 | ||
|
|
93ab9c55bc | ||
|
|
28715cdf62 | ||
|
|
bd5276116b | ||
|
|
b20fb9e9c3 | ||
|
|
d3a406a382 | ||
|
|
c70fe687e1 | ||
|
|
fc81685044 | ||
|
|
6e24d7caf5 | ||
|
|
6786cffb4d | ||
|
|
1805705ff2 | ||
|
|
64fbabba4d | ||
|
|
0db13872a1 | ||
|
|
694e199709 | ||
|
|
77cf0a06d6 | ||
|
|
d9c7bcaad9 | ||
|
|
d9469b7dcc | ||
|
|
1a57a4b04e | ||
|
|
10d871be2e | ||
|
|
8b75cc1891 | ||
|
|
f6522a9a79 | ||
|
|
96449c8aeb | ||
|
|
1d03027efe | ||
|
|
69750267f6 | ||
|
|
2640e91055 | ||
|
|
9ccefe90ee | ||
|
|
c538ea95ee | ||
|
|
933975ce70 | ||
|
|
d189feea87 | ||
|
|
9637658b89 | ||
|
|
c3c6265dc6 | ||
|
|
63f871f649 | ||
|
|
a9259fd30d | ||
|
|
43e1fd3f04 | ||
|
|
8e1e4344e4 | ||
|
|
2186b0b007 | ||
|
|
9f7238021b | ||
|
|
db2a5949dc | ||
|
|
73293095f6 | ||
|
|
879fae55ce | ||
|
|
b2da564469 | ||
|
|
b52834a2d8 | ||
|
|
c8c4ad37c8 | ||
|
|
6fa7375f13 | ||
|
|
9127a50433 | ||
|
|
cabbec3d3b | ||
|
|
e42b26fa73 | ||
|
|
018c0f18dc | ||
|
|
a4b0a1bafb | ||
|
|
6eededed2c | ||
|
|
b62455836a | ||
|
|
9dfe06e346 | ||
|
|
461c31e7ea | ||
|
|
29a1903b21 | ||
|
|
83401a2ecb | ||
|
|
3242457cce | ||
|
|
12f91fa85c | ||
|
|
85ba371b72 | ||
|
|
014c348a89 | ||
|
|
a44d041e93 | ||
|
|
9ea4afcaef | ||
|
|
9b32f5e598 | ||
|
|
f4dd8519a9 | ||
|
|
1b1be180c6 | ||
|
|
026bdc6f18 | ||
|
|
1207a646c7 | ||
|
|
3e90a9b22c | ||
|
|
459aa136c1 | ||
|
|
d164cb9363 | ||
|
|
a8fb990966 | ||
|
|
121f2e364f | ||
|
|
b968329fb3 | ||
|
|
d9ff379d59 | ||
|
|
5c9a8d21a3 | ||
|
|
9d24c1587f | ||
|
|
436821a831 | ||
|
|
ecb07dc682 | ||
|
|
8f08927aad | ||
|
|
e29fd8dc92 | ||
|
|
3af5eb076e | ||
|
|
c190c15889 | ||
|
|
540bdf35a5 | ||
|
|
33b4d88d52 | ||
|
|
ad3eec4abe | ||
|
|
7b87a9485e | ||
|
|
a8a027ed88 | ||
|
|
69b62032b2 | ||
|
|
f411659ad6 | ||
|
|
0c33eda68c | ||
|
|
4013c4c08f | ||
|
|
ae3c2d72a1 | ||
|
|
5a96f81338 | ||
|
|
31208f9883 | ||
|
|
a64a06d663 | ||
|
|
02a73762b0 | ||
|
|
2ca24a9aae | ||
|
|
fc1ea9166c | ||
|
|
71ee075cfa | ||
|
|
33f4a7544c | ||
|
|
d16b681362 | ||
|
|
5547b7e868 | ||
|
|
3722d0ca24 | ||
|
|
41d11372ce | ||
|
|
3582ef4125 | ||
|
|
8e32fe531a | ||
|
|
17201aad08 | ||
|
|
4fb70ac8e8 | ||
|
|
425de89469 | ||
|
|
5746cb43c3 | ||
|
|
704f31581f | ||
|
|
7cb7dde65d | ||
|
|
bdc86b5510 | ||
|
|
59816c9693 | ||
|
|
4f8b75a8f9 | ||
|
|
5ea8f2adb1 | ||
|
|
80cd607d17 | ||
|
|
488916bcd7 | ||
|
|
b5abeac093 | ||
|
|
ed266dea15 | ||
|
|
65558c7ee8 | ||
|
|
e5d2c33d9a | ||
|
|
7f00f5de99 | ||
|
|
b2906d09f4 | ||
|
|
3f0d036dad | ||
|
|
9d66cd85f0 | ||
|
|
0a21920ad9 | ||
|
|
bda37213f8 | ||
|
|
c725871a4e | ||
|
|
39a10681bf | ||
|
|
381cfa1486 | ||
|
|
4d4385b7ba | ||
|
|
ec7debe8c5 | ||
|
|
05591546b3 | ||
|
|
2b0644b495 | ||
|
|
a30bd9e2c7 | ||
|
|
6eed819364 | ||
|
|
a1780b1087 | ||
|
|
c70dc3181e | ||
|
|
8ee0435738 | ||
|
|
72e1415038 | ||
|
|
65c3653494 | ||
|
|
304a1e7f71 | ||
|
|
6972915a5c | ||
|
|
77ea248457 | ||
|
|
14c1f7ea85 | ||
|
|
0652a6ec5e | ||
|
|
cecd30309f | ||
|
|
22762d7d84 | ||
|
|
6715665072 | ||
|
|
b92d8275db | ||
|
|
b4acc378e3 | ||
|
|
a313d8b19d | ||
|
|
93152f8157 | ||
|
|
1e99602c68 | ||
|
|
1571ab6d12 | ||
|
|
44d6854bc5 | ||
|
|
727403d420 | ||
|
|
b06b130d20 | ||
|
|
94107f2210 | ||
|
|
adb8e06f0a | ||
|
|
8cb0ac09e8 | ||
|
|
8935d77d0f | ||
|
|
0adb8bf94b | ||
|
|
7e21437dfc | ||
|
|
bfba79448b | ||
|
|
5be49a4090 | ||
|
|
e0b0378329 | ||
|
|
5f3ae667b0 | ||
|
|
b14cf64124 | ||
|
|
d9f9ee96af | ||
|
|
a644dd6052 | ||
|
|
3e5b25b37a | ||
|
|
a693e2aa0c | ||
|
|
599b6fb0b4 | ||
|
|
9638667368 | ||
|
|
21f3f7dca2 | ||
|
|
b4bb16130c | ||
|
|
f1c9fba68e | ||
|
|
09df7795a9 | ||
|
|
6bb768de59 | ||
|
|
86badb8f41 | ||
|
|
54999ce735 | ||
|
|
851100b774 | ||
|
|
3ba2bafc55 | ||
|
|
cbd3066f57 | ||
|
|
f3506304d9 | ||
|
|
c9b90478dd | ||
|
|
2ca2ba7ffc | ||
|
|
3df1abd46e | ||
|
|
34f21a58c9 | ||
|
|
233b56f282 | ||
|
|
1cd7f605e3 | ||
|
|
efee7b575f | ||
|
|
870a813c61 | ||
|
|
24285c7885 | ||
|
|
69e2ab3977 | ||
|
|
8218c63bba | ||
|
|
d89a2af1aa | ||
|
|
09e9b18f96 | ||
|
|
e70bbcff62 | ||
|
|
085d2f1706 | ||
|
|
4746ba9489 | ||
|
|
27fab8acca | ||
|
|
734bb1b087 | ||
|
|
81d9e92450 | ||
|
|
5d794536e3 | ||
|
|
4f3efc50fc | ||
|
|
37ad22090c | ||
|
|
95fa102720 | ||
|
|
f37ad1ae58 | ||
|
|
fb2d8da53d | ||
|
|
87ee4bebb3 | ||
|
|
9bf0871ff6 | ||
|
|
70827393b7 | ||
|
|
c4e8eb94d6 | ||
|
|
e50a48bd4c | ||
|
|
10df1116bd | ||
|
|
03f45346a7 | ||
|
|
6170e8f1ae | ||
|
|
0a718e897b | ||
|
|
d243c3a8e8 | ||
|
|
49020d9bd0 | ||
|
|
1457f3fdea | ||
|
|
42c63b963f | ||
|
|
aecc1cab68 | ||
|
|
4e63e0a833 | ||
|
|
4540165157 | ||
|
|
a56f5284d1 | ||
|
|
eb675f59a3 | ||
|
|
35f8b6b52a | ||
|
|
6c76f0a067 | ||
|
|
48b626c08e | ||
|
|
b2f796cf73 | ||
|
|
b01f4b99e4 | ||
|
|
d11089c451 | ||
|
|
8ecdb4f5a6 | ||
|
|
86c2e86a12 | ||
|
|
54662a1b8a | ||
|
|
6cdee3ba5d | ||
|
|
c6df1e2093 | ||
|
|
663b3f4e09 | ||
|
|
2b6dadd5f1 | ||
|
|
bce1be698c | ||
|
|
c29af8c593 | ||
|
|
a7a86f960a | ||
|
|
56c81caf05 | ||
|
|
8c5b349196 | ||
|
|
1f631529d9 | ||
|
|
6f50f45aa8 | ||
|
|
e23343d7ed | ||
|
|
ef4ffca2bd | ||
|
|
382fe244c5 | ||
|
|
d374be5eee | ||
|
|
e28787d29b | ||
|
|
b7d152a42a | ||
|
|
79e080ea1f | ||
|
|
0087488d5f | ||
|
|
e56ac71d60 | ||
|
|
fc64f07d7f | ||
|
|
b0868de37b | ||
|
|
d950ff29b2 | ||
|
|
10bea2311e | ||
|
|
25a5e808f2 | ||
|
|
9d52c880c6 | ||
|
|
e5adf65f88 | ||
|
|
c1b5056bc9 | ||
|
|
837aef50c3 | ||
|
|
3446d1bfbc | ||
|
|
6bea2ac169 | ||
|
|
c675f4cdb5 | ||
|
|
be04f20b5b | ||
|
|
101968e2b5 | ||
|
|
67983961ca | ||
|
|
732c3c5e5c | ||
|
|
6ebbc62127 | ||
|
|
bb2ec8aadc | ||
|
|
b650cff080 | ||
|
|
a81bb20acd | ||
|
|
b40e880b70 | ||
|
|
b5b6ccc254 | ||
|
|
df417e425a | ||
|
|
3e702c6501 | ||
|
|
ea0b9c07ec | ||
|
|
c6c5bfa96b | ||
|
|
34c569baeb | ||
|
|
fef99920ed | ||
|
|
c110e6d0b1 | ||
|
|
7687def758 | ||
|
|
bf276d0073 | ||
|
|
6dea0f1054 | ||
|
|
061f2dc3e0 | ||
|
|
8ed3210996 | ||
|
|
d315699102 | ||
|
|
7d2e15a831 | ||
|
|
25028774d7 | ||
|
|
1bc52a26b7 | ||
|
|
d0f391f539 | ||
|
|
ca4c8244d5 | ||
|
|
25afc76ced | ||
|
|
f5b046f7eb | ||
|
|
563257930c | ||
|
|
b8b88a6879 | ||
|
|
166244a67b | ||
|
|
f50b0d204e | ||
|
|
0db3bea4ef | ||
|
|
98e7d651c3 | ||
|
|
75d4ac9827 | ||
|
|
2f3dadc186 | ||
|
|
f7b5753e7d | ||
|
|
3b51cd5504 | ||
|
|
53a21067e5 | ||
|
|
4b7de03050 | ||
|
|
88c7408276 | ||
|
|
ccdbc58ca8 | ||
|
|
d5493be261 | ||
|
|
04da4556b1 | ||
|
|
17c4a87ad3 | ||
|
|
8440a431d0 | ||
|
|
3334d36105 | ||
|
|
77c101e077 | ||
|
|
d3967c5dab | ||
|
|
54766dc3a2 | ||
|
|
8ce79fe5b2 | ||
|
|
82372886eb | ||
|
|
45b6819d10 | ||
|
|
b1e0638945 | ||
|
|
f1e47f36af | ||
|
|
3bb85c40eb | ||
|
|
8c37b7509e | ||
|
|
13c1bf71d1 | ||
|
|
f500844cbe | ||
|
|
c5bc1a0b25 | ||
|
|
8eee065aea | ||
|
|
289ea48588 | ||
|
|
f39db9728c | ||
|
|
92745495ef |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# .dockerignore
|
||||||
|
quartz-manager-frontend/node_modules
|
||||||
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 120
|
||||||
|
tab_width = 2
|
||||||
|
ij_continuation_indent_size = 2
|
||||||
46
.github/workflows/maven-release.yml
vendored
Normal file
46
.github/workflows/maven-release.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Publish package to the Maven Central Repository
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Java 21 for publishing to Maven Central Repository
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
server-id: maven-central-release
|
||||||
|
server-username: MAVEN_USERNAME
|
||||||
|
server-password: MAVEN_PASSWORD
|
||||||
|
gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
|
||||||
|
gpg-passphrase: MAVEN_GPG_PASSPHRASE
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file quartz-manager-parent/pom.xml
|
||||||
|
|
||||||
|
- name: Publish to maven central
|
||||||
|
run: mvn deploy --file quartz-manager-parent/pom.xml --batch-mode -P "release-maven-central,build-webjar"
|
||||||
|
env:
|
||||||
|
MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }}
|
||||||
|
MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }}
|
||||||
|
MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set up Java 21 for publishing to GitHub Packages
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Publish to GitHub Packages Apache Maven
|
||||||
|
run: mvn deploy --file quartz-manager-parent/pom.xml -P "deploy-github,build-webjar"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
36
.github/workflows/maven.yml
vendored
Normal file
36
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Java CI with Maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- master
|
||||||
|
paths: [ 'quartz-manager-parent/**' ]
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build and test with Maven
|
||||||
|
run: mvn -B package --file quartz-manager-parent/pom.xml
|
||||||
44
.github/workflows/npm.yml
vendored
Normal file
44
.github/workflows/npm.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||||
|
|
||||||
|
name: npm CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- master
|
||||||
|
paths: [ 'quartz-manager-frontend/**' ]
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- master
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./quartz-manager-frontend
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [22.x]
|
||||||
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: ./quartz-manager-frontend/package-lock.json
|
||||||
|
- name: 'install'
|
||||||
|
run: npm ci
|
||||||
|
- name: 'test'
|
||||||
|
run: npm test
|
||||||
|
- name: 'build'
|
||||||
|
run: npm run build --if-present
|
||||||
40
.github/workflows/sonar-java.yml
vendored
Normal file
40
.github/workflows/sonar-java.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: SonarCloud Analysis for Java
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
# paths: [ 'quartz-manager-parent/**' ]
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 21
|
||||||
|
distribution: 'zulu' # Alternative distribution options are available.
|
||||||
|
- name: Cache SonarCloud packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.sonar/cache
|
||||||
|
key: ${{ runner.os }}-sonar
|
||||||
|
restore-keys: ${{ runner.os }}-sonar
|
||||||
|
- name: Cache Maven packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.m2
|
||||||
|
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||||
|
restore-keys: ${{ runner.os }}-m2
|
||||||
|
- name: Build and analyze
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=fabioformosa_quartz-manager --file quartz-manager-parent/pom.xml
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
|||||||
/.project
|
/.project
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
.DS_Store
|
||||||
|
|||||||
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
language: java
|
||||||
|
jdk: openjdk8
|
||||||
|
before_script: cd quartz-manager-parent
|
||||||
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
## **v5.0.1**
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
* Added full job management: list eligible job classes, create stored jobs, update jobs, delete jobs, and trigger jobs on demand.
|
||||||
|
* Added trigger management APIs and UI flows to inspect, create, reschedule, pause, resume, and unschedule triggers.
|
||||||
|
* Added support for Quartz trigger types beyond simple triggers: cron, daily time interval, and calendar interval triggers.
|
||||||
|
* Added Quartz calendar management for annual, cron, daily, holiday, monthly, and weekly calendars.
|
||||||
|
* Added calendar-aware scheduling support, including calendar assignment to triggers and included-time checks.
|
||||||
|
* Redesigned the Quartz Manager dashboard with a broader operations view for scheduler, jobs, triggers, calendars, progress, and logs.
|
||||||
|
* Updated the embedded UI to Angular 21.
|
||||||
|
* Added support for Spring Boot 4 applications.
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
* Quartz Manager now requires Java 21+ and Spring Boot 4.x.
|
||||||
|
* Applications using Quartz Manager APIs must migrate from `javax.*` validation/annotation dependencies to `jakarta.*` equivalents through the Spring Boot 4 stack.
|
||||||
|
* Scheduler command endpoints now use `POST` operations and clearer action names: `/scheduler/start`, `/scheduler/standby`, `/scheduler/resume`, and `/scheduler/shutdown` replace the previous `GET` command endpoints.
|
||||||
|
* Simple trigger endpoints now include the trigger group in the path: `/simple-triggers/{group}/{name}`.
|
||||||
|
* New trigger creation should use the generalized `/triggers/{group}/{name}` API when working with cron, daily time interval, or calendar interval triggers.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
* Fixed WebSocket log retrieval for job execution logs.
|
||||||
|
* Fixed UI style regressions and improved readability in the dashboard, login page, job class display, and misfire instruction display.
|
||||||
|
* Improved API error handling for missing jobs, missing triggers, missing calendars, unsupported trigger types, and scheduling conflicts.
|
||||||
|
|
||||||
|
## **v4.1.1**
|
||||||
|
**NEW FEATURE** support for multiple triggers
|
||||||
|
|
||||||
|
## **v4.0.10**
|
||||||
|
Migrated to the new maven central repo
|
||||||
|
|
||||||
|
## **v4.0.9**
|
||||||
|
Fixed a bug which prevented to run the liquibase migration scripts in case of usage of quartz-manager-starter-persistence
|
||||||
|
|
||||||
|
## **v4.0.8**
|
||||||
|
Upgraded the frontend to angular v14
|
||||||
|
|
||||||
|
## **v4.0.6**
|
||||||
|
Minor bug fixes
|
||||||
|
|
||||||
|
## **v4.0.5**
|
||||||
|
Fixed potential security issues
|
||||||
|
|
||||||
|
## **v4.0.4**
|
||||||
|
* Conformed the trigger configuration to the Simple Trigger of Quartz
|
||||||
|
* **BREAKING CHANGE** Changed accordingly the API and the UI
|
||||||
|
* Made Quartz Manager embeddable in projects with existing quartz instance, security layer, swagger ui.
|
||||||
|
|
||||||
|
## **v3.1.0**
|
||||||
|
* Added a new persistence module to persist the quartz triggers in a postgresql database
|
||||||
|
|
||||||
|
## **v3.0.1**
|
||||||
|
|
||||||
|
Quartz-Manager is now publicly available into the maven central repo into 3 different packages.
|
||||||
|
You can import:
|
||||||
|
|
||||||
|
* `quartz-manager-starter-api` to have a REST API layer to control your scheduler
|
||||||
|
* `quartz-manager-starter-ui` to import the UI also, in your spring webapp.
|
||||||
|
* `quartz-manager-starter-security` if you want to give access to the quartz-manager UI and API only to authenticated users
|
||||||
34
Dockerfile
Normal file
34
Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
FROM maven:3.9.8-eclipse-temurin-21 AS build
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the pom.xml and download dependencies
|
||||||
|
COPY quartz-manager-parent/pom.xml ./quartz-manager-parent/
|
||||||
|
COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-common ./quartz-manager-parent/quartz-manager-common/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-starter-api ./quartz-manager-parent/quartz-manager-starter-api/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-starter-persistence ./quartz-manager-parent/quartz-manager-starter-persistence/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-starter-security ./quartz-manager-parent/quartz-manager-starter-security/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-starter-ui ./quartz-manager-parent/quartz-manager-starter-ui/
|
||||||
|
COPY quartz-manager-parent/quartz-manager-web-showcase ./quartz-manager-parent/quartz-manager-web-showcase/
|
||||||
|
COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/
|
||||||
|
COPY quartz-manager-frontend ./quartz-manager-frontend/
|
||||||
|
WORKDIR /app/quartz-manager-parent
|
||||||
|
RUN mvn clean package -DskipTests -P=build-webjar
|
||||||
|
|
||||||
|
|
||||||
|
# Stage 2: Create the final image
|
||||||
|
FROM openjdk:11-jre-slim
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the JAR file from the build stage
|
||||||
|
COPY --from=build /app/quartz-manager-parent/quartz-manager-web-showcase/target/*-SNAPSHOT.jar app.jar
|
||||||
|
|
||||||
|
# Expose the application port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed 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.
|
||||||
362
README.md
362
README.md
@@ -1,72 +1,340 @@
|
|||||||
# QUARTZ MANAGER
|
<div align="center">
|
||||||
GUI Manager for Quartz Scheduler.
|
|
||||||
|
|
||||||
Through this webapp you can launch and control your scheduled job. The GUI Console is composed by a managament panel to set trigger, start/stop scheduler and a log panel with a progress bar to display the job output.
|
# Quartz Manager
|
||||||
|
|
||||||
## SCREENSHOT
|
**A Spring Boot library and standalone web app that adds REST API and dashboard management to Quartz Scheduler.**
|
||||||

|
|
||||||
|
|
||||||
## HOW IT WORKS
|
[](https://github.com/fabioformosa/quartz-manager/actions/workflows/maven.yml)
|
||||||
* Set up the trigger into the left sidebar in terms of: daily frequency and and max occurrences.
|
[](https://github.com/fabioformosa/quartz-manager/actions/workflows/npm.yml)
|
||||||
* Press the start button
|
[](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager)
|
||||||
* The GUI manager updates the progress bar and reports all logs of your quartz job.
|
[](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager)
|
||||||
|
|
||||||
## QUICK START
|
[Choose Your Path](#choose-your-path) • [Features](#features) • [Quick Start](#quick-start) • [REST API](#rest-api) • [Security](#security) • [Persistence](#persistence) • [Roadmap](#roadmap)
|
||||||
**[requirements]** Make sure you have installed
|
|
||||||
* [Java 8](https://java.com/download/) or greater
|
|
||||||
* [Maven](https://maven.apache.org/)
|
|
||||||
* [npm](https://www.npmjs.com/get-npm), [node](https://nodejs.org) and [angular-cli](https://cli.angular.io/)
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[Quartz Scheduler](https://www.quartz-scheduler.org/) is powerful, but it does not ship with a REST API or an operations dashboard. Quartz Manager fills that gap for Spring Boot applications and can also run as a standalone scheduler web app.
|
||||||
|
|
||||||
|
Use it to start and stop a scheduler, create jobs, schedule triggers, manage calendars, inspect executions, and monitor job progress from HTTP endpoints or from a browser UI.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Choose Your Path
|
||||||
|
|
||||||
|
### 1. Add API And UI To Your Existing App
|
||||||
|
|
||||||
|
Use this path when you already have a Spring Boot application and want to add a Quartz management API, an embedded management panel, or both.
|
||||||
|
|
||||||
|
Current behavior: Quartz Manager creates and manages its own scheduler bean named `quartzManagerScheduler` by default. It can coexist with other Quartz schedulers in the same application, but it does not automatically take control of an arbitrary existing scheduler instance.
|
||||||
|
|
||||||
|
If you want Quartz Manager to manage an existing scheduler today, disable the default scheduler configuration with `quartz-manager.quartz.enabled=false` and provide a compatible bean named `quartzManagerScheduler`. First-class existing-scheduler integration is planned on the roadmap.
|
||||||
|
|
||||||
|
Your managed jobs must extend `AbstractQuartzManagerJob` so Quartz Manager can expose them as eligible jobs and stream their execution logs/progress to the UI.
|
||||||
|
|
||||||
|
If you also want the browser dashboard, see [Add The UI](#add-the-ui).
|
||||||
|
|
||||||
|
### 2. Add A New Scheduler To Your App
|
||||||
|
|
||||||
|
Use this path when your Spring Boot application does not have Quartz yet and you want to add a scheduler managed by Quartz Manager.
|
||||||
|
|
||||||
|
The easiest setup is to let Quartz Manager import, initialize, and manage a Quartz Scheduler for you. Import the API starter, create jobs extending `AbstractQuartzManagerJob`, configure the package that contains your jobs, and use the REST API or UI to create jobs and triggers.
|
||||||
|
|
||||||
|
You can later add optional modules for the embedded UI, JWT security, and PostgreSQL persistence.
|
||||||
|
|
||||||
|
If you also want the browser dashboard, see [Add The UI](#add-the-ui).
|
||||||
|
|
||||||
|
### 3. Run Quartz Manager As A Standalone App
|
||||||
|
|
||||||
|
Use this path when you want a standalone scheduler web application instead of embedding Quartz Manager into an existing product.
|
||||||
|
|
||||||
|
The `quartz-manager-web-showcase` application imports Quartz Manager API, UI, and security modules and runs with an embedded Quartz scheduler. It is useful as a ready-to-run management console, as a demo, and as a reference application.
|
||||||
|
|
||||||
|
Even in standalone mode, the jobs managed by Quartz Manager must extend `AbstractQuartzManagerJob`.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- REST API for scheduler, job, trigger, and calendar management.
|
||||||
|
- Embeddable management UI provided as a webjar: import it as a Maven dependency and open `/quartz-manager-ui` in the browser.
|
||||||
|
- Scheduler commands: start, standby, resume, and shutdown.
|
||||||
|
- Job management: list eligible job classes, create stored jobs, update jobs, delete jobs, and trigger jobs manually.
|
||||||
|
- Trigger management: create, inspect, reschedule, pause, resume, and unschedule triggers.
|
||||||
|
- Trigger types: simple, cron, daily time interval, and calendar interval.
|
||||||
|
- Calendar management: annual, cron, daily, holiday, monthly, and weekly calendars.
|
||||||
|
- Misfire handling for supported trigger types.
|
||||||
|
- WebSocket updates for job execution progress and logs.
|
||||||
|
- Optional OpenAPI/Swagger UI documentation.
|
||||||
|
- Optional JWT-based security with in-memory users.
|
||||||
|
- Optional PostgreSQL persistence using Quartz JDBC job store and Liquibase-managed schema creation.
|
||||||
|
|
||||||
|
In dependency snippets, replace `VERSION` with the version you want to use.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 21+
|
||||||
|
- Spring Boot 4.x
|
||||||
|
- Maven 3.9+
|
||||||
|
- Node.js and npm only if you build the frontend locally
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
| Module | Required | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `quartz-manager-starter-api` | Required | REST API, managed scheduler integration, jobs, triggers, calendars, OpenAPI support, and WebSocket updates |
|
||||||
|
| `quartz-manager-starter-ui` | Optional | Embeddable management UI provided as a webjar |
|
||||||
|
| `quartz-manager-starter-security` | Optional | JWT authentication for Quartz Manager API and UI |
|
||||||
|
| `quartz-manager-starter-persistence` | Optional | PostgreSQL-backed Quartz persistence and Liquibase schema setup |
|
||||||
|
| `quartz-manager-web-showcase` | Optional | Standalone/demo Spring Boot application using the Quartz Manager modules |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Path 1: Existing Spring Boot App
|
||||||
|
|
||||||
|
Add the API starter:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||||
|
<artifactId>quartz-manager-starter-api</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
</dependency>
|
||||||
```
|
```
|
||||||
#CLONE REPOSITORY
|
|
||||||
|
Create jobs by extending `AbstractQuartzManagerJob`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import it.fabioformosa.quartzmanager.api.jobs.AbstractQuartzManagerJob;
|
||||||
|
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
|
||||||
|
import org.quartz.JobExecutionContext;
|
||||||
|
|
||||||
|
public class SampleJob extends AbstractQuartzManagerJob {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogRecord doIt(JobExecutionContext context) {
|
||||||
|
return new LogRecord(LogRecord.LogType.INFO, "Hello from Quartz Manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure job discovery:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
quartz-manager.jobClassPackages=com.example.jobs
|
||||||
|
quartz-manager.oas.enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Quartz Manager creates a dedicated scheduler named `quartz-manager-scheduler`. If your app already has another Quartz scheduler, both can coexist.
|
||||||
|
|
||||||
|
Advanced existing-scheduler setup:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
quartz-manager.quartz.enabled=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Then provide a `Scheduler` bean named `quartzManagerScheduler`. This is the current integration point; a more explicit existing-scheduler mode is planned.
|
||||||
|
|
||||||
|
To add the browser dashboard to your application, see [Add The UI](#add-the-ui).
|
||||||
|
|
||||||
|
### Path 2: New Scheduler In Your App
|
||||||
|
|
||||||
|
Use the same API starter setup as Path 1, then let Quartz Manager create its managed scheduler.
|
||||||
|
|
||||||
|
Create one or more jobs extending `AbstractQuartzManagerJob`, configure `quartz-manager.jobClassPackages`, then create stored jobs and triggers through the REST API, Swagger UI, or the dashboard.
|
||||||
|
|
||||||
|
Default managed Quartz properties:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
org.quartz.scheduler.instanceName=quartz-manager-scheduler
|
||||||
|
org.quartz.threadPool.threadCount=1
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize the managed scheduler, add `managed-quartz.properties` to your classpath.
|
||||||
|
|
||||||
|
To add the browser dashboard to your application, see [Add The UI](#add-the-ui).
|
||||||
|
|
||||||
|
### Path 3: Standalone Quartz Manager App
|
||||||
|
|
||||||
|
Run the standalone showcase application when you want Quartz Manager as a ready-to-use scheduler web app.
|
||||||
|
|
||||||
|
```bash
|
||||||
git clone https://github.com/fabioformosa/quartz-manager.git
|
git clone https://github.com/fabioformosa/quartz-manager.git
|
||||||
|
cd quartz-manager/quartz-manager-parent
|
||||||
# START QUARTZ-MANAGER-BACKEND
|
mvn install -Pbuild-webjar
|
||||||
cd quartz-manager/quartz-manager-backend
|
cd quartz-manager-web-showcase
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
|
|
||||||
# START QUARTZ-MANAGER-FRONTEND
|
|
||||||
cd quartz-manager/quartz-manager-backend
|
|
||||||
npm install
|
|
||||||
npm start
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Open browser at [http://localhost:4200](http://localhost:4200)
|
Open the dashboard:
|
||||||
1. Log in with **default credentials**: `admin/admin`
|
|
||||||
|
|
||||||
If you are not confident with maven CLI, you can start it by your IDE. For more details [spring boot ref.](http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-running-your-application.html)
|
```text
|
||||||
|
http://localhost:8080/quartz-manager-ui/index.html
|
||||||
|
```
|
||||||
|
|
||||||
## HOW TO RUN YOUR SCHEDULED JOB
|
Open Swagger UI when OpenAPI is enabled:
|
||||||
By default, quartz-manager executes the dummy job that logs "hello world!".
|
|
||||||
Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) with yours. Follow these steps:
|
|
||||||
|
|
||||||
1. Let extend the super class `it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob`
|
```text
|
||||||
1. Change the scheduler settings, providing the class name of your job. Open class `it.fabioformosa.quartzmanager.configuration.SchedulerConfig` and for the method `jobDetail` replace SampleJob.class with YourJob.class
|
http://localhost:8080/swagger-ui.html
|
||||||
|
```
|
||||||
|
|
||||||
## HOW TO CHANGE SETTINGS
|
Default showcase credentials:
|
||||||
* Num of Threads: `/quartz-manager/src/main/resources/quartz.properties`
|
|
||||||
* Credentials: `it.fabioformosa.quartzmanager.configuration.WebSecurityConfig`
|
|
||||||
* quartz-manager backend context path (default `/quartz-manager`) and port (default `8080`): `/quartz-manager/src/main/resources/application.properties`
|
|
||||||
|
|
||||||
## Tech Overview
|
```text
|
||||||
|
admin / admin
|
||||||
|
```
|
||||||
|
|
||||||
**Backend Stack** Java 8, Spring Boot 2.1.4 (Spring MVC 5.1.6, Spring Security 5.1.5, Spring AOP 5.1.6), Quartz Scheduler 2.3.1
|
To plug in your own jobs today, add your job classes inside the cloned repository, rebuild the standalone application, and configure `quartz-manager.jobClassPackages` to include their package.
|
||||||
|
|
||||||
**Application Server** Tomcat (embedded)
|
A Docker-based standalone distribution is planned. It will provide a supported mechanism to attach external job classes without modifying the cloned repository.
|
||||||
|
|
||||||
**Frontend** Angular 7.2.13, Web-Socket (stompjs 2.3.3)
|
## Add The UI
|
||||||
|
|
||||||
**Style** angular material, FontAwesome 5
|
Add the UI starter when you want the embedded management panel in your Spring Boot app:
|
||||||
|
|
||||||
From quartz manager ver 2.x.x, the new structure of project is:
|
```xml
|
||||||
* REST backend (java based, using [http://www.quartz-scheduler.org/](http://www.quartz-scheduler.org/)
|
<dependency>
|
||||||
* Single Page Application frontend (angular 7)
|
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||||
|
<artifactId>quartz-manager-starter-ui</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
(The previous version of quartz manager was a monolithic backend that provided also frontend developed with angularjs 1.6.x. You can find it at the branch 1.x.x)
|
The UI is served from:
|
||||||
|
|
||||||
## Credits
|
```text
|
||||||
|
http://localhost:8080/quartz-manager-ui/index.html
|
||||||
|
```
|
||||||
|
|
||||||
* this project has been created from [angular-spring-starter](https://github.com/bfwg/angular-spring-starter)
|
## REST API
|
||||||
|
|
||||||
|
Quartz Manager exposes its API under `/quartz-manager`.
|
||||||
|
|
||||||
|
| Area | Endpoints |
|
||||||
|
| --- | --- |
|
||||||
|
| Scheduler | `GET /quartz-manager/scheduler`, `POST /quartz-manager/scheduler/start`, `POST /quartz-manager/scheduler/standby`, `POST /quartz-manager/scheduler/resume`, `POST /quartz-manager/scheduler/shutdown` |
|
||||||
|
| Job classes | `GET /quartz-manager/job-classes` |
|
||||||
|
| Jobs | `GET /quartz-manager/jobs`, `POST /quartz-manager/jobs/{group}/{name}`, `PUT /quartz-manager/jobs/{group}/{name}`, `POST /quartz-manager/jobs/{group}/{name}/trigger`, `DELETE /quartz-manager/jobs/{group}/{name}` |
|
||||||
|
| Triggers | `GET /quartz-manager/triggers`, `POST /quartz-manager/triggers/{group}/{name}`, `PUT /quartz-manager/triggers/{group}/{name}`, `POST /quartz-manager/triggers/{group}/{name}/pause`, `POST /quartz-manager/triggers/{group}/{name}/resume`, `DELETE /quartz-manager/triggers/{group}/{name}` |
|
||||||
|
| Calendars | `GET /quartz-manager/calendars`, `POST /quartz-manager/calendars/{name}`, `PUT /quartz-manager/calendars/{name}`, `DELETE /quartz-manager/calendars/{name}`, `POST /quartz-manager/calendars/{name}/included-time-test` |
|
||||||
|
|
||||||
|
Enable OpenAPI and Swagger UI with:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
quartz-manager.oas.enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:8080/swagger-ui.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Add the security starter when you want Quartz Manager API and UI protected by JWT authentication:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||||
|
<artifactId>quartz-manager-starter-security</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
quartz-manager:
|
||||||
|
security:
|
||||||
|
jwt:
|
||||||
|
secret: "change-me"
|
||||||
|
expiration-in-sec: 28800
|
||||||
|
header-strategy:
|
||||||
|
enabled: true
|
||||||
|
header: Authorization
|
||||||
|
cookie-strategy:
|
||||||
|
enabled: false
|
||||||
|
cookie: AUTH-TOKEN
|
||||||
|
accounts:
|
||||||
|
in-memory:
|
||||||
|
enabled: true
|
||||||
|
users:
|
||||||
|
- username: admin
|
||||||
|
password: admin
|
||||||
|
roles:
|
||||||
|
- ADMIN
|
||||||
|
```
|
||||||
|
|
||||||
|
Security is applied to `/quartz-manager/**`. The UI webjar path is ignored by the security filter chain, while API calls require authentication.
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
|
||||||
|
By default, Quartz Manager uses Quartz's in-memory job store. Scheduling data is lost when the application stops.
|
||||||
|
|
||||||
|
Add the persistence starter when you want Quartz Manager's managed scheduler to use PostgreSQL-backed Quartz persistence:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||||
|
<artifactId>quartz-manager-starter-persistence</artifactId>
|
||||||
|
<version>VERSION</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the Quartz Manager datasource:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
quartz-manager:
|
||||||
|
persistence:
|
||||||
|
quartz:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/quartzmanager
|
||||||
|
user: quartzmanager
|
||||||
|
password: quartzmanager
|
||||||
|
```
|
||||||
|
|
||||||
|
The persistence module configures Quartz `JobStoreTX`, uses the PostgreSQL delegate, and creates the required Quartz tables through Liquibase.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Example integrations are available in [quartz-manager-use-cases](https://github.com/fabioformosa/quartz-manager-use-cases).
|
||||||
|
|
||||||
|
The use cases cover simple Spring applications, secured and unsecured setups, existing application security, existing Quartz scenarios, and persistence.
|
||||||
|
|
||||||
|
## Current Limitations
|
||||||
|
|
||||||
|
- Quartz Manager creates and manages its own scheduler by default. Automatic discovery and first-class management of an arbitrary existing scheduler is not yet supported.
|
||||||
|
- Existing applications that want Quartz Manager to manage a pre-existing scheduler must currently provide it as a bean named `quartzManagerScheduler` and disable Quartz Manager's default scheduler creation.
|
||||||
|
- Persistence currently targets PostgreSQL.
|
||||||
|
- Cluster mode is not currently documented as a supported production mode.
|
||||||
|
- Managed jobs must extend `AbstractQuartzManagerJob` to be eligible for job discovery and UI log/progress streaming.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
The next priorities are tracked in the [project roadmap](https://github.com/users/fabioformosa/projects/1).
|
||||||
|
|
||||||
|
Planned improvements include:
|
||||||
|
|
||||||
|
- First-class support for managing an existing Quartz Scheduler instance.
|
||||||
|
- Cluster mode support.
|
||||||
|
- Additional persistence targets beyond PostgreSQL.
|
||||||
|
- OAuth2 client support.
|
||||||
|
- Continued UI improvements.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This repository contains the backend modules and the frontend application.
|
||||||
|
|
||||||
|
For local development, repository structure, build commands, and contribution details, see [quartz-manager-parent/README.md](https://github.com/fabioformosa/quartz-manager/blob/develop/quartz-manager-parent/README.md).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome. Open an issue to discuss bugs, questions, or feature proposals before starting larger changes.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Quartz Manager is released under the [Apache License 2.0](LICENSE).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If Quartz Manager is useful to you, consider starring the repository to support the project.
|
||||||
|
|||||||
45
cloudbuild.yaml
Normal file
45
cloudbuild.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
substitutions:
|
||||||
|
_REGION: europe-west8
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# Step 1: Google Cloud Build - Docker build&push
|
||||||
|
- name: 'gcr.io/k8s-skaffold/skaffold'
|
||||||
|
entrypoint: 'sh'
|
||||||
|
args:
|
||||||
|
- -xe
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
# Build and push images
|
||||||
|
sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml
|
||||||
|
sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml
|
||||||
|
skaffold build --file-output=/workspace/artifacts.json \
|
||||||
|
--default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \
|
||||||
|
--push=true
|
||||||
|
|
||||||
|
# Step 2: Google Cloud Deploy - deploy
|
||||||
|
- name: 'google/cloud-sdk:latest'
|
||||||
|
entrypoint: 'sh'
|
||||||
|
args:
|
||||||
|
- -xe
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml
|
||||||
|
gcloud config set deploy/region ${_REGION}
|
||||||
|
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml
|
||||||
|
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml
|
||||||
|
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml
|
||||||
|
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml
|
||||||
|
gcloud deploy releases create rel-${SHORT_SHA} \
|
||||||
|
--delivery-pipeline quartz-manager-pipeline \
|
||||||
|
--description "$(git log -1 --pretty='%s')" \
|
||||||
|
--build-artifacts /workspace/artifacts.json \
|
||||||
|
--verbosity=debug \
|
||||||
|
--annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA"
|
||||||
|
artifacts:
|
||||||
|
objects:
|
||||||
|
location: 'gs://$PROJECT_ID-gcdeploy-artifacts/'
|
||||||
|
paths:
|
||||||
|
- '/workspace/artifacts.json'
|
||||||
|
|
||||||
|
options:
|
||||||
|
logging: CLOUD_LOGGING_ONLY
|
||||||
BIN
docs/assets/quartz-manager-dashboard.png
Normal file
BIN
docs/assets/quartz-manager-dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
docs/assets/quartz-manager-openapidoc.png
Normal file
BIN
docs/assets/quartz-manager-openapidoc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,198 +0,0 @@
|
|||||||
<?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">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<groupId>it.fabioformosa</groupId>
|
|
||||||
<artifactId>quartz-manager</artifactId>
|
|
||||||
<version>2.1.1-SNAPSHOT</version>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
|
|
||||||
<name>quartz-manager</name>
|
|
||||||
<description>Manager Panel for Quartz Scheduler</description>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-parent</artifactId>
|
|
||||||
<version>2.1.4.RELEASE</version>
|
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
|
||||||
<java.version>1.8</java.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-mail</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-messaging</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-tx</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt</artifactId>
|
|
||||||
<version>0.9.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>joda-time</groupId>
|
|
||||||
<artifactId>joda-time</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-annotations</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.h2database</groupId>
|
|
||||||
<artifactId>h2</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.codehaus.groovy</groupId>
|
|
||||||
<artifactId>groovy</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.sourceforge.nekohtml</groupId>
|
|
||||||
<artifactId>nekohtml</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.rest-assured</groupId>
|
|
||||||
<artifactId>spring-mock-mvc</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
|
||||||
<artifactId>quartz</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.commons</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>1.3.2</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Reactor -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor</groupId>
|
|
||||||
<artifactId>reactor-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor</groupId>
|
|
||||||
<artifactId>reactor-net</artifactId>
|
|
||||||
<version>2.0.8.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor.spring</groupId>
|
|
||||||
<artifactId>reactor-spring-context</artifactId>
|
|
||||||
<version>2.0.7.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.netty</groupId>
|
|
||||||
<artifactId>netty-all</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.yaml</groupId>
|
|
||||||
<artifactId>snakeyaml</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<pluginManagement>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>repackage</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.8.0</version>
|
|
||||||
<configuration>
|
|
||||||
<source>1.8</source>
|
|
||||||
<target>1.8</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<!-- <plugin> -->
|
|
||||||
<!-- <groupId>org.codehaus.gmavenplus</groupId> -->
|
|
||||||
<!-- <artifactId>gmavenplus-plugin</artifactId> -->
|
|
||||||
<!-- <version>1.5</version> -->
|
|
||||||
<!-- <executions> -->
|
|
||||||
<!-- <execution> -->
|
|
||||||
<!-- <goals> -->
|
|
||||||
<!-- <goal>addSources</goal> -->
|
|
||||||
<!-- <goal>addTestSources</goal> -->
|
|
||||||
<!-- <goal>generateStubs</goal> -->
|
|
||||||
<!-- <goal>compile</goal> -->
|
|
||||||
<!-- <goal>testGenerateStubs</goal> -->
|
|
||||||
<!-- <goal>testCompile</goal> -->
|
|
||||||
<!-- <goal>removeStubs</goal> -->
|
|
||||||
<!-- <goal>removeTestStubs</goal> -->
|
|
||||||
<!-- </goals> -->
|
|
||||||
<!-- </execution> -->
|
|
||||||
<!-- </executions> -->
|
|
||||||
<!-- </plugin> -->
|
|
||||||
</plugins>
|
|
||||||
</pluginManagement>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package it.fabioformosa;
|
|
||||||
|
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
|
||||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
|
||||||
|
|
||||||
public class ServletInitializer extends SpringBootServletInitializer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
|
||||||
return application.sources(QuartManagerApplication.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.aspects;
|
|
||||||
|
|
||||||
import org.quartz.SchedulerException;
|
|
||||||
|
|
||||||
public interface ProgressUpdater {
|
|
||||||
|
|
||||||
void update() throws SchedulerException;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.aspects;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.quartz.DailyTimeIntervalTrigger;
|
|
||||||
import org.quartz.Scheduler;
|
|
||||||
import org.quartz.SchedulerException;
|
|
||||||
import org.quartz.SimpleTrigger;
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.dto.TriggerProgress;
|
|
||||||
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
|
|
||||||
|
|
||||||
//@Aspect
|
|
||||||
@Component
|
|
||||||
public class ProgressUpdaterImpl implements ProgressUpdater {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SimpMessageSendingOperations messagingTemplate;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private Scheduler scheduler;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TriggerMonitor triggerMonitor;
|
|
||||||
|
|
||||||
//@AfterReturning("execution(* logAndSend(..))")
|
|
||||||
// @Override
|
|
||||||
// public void updateProgress(JoinPoint joinPoint) {
|
|
||||||
// log.info("PROGRESS UPDATE!!!");
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update() throws SchedulerException {
|
|
||||||
TriggerProgress progress = new TriggerProgress();
|
|
||||||
|
|
||||||
Trigger trigger = scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
|
|
||||||
progress.setFinalFireTime(trigger.getFinalFireTime());
|
|
||||||
progress.setNextFireTime(trigger.getNextFireTime());
|
|
||||||
progress.setPreviousFireTime(trigger.getPreviousFireTime());
|
|
||||||
|
|
||||||
int timesTriggered = 0;
|
|
||||||
int repeatCount = 0;
|
|
||||||
|
|
||||||
if (trigger instanceof SimpleTrigger) {
|
|
||||||
SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
|
|
||||||
timesTriggered = simpleTrigger.getTimesTriggered();
|
|
||||||
repeatCount = simpleTrigger.getRepeatCount();
|
|
||||||
} else if (trigger instanceof DailyTimeIntervalTrigger) {
|
|
||||||
DailyTimeIntervalTrigger dailyTrigger = (DailyTimeIntervalTrigger) trigger;
|
|
||||||
timesTriggered = dailyTrigger.getTimesTriggered();
|
|
||||||
repeatCount = dailyTrigger.getRepeatCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trigger jobTrigger = triggerMonitor.getTrigger();
|
|
||||||
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
|
|
||||||
progress.setJobKey(jobTrigger.getJobKey().getName());
|
|
||||||
progress.setJobClass(jobTrigger.getClass().getSimpleName());
|
|
||||||
progress.setTimesTriggered(timesTriggered);
|
|
||||||
progress.setRepeatCount(repeatCount + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
messagingTemplate.convertAndSend("/topic/progress", progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.quartz.Job;
|
|
||||||
import org.quartz.JobDetail;
|
|
||||||
import org.quartz.SimpleTrigger;
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
import org.quartz.spi.JobFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
|
|
||||||
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
|
|
||||||
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.myjobs.SampleJob;
|
|
||||||
import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory;
|
|
||||||
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
|
|
||||||
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitorImpl;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnProperty(name = "quartz.enabled")
|
|
||||||
public class SchedulerConfig {
|
|
||||||
|
|
||||||
private static JobDetailFactoryBean createJobDetail(Class<? extends Job> jobClass) {
|
|
||||||
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
|
|
||||||
factoryBean.setJobClass(jobClass);
|
|
||||||
factoryBean.setDurability(false);
|
|
||||||
return factoryBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs,
|
|
||||||
int repeatCount) {
|
|
||||||
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
|
|
||||||
factoryBean.setJobDetail(jobDetail);
|
|
||||||
factoryBean.setStartDelay(0L);
|
|
||||||
factoryBean.setRepeatInterval(pollFrequencyMs);
|
|
||||||
factoryBean.setRepeatCount(repeatCount);
|
|
||||||
factoryBean
|
|
||||||
.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT);// in case of misfire, ignore all missed triggers and continue
|
|
||||||
return factoryBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "triggerMonitor")
|
|
||||||
public TriggerMonitor createTriggerMonitor(@Qualifier("jobTrigger") Trigger trigger) {
|
|
||||||
TriggerMonitor triggerMonitor = new TriggerMonitorImpl();
|
|
||||||
triggerMonitor.setTrigger(trigger);
|
|
||||||
return triggerMonitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public JobDetailFactoryBean jobDetail() {
|
|
||||||
return createJobDetail(SampleJob.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public JobFactory jobFactory(ApplicationContext applicationContext) {
|
|
||||||
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
|
|
||||||
jobFactory.setApplicationContext(applicationContext);
|
|
||||||
return jobFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Properties quartzProperties() throws IOException {
|
|
||||||
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
|
|
||||||
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
|
|
||||||
propertiesFactoryBean.afterPropertiesSet();
|
|
||||||
return propertiesFactoryBean.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "jobTrigger")
|
|
||||||
public SimpleTriggerFactoryBean sampleJobTrigger(@Qualifier("jobDetail") JobDetail jobDetail,
|
|
||||||
@Value("${job.frequency}") long frequency, @Value("${job.repeatCount}") int repeatCount) {
|
|
||||||
return createTrigger(jobDetail, frequency, repeatCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "scheduler")
|
|
||||||
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,
|
|
||||||
@Qualifier("jobTrigger") Trigger sampleJobTrigger) throws IOException {
|
|
||||||
SchedulerFactoryBean factory = new SchedulerFactoryBean();
|
|
||||||
factory.setJobFactory(jobFactory);
|
|
||||||
factory.setQuartzProperties(quartzProperties());
|
|
||||||
factory.setTriggers(sampleJobTrigger);
|
|
||||||
factory.setAutoStartup(false);
|
|
||||||
return factory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.ComboEntryPoint;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.AuthenticationFailureHandler;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.AuthenticationSuccessHandler;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
|
||||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Order(1)
|
|
||||||
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
http.csrf().disable() //
|
|
||||||
.antMatcher("/notifications").authorizeRequests().anyRequest().hasAnyRole("ADMIN").and()
|
|
||||||
.httpBasic();
|
|
||||||
|
|
||||||
// http.antMatcher("/logs/**").authorizeRequests().anyRequest()
|
|
||||||
// .permitAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Order(2)
|
|
||||||
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ComboEntryPoint comboEntryPoint;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(WebSecurity web) throws Exception {
|
|
||||||
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup").and() //
|
|
||||||
http.cors().and().csrf().disable()
|
|
||||||
.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and()//
|
|
||||||
.authorizeRequests().anyRequest().authenticated().and()//
|
|
||||||
.formLogin().loginPage("/api/login").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).and().logout()
|
|
||||||
.logoutRequestMatcher(new AntPathRequestMatcher("/api/logout"))
|
|
||||||
.logoutSuccessUrl("/manager");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Value("${quartz-manager.account.user}")
|
|
||||||
private String adminUser;
|
|
||||||
|
|
||||||
@Value("${quartz-manager.account.pwd}")
|
|
||||||
private String adminPwd;
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
|
||||||
auth.inMemoryAuthentication().withUser(adminUser).password(encoder.encode(adminPwd)).roles("ADMIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.ComboEntryPoint;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.AuthenticationFailureHandler;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.AuthenticationSuccessHandler;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.LogoutSuccess;
|
|
||||||
import it.fabioformosa.quartzmanager.security.auth.TokenAuthenticationFilter;
|
|
||||||
import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT Temporary disabled
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//@Configuration
|
|
||||||
//@EnableGlobalMethodSecurity(prePostEnabled = true)
|
|
||||||
public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
@Value("${jwt.cookie}")
|
|
||||||
private String TOKEN_COOKIE;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private CustomUserDetailsService jwtUserDetailsService;
|
|
||||||
|
|
||||||
// @Autowired
|
|
||||||
// private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
|
|
||||||
@Resource
|
|
||||||
private ComboEntryPoint comboEntryPoint;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private LogoutSuccess logoutSuccess;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Override
|
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
|
||||||
return super.authenticationManagerBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)
|
|
||||||
throws Exception {
|
|
||||||
authenticationManagerBuilder.userDetailsService(jwtUserDetailsService)
|
|
||||||
.passwordEncoder(passwordEncoder());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public TokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
|
|
||||||
return new TokenAuthenticationFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup")
|
|
||||||
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
|
|
||||||
http.cors().and().csrf().disable()
|
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
|
||||||
.exceptionHandling().authenticationEntryPoint(comboEntryPoint).and()
|
|
||||||
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class)
|
|
||||||
.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/api/login")
|
|
||||||
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler)
|
|
||||||
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/api/logout"))
|
|
||||||
.logoutSuccessHandler(logoutSuccess).deleteCookies(TOKEN_COOKIE);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
CorsConfigurationSource corsConfigurationSource() {
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
||||||
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
|
||||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
|
||||||
config.enableSimpleBroker("/topic");
|
|
||||||
config.setApplicationDestinationPrefixes("/job");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry.addEndpoint("/logs").setAllowedOrigins("/**").withSockJS();
|
|
||||||
registry.addEndpoint("/progress").setAllowedOrigins("/**").withSockJS();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.controllers;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.TokenHelper;
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.UserTokenState;
|
|
||||||
import it.fabioformosa.quartzmanager.security.service.impl.CustomUserDetailsService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT Temporary disabled
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//@RestController
|
|
||||||
//@RequestMapping( value = "/api", produces = MediaType.APPLICATION_JSON_VALUE )
|
|
||||||
public class AuthenticationController {
|
|
||||||
|
|
||||||
static class PasswordChanger {
|
|
||||||
public String oldPassword;
|
|
||||||
public String newPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private CustomUserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
TokenHelper tokenHelper;
|
|
||||||
|
|
||||||
@Value("${jwt.expires_in}")
|
|
||||||
private int EXPIRES_IN;
|
|
||||||
|
|
||||||
@Value("${jwt.cookie}")
|
|
||||||
private String TOKEN_COOKIE;
|
|
||||||
|
|
||||||
@RequestMapping(value = "/changePassword", method = RequestMethod.POST)
|
|
||||||
@PreAuthorize("hasRole('USER')")
|
|
||||||
public ResponseEntity<?> changePassword(@RequestBody PasswordChanger passwordChanger) {
|
|
||||||
userDetailsService.changePassword(passwordChanger.oldPassword, passwordChanger.newPassword);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
result.put( "result", "success" );
|
|
||||||
return ResponseEntity.accepted().body(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(value = "/refresh", method = RequestMethod.GET)
|
|
||||||
public ResponseEntity<?> refreshAuthenticationToken(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
|
|
||||||
String authToken = tokenHelper.getToken( request );
|
|
||||||
if (authToken != null && tokenHelper.canTokenBeRefreshed(authToken)) {
|
|
||||||
// TODO check user password last update
|
|
||||||
String refreshedToken = tokenHelper.refreshToken(authToken);
|
|
||||||
|
|
||||||
Cookie authCookie = new Cookie( TOKEN_COOKIE, refreshedToken );
|
|
||||||
authCookie.setPath( "/" );
|
|
||||||
authCookie.setHttpOnly( true );
|
|
||||||
authCookie.setMaxAge( EXPIRES_IN );
|
|
||||||
// Add cookie to response
|
|
||||||
response.addCookie( authCookie );
|
|
||||||
|
|
||||||
UserTokenState userTokenState = new UserTokenState(refreshedToken, EXPIRES_IN);
|
|
||||||
return ResponseEntity.ok(userTokenState);
|
|
||||||
} else {
|
|
||||||
UserTokenState userTokenState = new UserTokenState();
|
|
||||||
return ResponseEntity.accepted().body(userTokenState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.controllers;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.quartz.Scheduler;
|
|
||||||
import org.quartz.SchedulerException;
|
|
||||||
import org.quartz.SimpleScheduleBuilder;
|
|
||||||
import org.quartz.SimpleTrigger;
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
import org.quartz.TriggerBuilder;
|
|
||||||
import org.quartz.impl.triggers.SimpleTriggerImpl;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
|
|
||||||
import it.fabioformosa.quartzmanager.dto.TriggerProgress;
|
|
||||||
import it.fabioformosa.quartzmanager.enums.SchedulerStates;
|
|
||||||
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This controller provides scheduler info about config and status. It provides
|
|
||||||
* also methods to set new config and start/stop/resume the scheduler.
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/scheduler")
|
|
||||||
public class SchedulerController {
|
|
||||||
|
|
||||||
private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
|
|
||||||
private static final int SEC_IN_A_DAY = 60 * 60 * 24;
|
|
||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger(SchedulerController.class);
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private Scheduler scheduler;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TriggerMonitor triggerMonitor;
|
|
||||||
|
|
||||||
@RequestMapping(value = "/config", method = RequestMethod.GET)
|
|
||||||
public SchedulerConfigParam getConfig() {
|
|
||||||
log.debug("SCHEDULER - GET CONFIG params");
|
|
||||||
SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger();
|
|
||||||
|
|
||||||
int maxCount = simpleTrigger.getRepeatCount() + 1;
|
|
||||||
long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());
|
|
||||||
|
|
||||||
return new SchedulerConfigParam(triggersPerDay, maxCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/progress")
|
|
||||||
public TriggerProgress getProgressInfo() throws SchedulerException {
|
|
||||||
log.trace("SCHEDULER - GET PROGRESS INFO");
|
|
||||||
TriggerProgress progress = new TriggerProgress();
|
|
||||||
|
|
||||||
SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
|
|
||||||
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
|
|
||||||
progress.setJobKey(jobTrigger.getJobKey().getName());
|
|
||||||
progress.setJobClass(jobTrigger.getClass().getSimpleName());
|
|
||||||
progress.setTimesTriggered(jobTrigger.getTimesTriggered());
|
|
||||||
progress.setRepeatCount(jobTrigger.getRepeatCount());
|
|
||||||
progress.setFinalFireTime(jobTrigger.getFinalFireTime());
|
|
||||||
progress.setNextFireTime(jobTrigger.getNextFireTime());
|
|
||||||
progress.setPreviousFireTime(jobTrigger.getPreviousFireTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(produces = "application/json")
|
|
||||||
public Map<String, String> getStatus() throws SchedulerException {
|
|
||||||
log.trace("SCHEDULER - GET STATUS");
|
|
||||||
String schedulerState = "";
|
|
||||||
if (scheduler.isShutdown() || !scheduler.isStarted())
|
|
||||||
schedulerState = SchedulerStates.STOPPED.toString();
|
|
||||||
else if (scheduler.isStarted() && scheduler.isInStandbyMode())
|
|
||||||
schedulerState = SchedulerStates.PAUSED.toString();
|
|
||||||
else
|
|
||||||
schedulerState = SchedulerStates.RUNNING.toString();
|
|
||||||
return Collections.singletonMap("data", schedulerState.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/pause")
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
public void pause() throws SchedulerException {
|
|
||||||
log.info("SCHEDULER - PAUSE COMMAND");
|
|
||||||
scheduler.standby();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(value = "/config", method = RequestMethod.POST)
|
|
||||||
public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException {
|
|
||||||
log.info("SCHEDULER - NEW CONFIG {}", config);
|
|
||||||
SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger();
|
|
||||||
|
|
||||||
TriggerBuilder<SimpleTrigger> triggerBuilder = trigger.getTriggerBuilder();
|
|
||||||
|
|
||||||
int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
|
|
||||||
Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
|
|
||||||
.withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build();
|
|
||||||
|
|
||||||
scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger);
|
|
||||||
triggerMonitor.setTrigger(newTrigger);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/resume")
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
public void resume() throws SchedulerException {
|
|
||||||
log.info("SCHEDULER - RESUME COMMAND");
|
|
||||||
scheduler.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/run")
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
public void run() throws SchedulerException {
|
|
||||||
log.info("SCHEDULER - START COMMAND");
|
|
||||||
scheduler.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
@RequestMapping("/stop")
|
|
||||||
public void stop() throws SchedulerException {
|
|
||||||
log.info("SCHEDULER - STOP COMMAND");
|
|
||||||
scheduler.shutdown(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
|
|
||||||
return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
|
|
||||||
return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private int fromTriggerPerDayToSecInterval(long triggerPerDay) {
|
|
||||||
return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.controllers;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/session")
|
|
||||||
public class SessionController {
|
|
||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger(SessionController.class);
|
|
||||||
|
|
||||||
@RequestMapping("/invalidate")
|
|
||||||
@PreAuthorize("hasAuthority('ADMIN')")
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
public void invalidateSession(HttpSession session) {
|
|
||||||
session.invalidate();
|
|
||||||
log.info("Invalidated current session!");
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/refresh")
|
|
||||||
@PreAuthorize("hasAuthority('ADMIN')")
|
|
||||||
public HttpEntity<Void> refreshSession(HttpSession session) {
|
|
||||||
return new ResponseEntity<>(HttpStatus.OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.controllers;
|
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
public class UserController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT Temporary disabled
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @Autowired
|
|
||||||
// private UserService userService;
|
|
||||||
|
|
||||||
|
|
||||||
// @RequestMapping(method = POST, value = "/signup")
|
|
||||||
// public ResponseEntity<?> addUser(@RequestBody UserRequest userRequest,
|
|
||||||
// UriComponentsBuilder ucBuilder) {
|
|
||||||
//
|
|
||||||
// User existUser = this.userService.findByUsername(userRequest.getUsername());
|
|
||||||
// if (existUser != null)
|
|
||||||
// throw new ResourceConflictException(userRequest.getId(), "Username already exists");
|
|
||||||
// User user = this.userService.save(userRequest);
|
|
||||||
// HttpHeaders headers = new HttpHeaders();
|
|
||||||
// headers.setLocation(ucBuilder.path("/api/user/{userId}").buildAndExpand(user.getId()).toUri());
|
|
||||||
// return new ResponseEntity<>(user, HttpStatus.CREATED);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @RequestMapping(method = GET, value = "/user/all")
|
|
||||||
// public List<User> loadAll() {
|
|
||||||
// return this.userService.findAll();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @RequestMapping(method = GET, value = "/user/{userId}")
|
|
||||||
// public User loadById(@PathVariable Long userId) {
|
|
||||||
// return this.userService.findById(userId);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// @RequestMapping(method = GET, value = "/user/reset-credentials")
|
|
||||||
// public ResponseEntity<Map> resetCredentials() {
|
|
||||||
// this.userService.resetCredentials();
|
|
||||||
// Map<String, String> result = new HashMap<>();
|
|
||||||
// result.put("result", "success");
|
|
||||||
// return ResponseEntity.accepted().body(result);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We are not using userService.findByUsername here(we could), so it is good that we are making
|
|
||||||
* sure that the user has role "ROLE_USER" to access this endpoint.
|
|
||||||
*/
|
|
||||||
// @RequestMapping("/whoami")
|
|
||||||
// // @PreAuthorize("hasRole('USER')")
|
|
||||||
// public User user() {
|
|
||||||
// return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
|
||||||
// }
|
|
||||||
|
|
||||||
@RequestMapping("/whoami")
|
|
||||||
@PreAuthorize("isAuthenticated()")
|
|
||||||
public Object user() {
|
|
||||||
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.controllers;
|
|
||||||
|
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
|
||||||
import org.springframework.messaging.handler.annotation.SendTo;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
public class WebsocketController {
|
|
||||||
|
|
||||||
@MessageMapping({ "/logs", "/progress" })
|
|
||||||
@SendTo("/topic/logs")
|
|
||||||
public String subscribe() throws Exception {
|
|
||||||
return "subscribed";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.dto;
|
|
||||||
|
|
||||||
public class SchedulerConfigParam {
|
|
||||||
|
|
||||||
public long triggerPerDay;
|
|
||||||
public int maxCount;
|
|
||||||
|
|
||||||
public SchedulerConfigParam() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SchedulerConfigParam(long triggerPerDay, int maxCount) {
|
|
||||||
super();
|
|
||||||
this.triggerPerDay = triggerPerDay;
|
|
||||||
this.maxCount = maxCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxCount() {
|
|
||||||
return maxCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTriggerPerDay() {
|
|
||||||
return triggerPerDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxCount(int maxCount) {
|
|
||||||
this.maxCount = maxCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTriggerPerDay(long triggerPerDay) {
|
|
||||||
this.triggerPerDay = triggerPerDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "SchedulerConfigParam [triggerPerDay=" + triggerPerDay
|
|
||||||
+ ", maxCount=" + maxCount + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.dto;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class TriggerProgress {
|
|
||||||
|
|
||||||
private int timesTriggered;
|
|
||||||
|
|
||||||
private int repeatCount;
|
|
||||||
|
|
||||||
private Date finalFireTime;
|
|
||||||
|
|
||||||
private Date nextFireTime;
|
|
||||||
|
|
||||||
private Date previousFireTime;
|
|
||||||
|
|
||||||
private String jobKey;
|
|
||||||
|
|
||||||
private String jobClass;
|
|
||||||
|
|
||||||
public Date getFinalFireTime() {
|
|
||||||
return finalFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJobClass() {
|
|
||||||
return jobClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJobKey() {
|
|
||||||
return jobKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getNextFireTime() {
|
|
||||||
return nextFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPercentage() {
|
|
||||||
if (this.repeatCount <= 0)
|
|
||||||
return -1;
|
|
||||||
return Math.round((float) timesTriggered / (float) this.repeatCount
|
|
||||||
* 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getPreviousFireTime() {
|
|
||||||
return previousFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRepeatCount() {
|
|
||||||
return repeatCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimesTriggered() {
|
|
||||||
return timesTriggered;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinalFireTime(Date finalFireTime) {
|
|
||||||
this.finalFireTime = finalFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJobClass(String jobClass) {
|
|
||||||
this.jobClass = jobClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJobKey(String jobKey) {
|
|
||||||
this.jobKey = jobKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextFireTime(Date nextFireTime) {
|
|
||||||
this.nextFireTime = nextFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreviousFireTime(Date previousFireTime) {
|
|
||||||
this.previousFireTime = previousFireTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRepeatCount(int repeatCount) {
|
|
||||||
this.repeatCount = repeatCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimesTriggered(int timesTriggered) {
|
|
||||||
this.timesTriggered = timesTriggered;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.enums;
|
|
||||||
|
|
||||||
public enum SchedulerStates {
|
|
||||||
RUNNING, STOPPED, PAUSED
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.exceptions;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
|
|
||||||
@ControllerAdvice
|
|
||||||
public class ExceptionHandlingController {
|
|
||||||
|
|
||||||
@ExceptionHandler(ResourceConflictException.class)
|
|
||||||
public ResponseEntity<ExceptionResponse> resourceConflict(ResourceConflictException ex) {
|
|
||||||
ExceptionResponse response = new ExceptionResponse();
|
|
||||||
response.setErrorCode("Conflict");
|
|
||||||
response.setErrorMessage(ex.getMessage());
|
|
||||||
return new ResponseEntity<ExceptionResponse>(response, HttpStatus.CONFLICT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.exceptions;
|
|
||||||
|
|
||||||
public class ExceptionResponse {
|
|
||||||
|
|
||||||
private String errorCode;
|
|
||||||
private String errorMessage;
|
|
||||||
|
|
||||||
public ExceptionResponse() {}
|
|
||||||
|
|
||||||
public String getErrorCode() {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorCode(String errorCode) {
|
|
||||||
this.errorCode = errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorMessage(String errorMessage) {
|
|
||||||
this.errorMessage = errorMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.exceptions;
|
|
||||||
|
|
||||||
public class ResourceConflictException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1791564636123821405L;
|
|
||||||
|
|
||||||
private Long resourceId;
|
|
||||||
|
|
||||||
public ResourceConflictException(Long resourceId, String message) {
|
|
||||||
super(message);
|
|
||||||
setResourceId(resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getResourceId() {
|
|
||||||
return resourceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResourceId(Long resourceId) {
|
|
||||||
this.resourceId = resourceId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.jobs;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.quartz.Job;
|
|
||||||
import org.quartz.JobExecutionContext;
|
|
||||||
import org.quartz.SchedulerException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.aspects.ProgressUpdater;
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends this class to create a job that produces LogRecord to be displayed
|
|
||||||
* into the GUI panel
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class AbstractLoggingJob implements Job {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AbstractLoggingJob.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SimpMessageSendingOperations messagingTemplate;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ProgressUpdater progressUpdater;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param jobExecutionContext
|
|
||||||
* @return final log
|
|
||||||
*/
|
|
||||||
public abstract LogRecord doIt(JobExecutionContext jobExecutionContext);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void execute(JobExecutionContext jobExecutionContext) {
|
|
||||||
try {
|
|
||||||
LogRecord logMsg = doIt(jobExecutionContext);
|
|
||||||
logAndSend(logMsg);
|
|
||||||
progressUpdater.update();
|
|
||||||
} catch (SchedulerException e) {
|
|
||||||
log.error("Error updating progress " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logAndSend(LogRecord logRecord) {
|
|
||||||
log.info(logRecord.getMessage());
|
|
||||||
logRecord.setThreadName(Thread.currentThread().getName());
|
|
||||||
messagingTemplate.convertAndSend("/topic/logs", logRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.jobs.entities;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log record produced by a job at the end of each run
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class LogRecord {
|
|
||||||
|
|
||||||
public enum LogType {
|
|
||||||
INFO, WARN, ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date date;
|
|
||||||
private LogType type;
|
|
||||||
|
|
||||||
private String message;
|
|
||||||
private String threadName;
|
|
||||||
|
|
||||||
public LogRecord(LogType type, String msg) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
message = msg;
|
|
||||||
date = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getDate() {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getThreadName() {
|
|
||||||
return threadName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDate(Date date) {
|
|
||||||
this.date = date;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(String msg) {
|
|
||||||
message = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThreadName(String threadName) {
|
|
||||||
this.threadName = threadName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(LogType type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "LogRecord [date=" + date + ", type=" + type + ", message=" + message + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.jobs.myjobs;
|
|
||||||
|
|
||||||
import org.quartz.JobExecutionContext;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob;
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
|
|
||||||
|
|
||||||
public class SampleJob extends AbstractLoggingJob {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogRecord doIt(JobExecutionContext jobExecutionContext) {
|
|
||||||
return new LogRecord(LogType.INFO, "Hello!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.jobs.tests;
|
|
||||||
|
|
||||||
import org.quartz.JobExecutionContext;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob;
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord;
|
|
||||||
import it.fabioformosa.quartzmanager.jobs.entities.LogRecord.LogType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This job can be used to test the misfire policy. It pretends to be a long
|
|
||||||
* processing job (sleeping for a while)
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class MisfireTestJob extends AbstractLoggingJob {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(MisfireTestJob.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogRecord doIt(JobExecutionContext jobExecutionContext) {
|
|
||||||
try {
|
|
||||||
log.info("{} is going to sleep...", Thread.currentThread().getName());
|
|
||||||
|
|
||||||
Thread.sleep(10 * 1000);
|
|
||||||
|
|
||||||
log.info("{} woke up!", Thread.currentThread().getName());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LogRecord(LogType.INFO, "Hello!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.scheduler;
|
|
||||||
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
|
|
||||||
public interface TriggerMonitor {
|
|
||||||
|
|
||||||
void setTrigger(Trigger trigger);
|
|
||||||
|
|
||||||
Trigger getTrigger();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.scheduler;
|
|
||||||
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
|
|
||||||
public class TriggerMonitorImpl implements TriggerMonitor {
|
|
||||||
|
|
||||||
private Trigger trigger;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Trigger getTrigger() {
|
|
||||||
return trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrigger(Trigger trigger) {
|
|
||||||
this.trigger = trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.WebAttributes;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
public class AjaxAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
|
||||||
|
|
||||||
public class AjaxLoginAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
|
|
||||||
implements AuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
Authentication authentication) throws IOException, ServletException {
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
clearAuthenticationAttributes(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AjaxAuthenticationFilter(AuthenticationManager authenticationManager) {
|
|
||||||
setAuthenticationManager(authenticationManager);
|
|
||||||
setUsernameParameter("ajaxUsername");
|
|
||||||
setPasswordParameter("ajaxPassword");
|
|
||||||
setPostOnly(true);
|
|
||||||
setFilterProcessesUrl("/ajaxLogin");
|
|
||||||
|
|
||||||
setAuthenticationSuccessHandler(new AjaxLoginAuthSuccessHandler());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes temporary authentication-related data which may have been stored
|
|
||||||
* in the session during the authentication process.
|
|
||||||
*/
|
|
||||||
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
|
|
||||||
if (session == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class ComboEntryPoint extends LoginUrlAuthenticationEntryPoint {
|
|
||||||
|
|
||||||
private static final String LOGIN_FORM_URL = "/login";
|
|
||||||
|
|
||||||
public ComboEntryPoint() {
|
|
||||||
super(LOGIN_FORM_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
AuthenticationException authException) throws IOException, ServletException {
|
|
||||||
|
|
||||||
if (RESTRequestMatcher.isRestRequest(request)
|
|
||||||
|| WebsocketRequestMatcher.isWebsocketConnectionRequest(request))
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
|
||||||
else
|
|
||||||
super.commence(request, response, authException);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.web.util.matcher.ELRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
public class RESTRequestMatcher {
|
|
||||||
|
|
||||||
static private final Logger log = LoggerFactory.getLogger(RESTRequestMatcher.class);
|
|
||||||
|
|
||||||
static public RequestMatcher matcherRequestedWith = new ELRequestMatcher(
|
|
||||||
"hasHeader('X-Requested-With','XMLHttpRequest')");
|
|
||||||
static public RequestMatcher matcherAccept = new ELRequestMatcher(
|
|
||||||
"hasHeader('accept','application/json, text/plain, */*')");
|
|
||||||
|
|
||||||
static public boolean isRestRequest(HttpServletRequest request) {
|
|
||||||
log.trace("Detecting if it's an AJAX Request: " + request.getRequestURL() + " accept: "
|
|
||||||
+ request.getHeader("accept") + " " + " X-Requested-With: "
|
|
||||||
+ request.getHeader("X-Requested-With"));
|
|
||||||
return matcherRequestedWith.matches(request) || matcherAccept.matches(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
|
||||||
import io.jsonwebtoken.Jwts;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT Temporary disabled
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//@Component
|
|
||||||
public class TokenHelper {
|
|
||||||
|
|
||||||
@Value("${app.name}")
|
|
||||||
private String APP_NAME;
|
|
||||||
|
|
||||||
@Value("${jwt.secret}")
|
|
||||||
private String SECRET;
|
|
||||||
|
|
||||||
@Value("${jwt.expires_in}")
|
|
||||||
private int EXPIRES_IN;
|
|
||||||
|
|
||||||
@Value("${jwt.header}")
|
|
||||||
private String AUTH_HEADER;
|
|
||||||
|
|
||||||
@Value("${jwt.cookie}")
|
|
||||||
private String AUTH_COOKIE;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
UserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512;
|
|
||||||
|
|
||||||
public Boolean canTokenBeRefreshed(String token) {
|
|
||||||
try {
|
|
||||||
final Date expirationDate = getClaimsFromToken(token).getExpiration();
|
|
||||||
// String username = getUsernameFromToken(token);
|
|
||||||
// UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
||||||
return expirationDate.compareTo(generateCurrentDate()) > 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateToken(String username) {
|
|
||||||
return Jwts.builder()
|
|
||||||
.setIssuer( APP_NAME )
|
|
||||||
.setSubject(username)
|
|
||||||
.setIssuedAt(generateCurrentDate())
|
|
||||||
.setExpiration(generateExpirationDate())
|
|
||||||
.signWith( SIGNATURE_ALGORITHM, SECRET )
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a specific HTTP cookie in a request.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The HTTP request object.
|
|
||||||
* @param name
|
|
||||||
* The cookie name to look for.
|
|
||||||
* @return The cookie, or <code>null</code> if not found.
|
|
||||||
*/
|
|
||||||
public Cookie getCookieValueByName(HttpServletRequest request, String name) {
|
|
||||||
if (request.getCookies() == null)
|
|
||||||
return null;
|
|
||||||
for (int i = 0; i < request.getCookies().length; i++)
|
|
||||||
if (request.getCookies()[i].getName().equals(name))
|
|
||||||
return request.getCookies()[i];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getToken( HttpServletRequest request ) {
|
|
||||||
/**
|
|
||||||
* Getting the token from Cookie store
|
|
||||||
*/
|
|
||||||
Cookie authCookie = getCookieValueByName( request, AUTH_COOKIE );
|
|
||||||
if ( authCookie != null )
|
|
||||||
return authCookie.getValue();
|
|
||||||
/**
|
|
||||||
* Getting the token from Authentication header
|
|
||||||
* e.g Bearer your_token
|
|
||||||
*/
|
|
||||||
String authHeader = request.getHeader(AUTH_HEADER);
|
|
||||||
if ( authHeader != null && authHeader.startsWith("Bearer "))
|
|
||||||
return authHeader.substring(7);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsernameFromToken(String token) {
|
|
||||||
String username;
|
|
||||||
try {
|
|
||||||
final Claims claims = getClaimsFromToken(token);
|
|
||||||
username = claims.getSubject();
|
|
||||||
} catch (Exception e) {
|
|
||||||
username = null;
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String refreshToken(String token) {
|
|
||||||
String refreshedToken;
|
|
||||||
try {
|
|
||||||
final Claims claims = getClaimsFromToken(token);
|
|
||||||
claims.setIssuedAt(generateCurrentDate());
|
|
||||||
refreshedToken = generateToken(claims);
|
|
||||||
} catch (Exception e) {
|
|
||||||
refreshedToken = null;
|
|
||||||
}
|
|
||||||
return refreshedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date generateCurrentDate() {
|
|
||||||
return new Date(getCurrentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date generateExpirationDate() {
|
|
||||||
|
|
||||||
return new Date(getCurrentTimeMillis() + EXPIRES_IN * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Claims getClaimsFromToken(String token) {
|
|
||||||
Claims claims;
|
|
||||||
try {
|
|
||||||
claims = Jwts.parser()
|
|
||||||
.setSigningKey(SECRET)
|
|
||||||
.parseClaimsJws(token)
|
|
||||||
.getBody();
|
|
||||||
} catch (Exception e) {
|
|
||||||
claims = null;
|
|
||||||
}
|
|
||||||
return claims;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getCurrentTimeMillis() {
|
|
||||||
return DateTime.now().getMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateToken(Map<String, Object> claims) {
|
|
||||||
return Jwts.builder()
|
|
||||||
.setClaims(claims)
|
|
||||||
.setExpiration(generateExpirationDate())
|
|
||||||
.signWith( SIGNATURE_ALGORITHM, SECRET )
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class WebsocketRequestMatcher {
|
|
||||||
|
|
||||||
static private final Logger log = LoggerFactory.getLogger(WebsocketRequestMatcher.class);
|
|
||||||
|
|
||||||
static public boolean isWebsocketConnectionRequest(HttpServletRequest request) {
|
|
||||||
log.trace("Detecting if it's a Websocket Connection Request: " + request.getRequestURL());
|
|
||||||
return request.getServletPath().equals("/progress/info")
|
|
||||||
|| request.getServletPath().equals("/logs/info");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.auth;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
AuthenticationException exception) throws IOException, ServletException {
|
|
||||||
|
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.auth;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
// @Value("${jwt.expires_in}")
|
|
||||||
// private int EXPIRES_IN;
|
|
||||||
//
|
|
||||||
// @Value("${jwt.cookie}")
|
|
||||||
// private String TOKEN_COOKIE;
|
|
||||||
//
|
|
||||||
// @Autowired
|
|
||||||
// TokenHelper tokenHelper;
|
|
||||||
//
|
|
||||||
@Autowired
|
|
||||||
ObjectMapper objectMapper;
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
// Authentication authentication ) throws IOException, ServletException {
|
|
||||||
// clearAuthenticationAttributes(request);
|
|
||||||
// User user = (User)authentication.getPrincipal();
|
|
||||||
//
|
|
||||||
// String jws = tokenHelper.generateToken( user.getUsername() );
|
|
||||||
//
|
|
||||||
// // Create token auth Cookie
|
|
||||||
// Cookie authCookie = new Cookie( TOKEN_COOKIE, jws );
|
|
||||||
//
|
|
||||||
// authCookie.setHttpOnly( true );
|
|
||||||
//
|
|
||||||
// authCookie.setMaxAge( EXPIRES_IN );
|
|
||||||
//
|
|
||||||
// authCookie.setPath( "/" );
|
|
||||||
// // Add cookie to response
|
|
||||||
// response.addCookie( authCookie );
|
|
||||||
//
|
|
||||||
// // JWT is also in the response
|
|
||||||
// UserTokenState userTokenState = new UserTokenState(jws, EXPIRES_IN);
|
|
||||||
// String jwtResponse = objectMapper.writeValueAsString( userTokenState );
|
|
||||||
// response.setContentType("application/json");
|
|
||||||
// response.getWriter().write( jwtResponse );
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
Authentication authentication ) throws IOException, ServletException {
|
|
||||||
// clearAuthenticationAttributes(request);
|
|
||||||
response.setContentType("application/json");
|
|
||||||
response.getWriter().write( objectMapper.writeValueAsString("OK"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.auth;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class LogoutSuccess implements LogoutSuccessHandler {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
result.put( "result", "success" );
|
|
||||||
response.setContentType("application/json");
|
|
||||||
response.getWriter().write( objectMapper.writeValueAsString( result ) );
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.auth;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.TokenHelper;
|
|
||||||
|
|
||||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The below paths will get ignored by the filter
|
|
||||||
*/
|
|
||||||
public static final String ROOT_MATCHER = "/";
|
|
||||||
|
|
||||||
public static final String FAVICON_MATCHER = "/favicon.ico";
|
|
||||||
|
|
||||||
public static final String HTML_MATCHER = "/**/*.html";
|
|
||||||
|
|
||||||
public static final String CSS_MATCHER = "/**/*.css";
|
|
||||||
public static final String JS_MATCHER = "/**/*.js";
|
|
||||||
public static final String IMG_MATCHER = "/images/*";
|
|
||||||
public static final String LOGIN_MATCHER = "/auth/login";
|
|
||||||
public static final String LOGOUT_MATCHER = "/auth/logout";
|
|
||||||
private final Log logger = LogFactory.getLog(this.getClass());
|
|
||||||
@Autowired
|
|
||||||
TokenHelper tokenHelper;
|
|
||||||
@Autowired
|
|
||||||
UserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
private List<String> pathsToSkip = Arrays.asList(
|
|
||||||
ROOT_MATCHER,
|
|
||||||
HTML_MATCHER,
|
|
||||||
FAVICON_MATCHER,
|
|
||||||
CSS_MATCHER,
|
|
||||||
JS_MATCHER,
|
|
||||||
IMG_MATCHER,
|
|
||||||
LOGIN_MATCHER,
|
|
||||||
LOGOUT_MATCHER
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
|
||||||
|
|
||||||
|
|
||||||
String authToken = tokenHelper.getToken(request);
|
|
||||||
if (authToken != null && !skipPathRequest(request, pathsToSkip))
|
|
||||||
// get username from token
|
|
||||||
try {
|
|
||||||
String username = tokenHelper.getUsernameFromToken(authToken);
|
|
||||||
// get user
|
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
||||||
// create authentication
|
|
||||||
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
|
|
||||||
authentication.setToken(authToken);
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
|
|
||||||
|
|
||||||
chain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean skipPathRequest(HttpServletRequest request, List<String> pathsToSkip ) {
|
|
||||||
Assert.notNull(pathsToSkip, "path cannot be null.");
|
|
||||||
List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
|
|
||||||
OrRequestMatcher matchers = new OrRequestMatcher(m);
|
|
||||||
return matchers.matches(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.auth;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
|
|
||||||
|
|
||||||
public class TokenBasedAuthentication extends AbstractAuthenticationToken {
|
|
||||||
|
|
||||||
private String token;
|
|
||||||
private final UserDetails principle;
|
|
||||||
|
|
||||||
public TokenBasedAuthentication( UserDetails principle ) {
|
|
||||||
super( principle.getAuthorities() );
|
|
||||||
this.principle = principle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCredentials() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserDetails getPrincipal() {
|
|
||||||
return principle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAuthenticated() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken( String token ) {
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.model;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.GenerationType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary enabled only inMemoryAuthentication
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@Table(name="Authority")
|
|
||||||
public class Authority implements GrantedAuthority {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name="id")
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
Long id;
|
|
||||||
|
|
||||||
@Column(name="name")
|
|
||||||
String name;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthority() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.model;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.FetchType;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.GenerationType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.JoinTable;
|
|
||||||
import javax.persistence.ManyToMany;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary enabled only inMemoryAuthentication
|
|
||||||
*
|
|
||||||
* @author Fabio.Formosa
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@Table(name = "USER")
|
|
||||||
public class User implements UserDetails, Serializable {
|
|
||||||
@Id
|
|
||||||
@Column(name = "id")
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "username")
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Column(name = "password")
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
@Column(name = "firstname")
|
|
||||||
private String firstname;
|
|
||||||
|
|
||||||
@Column(name = "lastname")
|
|
||||||
private String lastname;
|
|
||||||
|
|
||||||
|
|
||||||
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
|
||||||
@JoinTable(name = "user_authority",
|
|
||||||
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
|
|
||||||
inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
|
|
||||||
private List<Authority> authorities;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
|
||||||
return authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstname() {
|
|
||||||
return firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastname() {
|
|
||||||
return lastname;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can add the below fields in the users table.
|
|
||||||
// For now, they are hardcoded.
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonLocked() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
|
||||||
public boolean isCredentialsNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthorities(List<Authority> authorities) {
|
|
||||||
this.authorities = authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstname(String firstname) {
|
|
||||||
this.firstname = firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastname(String lastname) {
|
|
||||||
|
|
||||||
this.lastname = lastname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.model;
|
|
||||||
|
|
||||||
|
|
||||||
public class UserRequest {
|
|
||||||
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
private String firstname;
|
|
||||||
|
|
||||||
private String lastname;
|
|
||||||
|
|
||||||
|
|
||||||
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 getFirstname() {
|
|
||||||
return firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstname(String firstname) {
|
|
||||||
this.firstname = firstname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastname() {
|
|
||||||
return lastname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastname(String lastname) {
|
|
||||||
this.lastname = lastname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.model;
|
|
||||||
|
|
||||||
public class UserTokenState {
|
|
||||||
private String access_token;
|
|
||||||
private Long expires_in;
|
|
||||||
|
|
||||||
public UserTokenState() {
|
|
||||||
this.access_token = null;
|
|
||||||
this.expires_in = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserTokenState(String access_token, long expires_in) {
|
|
||||||
this.access_token = access_token;
|
|
||||||
this.expires_in = expires_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccess_token() {
|
|
||||||
return access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getExpires_in() {
|
|
||||||
return expires_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccess_token(String access_token) {
|
|
||||||
this.access_token = access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpires_in(Long expires_in) {
|
|
||||||
this.expires_in = expires_in;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.repository;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.Authority;
|
|
||||||
|
|
||||||
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
|
|
||||||
Authority findByName(String name);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.repository;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.User;
|
|
||||||
|
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
|
||||||
User findByUsername( String username );
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.Authority;
|
|
||||||
|
|
||||||
public interface AuthorityService {
|
|
||||||
List<Authority> findById(Long id);
|
|
||||||
|
|
||||||
List<Authority> findByname(String name);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.User;
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.UserRequest;
|
|
||||||
|
|
||||||
public interface UserService {
|
|
||||||
List<User> findAll();
|
|
||||||
|
|
||||||
User findById(Long id);
|
|
||||||
|
|
||||||
User findByUsername(String username);
|
|
||||||
|
|
||||||
void resetCredentials();
|
|
||||||
|
|
||||||
User save(UserRequest user);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.service.impl;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.Authority;
|
|
||||||
import it.fabioformosa.quartzmanager.security.repository.AuthorityRepository;
|
|
||||||
import it.fabioformosa.quartzmanager.security.service.AuthorityService;
|
|
||||||
|
|
||||||
//@Service
|
|
||||||
public class AuthorityServiceImpl implements AuthorityService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthorityRepository authorityRepository;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Authority> findById(Long id) {
|
|
||||||
Authority auth = this.authorityRepository.getOne(id);
|
|
||||||
List<Authority> auths = new ArrayList<>();
|
|
||||||
auths.add(auth);
|
|
||||||
return auths;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Authority> findByname(String name) {
|
|
||||||
Authority auth = this.authorityRepository.findByName(name);
|
|
||||||
List<Authority> auths = new ArrayList<>();
|
|
||||||
auths.add(auth);
|
|
||||||
return auths;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.service.impl;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.User;
|
|
||||||
import it.fabioformosa.quartzmanager.security.repository.UserRepository;
|
|
||||||
|
|
||||||
|
|
||||||
//@Service
|
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
|
||||||
|
|
||||||
protected final Log LOGGER = LogFactory.getLog(getClass());
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserRepository userRepository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthenticationManager authenticationManager;
|
|
||||||
|
|
||||||
public void changePassword(String oldPassword, String newPassword) {
|
|
||||||
|
|
||||||
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
String username = currentUser.getName();
|
|
||||||
|
|
||||||
if (authenticationManager != null) {
|
|
||||||
LOGGER.debug("Re-authenticating user '"+ username + "' for password change request.");
|
|
||||||
|
|
||||||
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, oldPassword));
|
|
||||||
} else {
|
|
||||||
LOGGER.debug("No authentication manager set. can't change Password!");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debug("Changing password for user '"+ username + "'");
|
|
||||||
|
|
||||||
User user = (User) loadUserByUsername(username);
|
|
||||||
|
|
||||||
user.setPassword(passwordEncoder.encode(newPassword));
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
User user = userRepository.findByUsername(username);
|
|
||||||
if (user == null)
|
|
||||||
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
|
|
||||||
else
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.security.service.impl;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.Authority;
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.User;
|
|
||||||
import it.fabioformosa.quartzmanager.security.model.UserRequest;
|
|
||||||
import it.fabioformosa.quartzmanager.security.repository.UserRepository;
|
|
||||||
import it.fabioformosa.quartzmanager.security.service.AuthorityService;
|
|
||||||
import it.fabioformosa.quartzmanager.security.service.UserService;
|
|
||||||
|
|
||||||
//@Service
|
|
||||||
public class UserServiceImpl implements UserService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserRepository userRepository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthorityService authService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public List<User> findAll() throws AccessDeniedException {
|
|
||||||
List<User> result = userRepository.findAll();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public User findById(Long id) throws AccessDeniedException {
|
|
||||||
User u = userRepository.getOne(id);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
// @PreAuthorize("hasRole('USER')")
|
|
||||||
public User findByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
User u = userRepository.findByUsername(username);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetCredentials() {
|
|
||||||
List<User> users = userRepository.findAll();
|
|
||||||
for (User user : users) {
|
|
||||||
user.setPassword(passwordEncoder.encode("123"));
|
|
||||||
userRepository.save(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User save(UserRequest userRequest) {
|
|
||||||
User user = new User();
|
|
||||||
user.setUsername(userRequest.getUsername());
|
|
||||||
user.setPassword(passwordEncoder.encode(userRequest.getPassword()));
|
|
||||||
user.setFirstname(userRequest.getFirstname());
|
|
||||||
user.setLastname(userRequest.getLastname());
|
|
||||||
List<Authority> auth = authService.findByname("ROLE_USER");
|
|
||||||
user.setAuthorities(auth);
|
|
||||||
this.userRepository.save(user);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
server:
|
|
||||||
servlet:
|
|
||||||
context-path: /quartz-manager
|
|
||||||
session.timeout : 28800
|
|
||||||
port: 8080
|
|
||||||
|
|
||||||
app:
|
|
||||||
name: quartz-manager
|
|
||||||
|
|
||||||
spring:
|
|
||||||
thymeleaf:
|
|
||||||
cache: false
|
|
||||||
mode: LEGACYHTML5
|
|
||||||
|
|
||||||
quartz:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
job:
|
|
||||||
frequency: 4000
|
|
||||||
repeatCount: 19
|
|
||||||
|
|
||||||
jwt:
|
|
||||||
header: Authorization
|
|
||||||
expires_in: 600 # 10 minutes
|
|
||||||
secret: queenvictoria
|
|
||||||
cookie: AUTH-TOKEN
|
|
||||||
|
|
||||||
logging:
|
|
||||||
level:
|
|
||||||
org.springframework.web: WARN
|
|
||||||
it.fabioformosa: INFO
|
|
||||||
|
|
||||||
quartz-manager:
|
|
||||||
account:
|
|
||||||
user: admin
|
|
||||||
pwd: admin
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
-- the password hash is generated by BCrypt Calculator Generator(https://www.dailycred.com/article/bcrypt-calculator)
|
|
||||||
INSERT INTO user (id, username, password, firstname, lastname) VALUES (1, 'user', '{bcrypt}$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'John', 'Doe');
|
|
||||||
INSERT INTO user (id, username, password, firstname, lastname) VALUES (2, 'admin', '{bcrypt}$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'Admin', 'Admin');
|
|
||||||
|
|
||||||
INSERT INTO authority (id, name) VALUES (1, 'ROLE_USER');
|
|
||||||
INSERT INTO authority (id, name) VALUES (2, 'ROLE_ADMIN');
|
|
||||||
|
|
||||||
INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1);
|
|
||||||
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 1);
|
|
||||||
INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
org.quartz.scheduler.instanceName=example
|
|
||||||
org.quartz.scheduler.instanceId=AUTO
|
|
||||||
org.quartz.threadPool.threadCount=1
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package it.fabioformosa;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
|
||||||
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
|
||||||
@SpringBootTest(classes = QuartManagerApplication.class)
|
|
||||||
@WebAppConfiguration
|
|
||||||
public class QuartManagerApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void contextLoads() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
12
quartz-manager-frontend/.browserslistrc
Normal file
12
quartz-manager-frontend/.browserslistrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||||
|
# For additional information regarding the format and rule options, please see:
|
||||||
|
# https://github.com/browserslist/browserslist#queries
|
||||||
|
|
||||||
|
# You can see what browsers were selected by your queries by running:
|
||||||
|
# npx browserslist
|
||||||
|
|
||||||
|
last 2 Chrome versions
|
||||||
|
last 2 Firefox versions
|
||||||
|
last 2 Edge versions
|
||||||
|
last 2 Safari versions
|
||||||
|
last 2 iOS versions
|
||||||
178
quartz-manager-frontend/.eslintrc.json
Normal file
178
quartz-manager-frontend/.eslintrc.json
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
👋 Hi! This file was autogenerated by tslint-to-eslint-config.
|
||||||
|
https://github.com/typescript-eslint/tslint-to-eslint-config
|
||||||
|
|
||||||
|
It represents the closest reasonable ESLint configuration to this
|
||||||
|
project's original TSLint configuration.
|
||||||
|
|
||||||
|
We recommend eventually switching this configuration to extend from
|
||||||
|
the recommended rulesets in typescript-eslint.
|
||||||
|
https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md
|
||||||
|
|
||||||
|
Happy linting! 💖
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"eslint-plugin-import",
|
||||||
|
"@angular-eslint/eslint-plugin",
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"root": true,
|
||||||
|
"rules": {
|
||||||
|
"@angular-eslint/component-class-suffix": "off",
|
||||||
|
"@angular-eslint/component-selector": "off",
|
||||||
|
"@angular-eslint/directive-class-suffix": "error",
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "attribute",
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "camelCase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@angular-eslint/no-input-rename": "error",
|
||||||
|
"@angular-eslint/no-inputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/no-output-rename": "error",
|
||||||
|
"@angular-eslint/no-outputs-metadata-property": "error",
|
||||||
|
"@angular-eslint/use-lifecycle-interface": "error",
|
||||||
|
"@angular-eslint/use-pipe-transform-interface": "error",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "error",
|
||||||
|
"@typescript-eslint/dot-notation": "off",
|
||||||
|
"@typescript-eslint/explicit-member-accessibility": [
|
||||||
|
"off",
|
||||||
|
{
|
||||||
|
"accessibility": "explicit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/indent": "off",
|
||||||
|
"@typescript-eslint/member-delimiter-style": [
|
||||||
|
"off",
|
||||||
|
{
|
||||||
|
"multiline": {
|
||||||
|
"delimiter": "none",
|
||||||
|
"requireLast": true
|
||||||
|
},
|
||||||
|
"singleline": {
|
||||||
|
"delimiter": "semi",
|
||||||
|
"requireLast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "off",
|
||||||
|
"@typescript-eslint/naming-convention": "off",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"ignoreParameters": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-shadow": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"hoist": "all"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-unused-expressions": "error",
|
||||||
|
"@typescript-eslint/prefer-function-type": "error",
|
||||||
|
"@typescript-eslint/semi": [
|
||||||
|
"off",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"@typescript-eslint/type-annotation-spacing": "off",
|
||||||
|
"@typescript-eslint/unified-signatures": "error",
|
||||||
|
"brace-style": [
|
||||||
|
"error",
|
||||||
|
"1tbs"
|
||||||
|
],
|
||||||
|
"curly": "error",
|
||||||
|
"dot-notation": "off",
|
||||||
|
"eol-last": "off",
|
||||||
|
"eqeqeq": [
|
||||||
|
"error",
|
||||||
|
"smart"
|
||||||
|
],
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"id-denylist": "off",
|
||||||
|
"id-match": "off",
|
||||||
|
"import/no-deprecated": "warn",
|
||||||
|
"indent": "off",
|
||||||
|
"max-len": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"code": 140
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-bitwise": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-console": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allow": [
|
||||||
|
"log",
|
||||||
|
"warn",
|
||||||
|
"dir",
|
||||||
|
"timeLog",
|
||||||
|
"assert",
|
||||||
|
"clear",
|
||||||
|
"count",
|
||||||
|
"countReset",
|
||||||
|
"group",
|
||||||
|
"groupEnd",
|
||||||
|
"table",
|
||||||
|
"dirxml",
|
||||||
|
"error",
|
||||||
|
"groupCollapsed",
|
||||||
|
"Console",
|
||||||
|
"profile",
|
||||||
|
"profileEnd",
|
||||||
|
"timeStamp",
|
||||||
|
"context"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-debugger": "error",
|
||||||
|
"no-empty": "off",
|
||||||
|
"no-empty-function": "off",
|
||||||
|
"no-eval": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-redeclare": "error",
|
||||||
|
"no-restricted-imports": "error",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-trailing-spaces": "off",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-unused-expressions": "off",
|
||||||
|
"no-unused-labels": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"quotes": "off",
|
||||||
|
"radix": "error",
|
||||||
|
"semi": "off",
|
||||||
|
"spaced-comment": [
|
||||||
|
"error",
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"markers": [
|
||||||
|
"/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid-typeof": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal file
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:sonarjs/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "tsconfig.json",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"sonarjs"
|
||||||
|
],
|
||||||
|
"root": true
|
||||||
|
}
|
||||||
4
quartz-manager-frontend/.gitignore
vendored
4
quartz-manager-frontend/.gitignore
vendored
@@ -40,6 +40,4 @@ testem.log
|
|||||||
# System Files
|
# System Files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
/.angular/
|
||||||
#package-lock.json
|
|
||||||
package-lock.json
|
|
||||||
|
|||||||
47
quartz-manager-frontend/.prettierignore
Normal file
47
quartz-manager-frontend/.prettierignore
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
src/polyfills.ts
|
||||||
|
src/typings.d.ts
|
||||||
|
_test.ts
|
||||||
|
|
||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# e2e
|
||||||
|
/e2e/*.js
|
||||||
|
/e2e/*.map
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
/.angular/
|
||||||
11
quartz-manager-frontend/.prettierrc.json
Normal file
11
quartz-manager-frontend/.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"printWidth": 80
|
||||||
|
}
|
||||||
@@ -3,34 +3,53 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"angular-spring-starter": {
|
"quartz-manager-ui": {
|
||||||
"root": "",
|
"root": "",
|
||||||
|
"prefix": "qrzmng",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"architect": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "../server/src/main/resources/static",
|
"aot": true,
|
||||||
|
"outputPath": "dist",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
"tsConfig": "src/tsconfig.app.json",
|
"tsConfig": "src/tsconfig.app.json",
|
||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"allowedCommonJsDependencies": [
|
||||||
|
"@stomp/stompjs", "stompjs", "sockjs-client", "angular2-uuid"
|
||||||
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/favicon.ico"
|
"src/favicon.ico"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.css"
|
"src/styles.css",
|
||||||
|
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
},
|
||||||
"production": {
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "6kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
"optimization": true,
|
"optimization": true,
|
||||||
"outputHashing": "all",
|
"outputHashing": "all",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"extractCss": true,
|
|
||||||
"namedChunks": false,
|
"namedChunks": false,
|
||||||
"aot": true,
|
"aot": true,
|
||||||
"extractLicenses": true,
|
"extractLicenses": true,
|
||||||
@@ -48,81 +67,60 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "angular-spring-starter:build"
|
"buildTarget": "quartz-manager-ui:build:development"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "angular-spring-starter:build:production"
|
"buildTarget": "quartz-manager-ui:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "angular-spring-starter:build"
|
"buildTarget": "quartz-manager-ui:build"
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "src/test.ts",
|
|
||||||
"karmaConfig": "./karma.conf.js",
|
|
||||||
"polyfills": "src/polyfills.ts",
|
|
||||||
"tsConfig": "src/tsconfig.spec.json",
|
|
||||||
"scripts": [],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.css"
|
|
||||||
],
|
|
||||||
"assets": [
|
|
||||||
"src/assets",
|
|
||||||
"src/favicon.ico"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
"builder": "@angular-eslint/builder:lint",
|
||||||
"options": {
|
"options": {
|
||||||
"tsConfig": [
|
"eslintConfig": ".eslintrc.json",
|
||||||
"src/tsconfig.app.json",
|
"lintFilePatterns": ["**/*.spec.ts", "**/*.ts"]
|
||||||
"src/tsconfig.spec.json"
|
|
||||||
],
|
|
||||||
"exclude": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"angular-spring-starter-e2e": {
|
|
||||||
"root": "e2e",
|
|
||||||
"sourceRoot": "e2e",
|
|
||||||
"projectType": "application",
|
|
||||||
"architect": {
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@angular-devkit/build-angular:protractor",
|
|
||||||
"options": {
|
|
||||||
"protractorConfig": "./protractor.conf.js",
|
|
||||||
"devServerTarget": "angular-spring-starter:serve"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"e2e/tsconfig.e2e.json"
|
|
||||||
],
|
|
||||||
"exclude": []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultProject": "angular-spring-starter",
|
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
"prefix": "app",
|
"prefix": "qrzmng",
|
||||||
"styleext": "css"
|
"style": "css",
|
||||||
|
"type": "component"
|
||||||
},
|
},
|
||||||
"@schematics/angular:directive": {
|
"@schematics/angular:directive": {
|
||||||
"prefix": "app"
|
"prefix": "qrzmng",
|
||||||
|
"type": "directive"
|
||||||
|
},
|
||||||
|
"@schematics/angular:service": {
|
||||||
|
"type": "service"
|
||||||
|
},
|
||||||
|
"@schematics/angular:guard": {
|
||||||
|
"typeSeparator": "."
|
||||||
|
},
|
||||||
|
"@schematics/angular:interceptor": {
|
||||||
|
"typeSeparator": "."
|
||||||
|
},
|
||||||
|
"@schematics/angular:module": {
|
||||||
|
"typeSeparator": "."
|
||||||
|
},
|
||||||
|
"@schematics/angular:pipe": {
|
||||||
|
"typeSeparator": "."
|
||||||
|
},
|
||||||
|
"@schematics/angular:resolver": {
|
||||||
|
"typeSeparator": "."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
quartz-manager-frontend/eslint.sonar.config.mjs
Normal file
30
quartz-manager-frontend/eslint.sonar.config.mjs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sonarjs from 'eslint-plugin-sonarjs';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ['src/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
sourceType: 'module'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
sonarjs
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...sonarjs.configs.recommended.rules,
|
||||||
|
'sonarjs/deprecation': 'off',
|
||||||
|
'sonarjs/no-commented-code': 'off',
|
||||||
|
'sonarjs/no-dead-store': 'off',
|
||||||
|
'sonarjs/no-incomplete-assertions': 'off',
|
||||||
|
'sonarjs/no-primitive-wrappers': 'off',
|
||||||
|
'sonarjs/no-unused-vars': 'off',
|
||||||
|
'sonarjs/prefer-promise-shorthand': 'off',
|
||||||
|
'sonarjs/todo-tag': 'off',
|
||||||
|
'sonarjs/unused-import': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
20
quartz-manager-frontend/jest.config.js
Normal file
20
quartz-manager-frontend/jest.config.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const {createEsmPreset} = require('jest-preset-angular/presets');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...createEsmPreset({
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$'
|
||||||
|
}),
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^tslib$': '<rootDir>/node_modules/tslib/tslib.es6.mjs',
|
||||||
|
'^rxjs$': '<rootDir>/node_modules/rxjs/dist/cjs/index.js',
|
||||||
|
'^rxjs/operators$': '<rootDir>/node_modules/rxjs/dist/cjs/operators/index.js',
|
||||||
|
'^rxjs/(.*)$': '<rootDir>/node_modules/rxjs/dist/cjs/$1',
|
||||||
|
'^@fortawesome/fontawesome$': '<rootDir>/node_modules/@fortawesome/fontawesome/index.js',
|
||||||
|
'^@fortawesome/fontawesome-free-solid$': '<rootDir>/node_modules/@fortawesome/fontawesome-free-solid/index.js'
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'node_modules/(?!(@angular|@fortawesome|@stomp/rx-stomp|@stomp/stompjs|.*\\.mjs$)/)'
|
||||||
|
]
|
||||||
|
};
|
||||||
3
quartz-manager-frontend/jest.setup.ts
Normal file
3
quartz-manager-frontend/jest.setup.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import {setupZoneTestEnv} from 'jest-preset-angular/setup-env/zone/index.mjs';
|
||||||
|
|
||||||
|
setupZoneTestEnv();
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Karma configuration file, see link for more information
|
|
||||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
config.set({
|
|
||||||
basePath: '',
|
|
||||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-chrome-launcher'),
|
|
||||||
require('karma-jasmine-html-reporter'),
|
|
||||||
require('karma-coverage-istanbul-reporter'),
|
|
||||||
require('@angular-devkit/build-angular/plugins/karma')
|
|
||||||
],
|
|
||||||
client:{
|
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
||||||
},
|
|
||||||
files: [
|
|
||||||
|
|
||||||
],
|
|
||||||
preprocessors: {
|
|
||||||
|
|
||||||
},
|
|
||||||
mime: {
|
|
||||||
'text/x-typescript': ['ts','tsx']
|
|
||||||
},
|
|
||||||
coverageIstanbulReporter: {
|
|
||||||
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
|
||||||
fixWebpackSourcePaths: true
|
|
||||||
},
|
|
||||||
|
|
||||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
|
||||||
? ['progress', 'coverage-istanbul']
|
|
||||||
: ['progress', 'kjhtml'],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ['Chrome'],
|
|
||||||
singleRun: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
23059
quartz-manager-frontend/package-lock.json
generated
Normal file
23059
quartz-manager-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,60 +5,69 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --proxy-config proxy.conf.json",
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
"build": "ng build",
|
"build": "ng build --configuration production",
|
||||||
"test": "ng test",
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e"
|
"lint:sonar": "eslint -c eslint.sonar.config.mjs \"src/**/*.ts\"",
|
||||||
|
"lint:sonar:fix": "eslint -c eslint.sonar.config.mjs \"src/**/*.ts\" --fix"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "7.2.13",
|
"@angular/animations": "21.2.12",
|
||||||
"@angular/cdk": "7.3.7",
|
"@angular/cdk": "21.2.10",
|
||||||
"@angular/common": "7.2.13",
|
"@angular/common": "21.2.12",
|
||||||
"@angular/compiler": "7.2.13",
|
"@angular/compiler": "21.2.12",
|
||||||
"@angular/core": "7.2.13",
|
"@angular/core": "21.2.12",
|
||||||
"@angular/flex-layout": "7.0.0-beta.24",
|
"@angular/forms": "21.2.12",
|
||||||
"@angular/forms": "7.2.13",
|
"@angular/material": "21.2.10",
|
||||||
"@angular/http": "7.2.13",
|
"@angular/platform-browser": "21.2.12",
|
||||||
"@angular/material": "7.3.7",
|
"@angular/platform-browser-dynamic": "21.2.12",
|
||||||
"@angular/platform-browser": "7.2.13",
|
"@angular/platform-server": "21.2.12",
|
||||||
"@angular/platform-browser-dynamic": "7.2.13",
|
"@angular/router": "21.2.12",
|
||||||
"@angular/platform-server": "7.2.13",
|
"@auth0/angular-jwt": "5.2.0",
|
||||||
"@angular/router": "7.2.13",
|
"@danielmoncada/angular-datetime-picker": "21.0.0",
|
||||||
"@fortawesome/fontawesome": "^1.1.4",
|
"@fortawesome/fontawesome": "^1.1.4",
|
||||||
"@fortawesome/fontawesome-free-regular": "^5.0.8",
|
"@fortawesome/fontawesome-free-regular": "^5.0.8",
|
||||||
"@fortawesome/fontawesome-free-solid": "^5.0.8",
|
"@fortawesome/fontawesome-free-solid": "^5.0.8",
|
||||||
"@stomp/ng2-stompjs": "^0.6.3",
|
"@stomp/rx-stomp": "2.4.0",
|
||||||
"core-js": "2.5.1",
|
"@stomp/stompjs": "^7.2.0",
|
||||||
"hammerjs": "2.0.8",
|
"hammerjs": "2.0.8",
|
||||||
"net": "^1.0.2",
|
"roboto-fontface": "^0.10.0",
|
||||||
"rxjs": "6.4.0",
|
"rxjs": "^7.8.2",
|
||||||
|
"sockjs-client": "^1.1.1",
|
||||||
"stompjs": "^2.3.3",
|
"stompjs": "^2.3.3",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^2.8.1",
|
||||||
"zone.js": "0.8.18"
|
"uuid": "^13.0.0",
|
||||||
|
"zone.js": "~0.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.13.0",
|
"@angular-devkit/build-angular": "^21.2.10",
|
||||||
"@angular-devkit/core": "^0.2.0",
|
"@angular-devkit/core": "^21.2.10",
|
||||||
"@angular/cli": "7.3.7",
|
"@angular-eslint/builder": "21.3.1",
|
||||||
"@angular/compiler-cli": "7.2.13",
|
"@angular-eslint/eslint-plugin": "21.3.1",
|
||||||
"@angular/language-service": "7.2.13",
|
"@angular-eslint/eslint-plugin-template": "21.3.1",
|
||||||
|
"@angular-eslint/schematics": "21.3.1",
|
||||||
|
"@angular-eslint/template-parser": "21.3.1",
|
||||||
|
"@angular/cli": "^21.2.10",
|
||||||
|
"@angular/compiler-cli": "21.2.12",
|
||||||
|
"@angular/language-service": "21.2.12",
|
||||||
"@types/hammerjs": "2.0.34",
|
"@types/hammerjs": "2.0.34",
|
||||||
"@types/jasmine": "2.5.54",
|
"@types/jasmine": "^5.1.13",
|
||||||
"@types/jasminewd2": "2.0.3",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "6.0.90",
|
"@types/node": "^22.13.14",
|
||||||
"codelyzer": "4.2.1",
|
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||||
"jasmine-core": "2.6.4",
|
"@typescript-eslint/parser": "^8.48.1",
|
||||||
"jasmine-spec-reporter": "4.1.1",
|
"eslint": "^9.39.1",
|
||||||
"karma": "1.7.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"karma-chrome-launcher": "2.1.1",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"karma-cli": "1.0.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"karma-coverage-istanbul-reporter": "1.3.0",
|
"eslint-plugin-sonarjs": "^4.0.3",
|
||||||
"karma-jasmine": "1.1.0",
|
"jest": "30.4.1",
|
||||||
"karma-jasmine-html-reporter": "0.2.2",
|
"jest-environment-jsdom": "^30.2.0",
|
||||||
"protractor": "5.1.2",
|
"jest-preset-angular": "^16.1.5",
|
||||||
"ts-node": "3.0.6",
|
"jsdom": "^27.3.0",
|
||||||
"tslint": "5.7.0",
|
"prettier": "^2.8.1",
|
||||||
"typescript": "3.2.4"
|
"prettier-eslint": "^15.0.1",
|
||||||
|
"typescript": "5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"/quartz-manager": {
|
"/quartz-manager": {
|
||||||
"target": "http://localhost:8080",
|
"target": "http://localhost:8080",
|
||||||
"secure": false,
|
"secure": false,
|
||||||
|
"cookiePathRewrite": "/",
|
||||||
"ws":true
|
"ws":true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
quartz-manager-frontend/src/animate.css
vendored
Normal file
7
quartz-manager-frontend/src/animate.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,15 +1,13 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import { AppComponent } from './app.component';
|
import {LoginComponent} from './views/login';
|
||||||
import { LoginComponent } from './login';
|
import {AdminGuard, GuestGuard} from './guards';
|
||||||
import { LoginGuard } from './guard';
|
import {NotFoundComponent} from './views/not-found';
|
||||||
import { GuestGuard, AdminGuard } from './guard';
|
import {ForbiddenComponent} from './views/forbidden';
|
||||||
import { NotFoundComponent } from './not-found';
|
|
||||||
import { ChangePasswordComponent } from './change-password';
|
import {ManagerComponent} from './views/manager';
|
||||||
import { ForbiddenComponent } from './forbidden';
|
import {GenericErrorComponent} from './views/error/genericError.component';
|
||||||
|
|
||||||
import { ManagerComponent } from './manager';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -28,11 +26,6 @@ export const routes: Routes = [
|
|||||||
component: LoginComponent,
|
component: LoginComponent,
|
||||||
canActivate: [GuestGuard]
|
canActivate: [GuestGuard]
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// path: 'change-password',
|
|
||||||
// component: ChangePasswordComponent,
|
|
||||||
// canActivate: [LoginGuard]
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
path: '404',
|
path: '404',
|
||||||
component: NotFoundComponent
|
component: NotFoundComponent
|
||||||
@@ -41,6 +34,10 @@ export const routes: Routes = [
|
|||||||
path: '403',
|
path: '403',
|
||||||
component: ForbiddenComponent
|
component: ForbiddenComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'error',
|
||||||
|
component: GenericErrorComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: '/404'
|
redirectTo: '/404'
|
||||||
@@ -48,7 +45,9 @@ export const routes: Routes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes, {
|
||||||
|
initialNavigation: 'disabled'
|
||||||
|
})],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: []
|
providers: []
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<app-header></app-header>
|
@if (isOperationsConsoleRoute()) {
|
||||||
<div class="content">
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
} @else {
|
||||||
<app-footer></app-footer>
|
<div class="app-shell flex flex-column justify-space-between h-100">
|
||||||
|
<app-header class="flex-none"></app-header>
|
||||||
|
<div class="content flex h-100">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
<app-footer class="flex-none"></app-footer>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,20 +2,10 @@
|
|||||||
display: block;
|
display: block;
|
||||||
color: rgba(0,0,0,.54);
|
color: rgba(0,0,0,.54);
|
||||||
font-family: Roboto,"Helvetica Neue";
|
font-family: Roboto,"Helvetica Neue";
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
margin: 50px 70px;
|
padding: 20px;
|
||||||
}
|
max-height: calc(100vh - 169px);
|
||||||
|
|
||||||
@media screen and (min-width: 600px) and (max-width: 1279px) {
|
|
||||||
.content {
|
|
||||||
margin: 20px 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
|
||||||
.content {
|
|
||||||
margin: 8px 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,24 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
import {TestBed, waitForAsync} from '@angular/core/testing';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { ManagerComponent } from './manager';
|
import { MockApiService } from './services/mocks/api.service.mock';
|
||||||
import { LoginComponent } from './login';
|
|
||||||
import { MockApiService } from './service/mocks/api.service.mock';
|
|
||||||
|
|
||||||
import { LoginGuard } from './guard';
|
import { FooterComponent} from './components';
|
||||||
import { NotFoundComponent } from './not-found';
|
|
||||||
import {
|
|
||||||
FooterComponent,
|
|
||||||
GithubComponent,
|
|
||||||
} from './component';
|
|
||||||
|
|
||||||
import {
|
import {MatIconRegistry} from '@angular/material/icon';
|
||||||
MatToolbarModule,
|
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||||
MatIconRegistry
|
|
||||||
} from '@angular/material';
|
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiService,
|
ApiService,
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService,
|
UserService,
|
||||||
FooService,
|
|
||||||
ConfigService
|
ConfigService
|
||||||
} from './service';
|
} from './services';
|
||||||
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@@ -48,14 +36,13 @@ describe('AppComponent', () => {
|
|||||||
},
|
},
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService,
|
UserService,
|
||||||
FooService,
|
|
||||||
ConfigService
|
ConfigService
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create the app', async(() => {
|
it('should create the app', waitForAsync(() => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.debugElement.componentInstance;
|
const app = fixture.debugElement.componentInstance;
|
||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
// I remove temporary fontawesome5 and downgrade to fontawesome4
|
import fontawesome from '@fortawesome/fontawesome';
|
||||||
import fontawesome from '@fortawesome/fontawesome';
|
import {
|
||||||
import solid from '@fortawesome/fontawesome-free-solid/';
|
faCheckCircle,
|
||||||
fontawesome.library.add(solid);
|
faExclamationCircle,
|
||||||
|
faExclamationTriangle,
|
||||||
|
faPause,
|
||||||
|
faPlay,
|
||||||
|
faTimesCircle
|
||||||
|
} from '@fortawesome/fontawesome-free-solid';
|
||||||
|
|
||||||
|
fontawesome.library.add(faCheckCircle, faExclamationCircle, faExclamationTriangle, faPause, faPlay, faTimesCircle);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: false
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
}
|
constructor(private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
isOperationsConsoleRoute(): boolean {
|
||||||
|
const url = this.router.url || '/';
|
||||||
|
return url === '/' || url.startsWith('/manager');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,146 +1,149 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule, APP_INITIALIZER} from '@angular/core';
|
import { NgModule, APP_INITIALIZER} from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { HttpModule } from '@angular/http';
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
|
||||||
|
import {JWT_OPTIONS, JwtModule} from '@auth0/angular-jwt';
|
||||||
|
|
||||||
// material
|
// material
|
||||||
import {
|
import {MatIconRegistry} from '@angular/material/icon';
|
||||||
MatButtonModule,
|
import {MatInputModule} from '@angular/material/input';
|
||||||
MatMenuModule,
|
import {MatChipsModule} from '@angular/material/chips';
|
||||||
MatIconModule,
|
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||||
MatToolbarModule,
|
import {MatProgressBarModule} from '@angular/material/progress-bar';
|
||||||
MatTooltipModule,
|
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||||
MatCardModule,
|
import {MatMenuModule} from '@angular/material/menu';
|
||||||
MatChipsModule,
|
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||||
MatInputModule,
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
MatIconRegistry,
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
MatProgressSpinnerModule,
|
import {MatCardModule} from '@angular/material/card';
|
||||||
MatProgressBarModule,
|
import {MatSelectModule} from '@angular/material/select';
|
||||||
} from '@angular/material';
|
import {MatListModule} from '@angular/material/list';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import {MatDialogModule} from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import {OwlDateTimeModule, OwlNativeDateTimeModule} from '@danielmoncada/angular-datetime-picker';
|
||||||
|
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { ManagerComponent } from './manager';
|
import { ManagerComponent } from './views/manager';
|
||||||
import { LoginComponent } from './login';
|
import { LoginComponent } from './views/login';
|
||||||
import { LoginGuard, GuestGuard, AdminGuard } from './guard';
|
import { LoginGuard, GuestGuard, AdminGuard } from './guards';
|
||||||
import { NotFoundComponent } from './not-found';
|
import { NotFoundComponent } from './views/not-found';
|
||||||
import { AccountMenuComponent } from './component/header/account-menu/account-menu.component';
|
import { AccountMenuComponent } from './components/header/account-menu/account-menu.component';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
GithubComponent,
|
GithubComponent,
|
||||||
SchedulerConfigComponent,
|
|
||||||
SchedulerControlComponent,
|
SchedulerControlComponent,
|
||||||
LogsPanelComponent,
|
LogsPanelComponent,
|
||||||
ProgressPanelComponent
|
ProgressPanelComponent,
|
||||||
} from './component';
|
TriggerListComponent,
|
||||||
|
SimpleTriggerConfigComponent
|
||||||
|
} from './components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiService,
|
ApiService,
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService,
|
UserService,
|
||||||
FooService,
|
|
||||||
SchedulerService,
|
SchedulerService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
ProgressWebsocketService,
|
getHtmlBaseUrl,
|
||||||
LogsWebsocketService
|
LogsRxWebsocketService,
|
||||||
} from './service';
|
ProgressRxWebsocketService,
|
||||||
import { ChangePasswordComponent } from './change-password/change-password.component';
|
TriggerService,
|
||||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
CalendarService
|
||||||
|
} from './services';
|
||||||
|
import { ForbiddenComponent } from './views/forbidden/forbidden.component';
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import JobService from './services/job.service';
|
||||||
|
import {GenericErrorComponent} from './views/error/genericError.component';
|
||||||
|
|
||||||
export function initUserFactory(userService: UserService) {
|
export function initUserFactory(userService: UserService) {
|
||||||
return () => userService.jsessionInitUser();
|
return () => userService.fetchLoggedUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
// const stompConfig: StompConfig = {
|
export function jwtOptionsFactory(apiService: ApiService) {
|
||||||
// // Which server?
|
return {
|
||||||
// url: 'ws://localhost:8080/quartz-manager/progress',
|
tokenGetter: () => {
|
||||||
|
return apiService.getToken();
|
||||||
|
},
|
||||||
|
allowedDomains: ['localhost:8080', 'localhost:4200']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // Headers
|
@NgModule({ declarations: [
|
||||||
// // Typical keys: login, passcode, host
|
AppComponent,
|
||||||
// headers: {
|
HeaderComponent,
|
||||||
// login: 'admin',
|
FooterComponent,
|
||||||
// passcode: 'admin'
|
ManagerComponent,
|
||||||
// },
|
GithubComponent,
|
||||||
|
LoginComponent,
|
||||||
// // How often to heartbeat?
|
NotFoundComponent,
|
||||||
// // Interval in milliseconds, set to 0 to disable
|
AccountMenuComponent,
|
||||||
// heartbeat_in: 0, // Typical value 0 - disabled
|
SimpleTriggerConfigComponent,
|
||||||
// heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
|
SchedulerControlComponent,
|
||||||
// // Wait in milliseconds before attempting auto reconnect
|
LogsPanelComponent,
|
||||||
// // Set to 0 to disable
|
ProgressPanelComponent,
|
||||||
// // Typical value 5000 (5 seconds)
|
ForbiddenComponent,
|
||||||
// reconnect_delay: 5000,
|
GenericErrorComponent,
|
||||||
|
TriggerListComponent
|
||||||
// // Will log diagnostics on console
|
],
|
||||||
// debug: true
|
bootstrap: [AppComponent], imports: [BrowserAnimationsModule,
|
||||||
// };
|
BrowserModule,
|
||||||
|
FormsModule,
|
||||||
@NgModule({
|
ReactiveFormsModule,
|
||||||
declarations: [
|
AppRoutingModule,
|
||||||
AppComponent,
|
JwtModule.forRoot({
|
||||||
HeaderComponent,
|
jwtOptionsProvider: {
|
||||||
FooterComponent,
|
provide: JWT_OPTIONS,
|
||||||
ManagerComponent,
|
useFactory: jwtOptionsFactory,
|
||||||
GithubComponent,
|
deps: [ApiService]
|
||||||
LoginComponent,
|
}
|
||||||
NotFoundComponent,
|
}),
|
||||||
AccountMenuComponent,
|
MatDialogModule,
|
||||||
SchedulerConfigComponent,
|
MatMenuModule,
|
||||||
SchedulerControlComponent,
|
MatTooltipModule,
|
||||||
LogsPanelComponent,
|
MatButtonModule,
|
||||||
ProgressPanelComponent,
|
MatChipsModule,
|
||||||
ChangePasswordComponent,
|
MatIconModule,
|
||||||
ForbiddenComponent
|
MatInputModule,
|
||||||
],
|
MatSelectModule,
|
||||||
imports: [
|
MatToolbarModule,
|
||||||
BrowserAnimationsModule,
|
MatCardModule,
|
||||||
BrowserModule,
|
MatListModule,
|
||||||
FormsModule,
|
MatProgressSpinnerModule,
|
||||||
ReactiveFormsModule,
|
MatProgressBarModule,
|
||||||
HttpModule,
|
OwlDateTimeModule,
|
||||||
HttpClientModule,
|
OwlNativeDateTimeModule,
|
||||||
AppRoutingModule,
|
MatSidenavModule,
|
||||||
MatMenuModule,
|
], providers: [
|
||||||
MatTooltipModule,
|
{
|
||||||
MatButtonModule,
|
provide: APP_BASE_HREF,
|
||||||
MatChipsModule,
|
useValue: getHtmlBaseUrl()
|
||||||
MatIconModule,
|
},
|
||||||
MatInputModule,
|
{
|
||||||
MatToolbarModule,
|
'provide': APP_INITIALIZER,
|
||||||
MatCardModule,
|
'useFactory': initUserFactory,
|
||||||
MatProgressSpinnerModule,
|
'deps': [UserService],
|
||||||
MatProgressBarModule,
|
'multi': true
|
||||||
FlexLayoutModule
|
},
|
||||||
],
|
LoginGuard,
|
||||||
providers: [
|
GuestGuard,
|
||||||
LoginGuard,
|
AdminGuard,
|
||||||
GuestGuard,
|
SchedulerService,
|
||||||
AdminGuard,
|
JobService,
|
||||||
FooService,
|
TriggerService,
|
||||||
SchedulerService,
|
CalendarService,
|
||||||
ProgressWebsocketService,
|
ProgressRxWebsocketService,
|
||||||
LogsWebsocketService,
|
LogsRxWebsocketService,
|
||||||
AuthService,
|
AuthService,
|
||||||
ApiService,
|
ApiService,
|
||||||
UserService,
|
UserService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
MatIconRegistry,
|
MatIconRegistry,
|
||||||
{
|
provideHttpClient(withInterceptorsFromDi())
|
||||||
'provide': APP_INITIALIZER,
|
] })
|
||||||
'useFactory': initUserFactory,
|
|
||||||
'deps': [UserService],
|
|
||||||
'multi': true
|
|
||||||
}
|
|
||||||
// StompService,
|
|
||||||
// ServerSocket
|
|
||||||
// {
|
|
||||||
// provide: StompConfig,
|
|
||||||
// useValue: stompConfig
|
|
||||||
// }
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="content" fxLayout="row" fxLayoutAlign="center">
|
|
||||||
<mat-card elevation="5" fxFlex>
|
|
||||||
<mat-card-subtitle>Change Your Password</mat-card-subtitle>
|
|
||||||
<p [class]="notification.msgType" *ngIf="notification">{{notification.msgBody}}</p>
|
|
||||||
<mat-card-content>
|
|
||||||
<form *ngIf="!submitted" [formGroup]="form" (ngSubmit)="onSubmit()" #changePasswordForm="ngForm">
|
|
||||||
<mat-form-field>
|
|
||||||
<input matInput formControlName="oldPassword" required type="password" placeholder="old password">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field>
|
|
||||||
<input matInput formControlName="newPassword" required type="password" placeholder="new password">
|
|
||||||
</mat-form-field>
|
|
||||||
<button type="submit" [disabled]="!changePasswordForm.form.valid" mat-raised-button color="primary">Change Password</button>
|
|
||||||
</form>
|
|
||||||
<mat-spinner *ngIf="submitted" mode="indeterminate"></mat-spinner>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card {
|
|
||||||
max-width: 350px;
|
|
||||||
text-align: center;
|
|
||||||
animation: fadein 1s;
|
|
||||||
-o-animation: fadein 1s; /* Opera */
|
|
||||||
-moz-animation: fadein 1s; /* Firefox */
|
|
||||||
-webkit-animation: fadein 1s; /* Safari and Chrome */
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-form-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-spinner {
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
margin: 20px auto 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #D50000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #8BC34A;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
|
||||||
|
|
||||||
.content {
|
|
||||||
/* https://github.com/angular/flex-layout/issues/295 */
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-card {
|
|
||||||
/* https://github.com/angular/flex-layout/issues/295 */
|
|
||||||
display: block !important;
|
|
||||||
max-width: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import {
|
|
||||||
ApiService,
|
|
||||||
AuthService,
|
|
||||||
UserService,
|
|
||||||
ConfigService
|
|
||||||
} from '../service';
|
|
||||||
import { MockApiService } from '../service/mocks';
|
|
||||||
|
|
||||||
import { ChangePasswordComponent } from './change-password.component';
|
|
||||||
|
|
||||||
describe('ChangePasswordComponent', () => {
|
|
||||||
let component: ChangePasswordComponent;
|
|
||||||
let fixture: ComponentFixture<ChangePasswordComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
RouterTestingModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
ChangePasswordComponent
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: ApiService,
|
|
||||||
useClass: MockApiService
|
|
||||||
},
|
|
||||||
AuthService,
|
|
||||||
UserService,
|
|
||||||
ConfigService
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ChangePasswordComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
||||||
import { AuthService } from 'app/service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { DisplayMessage } from '../shared/models/display-message';
|
|
||||||
import { delay, mergeMap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-change-password',
|
|
||||||
templateUrl: './change-password.component.html',
|
|
||||||
styleUrls: ['./change-password.component.scss']
|
|
||||||
})
|
|
||||||
export class ChangePasswordComponent implements OnInit {
|
|
||||||
|
|
||||||
form: FormGroup;
|
|
||||||
/**
|
|
||||||
* Boolean used in telling the UI
|
|
||||||
* that the form has been submitted
|
|
||||||
* and is awaiting a response
|
|
||||||
*/
|
|
||||||
submitted = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Diagnostic message from received
|
|
||||||
* form request error
|
|
||||||
*/
|
|
||||||
notification: DisplayMessage;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private authService: AuthService,
|
|
||||||
private router: Router,
|
|
||||||
private formBuilder: FormBuilder
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
|
|
||||||
this.form = this.formBuilder.group({
|
|
||||||
oldPassword: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(64)])],
|
|
||||||
newPassword: ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(32)])]
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
/**
|
|
||||||
* Innocent until proven guilty
|
|
||||||
*/
|
|
||||||
this.notification = undefined;
|
|
||||||
this.submitted = true;
|
|
||||||
|
|
||||||
this.authService.changePassowrd(this.form.value)
|
|
||||||
.pipe(delay(1000), mergeMap(() => this.authService.logout()))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.router.navigate(['/login', { msgType: 'success', msgBody: 'Success! Please sign in with your new password.'}]);
|
|
||||||
}, error => {
|
|
||||||
this.submitted = false;
|
|
||||||
this.notification = { msgType: 'error', msgBody: 'Invalid old password.'};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './change-password.component';
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<p style="margin: 0px auto; padding: 0px; color: rgba(255, 255, 255, 0.541176); max-width: 356px;">
|
|
||||||
Hand crafted with love by
|
|
||||||
<a href="https://github.com/fabioformosa" style="color: rgba(255, 255, 255, 0.870588);">Fabio Formosa</a>
|
|
||||||
</p>
|
|
||||||
<a style="margin-top: 22px;" mat-icon-button href="https://github.com/fabioformosa/quartz-manager">
|
|
||||||
<img src="assets/image/github.png"/>
|
|
||||||
</a>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
<button mat-menu-item [routerLink]="['/change-password']">CHANGE PASSWORD</button>
|
|
||||||
<button mat-menu-item (click)="logout()">SIGN OUT</button>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<mat-toolbar color="primary" class="app-navbar">
|
|
||||||
<button mat-button mat-ripple routerLink="/">
|
|
||||||
<img alt="Quartz Manager" class="app-angular-logo" src="assets/image/angular-white-transparent.svg">
|
|
||||||
<span>Quartz Manager</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="right">
|
|
||||||
<div fxFlex="1 1 auto" fxLayout="row" fxLayoutAlign="flex-end center">
|
|
||||||
<button *ngIf="!hasSignedIn()" routerLink="/signup" mat-button mat-ripple>
|
|
||||||
<span>Sign up</span>
|
|
||||||
</button>
|
|
||||||
<button *ngIf="!hasSignedIn()" routerLink="/login" mat-button mat-ripple>
|
|
||||||
<span>Login</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="greeting-button"
|
|
||||||
*ngIf="hasSignedIn()"
|
|
||||||
mat-button mat-ripple
|
|
||||||
[matMenuTriggerFor]="accountMenu">
|
|
||||||
<span>Hi, {{userName()}}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="greeting-hamburger"
|
|
||||||
*ngIf="hasSignedIn()"
|
|
||||||
mat-icon-button mat-ripple
|
|
||||||
[matMenuTriggerFor]="accountMenu">
|
|
||||||
<mat-icon>menu</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-menu #accountMenu
|
|
||||||
class="app-header-accountMenu"
|
|
||||||
yposition="below"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<app-account-menu ></app-account-menu>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title><b>JOB LOGS</b></mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<div id="logs">
|
|
||||||
<div *ngFor = "let log of logs" fxLayout="row" fxLayout.xs="column">
|
|
||||||
<div fxFlex="1 1 20%">
|
|
||||||
<span [ngClass]="{'animated zoomIn firstLog': $first}"> [{{log.time|date:'medium'}}]</span>
|
|
||||||
</div>
|
|
||||||
<div fxFlex="1 1 10%">
|
|
||||||
<span [ngClass]="{'animated zoomIn firstLog': $first}">
|
|
||||||
<i class = "fas" [ngClass]="{'fa-check-circle green': log.type == 'INFO',
|
|
||||||
'fa-exclamation-triangle yellow': log.type == 'WARN',
|
|
||||||
'fa-times-circle red': log.type == 'ERROR'}"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div fxFlex="1 1 20%">
|
|
||||||
<span [ngClass]="{'animated zoomIn firstLog': $first}">
|
|
||||||
{{log.threadName}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div fxFlex="1 1 50%">
|
|
||||||
<span [ngClass]="{'animated zoomIn firstLog': $first}"> {{log.msg}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.red{
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.green{
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yellow{
|
|
||||||
color: gold;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
|
||||||
|
|
||||||
import { LogsWebsocketService, ApiService } from '../../service';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'logs-panel',
|
|
||||||
templateUrl: './logs-panel.component.html',
|
|
||||||
styleUrls: ['./logs-panel.component.scss']
|
|
||||||
})
|
|
||||||
export class LogsPanelComponent implements OnInit {
|
|
||||||
|
|
||||||
MAX_LOGS : number = 20;
|
|
||||||
|
|
||||||
logs : Array<any> = new Array();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private logsWebsocketService: LogsWebsocketService,
|
|
||||||
private apiService : ApiService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
let obs = this.logsWebsocketService.getObservable()
|
|
||||||
obs.subscribe({
|
|
||||||
'next' : this.onNewLogMsg,
|
|
||||||
'error' : (err) => {console.log(err)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onNewLogMsg = (receivedMsg) => {
|
|
||||||
if(receivedMsg.type == 'SUCCESS')
|
|
||||||
this._showNewLog(receivedMsg.message);
|
|
||||||
else if(receivedMsg.type == 'ERROR')
|
|
||||||
this._refreshSession(); //if websocket has been closed for session expiration, try to refresh it
|
|
||||||
};
|
|
||||||
|
|
||||||
_showNewLog = (logRecord) => {
|
|
||||||
if(this.logs.length > this.MAX_LOGS)
|
|
||||||
this.logs.pop();
|
|
||||||
|
|
||||||
this.logs.unshift({
|
|
||||||
time : logRecord.date,
|
|
||||||
type : logRecord.type,
|
|
||||||
msg : logRecord.message,
|
|
||||||
threadName : logRecord.threadName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_refreshSession = () => {
|
|
||||||
this.apiService.get('/quartz-manager/session/refresh')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<!-- <div class="progress" [hidden]="progress.percentage < 0">
|
|
||||||
<div class="progress-bar"
|
|
||||||
role="progressbar"
|
|
||||||
[ngStyle]="{width: percentageStr}">
|
|
||||||
{{percentageStr}}
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<mat-card>
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title><b>JOB PROGRESS</b></mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<div>
|
|
||||||
<mat-progress-bar mode="determinate" value="{{progress.percentage}}"></mat-progress-bar>
|
|
||||||
{{percentageStr}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<mat-chip>counter</mat-chip>
|
|
||||||
<span class="animated pulse">{{progress.timesTriggered}}</span> <span ng-show="progress.repeatCount > 0">/ {{progress.repeatCount}} </span>
|
|
||||||
|
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
<mat-chip>job key</mat-chip> <span class="animated pulse">{{progress.jobKey}}</span><br>
|
|
||||||
<mat-chip>job class</mat-chip> <span class="animated pulse">{{progress.jobClass}}</span><br/>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<mat-chip>prev fire time</mat-chip> <span class="animated pulse">{{progress.previousFireTime|date:'dd-MM-yyyy HH:mm:ss'}}</span><br/>
|
|
||||||
<mat-chip>next fire time</mat-chip> <span class="animated pulse">{{progress.nextFireTime|date:'dd-MM-yyyy HH:mm:ss'}}</span><br/>
|
|
||||||
<mat-chip>final fire time</mat-chip> <span class="animated pulse">{{progress.finalFireTime|date:'dd-MM-yyyy HH:mm:ss'}}</span><br/>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
|
|
||||||
import { ProgressWebsocketService } from '../../service';
|
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
// import {Message} from '@stomp/stompjs';
|
|
||||||
|
|
||||||
// import { Subscription } from 'rxjs/Subscription';
|
|
||||||
// import {StompService} from '@stomp/ng2-stompjs';
|
|
||||||
|
|
||||||
// import { QueueingSubject } from 'queueing-subject'
|
|
||||||
// import websocketConnect from 'rxjs-websockets'
|
|
||||||
// import 'rxjs/add/operator/share'
|
|
||||||
// import {ServerSocket} from '../../service/qz.socket.service'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'progress-panel',
|
|
||||||
templateUrl: './progress-panel.component.html',
|
|
||||||
styleUrls: ['./progress-panel.component.scss']
|
|
||||||
})
|
|
||||||
export class ProgressPanelComponent implements OnInit {
|
|
||||||
|
|
||||||
progress : any = {}
|
|
||||||
percentageStr : string
|
|
||||||
|
|
||||||
// // Stream of messages
|
|
||||||
// private subscription: Subscription;
|
|
||||||
// public messages: Observable<Message>;
|
|
||||||
// // Subscription status
|
|
||||||
// public subscribed: boolean;
|
|
||||||
// // Array of historic message (bodies)
|
|
||||||
// public mq: Array<string> = [];
|
|
||||||
|
|
||||||
|
|
||||||
// private socketSubscription
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private progressWebsocketService: ProgressWebsocketService,
|
|
||||||
// private _stompService: StompService,
|
|
||||||
// private serverSocket : ServerSocket
|
|
||||||
) { }
|
|
||||||
|
|
||||||
onNewProgressMsg = (receivedMsg) => {
|
|
||||||
if (receivedMsg.type == 'SUCCESS') {
|
|
||||||
var newStatus = receivedMsg.message;
|
|
||||||
this.progress = newStatus;
|
|
||||||
this.percentageStr = this.progress.percentage + '%';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
let obs = this.progressWebsocketService.getObservable()
|
|
||||||
obs.subscribe({
|
|
||||||
'next' : this.onNewProgressMsg,
|
|
||||||
'error' : (err) => {console.log(err)}
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.subscribed = false;
|
|
||||||
// this.subscribe();
|
|
||||||
|
|
||||||
// this.serverSocket.connect()
|
|
||||||
// this.socketSubscription = this.serverSocket.messages.subscribe((message: string) => {
|
|
||||||
// console.log('received message from server: ', message)
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
// public subscribe() {
|
|
||||||
// if (this.subscribed) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Stream of messages
|
|
||||||
// this.messages = this._stompService.subscribe('/topic/progress');
|
|
||||||
|
|
||||||
// // Subscribe a function to be run on_next message
|
|
||||||
// this.subscription = this.messages.subscribe(this.on_next);
|
|
||||||
|
|
||||||
// this.subscribed = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public on_next = (message: Message) => {
|
|
||||||
// this.mq.push(message.body + '\n');
|
|
||||||
// console.log(message);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './scheduler-config.component';
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user