mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-15 06:10:29 +09:00
Compare commits
112 Commits
angular5
...
quartz-man
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b01f4b99e4 | ||
|
|
d11089c451 | ||
|
|
86c2e86a12 | ||
|
|
54662a1b8a | ||
|
|
6cdee3ba5d | ||
|
|
c6df1e2093 | ||
|
|
663b3f4e09 | ||
|
|
c29af8c593 | ||
|
|
a7a86f960a | ||
|
|
56c81caf05 | ||
|
|
8c5b349196 | ||
|
|
1f631529d9 | ||
|
|
6f50f45aa8 | ||
|
|
e23343d7ed | ||
|
|
ef4ffca2bd | ||
|
|
382fe244c5 | ||
|
|
d374be5eee | ||
|
|
e28787d29b | ||
|
|
79e080ea1f | ||
|
|
0087488d5f | ||
|
|
e56ac71d60 | ||
|
|
fc64f07d7f | ||
|
|
b0868de37b | ||
|
|
d950ff29b2 | ||
|
|
10bea2311e | ||
|
|
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 | ||
|
|
df59999034 | ||
|
|
1e6f89678c | ||
|
|
4103779e4e | ||
|
|
919393e58c | ||
|
|
a1f279c161 | ||
|
|
03a4d68993 | ||
|
|
cb6dd7933a | ||
|
|
bcff3ebce7 | ||
|
|
8ec980763b | ||
|
|
139e6871bf | ||
|
|
bde642cf52 | ||
|
|
fc8101aa40 | ||
|
|
8dfd627c23 | ||
|
|
3199437c8c |
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
|
||||||
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.
|
||||||
74
README.md
74
README.md
@@ -1,32 +1,62 @@
|
|||||||
#QUARTZ MANAGER
|
[](https://gitter.im/quartz-manager/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
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
|
||||||
|
UI Manager for Quartz Scheduler.
|
||||||
|
|
||||||
## SCREENSHOT
|
Through this webapp you can launch and control your scheduled job. The UI Console is composed by a management panel to set trigger, start/stop scheduler and a log panel with a progress bar to display the job output.
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
## HOW IT WORKS
|
## HOW IT WORKS
|
||||||
* Set up the trigger into the left sidebar in terms of: daily frequency and and max occurrences.
|
* Set up the trigger into the left sidebar in terms of: daily frequency and and max occurrences.
|
||||||
* Press the start button
|
* Press the start button
|
||||||
* The GUI manager updates the progress bar and reports all logs of your quartz job.
|
* The GUI manager updates the progress bar and reports all logs of your quartz job.
|
||||||
|
|
||||||
## QUICK START
|
## ROADMAP
|
||||||
|
Open the [Project Roadmap](https://github.com/fabioformosa/quartz-manager/projects) to take a look at the plan of Quartz Manager.
|
||||||
|
Currently this project might be useful to look how to import Quartz Library in a spring boot application. For this purpose, browse the folder `quartz-manager-parent/quartz-manager-api`.
|
||||||
|
We're just working to create a library, from project `quartz-manager-parent/quartz-manager-api`, to be imported in your spring boot where you have your job to be scheduled.
|
||||||
|
|
||||||
|
Take a loot to the project [Quartz-Manager Demo](https://github.com/fabioformosa/quartz-manager-demo), it is an example of how-to:
|
||||||
|
* import the quartz-manager-api library in your webapp
|
||||||
|
* include the quartz-manager frontend (angular based) through a webjar
|
||||||
|
* set properties into the application.yml
|
||||||
|
* add a secure layer to allow the API only to logged users
|
||||||
|
* schedule a custom job (a dummy `hello world`)
|
||||||
|
|
||||||
|
**NB: In few days, we'll release the library jar of quartz-manager into the maven central repo.**
|
||||||
|
|
||||||
|
Next steps in the roadmap are:
|
||||||
|
* to add a persistent layer to save all job setup.
|
||||||
|
* to add a complete setup UI panel for quartz, in term of cronjobs and multiple jobs.
|
||||||
|
* to add CI/CD pipeline to ease the deploy pulling a docker container.
|
||||||
|
* Enabling adapters for integrations: kafka, etc.
|
||||||
|
|
||||||
|
## PROJECT STRUCTURE
|
||||||
|
* **quartz-parent/quartz-manager-api** is the library that can be imported in webapp to have the quartz-manager API.
|
||||||
|
* **quartz-parent/quartz-manager-webjar** is a maven module to build and package the angular frontend in a webjar.
|
||||||
|
* **quartz-parent/quartz-manager-security** is ther library that can be imported in a webapp to have a security layer (login) over the quartz-manager API.
|
||||||
|
* **quartz-parent/quartz-manager-web-showcase** is an example of webapp that imports quartz-manager-api. Useful to develop the frontend started locally with the webpack dev server.
|
||||||
|
* **quartz-frontend** is the angular app that interacts with the Quartz Manager API.
|
||||||
|
|
||||||
|
## HOW-TO CONTRIBUTE
|
||||||
**[requirements]** Make sure you have installed
|
**[requirements]** Make sure you have installed
|
||||||
* [Java 8](https://java.com/download/) or greater
|
* [Java 8](https://java.com/download/) or greater
|
||||||
* [Maven](https://maven.apache.org/)
|
* [Maven](https://maven.apache.org/)
|
||||||
* [npm](https://www.npmjs.com/get-npm), [node](https://nodejs.org) and [angular-cli](https://cli.angular.io/)
|
* [npm](https://www.npmjs.com/get-npm), [node](https://nodejs.org) and [angular-cli](https://cli.angular.io/)
|
||||||
|
|
||||||
|
To build&run quartz-manager in your machine:
|
||||||
|
|
||||||
```
|
```
|
||||||
#CLONE REPOSITORY
|
#CLONE REPOSITORY
|
||||||
git clone https://github.com/fabioformosa/quartz-manager.git
|
git clone https://github.com/fabioformosa/quartz-manager.git
|
||||||
|
|
||||||
# START QUARTZ-MANAGER-BACKEND
|
# START QUARTZ-MANAGER-WEB
|
||||||
cd quartz-manager/quartz-manager-backend
|
cd quartz-manager/quartz-parent/quartz-manager-web
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
|
|
||||||
# START QUARTZ-MANAGER-FRONTEND
|
# START QUARTZ-MANAGER-FRONTEND
|
||||||
cd quartz-manager/quartz-manager-backend
|
cd quartz-manager/quartz-manager-frontend
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
|
|
||||||
@@ -37,34 +67,42 @@ npm start
|
|||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
||||||
|
|
||||||
## HOW TO RUN YOUR SCHEDULED JOB
|
## HOW TO RUN YOUR SCHEDULED JOB
|
||||||
By default, quartz-manager executes the dummy job that logs "hello world!".
|
By default, quartz-manager-web executes the dummy job that logs "hello world!".
|
||||||
Replace the dummy job (class: `it.fabioformosa.quartzmanager.jobs.SampleJob`) with yours. Follow these steps:
|
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`
|
1. Extend the super class `it.fabioformosa.quartzmanager.jobs.AbstractLoggingJob`
|
||||||
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
|
1. set property `quartz-manager.jobClass` with qualified name of your custom Job Class (default job is SampleJob.class)
|
||||||
|
|
||||||
## HOW TO CHANGE SETTINGS
|
## HOW TO CHANGE SETTINGS
|
||||||
* Num of Threads: `/quartz-manager/src/main/resources/quartz.properties`
|
* Num of Threads: `/quartz-manager-parent/quartz-manager-web/src/main/resources/quartz.properties`
|
||||||
* Credentials: `it.fabioformosa.quartzmanager.configuration.WebSecurityConfig`
|
* Credentials: To change admin's password, set ENV var `quartz-manager.account.pwd`
|
||||||
* quartz-manager backend context path (default `/quartz-manager`) and port (default `8080`): `/quartz-manager/src/main/resources/application.properties`
|
* quartz-manager backend context path (default `/quartz-manager`) and port (default `8080`): `/quartz-manager/src/main/resources/application.properties`
|
||||||
|
|
||||||
|
## HOW TO BROWSE REST API DOC
|
||||||
|
Swagger has been added as library. So, you can get REST API doc opening: [http://localhost:8080/quartz-manager/swagger-ui.html](http://localhost:8080/quartz-manager/swagger-ui.html)
|
||||||
|
|
||||||
## Tech Overview
|
## Tech Overview
|
||||||
|
|
||||||
**Backend Stack** Java 8, Spring Boot 1.5.9 (Spring MVC 4.3.13, Spring Security 4.2.3, Spring AOP 4.3.13), Quartz Scheduler 2.2.2
|
**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
|
||||||
|
|
||||||
**Application Server** Tomcat (embedded)
|
**Application Server** Tomcat (embedded)
|
||||||
|
|
||||||
**Frontend** Angular 5.2.0, Web-Socket (stompjs 2.3.3)
|
**Frontend** Angular 9.1.4, Web-Socket (stompjs 2.3.3)
|
||||||
|
|
||||||
**Style** angular material, FontAwesome 5
|
**Style** angular material, FontAwesome 5
|
||||||
|
|
||||||
From quartz manager ver 2.x.x, the new structure of project is:
|
From quartz manager ver 2.x.x, the new structure of project is:
|
||||||
* REST backend (java based, using [http://www.quartz-scheduler.org/](http://www.quartz-scheduler.org/)
|
* REST API backend
|
||||||
* Single Page Application frontend (angular 5)
|
* Single Page Application frontend (angular 9)
|
||||||
|
|
||||||
(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 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)
|
||||||
|
|
||||||
|
## Contributes
|
||||||
|
|
||||||
|
Every contribution is welcome. Open a issue, so we can discuss about new features and implement them.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* this project has been created from [angular-spring-starter](https://github.com/bfwg/angular-spring-starter)
|
* this project has been created from [angular-spring-starter](https://github.com/bfwg/angular-spring-starter)
|
||||||
|
|||||||
@@ -1,190 +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.0.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-starter-parent</artifactId>
|
|
||||||
<version>1.5.9.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>
|
|
||||||
<version>3.0.5</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
|
||||||
<artifactId>quartz</artifactId>
|
|
||||||
<version>2.2.2</version>
|
|
||||||
</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>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor.spring</groupId>
|
|
||||||
<artifactId>reactor-spring-context</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.netty</groupId>
|
|
||||||
<artifactId>netty-all</artifactId>
|
|
||||||
<version>4.0.31.Final</version>
|
|
||||||
</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.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,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,93 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
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<?> 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,75 +0,0 @@
|
|||||||
package it.fabioformosa.quartzmanager.configuration;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
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.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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
auth.inMemoryAuthentication().withUser("admin").password("admin").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,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,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,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,37 +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) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
Authority auth = this.authorityRepository.findOne(id);
|
|
||||||
List<Authority> auths = new ArrayList<>();
|
|
||||||
auths.add(auth);
|
|
||||||
return auths;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Authority> findByname(String name) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
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.findOne(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,30 +0,0 @@
|
|||||||
server:
|
|
||||||
context-path: /quartz-manager
|
|
||||||
port: 8080
|
|
||||||
session.timeout : 28800
|
|
||||||
|
|
||||||
spring:
|
|
||||||
thymeleaf:
|
|
||||||
cache: false
|
|
||||||
mode: LEGACYHTML5
|
|
||||||
|
|
||||||
quartz:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
job:
|
|
||||||
frequency: 4000
|
|
||||||
repeatCount: 19
|
|
||||||
|
|
||||||
logging:
|
|
||||||
level:
|
|
||||||
org.springframework.web: WARN
|
|
||||||
it.fabioformosa: INFO
|
|
||||||
|
|
||||||
app:
|
|
||||||
name: quartz-manager
|
|
||||||
|
|
||||||
jwt:
|
|
||||||
header: Authorization
|
|
||||||
expires_in: 600 # 10 minutes
|
|
||||||
secret: queenvictoria
|
|
||||||
cookie: AUTH-TOKEN
|
|
||||||
@@ -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', '$2a$04$Vbug2lwwJGrvUXTj6z7ff.97IzVBkrJ1XfApfGNl.Z695zqcnPYra', 'John', 'Doe');
|
|
||||||
INSERT INTO user (id, username, password, firstname, lastname) VALUES (2, 'admin', '$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,57 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"project": {
|
|
||||||
"name": "angular-spring-starter"
|
|
||||||
},
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"root": "src",
|
|
||||||
"outDir": "../server/src/main/resources/static",
|
|
||||||
"assets": [
|
|
||||||
"assets",
|
|
||||||
"favicon.ico"
|
|
||||||
],
|
|
||||||
"index": "index.html",
|
|
||||||
"main": "main.ts",
|
|
||||||
"polyfills": "polyfills.ts",
|
|
||||||
"test": "test.ts",
|
|
||||||
"tsconfig": "tsconfig.app.json",
|
|
||||||
"testTsconfig": "tsconfig.spec.json",
|
|
||||||
"prefix": "app",
|
|
||||||
"styles": [
|
|
||||||
"styles.css"
|
|
||||||
],
|
|
||||||
"scripts": [],
|
|
||||||
"environmentSource": "environments/environment.ts",
|
|
||||||
"environments": {
|
|
||||||
"dev": "environments/environment.ts",
|
|
||||||
"prod": "environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"e2e": {
|
|
||||||
"protractor": {
|
|
||||||
"config": "./protractor.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": [
|
|
||||||
{
|
|
||||||
"project": "src/tsconfig.app.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"project": "src/tsconfig.spec.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"project": "e2e/tsconfig.e2e.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"test": {
|
|
||||||
"karma": {
|
|
||||||
"config": "./karma.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaults": {
|
|
||||||
"styleExt": "css",
|
|
||||||
"component": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
118
quartz-manager-frontend/angular.json
Normal file
118
quartz-manager-frontend/angular.json
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"angular-spring-starter": {
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"aot": true,
|
||||||
|
"outputPath": "dist",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"tsConfig": "src/tsconfig.app.json",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
"src/favicon.ico"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "6kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"aot": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "angular-spring-starter:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "angular-spring-starter:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "angular-spring-starter:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"src/tsconfig.app.json",
|
||||||
|
"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/angular:component": {
|
||||||
|
"prefix": "app",
|
||||||
|
"style": "css"
|
||||||
|
},
|
||||||
|
"@schematics/angular:directive": {
|
||||||
|
"prefix": "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
quartz-manager-frontend/browserslist
Normal file
12
quartz-manager-frontend/browserslist
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
|
||||||
|
|
||||||
|
> 0.5%
|
||||||
|
last 2 versions
|
||||||
|
Firefox ESR
|
||||||
|
not dead
|
||||||
|
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||||
1
quartz-manager-frontend/jest.setup.ts
Normal file
1
quartz-manager-frontend/jest.setup.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import 'jest-preset-angular';
|
||||||
@@ -4,33 +4,31 @@
|
|||||||
module.exports = function (config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
basePath: '',
|
basePath: '',
|
||||||
frameworks: ['jasmine', '@angular/cli'],
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
plugins: [
|
plugins: [
|
||||||
require('karma-jasmine'),
|
require('karma-jasmine'),
|
||||||
require('karma-chrome-launcher'),
|
require('karma-chrome-launcher'),
|
||||||
require('karma-jasmine-html-reporter'),
|
require('karma-jasmine-html-reporter'),
|
||||||
require('karma-coverage-istanbul-reporter'),
|
require('karma-coverage-istanbul-reporter'),
|
||||||
require('@angular/cli/plugins/karma')
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
],
|
],
|
||||||
client:{
|
client:{
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
{ pattern: './src/test.ts', watched: false }
|
|
||||||
],
|
],
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'./src/test.ts': ['@angular/cli']
|
|
||||||
},
|
},
|
||||||
mime: {
|
mime: {
|
||||||
'text/x-typescript': ['ts','tsx']
|
'text/x-typescript': ['ts','tsx']
|
||||||
},
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
reports: [ 'html', 'lcovonly' ],
|
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
||||||
fixWebpackSourcePaths: true
|
fixWebpackSourcePaths: true
|
||||||
},
|
},
|
||||||
angularCli: {
|
|
||||||
environment: 'dev'
|
|
||||||
},
|
|
||||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
reporters: config.angularCli && config.angularCli.codeCoverage
|
||||||
? ['progress', 'coverage-istanbul']
|
? ['progress', 'coverage-istanbul']
|
||||||
: ['progress', 'kjhtml'],
|
: ['progress', 'kjhtml'],
|
||||||
|
|||||||
@@ -5,48 +5,53 @@
|
|||||||
"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 --prod",
|
||||||
"test": "ng test",
|
"test": "jest",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e"
|
"e2e": "ng e2e"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "5.2.8",
|
"@angular/animations": "9.1.4",
|
||||||
"@angular/cdk": "5.2.4",
|
"@angular/cdk": "9.2.1",
|
||||||
"@angular/common": "5.2.8",
|
"@angular/common": "9.1.4",
|
||||||
"@angular/compiler": "5.2.8",
|
"@angular/compiler": "9.1.4",
|
||||||
"@angular/core": "5.2.8",
|
"@angular/core": "9.1.4",
|
||||||
"@angular/flex-layout": "5.0.0-beta.13",
|
"@angular/flex-layout": "9.0.0-beta.29",
|
||||||
"@angular/forms": "5.2.8",
|
"@angular/forms": "9.1.4",
|
||||||
"@angular/http": "5.2.8",
|
"@angular/material": "9.2.1",
|
||||||
"@angular/material": "5.2.4",
|
"@angular/platform-browser": "9.1.4",
|
||||||
"@angular/platform-browser": "5.2.8",
|
"@angular/platform-browser-dynamic": "9.1.4",
|
||||||
"@angular/platform-browser-dynamic": "5.2.8",
|
"@angular/platform-server": "9.1.4",
|
||||||
"@angular/platform-server": "5.2.8",
|
"@angular/router": "9.1.4",
|
||||||
"@angular/router": "5.2.8",
|
"@auth0/angular-jwt": "^4.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/ng2-stompjs": "^0.6.3",
|
||||||
"core-js": "2.5.1",
|
"core-js": "2.5.1",
|
||||||
"hammerjs": "2.0.8",
|
"hammerjs": "2.0.8",
|
||||||
"rxjs": "5.5.2",
|
"net": "^1.0.2",
|
||||||
|
"rxjs": "6.5.5",
|
||||||
"stompjs": "^2.3.3",
|
"stompjs": "^2.3.3",
|
||||||
"zone.js": "0.8.18"
|
"tslib": "^1.10.0",
|
||||||
|
"zone.js": "~0.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/core": "^0.2.0",
|
"@angular-devkit/build-angular": "~0.901.4",
|
||||||
"@angular/cli": "1.5.3",
|
"@angular-devkit/core": "^9.1.4",
|
||||||
"@angular/compiler-cli": "5.2.8",
|
"@angular/cli": "9.1.4",
|
||||||
"@angular/language-service": "5.2.8",
|
"@angular/compiler-cli": "9.1.4",
|
||||||
|
"@angular/language-service": "9.1.4",
|
||||||
"@types/hammerjs": "2.0.34",
|
"@types/hammerjs": "2.0.34",
|
||||||
"@types/jasmine": "2.5.54",
|
"@types/jasmine": "2.5.54",
|
||||||
"@types/jasminewd2": "2.0.3",
|
"@types/jasminewd2": "2.0.3",
|
||||||
"@types/node": "6.0.90",
|
"@types/node": "^12.11.1",
|
||||||
"codelyzer": "4.2.1",
|
"codelyzer": "^5.1.2",
|
||||||
"jasmine-core": "2.6.4",
|
"jasmine-core": "2.6.4",
|
||||||
"jasmine-spec-reporter": "4.1.1",
|
"jasmine-spec-reporter": "4.1.1",
|
||||||
|
"jest": "^26.0.1",
|
||||||
|
"jest-preset-angular": "^8.2.0",
|
||||||
"karma": "1.7.1",
|
"karma": "1.7.1",
|
||||||
"karma-chrome-launcher": "2.1.1",
|
"karma-chrome-launcher": "2.1.1",
|
||||||
"karma-cli": "1.0.1",
|
"karma-cli": "1.0.1",
|
||||||
@@ -56,6 +61,10 @@
|
|||||||
"protractor": "5.1.2",
|
"protractor": "5.1.2",
|
||||||
"ts-node": "3.0.6",
|
"ts-node": "3.0.6",
|
||||||
"tslint": "5.7.0",
|
"tslint": "5.7.0",
|
||||||
"typescript": "2.4.2"
|
"typescript": "3.8.3"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-preset-angular",
|
||||||
|
"setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { LoginComponent } from './login';
|
import { LoginComponent } from './views/login';
|
||||||
import { LoginGuard } from './guard';
|
import { LoginGuard } from './guards';
|
||||||
import { GuestGuard, AdminGuard } from './guard';
|
import { GuestGuard, AdminGuard } from './guards';
|
||||||
import { NotFoundComponent } from './not-found';
|
import { NotFoundComponent } from './views/not-found';
|
||||||
import { ChangePasswordComponent } from './change-password';
|
import { ChangePasswordComponent } from './views/change-password';
|
||||||
import { ForbiddenComponent } from './forbidden';
|
import { ForbiddenComponent } from './views/forbidden';
|
||||||
|
|
||||||
import { ManagerComponent } from './manager';
|
import { ManagerComponent } from './views/manager';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,34 +2,20 @@ import { TestBed, async } 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 { HomeComponent } from './home';
|
import { MockApiService } from './services/mocks/api.service.mock';
|
||||||
import { ManagerComponent } from './manager';
|
|
||||||
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 {
|
|
||||||
ApiCardComponent,
|
|
||||||
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(async(() => {
|
||||||
@@ -50,7 +36,6 @@ describe('AppComponent', () => {
|
|||||||
},
|
},
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService,
|
UserService,
|
||||||
FooService,
|
|
||||||
ConfigService
|
ConfigService
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
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 { HttpClientModule } 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,
|
|
||||||
} from '@angular/material';
|
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
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,
|
||||||
@@ -35,25 +36,28 @@ import {
|
|||||||
SchedulerControlComponent,
|
SchedulerControlComponent,
|
||||||
LogsPanelComponent,
|
LogsPanelComponent,
|
||||||
ProgressPanelComponent
|
ProgressPanelComponent
|
||||||
} from './component';
|
} from './components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiService,
|
ApiService,
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService,
|
UserService,
|
||||||
FooService,
|
|
||||||
SchedulerService,
|
SchedulerService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
ProgressWebsocketService,
|
ProgressWebsocketService,
|
||||||
LogsWebsocketService
|
LogsWebsocketService,
|
||||||
} from './service';
|
getHtmlBaseUrl
|
||||||
import { ChangePasswordComponent } from './change-password/change-password.component';
|
} from './services';
|
||||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
import { ChangePasswordComponent } from './views/change-password/change-password.component';
|
||||||
|
import { ForbiddenComponent } from './views/forbidden/forbidden.component';
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
export function initUserFactory(userService: UserService) {
|
export function initUserFactory(userService: UserService) {
|
||||||
return () => userService.jsessionInitUser();
|
return () => userService.jsessionInitUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// const stompConfig: StompConfig = {
|
// const stompConfig: StompConfig = {
|
||||||
// // Which server?
|
// // Which server?
|
||||||
// url: 'ws://localhost:8080/quartz-manager/progress',
|
// url: 'ws://localhost:8080/quartz-manager/progress',
|
||||||
@@ -78,6 +82,15 @@ export function initUserFactory(userService: UserService) {
|
|||||||
// debug: true
|
// debug: true
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
export function jwtOptionsFactory(apiService: ApiService) {
|
||||||
|
return {
|
||||||
|
tokenGetter: () => {
|
||||||
|
return apiService.getToken();
|
||||||
|
},
|
||||||
|
whitelistedDomains: ['localhost:8080', 'localhost:4200']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@@ -100,9 +113,15 @@ export function initUserFactory(userService: UserService) {
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
HttpModule,
|
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
|
JwtModule.forRoot({
|
||||||
|
jwtOptionsProvider: {
|
||||||
|
provide: JWT_OPTIONS,
|
||||||
|
useFactory: jwtOptionsFactory,
|
||||||
|
deps: [ApiService]
|
||||||
|
}
|
||||||
|
}),
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
@@ -116,10 +135,19 @@ export function initUserFactory(userService: UserService) {
|
|||||||
FlexLayoutModule
|
FlexLayoutModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_BASE_HREF,
|
||||||
|
useValue: getHtmlBaseUrl()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'provide': APP_INITIALIZER,
|
||||||
|
'useFactory': initUserFactory,
|
||||||
|
'deps': [UserService],
|
||||||
|
'multi': true
|
||||||
|
},
|
||||||
LoginGuard,
|
LoginGuard,
|
||||||
GuestGuard,
|
GuestGuard,
|
||||||
AdminGuard,
|
AdminGuard,
|
||||||
FooService,
|
|
||||||
SchedulerService,
|
SchedulerService,
|
||||||
ProgressWebsocketService,
|
ProgressWebsocketService,
|
||||||
LogsWebsocketService,
|
LogsWebsocketService,
|
||||||
@@ -127,13 +155,7 @@ export function initUserFactory(userService: UserService) {
|
|||||||
ApiService,
|
ApiService,
|
||||||
UserService,
|
UserService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
MatIconRegistry,
|
MatIconRegistry
|
||||||
{
|
|
||||||
'provide': APP_INITIALIZER,
|
|
||||||
'useFactory': initUserFactory,
|
|
||||||
'deps': [UserService],
|
|
||||||
'multi': true
|
|
||||||
}
|
|
||||||
// StompService,
|
// StompService,
|
||||||
// ServerSocket
|
// ServerSocket
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
ConfigService,
|
ConfigService,
|
||||||
ApiService,
|
ApiService,
|
||||||
UserService
|
UserService
|
||||||
} from '../../../service';
|
} from '../../../services';
|
||||||
import {
|
import {
|
||||||
MockUserService,
|
MockUserService,
|
||||||
MockApiService
|
MockApiService
|
||||||
} from '../../../service/mocks';
|
} from '../../../services/mocks';
|
||||||
import { AccountMenuComponent } from './account-menu.component';
|
import { AccountMenuComponent } from './account-menu.component';
|
||||||
|
|
||||||
describe('AccountMenuComponent', () => {
|
describe('AccountMenuComponent', () => {
|
||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
ConfigService,
|
ConfigService,
|
||||||
AuthService,
|
AuthService,
|
||||||
UserService
|
UserService
|
||||||
} from '../../../service';
|
} from '../../../services';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -3,20 +3,15 @@
|
|||||||
<img alt="Quartz Manager" class="app-angular-logo" src="assets/image/angular-white-transparent.svg">
|
<img alt="Quartz Manager" class="app-angular-logo" src="assets/image/angular-white-transparent.svg">
|
||||||
<span>Quartz Manager</span>
|
<span>Quartz Manager</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div fxFlex="1 1 auto" fxLayout="row" fxLayoutAlign="flex-end center">
|
<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>
|
<button *ngIf="!hasSignedIn()" routerLink="/login" mat-button mat-ripple>
|
||||||
<span>Login</span>
|
<span>Login</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="greeting-button"
|
class="greeting-button"
|
||||||
*ngIf="hasSignedIn()"
|
*ngIf="hasSignedIn() && !noAuthenticationRequired()"
|
||||||
mat-button mat-ripple
|
mat-button mat-ripple
|
||||||
[matMenuTriggerFor]="accountMenu">
|
[matMenuTriggerFor]="accountMenu">
|
||||||
<span>Hi, {{userName()}}</span>
|
<span>Hi, {{userName()}}</span>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/deep/ {
|
::ng-deep {
|
||||||
.app-header-accountMenu.mat-menu-panel {
|
.app-header-accountMenu.mat-menu-panel {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
max-width: initial;
|
max-width: initial;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
UserService,
|
UserService,
|
||||||
AuthService
|
AuthService,
|
||||||
} from '../../service';
|
NO_AUTH
|
||||||
|
} from '../../services';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -31,6 +32,9 @@ export class HeaderComponent implements OnInit {
|
|||||||
return !!this.userService.currentUser;
|
return !!this.userService.currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noAuthenticationRequired = () => this.hasSignedIn() && this.userService.currentUser === NO_AUTH;
|
||||||
|
|
||||||
|
|
||||||
userName() {
|
userName() {
|
||||||
const user = this.userService.currentUser;
|
const user = this.userService.currentUser;
|
||||||
return user.username;
|
return user.username;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
|
||||||
import { LogsWebsocketService, ApiService } from '../../service';
|
import { LogsWebsocketService, ApiService } from '../../services';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'logs-panel',
|
selector: 'logs-panel',
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
|
||||||
import { ProgressWebsocketService } from '../../service';
|
import { ProgressWebsocketService } from '../../services';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
// import {Message} from '@stomp/stompjs';
|
// import {Message} from '@stomp/stompjs';
|
||||||
|
|
||||||
// import { Subscription } from 'rxjs/Subscription';
|
// import { Subscription } from 'rxjs/Subscription';
|
||||||
@@ -10,7 +10,7 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
// import { QueueingSubject } from 'queueing-subject'
|
// import { QueueingSubject } from 'queueing-subject'
|
||||||
// import websocketConnect from 'rxjs-websockets'
|
// import websocketConnect from 'rxjs-websockets'
|
||||||
// import 'rxjs/add/operator/share'
|
// import 'rxjs/add/operator/share'
|
||||||
// import {ServerSocket} from '../../service/qz.socket.service'
|
// import {ServerSocket} from '../../services/qz.socket.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'progress-panel',
|
selector: 'progress-panel',
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
import { SchedulerService } from '../../service';
|
import { SchedulerService } from '../../services';
|
||||||
import { SchedulerConfig } from '../../model/schedulerConfig.model'
|
import { SchedulerConfig } from '../../model/schedulerConfig.model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,7 +24,7 @@ export class SchedulerConfigComponent implements OnInit {
|
|||||||
this.schedulerService.getConfig()
|
this.schedulerService.getConfig()
|
||||||
.subscribe(res => {
|
.subscribe(res => {
|
||||||
this.config = new SchedulerConfig(res.triggerPerDay, res.maxCount)
|
this.config = new SchedulerConfig(res.triggerPerDay, res.maxCount)
|
||||||
this.configBackup = res.maxCount
|
this.configBackup = res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
import { UserService, SchedulerService } from '../../service';
|
import { UserService, SchedulerService } from '../../services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'scheduler-control',
|
selector: 'scheduler-control',
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { TestBed, async, inject } from '@angular/core/testing';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { UserService } from '../service';
|
|
||||||
import { AdminGuard } from './admin.guard';
|
|
||||||
import { MockUserService } from '../service/mocks';
|
|
||||||
|
|
||||||
export class RouterStub {
|
|
||||||
navigate(commands?: any[], extras?: any) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('AdminGuard', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [
|
|
||||||
AdminGuard,
|
|
||||||
{
|
|
||||||
provide: Router,
|
|
||||||
useClass: RouterStub
|
|
||||||
}
|
|
||||||
{
|
|
||||||
provide: UserService,
|
|
||||||
useClass: MockUserService
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should ...', inject([AdminGuard], (guard: AdminGuard) => {
|
|
||||||
expect(guard).toBeTruthy();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
105
quartz-manager-frontend/src/app/guards/admin.guard.spec.ts
Normal file
105
quartz-manager-frontend/src/app/guards/admin.guard.spec.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { TestBed, async, inject } from '@angular/core/testing';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NO_AUTH, UserService } from '../services';
|
||||||
|
import { AdminGuard } from './admin.guard';
|
||||||
|
import {jest} from '@jest/globals'
|
||||||
|
|
||||||
|
export class RouterStub {
|
||||||
|
navigate(commands?: any[], extras?: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RouterSpy = jest.spyOn(RouterStub.prototype, 'navigate');
|
||||||
|
|
||||||
|
const MockUserServiceNoAuth = jest.fn(() => ({currentUser: NO_AUTH}));
|
||||||
|
const MockUserService = jest.fn(() => ({
|
||||||
|
currentUser: {
|
||||||
|
authorities: ['ROLE_ADMIN']
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const MockUserServiceForbidden = jest.fn(() => ({
|
||||||
|
currentUser: {
|
||||||
|
authorities: ['ROLE_GUEST']
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AdminGuard NoAuth', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
AdminGuard,
|
||||||
|
{
|
||||||
|
provide: Router,
|
||||||
|
useClass: RouterStub
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useClass: MockUserServiceNoAuth
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('returns true if user is NO_AUTH',inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard.canActivate(null, null)).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AdminGuard activates the route', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
AdminGuard,
|
||||||
|
{
|
||||||
|
provide: Router,
|
||||||
|
useClass: RouterStub
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useClass: MockUserService
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('returns true if user has admin role',inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard.canActivate(null, null)).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AdminGuard redirects to 403', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
AdminGuard,
|
||||||
|
{
|
||||||
|
provide: Router,
|
||||||
|
useClass: RouterStub
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useClass: MockUserServiceForbidden
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('returns false if user is not authorized',inject([AdminGuard], (guard: AdminGuard) => {
|
||||||
|
expect(guard.canActivate(null, null)).toBeFalsy();
|
||||||
|
expect(RouterSpy).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
import { UserService } from '../service';
|
import { UserService } from '../services';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminGuard implements CanActivate {
|
export class AdminGuard implements CanActivate {
|
||||||
@@ -9,13 +9,14 @@ export class AdminGuard implements CanActivate {
|
|||||||
|
|
||||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
||||||
if (this.userService.currentUser) {
|
if (this.userService.currentUser) {
|
||||||
if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1) {
|
if(this.userService.currentUser === 'NO_AUTH')
|
||||||
return true;
|
return true;
|
||||||
} else {
|
if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1)
|
||||||
|
return true;
|
||||||
|
else {
|
||||||
this.router.navigate(['/403']);
|
this.router.navigate(['/403']);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log('NOT AN ADMIN ROLE');
|
console.log('NOT AN ADMIN ROLE');
|
||||||
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
|
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router, CanActivate } from '@angular/router';
|
import { Router, CanActivate } from '@angular/router';
|
||||||
import { UserService } from '../service';
|
import { UserService } from '../services';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GuestGuard implements CanActivate {
|
export class GuestGuard implements CanActivate {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router, CanActivate } from '@angular/router';
|
import { Router, CanActivate } from '@angular/router';
|
||||||
import { UserService } from '../service';
|
import { UserService } from '../services';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoginGuard implements CanActivate {
|
export class LoginGuard implements CanActivate {
|
||||||
@@ -4,11 +4,14 @@ export class SocketOption{
|
|||||||
brokerName : string;
|
brokerName : string;
|
||||||
reconnectionTimeout : number = 30000
|
reconnectionTimeout : number = 30000
|
||||||
|
|
||||||
constructor(socketUrl : string, topicName : string, brokerName : string = null, reconnectionTimeout : number = 30000){
|
getAccessToken: Function = () => null;
|
||||||
|
|
||||||
|
constructor(socketUrl : string, topicName : string, getAccessToken?: Function, brokerName : string = null, reconnectionTimeout : number = 30000){
|
||||||
this.socketUrl = socketUrl;
|
this.socketUrl = socketUrl;
|
||||||
this.topicName = topicName;
|
this.topicName = topicName;
|
||||||
this.brokerName = brokerName;
|
this.brokerName = brokerName;
|
||||||
this.reconnectionTimeout = reconnectionTimeout;
|
this.reconnectionTimeout = reconnectionTimeout;
|
||||||
|
this.getAccessToken = getAccessToken || (() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Headers } from '@angular/http';
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
import { ConfigService } from './config.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class FooService {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private apiService: ApiService,
|
|
||||||
private config: ConfigService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
getFoo() {
|
|
||||||
return this.apiService.get(this.config.foo_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Injectable, OnInit } from '@angular/core';
|
|
||||||
import { WebsocketService } from '.';
|
|
||||||
import { SocketOption } from '../model/SocketOption.model';
|
|
||||||
|
|
||||||
Injectable()
|
|
||||||
export class LogsWebsocketService extends WebsocketService {
|
|
||||||
|
|
||||||
constructor(){
|
|
||||||
super(new SocketOption('/quartz-manager/logs', '/topic/logs'))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Injectable, OnInit } from '@angular/core';
|
|
||||||
import { WebsocketService } from '.';
|
|
||||||
import { SocketOption } from '../model/SocketOption.model';
|
|
||||||
|
|
||||||
Injectable()
|
|
||||||
export class ProgressWebsocketService extends WebsocketService {
|
|
||||||
|
|
||||||
constructor(){
|
|
||||||
super(new SocketOption('/quartz-manager/progress', '/topic/progress'))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Headers } from '@angular/http';
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SchedulerService {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private apiService: ApiService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
startScheduler = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler/run')
|
|
||||||
}
|
|
||||||
|
|
||||||
stopScheduler = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler/stop')
|
|
||||||
}
|
|
||||||
|
|
||||||
pauseScheduler = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler/pause')
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeScheduler = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler/resume')
|
|
||||||
}
|
|
||||||
|
|
||||||
getStatus = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler')
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfig = () => {
|
|
||||||
return this.apiService.get('/quartz-manager/scheduler/config')
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConfig = (config: Object) => {
|
|
||||||
return this.apiService.post('/quartz-manager/scheduler/config', config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFoo() {
|
|
||||||
// return this.apiService.get(this.config.foo_url);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
83
quartz-manager-frontend/src/app/services/api.service.spec.ts
Normal file
83
quartz-manager-frontend/src/app/services/api.service.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Router} from '@angular/router';
|
||||||
|
import {jest} from '@jest/globals'
|
||||||
|
|
||||||
|
class Data{
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpResponseMock {
|
||||||
|
constructor(
|
||||||
|
public body: unknown,
|
||||||
|
public opts?: {
|
||||||
|
headers?:
|
||||||
|
| HttpHeaders
|
||||||
|
| {
|
||||||
|
[name: string]: string | string[];
|
||||||
|
};
|
||||||
|
status?: number;
|
||||||
|
statusText?: string;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const routerSpy = jest.spyOn(Router.prototype, 'navigateByUrl');
|
||||||
|
|
||||||
|
describe('ApiServiceTest', () => {
|
||||||
|
|
||||||
|
let apiService: ApiService;
|
||||||
|
let httpClient: HttpClient;
|
||||||
|
let httpTestingController: HttpTestingController;
|
||||||
|
|
||||||
|
const SAMPLE_URL = '/sample-url';
|
||||||
|
const URL_401 = '/url-response-401';
|
||||||
|
const testData: Data = {name: 'Test Data'};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
providers: [ApiService, {provide: Router, useValue: routerSpy}]
|
||||||
|
});
|
||||||
|
apiService = TestBed.inject(ApiService);
|
||||||
|
|
||||||
|
httpClient = TestBed.inject(HttpClient);
|
||||||
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', (): void => {
|
||||||
|
expect(apiService).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can test HttpClient.get', (): void => {
|
||||||
|
|
||||||
|
apiService.get(SAMPLE_URL).subscribe((res: Data) => {
|
||||||
|
expect(res).toEqual(testData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne(SAMPLE_URL)
|
||||||
|
expect(req.request.method).toEqual('GET');
|
||||||
|
req.flush(new HttpResponseMock(testData));
|
||||||
|
httpTestingController.verify();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesn\'t do anything if 401 is received', (): void => {
|
||||||
|
|
||||||
|
apiService.get(URL_401).subscribe((res: Data) => {
|
||||||
|
expect(false);
|
||||||
|
}, (error) =>
|
||||||
|
{
|
||||||
|
expect(error.status).toBe(401);
|
||||||
|
expect(routerSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne(URL_401)
|
||||||
|
expect(req.request.method).toEqual('GET');
|
||||||
|
req.flush(null, {status: 401, statusText: 'unauthenticated'});
|
||||||
|
httpTestingController.verify();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { HttpClient, HttpHeaders, HttpResponse, HttpRequest, HttpEventType, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpHeaders, HttpResponse, HttpRequest, HttpEventType, HttpParams } from '@angular/common/http';
|
||||||
|
import { Router} from '@angular/router';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import 'rxjs/Rx';
|
import { catchError, map, filter, tap } from 'rxjs/operators'
|
||||||
import 'rxjs/add/observable/throw';
|
import { serialize } from '../shared/utilities/serialize';
|
||||||
import { serialize } from 'app/shared/utilities/serialize';
|
|
||||||
|
|
||||||
export enum RequestMethod {
|
export enum RequestMethod {
|
||||||
Get = 'GET',
|
Get = 'GET',
|
||||||
@@ -18,12 +18,31 @@ export enum RequestMethod {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
|
|
||||||
|
private static extractTokenFromHttpResponse(res: HttpResponse<any>): string {
|
||||||
|
let authorization: string = null;
|
||||||
|
let headers: HttpHeaders = res.headers;
|
||||||
|
if (headers && headers.has('Authorization')){
|
||||||
|
authorization = headers.get('Authorization');
|
||||||
|
if(authorization.startsWith('Bearer '))
|
||||||
|
authorization = authorization.substring(7);
|
||||||
|
}
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
headers = new HttpHeaders({
|
headers = new HttpHeaders({
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor( private http: HttpClient) { }
|
private jwtToken: string;
|
||||||
|
|
||||||
|
constructor( private http: HttpClient, private router: Router) { }
|
||||||
|
|
||||||
|
setToken(token: string) {
|
||||||
|
this.jwtToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken = () => this.jwtToken;
|
||||||
|
|
||||||
get(path: string, args?: any): Observable<any> {
|
get(path: string, args?: any): Observable<any> {
|
||||||
const options = {
|
const options = {
|
||||||
@@ -31,12 +50,11 @@ export class ApiService {
|
|||||||
withCredentials: true
|
withCredentials: true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (args) {
|
if (args)
|
||||||
options['params'] = serialize(args);
|
options['params'] = serialize(args);
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.get(path, options)
|
return this.http.get(path, options)
|
||||||
.catch(this.checkError.bind(this));
|
.pipe(catchError(this.checkError.bind(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
post(path: string, body: any, customHeaders?: HttpHeaders): Observable<any> {
|
post(path: string, body: any, customHeaders?: HttpHeaders): Observable<any> {
|
||||||
@@ -52,25 +70,36 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private request(path: string, body: any, method = RequestMethod.Post, customHeaders?: HttpHeaders): Observable<any> {
|
private request(path: string, body: any, method = RequestMethod.Post, customHeaders?: HttpHeaders): Observable<any> {
|
||||||
const req = new HttpRequest(method, path, body, {
|
const options = {
|
||||||
headers: customHeaders || this.headers,
|
headers: customHeaders || this.headers,
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const req = new HttpRequest(method, path, body, options);
|
||||||
|
|
||||||
return this.http.request(req)
|
return this.http.request(req)
|
||||||
.filter(response => response instanceof HttpResponse)
|
.pipe(
|
||||||
.map((response: HttpResponse<any>) => response.body)
|
filter(response => response instanceof HttpResponse),
|
||||||
.catch(error => this.checkError(error));
|
tap((resp: HttpResponse<any>) => {
|
||||||
|
let jwtToken = ApiService.extractTokenFromHttpResponse(resp);
|
||||||
|
if(jwtToken)
|
||||||
|
this.setToken(jwtToken);
|
||||||
|
}),
|
||||||
|
map((response: HttpResponse<any>) => response.body),
|
||||||
|
catchError(error => this.checkError(error))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display error if logged in, otherwise redirect to IDP
|
// Display error if logged in, otherwise redirect to IDP
|
||||||
private checkError(error: any): any {
|
private checkError(error: any): any {
|
||||||
if (error && error.status === 401) {
|
if (error && error.status === 401) {
|
||||||
// this.redirectIfUnauth(error);
|
this.router.navigate(['/login']);
|
||||||
} else {
|
} else {
|
||||||
// this.displayError(error);
|
// this.displayError(error);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@@ -20,10 +20,13 @@ export class AuthService {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
});
|
});
|
||||||
const body = `username=${user.username}&password=${user.password}`;
|
const body = `username=${user.username}&password=${user.password}`;
|
||||||
return this.apiService.post(this.config.login_url, body, loginHeaders).map(() => {
|
return this.apiService.post(this.config.login_url, body, loginHeaders)
|
||||||
console.log("Login success");
|
.pipe(
|
||||||
this.userService.getMyInfo().subscribe();
|
map(() => {
|
||||||
});
|
console.log("Login success");
|
||||||
|
this.userService.getMyInfo().subscribe();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
signup(user){
|
signup(user){
|
||||||
@@ -31,20 +34,22 @@ export class AuthService {
|
|||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
});
|
});
|
||||||
return this.apiService.post(this.config.signup_url, JSON.stringify(user), signupHeaders).map(() =>{
|
return this.apiService.post(this.config.signup_url, JSON.stringify(user), signupHeaders).pipe(map(() =>{
|
||||||
console.log("Sign up success");
|
console.log("Sign up success");
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
return this.apiService.post(this.config.logout_url, {})
|
return this.apiService.post(this.config.logout_url, {})
|
||||||
.map(() => {
|
.pipe(map(() => {
|
||||||
|
this.apiService.setToken(null);
|
||||||
this.userService.currentUser = null;
|
this.userService.currentUser = null;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
changePassowrd(passwordChanger) {
|
changePassword(passwordChanger) {
|
||||||
return this.apiService.post(this.config.change_password_url, passwordChanger);
|
return this.apiService.post(this.config.change_password_url, passwordChanger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
|
const WEBJAR_PATH = '/quartz-manager-ui/';
|
||||||
|
|
||||||
|
export function getHtmlBaseUrl(){
|
||||||
|
const baseUrl = getBaseUrl() || '/';
|
||||||
|
return environment.production ? getBaseUrl() + WEBJAR_PATH: '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBaseUrl(){
|
||||||
|
if(environment.production){
|
||||||
|
let contextPath: string = window.location.pathname.split('/')[1] || '';
|
||||||
|
if(contextPath && ('/' + contextPath + '/') === WEBJAR_PATH)
|
||||||
|
return '';
|
||||||
|
if(contextPath)
|
||||||
|
contextPath = '/' + contextPath;
|
||||||
|
return contextPath;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfigService {
|
export class ConfigService {
|
||||||
|
|
||||||
private _api_url = '/quartz-manager/api'
|
private _api_url = getBaseUrl() + '/quartz-manager/api'
|
||||||
|
|
||||||
private _refresh_token_url = this._api_url + '/refresh';
|
private _refresh_token_url = this._api_url + '/refresh';
|
||||||
|
|
||||||
@@ -22,8 +42,6 @@ export class ConfigService {
|
|||||||
|
|
||||||
private _reset_credentials_url = this._user_url + '/reset-credentials';
|
private _reset_credentials_url = this._user_url + '/reset-credentials';
|
||||||
|
|
||||||
private _foo_url = this._api_url + '/foo';
|
|
||||||
|
|
||||||
private _signup_url = this._api_url + '/signup';
|
private _signup_url = this._api_url + '/signup';
|
||||||
|
|
||||||
get reset_credentials_url(): string {
|
get reset_credentials_url(): string {
|
||||||
@@ -54,10 +72,6 @@ export class ConfigService {
|
|||||||
return this._change_password_url;
|
return this._change_password_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
get foo_url(): string {
|
|
||||||
return this._foo_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
get signup_url():string {
|
get signup_url():string {
|
||||||
return this._signup_url;
|
return this._signup_url;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ export * from './api.service';
|
|||||||
export * from './user.service';
|
export * from './user.service';
|
||||||
export * from './config.service';
|
export * from './config.service';
|
||||||
export * from './auth.service';
|
export * from './auth.service';
|
||||||
export * from './foo.service';
|
|
||||||
export * from './scheduler.service';
|
export * from './scheduler.service';
|
||||||
export * from './websocket.service';
|
export * from './websocket.service';
|
||||||
export * from './progress.websocket.service';
|
export * from './progress.websocket.service';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { WebsocketService, ApiService, getBaseUrl } from '.';
|
||||||
|
import { SocketOption } from '../model/SocketOption.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogsWebsocketService extends WebsocketService {
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService){
|
||||||
|
super(new SocketOption( getBaseUrl() +'/quartz-manager/logs', '/topic/logs', apiService.getToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { WebsocketService, ApiService, getBaseUrl } from '.';
|
||||||
|
import { SocketOption } from '../model/SocketOption.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ProgressWebsocketService extends WebsocketService {
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService){
|
||||||
|
super(new SocketOption(getBaseUrl() + '/quartz-manager/progress', '/topic/progress', apiService.getToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { getBaseUrl } from '.';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SchedulerService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
startScheduler = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/run')
|
||||||
|
}
|
||||||
|
|
||||||
|
stopScheduler = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/stop')
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseScheduler = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/pause')
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeScheduler = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/resume')
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatus = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler')
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig = () => {
|
||||||
|
return this.apiService.get(getBaseUrl() + '/quartz-manager/scheduler/config')
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig = (config: Object) => {
|
||||||
|
return this.apiService.post(getBaseUrl() + '/quartz-manager/scheduler/config', config)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user