mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2026-05-14 22:00:30 +09:00
Compare commits
66 Commits
dependabot
...
v4.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7653dc73e | ||
|
|
e6a7b35f6a | ||
|
|
3088a2fec1 | ||
|
|
bdd0caa026 | ||
|
|
068b0eed34 | ||
|
|
b2f9692815 | ||
|
|
5fc6c56409 | ||
|
|
d7a78c57ae | ||
|
|
31658416f5 | ||
|
|
1d81e88684 | ||
|
|
f55a58b100 | ||
|
|
902542e480 | ||
|
|
c198d32bd5 | ||
|
|
56d9f5d94f | ||
|
|
45f66d51fe | ||
|
|
95769248a3 | ||
|
|
dcbf3eb277 | ||
|
|
fab977fd7d | ||
|
|
9d2a01ebbe | ||
|
|
9a0789cab0 | ||
|
|
e5a6b8b32b | ||
|
|
4537c8e304 | ||
|
|
82ac821b34 | ||
|
|
0a4a31ae65 | ||
|
|
3b325536e8 | ||
|
|
307c6eab98 | ||
|
|
b1ff70265f | ||
|
|
226296737d | ||
|
|
a5750d1d0d | ||
|
|
f71c9b20ab | ||
|
|
9cc55492dc | ||
|
|
6d36e4620c | ||
|
|
4d5e8f62c3 | ||
|
|
8ba33f25b4 | ||
|
|
7a742d5aea | ||
|
|
abd25d6158 | ||
|
|
9f46e52564 | ||
|
|
e6927209e5 | ||
|
|
63fbedbdc8 | ||
|
|
0148056b1f | ||
|
|
ad6a61f3df | ||
|
|
5cb73019de | ||
|
|
8a8e878e47 | ||
|
|
fae82e1d4e | ||
|
|
e0011913c2 | ||
|
|
a59b6a6c96 | ||
|
|
68aaab6ac4 | ||
|
|
a1d8b12e98 | ||
|
|
45d6a4c59a | ||
|
|
e6f6fb5f06 | ||
|
|
13c438d097 | ||
|
|
412e455907 | ||
|
|
75d630aad0 | ||
|
|
9eddc0b1fd | ||
|
|
fa4ede5179 | ||
|
|
3aa672031a | ||
|
|
82ca186bff | ||
|
|
727a11fcea | ||
|
|
7106dc0fbb | ||
|
|
a2c8ecb227 | ||
|
|
e4c771e364 | ||
|
|
261dd8b624 | ||
|
|
ac63576704 | ||
|
|
a3b92443c4 | ||
|
|
527ee1200a | ||
|
|
7fd174b313 |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
# .dockerignore
|
||||
quartz-manager-frontend/node_modules
|
||||
6
.github/workflows/maven-release.yml
vendored
6
.github/workflows/maven-release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
server-id: ossrh
|
||||
server-id: maven-central-release
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_PASSWORD
|
||||
gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
|
||||
@@ -31,8 +31,8 @@ jobs:
|
||||
- name: Publish to maven central
|
||||
run: mvn deploy --file quartz-manager-parent/pom.xml --batch-mode -P "release-maven-central,build-webjar"
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
|
||||
MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }}
|
||||
MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
|
||||
|
||||
- name: Set up Java 11 for publishing to GitHub Packages
|
||||
|
||||
15
.github/workflows/sonar-java.yml
vendored
15
.github/workflows/sonar-java.yml
vendored
@@ -7,27 +7,28 @@ on:
|
||||
# paths: [ 'quartz-manager-parent/**' ]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
name: Build and analyze
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: 'zulu' # Alternative distribution options are available.
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/.project
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
## **v4.1.0**
|
||||
**NEW FEATURE** support for multiple triggers
|
||||
|
||||
## **v4.0.10**
|
||||
Migrated to the new maven central repo
|
||||
|
||||
## **v4.0.9**
|
||||
Fixed a bug which prevented to run the liquibase migration scripts in case of usage of quartz-manager-starter-persistence
|
||||
|
||||
## **v4.0.8**
|
||||
Upgraded the frontend to angular v14
|
||||
|
||||
|
||||
34
Dockerfile
Normal file
34
Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
FROM maven:3.9.8-eclipse-temurin-21 AS build
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the pom.xml and download dependencies
|
||||
COPY quartz-manager-parent/pom.xml ./quartz-manager-parent/
|
||||
COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/
|
||||
COPY quartz-manager-parent/quartz-manager-common ./quartz-manager-parent/quartz-manager-common/
|
||||
COPY quartz-manager-parent/quartz-manager-starter-api ./quartz-manager-parent/quartz-manager-starter-api/
|
||||
COPY quartz-manager-parent/quartz-manager-starter-persistence ./quartz-manager-parent/quartz-manager-starter-persistence/
|
||||
COPY quartz-manager-parent/quartz-manager-starter-security ./quartz-manager-parent/quartz-manager-starter-security/
|
||||
COPY quartz-manager-parent/quartz-manager-starter-ui ./quartz-manager-parent/quartz-manager-starter-ui/
|
||||
COPY quartz-manager-parent/quartz-manager-web-showcase ./quartz-manager-parent/quartz-manager-web-showcase/
|
||||
COPY quartz-manager-parent/lombok.config ./quartz-manager-parent/
|
||||
COPY quartz-manager-frontend ./quartz-manager-frontend/
|
||||
WORKDIR /app/quartz-manager-parent
|
||||
RUN mvn clean package -DskipTests -P=build-webjar
|
||||
|
||||
|
||||
# Stage 2: Create the final image
|
||||
FROM openjdk:11-jre-slim
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the JAR file from the build stage
|
||||
COPY --from=build /app/quartz-manager-parent/quartz-manager-web-showcase/target/*-SNAPSHOT.jar app.jar
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
19
README.md
19
README.md
@@ -3,6 +3,8 @@
|
||||
[](https://maven-badges.herokuapp.com/maven-central/it.fabioformosa.quartz-manager/quartz-manager-starter-api)
|
||||
[](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager) [](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager) [](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager) [](https://sonarcloud.io/summary/new_code?id=fabioformosa_quartz-manager)
|
||||
|
||||
# Table Of Contents
|
||||
|
||||
|
||||
[QUARTZ MANAGER](https://github.com/fabioformosa/quartz-manager#quartz-manager)
|
||||
[Quartz Manager UI](https://github.com/fabioformosa/quartz-manager#quartz-manager-ui)
|
||||
@@ -80,12 +82,12 @@ Add the dependency, make eligible for Quart Manager the job classes and set the
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-api</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.0.9</version>
|
||||
</dependency>
|
||||
```
|
||||
#### Gradle
|
||||
```
|
||||
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-api', version: '4.0.8'
|
||||
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-api', version: '4.0.9'
|
||||
```
|
||||
|
||||
### Step 2. Quartz Manager Job Classes
|
||||
@@ -168,12 +170,12 @@ You can optionally import the following dependency to have the UI Dashboard to i
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-ui</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.0.9</version>
|
||||
</dependency>
|
||||
```
|
||||
#### Gradle
|
||||
```
|
||||
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-ui', version: '4.0.8'
|
||||
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-ui', version: '4.0.9'
|
||||
```
|
||||
|
||||
### Reach out the UI Console at URL
|
||||
@@ -203,14 +205,14 @@ Future development: the Quart Manager Security OAuth2 client.
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-security</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.0.9</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### Gradle
|
||||
|
||||
```
|
||||
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-security', version: '4.0.8'
|
||||
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-security', version: '4.0.9'
|
||||
```
|
||||
|
||||
|
||||
@@ -240,14 +242,14 @@ The pre-requesite is the availability of Postgresql database where Quartz Manage
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-persistence</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.0.9</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### Gradle
|
||||
|
||||
```
|
||||
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-persistence', version: '4.0.8'
|
||||
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-persistence', version: '4.0.9'
|
||||
```
|
||||
|
||||
### Quartz Manager Persistence Lib - App Props
|
||||
@@ -269,6 +271,7 @@ You can find some examples of different scenarios of projects which import Quart
|
||||
* *existing-security-header-based* - It demonstrates how Quartz Manager Security can coexists with another Spring Security Config present in your project. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager Security.
|
||||
* *existing-quartz* - It demonstrates how to Quartz Manager can coexist with a Quartz instance already present in your project Imported libraries: Quartz Manager API, Quartz Manager UI.
|
||||
* *existing-quartz-and-storage* - It demonstrates how to Quartz Manager Persistence can coexist with a Quartz instance already present in your project. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager Persistence.
|
||||
* *with-persistence* - It demonstrates how to import the Quartz Manager Persistence and get created the quartz tables automatically at the bootstrap
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
45
cloudbuild.yaml
Normal file
45
cloudbuild.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
substitutions:
|
||||
_REGION: europe-west8
|
||||
steps:
|
||||
|
||||
# Step 1: Google Cloud Build - Docker build&push
|
||||
- name: 'gcr.io/k8s-skaffold/skaffold'
|
||||
entrypoint: 'sh'
|
||||
args:
|
||||
- -xe
|
||||
- -c
|
||||
- |
|
||||
# Build and push images
|
||||
sed -i s/_IMAGE_TAG_POLICY/$SHORT_SHA/g skaffold.yaml
|
||||
sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml
|
||||
skaffold build --file-output=/workspace/artifacts.json \
|
||||
--default-repo=${_REGION}-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone \
|
||||
--push=true
|
||||
|
||||
# Step 2: Google Cloud Deploy - deploy
|
||||
- name: 'google/cloud-sdk:latest'
|
||||
entrypoint: 'sh'
|
||||
args:
|
||||
- -xe
|
||||
- -c
|
||||
- |
|
||||
sed -i s/_HELM_APP_VERSION/$SHORT_SHA/g ./quartz-manager-parent/quartz-manager-web-showcase/helm/Chart.yaml
|
||||
gcloud config set deploy/region ${_REGION}
|
||||
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/pipeline.yaml
|
||||
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/dev.yaml
|
||||
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/staging.yaml
|
||||
gcloud deploy apply --file ./quartz-manager-parent/quartz-manager-web-showcase/deploy/prod.yaml
|
||||
gcloud deploy releases create rel-${SHORT_SHA} \
|
||||
--delivery-pipeline quartz-manager-pipeline \
|
||||
--description "$(git log -1 --pretty='%s')" \
|
||||
--build-artifacts /workspace/artifacts.json \
|
||||
--verbosity=debug \
|
||||
--annotations "commit_ui=https://source.cloud.google.com/$PROJECT_ID/quartz-manager-standalone/+/$COMMIT_SHA"
|
||||
artifacts:
|
||||
objects:
|
||||
location: 'gs://$PROJECT_ID-gcdeploy-artifacts/'
|
||||
paths:
|
||||
- '/workspace/artifacts.json'
|
||||
|
||||
options:
|
||||
logging: CLOUD_LOGGING_ONLY
|
||||
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal file
19
quartz-manager-frontend/.eslintrc.sonar.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:sonarjs/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "tsconfig.json",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"sonarjs"
|
||||
],
|
||||
"root": true
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"angular-spring-starter": {
|
||||
"quartz-manager-ui": {
|
||||
"root": "",
|
||||
"prefix": "qrzmng",
|
||||
"sourceRoot": "src",
|
||||
@@ -19,7 +19,7 @@
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"allowedCommonJsDependencies": [
|
||||
"stompjs", "sockjs-client", "moment"
|
||||
"stompjs", "sockjs-client", "moment", "angular2-uuid"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
@@ -31,6 +31,14 @@
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
},
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
@@ -58,18 +66,18 @@
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "angular-spring-starter:build"
|
||||
"browserTarget": "quartz-manager-ui:build:development"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "angular-spring-starter:build:production"
|
||||
"browserTarget": "quartz-manager-ui:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "angular-spring-starter:build"
|
||||
"browserTarget": "quartz-manager-ui:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
@@ -81,7 +89,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"angular-spring-starter-e2e": {
|
||||
"quartz-manager-ui-e2e": {
|
||||
"root": "e2e",
|
||||
"sourceRoot": "e2e",
|
||||
"projectType": "application",
|
||||
@@ -90,7 +98,7 @@
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "angular-spring-starter:serve"
|
||||
"devServerTarget": "quartz-manager-ui:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
|
||||
74
quartz-manager-frontend/package-lock.json
generated
74
quartz-manager-frontend/package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"@fortawesome/fontawesome": "^1.1.4",
|
||||
"@fortawesome/fontawesome-free-regular": "^5.0.8",
|
||||
"@fortawesome/fontawesome-free-solid": "^5.0.8",
|
||||
"@stomp/ng2-stompjs": "^0.6.3",
|
||||
"@stomp/rx-stomp": "1.2.0",
|
||||
"core-js": "2.5.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"moment": "^2.29.1",
|
||||
@@ -62,6 +62,7 @@
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sonarjs": "^0.16.0",
|
||||
"jasmine-core": "~4.5.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jest": "28.1.3",
|
||||
@@ -4229,19 +4230,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stomp/ng2-stompjs": {
|
||||
"version": "0.6.4",
|
||||
"license": "MIT",
|
||||
"node_modules/@stomp/rx-stomp": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stomp/rx-stomp/-/rx-stomp-1.2.0.tgz",
|
||||
"integrity": "sha512-QLzPe3q0EwLB+cVWdUFEO4z5tyR+kPnXJANKN2UvB7Spz/oViHF959cydmXdQWaK7NHp86VO54TgFfXbHVnSLg==",
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": "^4.0.0 >=4.0.2"
|
||||
"@stomp/stompjs": "^6.0.0 >=6.1.1",
|
||||
"angular2-uuid": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@stomp/stompjs": {
|
||||
"version": "4.0.8",
|
||||
"license": "Apache-2.0",
|
||||
"optionalDependencies": {
|
||||
"websocket": "^1.0.24"
|
||||
}
|
||||
"node_modules/@stomp/rx-stomp/node_modules/@stomp/stompjs": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-6.1.2.tgz",
|
||||
"integrity": "sha512-FHDTrIFM5Ospi4L3Xhj6v2+NzCVAeNDcBe95YjUWhWiRMrBF6uN3I7AUOlRgT6jU/2WQvvYK8ZaIxFfxFp+uHQ=="
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
@@ -5419,6 +5420,11 @@
|
||||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/angular2-uuid": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/angular2-uuid/-/angular2-uuid-1.1.1.tgz",
|
||||
"integrity": "sha512-6AXPyii9q8KBFGagybLNVmdGJLPcVZAhmv3odNGSJIA18LuJ3xOe6uN9GvjlQsGfdmYeuxlsGnFEUu7gPhkc+g=="
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
@@ -8599,6 +8605,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-sonarjs": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.16.0.tgz",
|
||||
"integrity": "sha512-al8ojAzcQW8Eu0tWn841ldhPpPcjrJ59TzzTfAVWR45bWvdAASCmrGl8vK0MWHyKVDdC0i17IGbtQQ1KgxLlVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
@@ -22763,16 +22781,20 @@
|
||||
"version": "3.1.0",
|
||||
"dev": true
|
||||
},
|
||||
"@stomp/ng2-stompjs": {
|
||||
"version": "0.6.4",
|
||||
"@stomp/rx-stomp": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@stomp/rx-stomp/-/rx-stomp-1.2.0.tgz",
|
||||
"integrity": "sha512-QLzPe3q0EwLB+cVWdUFEO4z5tyR+kPnXJANKN2UvB7Spz/oViHF959cydmXdQWaK7NHp86VO54TgFfXbHVnSLg==",
|
||||
"requires": {
|
||||
"@stomp/stompjs": "^4.0.0 >=4.0.2"
|
||||
}
|
||||
},
|
||||
"@stomp/stompjs": {
|
||||
"version": "4.0.8",
|
||||
"requires": {
|
||||
"websocket": "^1.0.24"
|
||||
"@stomp/stompjs": "^6.0.0 >=6.1.1",
|
||||
"angular2-uuid": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-6.1.2.tgz",
|
||||
"integrity": "sha512-FHDTrIFM5Ospi4L3Xhj6v2+NzCVAeNDcBe95YjUWhWiRMrBF6uN3I7AUOlRgT6jU/2WQvvYK8ZaIxFfxFp+uHQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
@@ -23670,6 +23692,11 @@
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"angular2-uuid": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/angular2-uuid/-/angular2-uuid-1.1.1.tgz",
|
||||
"integrity": "sha512-6AXPyii9q8KBFGagybLNVmdGJLPcVZAhmv3odNGSJIA18LuJ3xOe6uN9GvjlQsGfdmYeuxlsGnFEUu7gPhkc+g=="
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
@@ -26034,6 +26061,13 @@
|
||||
"prettier-linter-helpers": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-sonarjs": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.16.0.tgz",
|
||||
"integrity": "sha512-al8ojAzcQW8Eu0tWn841ldhPpPcjrJ59TzzTfAVWR45bWvdAASCmrGl8vK0MWHyKVDdC0i17IGbtQQ1KgxLlVA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"build": "ng build --configuration production",
|
||||
"test": "jest",
|
||||
"lint": "ng lint",
|
||||
"lint:sonar": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\"",
|
||||
"lint:sonar:fix": "eslint --no-eslintrc -c .eslintrc.sonar.json \"src/**/*.ts\" --fix",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
@@ -30,7 +32,7 @@
|
||||
"@fortawesome/fontawesome": "^1.1.4",
|
||||
"@fortawesome/fontawesome-free-regular": "^5.0.8",
|
||||
"@fortawesome/fontawesome-free-solid": "^5.0.8",
|
||||
"@stomp/ng2-stompjs": "^0.6.3",
|
||||
"@stomp/rx-stomp": "1.2.0",
|
||||
"core-js": "2.5.1",
|
||||
"hammerjs": "2.0.8",
|
||||
"moment": "^2.29.1",
|
||||
@@ -65,6 +67,7 @@
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-sonarjs": "^0.16.0",
|
||||
"jasmine-core": "~4.5.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jest": "28.1.3",
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
||||
declare var __karma__: any;
|
||||
declare var require: any;
|
||||
declare let __karma__: any;
|
||||
declare let require: any;
|
||||
|
||||
// Prevent Karma from running prematurely.
|
||||
__karma__.loaded = function () {};
|
||||
|
||||
@@ -43,7 +43,8 @@ import {
|
||||
SchedulerControlComponent,
|
||||
LogsPanelComponent,
|
||||
ProgressPanelComponent,
|
||||
TriggerListComponent
|
||||
TriggerListComponent,
|
||||
SimpleTriggerConfigComponent
|
||||
} from './components';
|
||||
|
||||
import {
|
||||
@@ -52,14 +53,13 @@ import {
|
||||
UserService,
|
||||
SchedulerService,
|
||||
ConfigService,
|
||||
ProgressWebsocketService,
|
||||
LogsWebsocketService,
|
||||
getHtmlBaseUrl,
|
||||
LogsRxWebsocketService,
|
||||
ProgressRxWebsocketService,
|
||||
TriggerService
|
||||
} from './services';
|
||||
import { ForbiddenComponent } from './views/forbidden/forbidden.component';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import {SimpleTriggerConfigComponent} from './components/simple-trigger-config';
|
||||
import JobService from './services/job.service';
|
||||
import {GenericErrorComponent} from './views/error/genericError.component';
|
||||
|
||||
@@ -67,31 +67,6 @@ export function initUserFactory(userService: UserService) {
|
||||
return () => userService.fetchLoggedUser();
|
||||
}
|
||||
|
||||
|
||||
// const stompConfig: StompConfig = {
|
||||
// // Which server?
|
||||
// url: 'ws://localhost:8080/quartz-manager/progress',
|
||||
|
||||
// // Headers
|
||||
// // Typical keys: login, passcode, host
|
||||
// headers: {
|
||||
// login: 'admin',
|
||||
// passcode: 'admin'
|
||||
// },
|
||||
|
||||
// // How often to heartbeat?
|
||||
// // Interval in milliseconds, set to 0 to disable
|
||||
// heartbeat_in: 0, // Typical value 0 - disabled
|
||||
// heartbeat_out: 20000, // Typical value 20000 - every 20 seconds
|
||||
// // Wait in milliseconds before attempting auto reconnect
|
||||
// // Set to 0 to disable
|
||||
// // Typical value 5000 (5 seconds)
|
||||
// reconnect_delay: 5000,
|
||||
|
||||
// // Will log diagnostics on console
|
||||
// debug: true
|
||||
// };
|
||||
|
||||
export function jwtOptionsFactory(apiService: ApiService) {
|
||||
return {
|
||||
tokenGetter: () => {
|
||||
@@ -168,19 +143,13 @@ export function jwtOptionsFactory(apiService: ApiService) {
|
||||
SchedulerService,
|
||||
JobService,
|
||||
TriggerService,
|
||||
ProgressWebsocketService,
|
||||
LogsWebsocketService,
|
||||
ProgressRxWebsocketService,
|
||||
LogsRxWebsocketService,
|
||||
AuthService,
|
||||
ApiService,
|
||||
UserService,
|
||||
ConfigService,
|
||||
MatIconRegistry
|
||||
// StompService,
|
||||
// ServerSocket
|
||||
// {
|
||||
// provide: StompConfig,
|
||||
// useValue: stompConfig
|
||||
// }
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from './logs-panel';
|
||||
export * from './scheduler-control';
|
||||
export * from './progress-panel';
|
||||
export * from './trigger-list';
|
||||
export * from './simple-trigger-config';
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
<mat-card-subtitle><b>JOB LOGS</b></mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content style="position: relative; height: calc(100% - 3em);">
|
||||
<div *ngIf="!logs || logs.length == 0" fxLayout="row" fxFlexAlign="center stretch" style="text-align: center">
|
||||
<div fxFill style="height: 100%;">
|
||||
<img src="assets/image/logs.svg" alt="no logs" width="320" style="margin-top: 6em;" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="logs" style="overflow-y: auto; position: absolute; left: 0; right: 0; top: 0; bottom: 0; overflow: auto;">
|
||||
<div *ngIf="!selectedTriggerName && (!logs || logs.length == 0)" fxLayout="row" fxFlexAlign="center stretch" style="text-align: center">
|
||||
<div fxFill style="height: 100%;">
|
||||
<img src="assets/image/logs.svg" alt="no logs" width="320" style="margin-top: 6em;" />
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isWaitingForLogs()" class="waitingLogs" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="12px">
|
||||
<mat-spinner diameter="36"></mat-spinner>
|
||||
<div>Waiting for logs from {{selectedTriggerName}}...</div>
|
||||
</div>
|
||||
<div id="logs" style="overflow-y: auto; position: absolute; left: 0; right: 0; top: 0; bottom: 0; overflow: auto;">
|
||||
<div
|
||||
*ngFor = "let log of logs; let first = first" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start" fxLayoutGap="10px">
|
||||
<div fxFlex="0 1 300px">
|
||||
|
||||
@@ -9,11 +9,18 @@
|
||||
color: gold;
|
||||
}
|
||||
|
||||
#logs{
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* ===== Scrollbar CSS ===== */
|
||||
#logs{
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.waitingLogs {
|
||||
color: #6b7280;
|
||||
height: 100%;
|
||||
min-height: 180px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ===== Scrollbar CSS ===== */
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: auto;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import {Subject} from 'rxjs';
|
||||
import {LogsPanelComponent} from './logs-panel.component';
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
import {jest} from '@jest/globals';
|
||||
|
||||
describe('LogsPanelComponent', () => {
|
||||
|
||||
it('should subscribe to the selected trigger logs topic', () => {
|
||||
const messages = new Subject<any>();
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
|
||||
expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-1');
|
||||
expect(component.selectedTriggerName).toEqual('trigger-1');
|
||||
expect(component.isWaitingForLogs()).toBeTruthy();
|
||||
|
||||
const logRecord = {
|
||||
date: new Date(),
|
||||
type: 'INFO',
|
||||
message: 'job completed',
|
||||
threadName: 'worker-1'
|
||||
};
|
||||
messages.next({body: JSON.stringify(logRecord)});
|
||||
|
||||
expect(component.logs[0]).toEqual({
|
||||
time: logRecord.date.toISOString(),
|
||||
type: 'INFO',
|
||||
msg: 'job completed',
|
||||
threadName: 'worker-1'
|
||||
});
|
||||
expect(component.isWaitingForLogs()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should unsubscribe from the previous topic when the trigger changes', () => {
|
||||
const firstMessages = new Subject<any>();
|
||||
const secondMessages = new Subject<any>();
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
const firstSubscription = component.topicSubscription;
|
||||
jest.spyOn(firstSubscription, 'unsubscribe');
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-2', null);
|
||||
|
||||
expect(firstSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(logsRxWebsocketService.watch).toHaveBeenCalledWith('/topic/logs/trigger-2');
|
||||
});
|
||||
|
||||
it('should clear logs when the trigger changes', () => {
|
||||
const firstMessages = new Subject<any>();
|
||||
const secondMessages = new Subject<any>();
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
firstMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
|
||||
expect(component.logs.length).toEqual(1);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-2', null);
|
||||
expect(component.logs).toEqual([]);
|
||||
expect(component.selectedTriggerName).toEqual('trigger-2');
|
||||
expect(component.isWaitingForLogs()).toBeTruthy();
|
||||
|
||||
secondMessages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'second log', threadName: 'worker-2'})});
|
||||
expect(component.logs.length).toEqual(1);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
expect(component.logs).toEqual([]);
|
||||
expect(component.selectedTriggerName).toEqual('trigger-1');
|
||||
expect(component.isWaitingForLogs()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should clear logs when no trigger is selected', () => {
|
||||
const messages = new Subject<any>();
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
messages.next({body: JSON.stringify({date: new Date(), type: 'INFO', message: 'first log', threadName: 'worker-1'})});
|
||||
|
||||
component.triggerKey = null;
|
||||
|
||||
expect(component.logs).toEqual([]);
|
||||
expect(component.selectedTriggerName).toBeNull();
|
||||
expect(component.isWaitingForLogs()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore destroy when no topic was selected', () => {
|
||||
const logsRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
};
|
||||
const component = new LogsPanelComponent(logsRxWebsocketService as any, null);
|
||||
|
||||
expect(() => component.ngOnDestroy()).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,42 +1,84 @@
|
||||
import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
|
||||
import {ApiService} from '../../services';
|
||||
import {LogsRxWebsocketService} from '../../services/logs.rx-websocket.service';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
|
||||
import {LogsWebsocketService, ApiService} from '../../services';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'logs-panel',
|
||||
templateUrl: './logs-panel.component.html',
|
||||
styleUrls: ['./logs-panel.component.scss']
|
||||
})
|
||||
export class LogsPanelComponent implements OnInit {
|
||||
export class LogsPanelComponent implements OnInit, OnDestroy {
|
||||
|
||||
MAX_LOGS = 30;
|
||||
|
||||
logs = new Array();
|
||||
logs = new Array();
|
||||
|
||||
selectedTriggerName: string;
|
||||
|
||||
topicSubscription;
|
||||
|
||||
private selectedTriggerKey: TriggerKey;
|
||||
|
||||
constructor(
|
||||
private logsWebsocketService: LogsWebsocketService,
|
||||
private logsRxWebsocketService: LogsRxWebsocketService,
|
||||
private apiService: ApiService
|
||||
) {
|
||||
}
|
||||
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
if (!triggerKey || !triggerKey.name) {
|
||||
this._unsubscribeFromTopic();
|
||||
this.selectedTriggerKey = null;
|
||||
this.selectedTriggerName = null;
|
||||
this._resetLogs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTriggerKey?.name === triggerKey.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._resetLogs();
|
||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||
this.selectedTriggerName = triggerKey.name;
|
||||
this._subscribeToTheTopic(this.selectedTriggerKey);
|
||||
}
|
||||
|
||||
isWaitingForLogs = (): boolean => !!this.selectedTriggerName && (!this.logs || this.logs.length === 0);
|
||||
|
||||
ngOnInit() {
|
||||
const obs = this.logsWebsocketService.getObservable()
|
||||
obs.subscribe({
|
||||
'next': this.onNewLogMsg,
|
||||
'error': (err) => {
|
||||
console.log(err)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onNewLogMsg = (receivedMsg) => {
|
||||
if (receivedMsg.type === 'SUCCESS') {
|
||||
this._showNewLog(receivedMsg.message);
|
||||
} else if (receivedMsg.type === 'ERROR') {
|
||||
this._refreshSession();
|
||||
} // if websocket has been closed for session expiration, try to refresh it
|
||||
};
|
||||
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
||||
this._unsubscribeFromTopic();
|
||||
this.topicSubscription = this.logsRxWebsocketService.watch(`/topic/logs/${triggerKey.name}`)
|
||||
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||
.subscribe(this._showNewLog, (err) => {
|
||||
console.log(err);
|
||||
// TODO in case of 401
|
||||
// this.apiService.get('/quartz-manager/session/refresh');
|
||||
});
|
||||
};
|
||||
|
||||
ngOnDestroy() {
|
||||
this._unsubscribeFromTopic();
|
||||
}
|
||||
|
||||
private _unsubscribeFromTopic() {
|
||||
if (this.topicSubscription) {
|
||||
this.topicSubscription.unsubscribe();
|
||||
this.topicSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _resetLogs() {
|
||||
this.logs = [];
|
||||
}
|
||||
|
||||
_showNewLog = (logRecord) => {
|
||||
if (this.logs.length > this.MAX_LOGS) {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<mat-card style="padding-bottom: 0">
|
||||
<mat-card style="padding-bottom: 0" [ngClass]="{'progress-updated': progressUpdated}">
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle><b>JOB PROGRESS</b></mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div id="progressBarBox" *ngIf="progress.percentage !== -1">
|
||||
<div id="progressBarBox" *ngIf="progress.percentage !== -1">
|
||||
<mat-progress-bar mode="determinate" value="{{progress.percentage}}"></mat-progress-bar>
|
||||
{{percentageStr}}
|
||||
</div>
|
||||
|
||||
@@ -31,3 +31,21 @@
|
||||
.fireBoxContent{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-updated {
|
||||
animation: progressUpdatePulse 700ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes progressUpdatePulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(63, 81, 181, 0.35);
|
||||
}
|
||||
|
||||
45% {
|
||||
box-shadow: 0 0 0 6px rgba(63, 81, 181, 0.16);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(63, 81, 181, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import {Subject} from 'rxjs';
|
||||
import {ProgressPanelComponent} from './progress-panel.component';
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
import {jest} from '@jest/globals';
|
||||
|
||||
describe('ProgressPanelComponent', () => {
|
||||
|
||||
it('should subscribe to the selected trigger progress topic', () => {
|
||||
jest.useFakeTimers();
|
||||
const messages = new Subject<any>();
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
|
||||
expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-1');
|
||||
|
||||
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(component.progress.percentage).toEqual(75);
|
||||
expect(component.percentageStr).toEqual('75%');
|
||||
expect(component.progressUpdated).toBeTruthy();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should unsubscribe from the previous topic when the trigger changes', () => {
|
||||
const firstMessages = new Subject<any>();
|
||||
const secondMessages = new Subject<any>();
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
const firstSubscription = component.topicSubscription;
|
||||
jest.spyOn(firstSubscription, 'unsubscribe');
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-2', null);
|
||||
|
||||
expect(firstSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(progressRxWebsocketService.watch).toHaveBeenCalledWith('/topic/progress/trigger-2');
|
||||
});
|
||||
|
||||
it('should reset progress when the trigger changes', () => {
|
||||
const firstMessages = new Subject<any>();
|
||||
const secondMessages = new Subject<any>();
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
.mockReturnValueOnce(secondMessages.asObservable())
|
||||
.mockReturnValueOnce(firstMessages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
firstMessages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
expect(component.progress.percentage).toEqual(75);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-2', null);
|
||||
expect(component.progress.percentage).toEqual(-1);
|
||||
expect(component.percentageStr).toBeNull();
|
||||
expect(component.progressUpdated).toBeFalsy();
|
||||
|
||||
secondMessages.next({body: JSON.stringify({percentage: 20, timesTriggered: 1})});
|
||||
expect(component.progress.percentage).toEqual(20);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
expect(component.progress.percentage).toEqual(-1);
|
||||
});
|
||||
|
||||
it('should reset progress when no trigger is selected', () => {
|
||||
const messages = new Subject<any>();
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn(() => messages.asObservable())
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
|
||||
component.triggerKey = new TriggerKey('trigger-1', null);
|
||||
messages.next({body: JSON.stringify({percentage: 75, timesTriggered: 3})});
|
||||
|
||||
component.triggerKey = null;
|
||||
|
||||
expect(component.progress.percentage).toEqual(-1);
|
||||
expect(component.percentageStr).toBeNull();
|
||||
expect(component.progressUpdated).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore destroy when no topic was selected', () => {
|
||||
const progressRxWebsocketService = {
|
||||
watch: jest.fn()
|
||||
};
|
||||
const component = new ProgressPanelComponent(progressRxWebsocketService as any);
|
||||
|
||||
expect(() => component.ngOnDestroy()).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,84 +1,91 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
|
||||
import {ProgressWebsocketService, QuartzManagerWebsocketMessage} from '../../services';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core'
|
||||
import TriggerFiredBundle from '../../model/trigger-fired-bundle.model';
|
||||
// import {Message} from '@stomp/stompjs';
|
||||
|
||||
// import { Subscription } from 'rxjs/Subscription';
|
||||
// import {StompService} from '@stomp/ng2-stompjs';
|
||||
|
||||
// import { QueueingSubject } from 'queueing-subject'
|
||||
// import websocketConnect from 'rxjs-websockets'
|
||||
// import 'rxjs/add/operator/share'
|
||||
// import {ServerSocket} from '../../services/qz.socket.service'
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
import {ProgressRxWebsocketService} from '../../services/progress.rx-websocket.service';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'progress-panel',
|
||||
templateUrl: './progress-panel.component.html',
|
||||
styleUrls: ['./progress-panel.component.scss']
|
||||
})
|
||||
export class ProgressPanelComponent implements OnInit {
|
||||
|
||||
progress: TriggerFiredBundle = new TriggerFiredBundle();
|
||||
percentageStr: string;
|
||||
|
||||
// // Stream of messages
|
||||
// private subscription: Subscription;
|
||||
// public messages: Observable<Message>;
|
||||
// // Subscription status
|
||||
// public subscribed: boolean;
|
||||
// // Array of historic message (bodies)
|
||||
// public mq: Array<string> = [];
|
||||
export class ProgressPanelComponent implements OnInit, OnDestroy {
|
||||
|
||||
progress: TriggerFiredBundle = ProgressPanelComponent._buildEmptyProgress();
|
||||
percentageStr: string;
|
||||
progressUpdated = false;
|
||||
|
||||
topicSubscription;
|
||||
private selectedTriggerKey: TriggerKey;
|
||||
|
||||
constructor(
|
||||
private progressWebsocketService: ProgressWebsocketService,
|
||||
// private _stompService: StompService,
|
||||
// private serverSocket : ServerSocket
|
||||
private progressRxWebsocketService: ProgressRxWebsocketService
|
||||
) { }
|
||||
|
||||
onNewProgressMsg = (receivedMsg: QuartzManagerWebsocketMessage) => {
|
||||
if (receivedMsg.type === 'SUCCESS') {
|
||||
const newStatus = receivedMsg.message;
|
||||
this.progress = newStatus;
|
||||
this.percentageStr = this.progress.percentage + '%';
|
||||
}
|
||||
}
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
if (!triggerKey || !triggerKey.name) {
|
||||
this._unsubscribeFromTopic();
|
||||
this.selectedTriggerKey = null;
|
||||
this._resetProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTriggerKey?.name === triggerKey.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._resetProgress();
|
||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||
this._subscribeToTheTopic(this.selectedTriggerKey);
|
||||
}
|
||||
|
||||
private _subscribeToTheTopic = (triggerKey: TriggerKey) => {
|
||||
this._unsubscribeFromTopic();
|
||||
this.topicSubscription = this.progressRxWebsocketService.watch(`/topic/progress/${triggerKey.name}`)
|
||||
.pipe(map((msg: any) => JSON.parse(msg.body)))
|
||||
.subscribe(this.onNewProgressMsg, (err) => {
|
||||
console.log(err);
|
||||
// TODO in case of 401
|
||||
// this.apiService.get('/quartz-manager/session/refresh');
|
||||
});
|
||||
};
|
||||
|
||||
onNewProgressMsg = (receivedMsg) => {
|
||||
this.progress = receivedMsg;
|
||||
this.percentageStr = this.progress.percentage + '%';
|
||||
this._markProgressUpdated();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const obs = this.progressWebsocketService.getObservable()
|
||||
obs.subscribe({
|
||||
'next' : this.onNewProgressMsg,
|
||||
'error' : (err) => {console.log(err)}
|
||||
});
|
||||
|
||||
// this.subscribed = false;
|
||||
// this.subscribe();
|
||||
|
||||
// this.serverSocket.connect()
|
||||
// this.socketSubscription = this.serverSocket.messages.subscribe((message: string) => {
|
||||
// console.log('received message from server: ', message)
|
||||
// })
|
||||
}
|
||||
|
||||
// public subscribe() {
|
||||
// if (this.subscribed) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Stream of messages
|
||||
// this.messages = this._stompService.subscribe('/topic/progress');
|
||||
|
||||
// // Subscribe a function to be run on_next message
|
||||
// this.subscription = this.messages.subscribe(this.on_next);
|
||||
|
||||
// this.subscribed = true;
|
||||
// }
|
||||
|
||||
// public on_next = (message: Message) => {
|
||||
// this.mq.push(message.body + '\n');
|
||||
// console.log(message);
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._unsubscribeFromTopic();
|
||||
}
|
||||
|
||||
private _unsubscribeFromTopic() {
|
||||
if (this.topicSubscription) {
|
||||
this.topicSubscription.unsubscribe();
|
||||
this.topicSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _resetProgress() {
|
||||
this.progress = ProgressPanelComponent._buildEmptyProgress();
|
||||
this.percentageStr = null;
|
||||
this.progressUpdated = false;
|
||||
}
|
||||
|
||||
private _markProgressUpdated() {
|
||||
this.progressUpdated = false;
|
||||
setTimeout(() => this.progressUpdated = true);
|
||||
}
|
||||
|
||||
private static _buildEmptyProgress() {
|
||||
const progress = new TriggerFiredBundle();
|
||||
progress.percentage = -1;
|
||||
return progress;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ import {MatDividerModule} from '@angular/material/divider';
|
||||
|
||||
describe('SchedulerControlComponent', () => {
|
||||
|
||||
const schedulerUrl = '/quartz-manager/scheduler';
|
||||
const schedulerButtonSelector = '#schedulerControllerBtn';
|
||||
const schedulerName = 'test-scheduler';
|
||||
const schedulerId = 'test-id';
|
||||
const stoppedStatus = 'STOPPED';
|
||||
|
||||
let component: SchedulerControlComponent;
|
||||
let fixture: ComponentFixture<SchedulerControlComponent>;
|
||||
|
||||
@@ -38,16 +44,16 @@ describe('SchedulerControlComponent', () => {
|
||||
|
||||
it('should display the play button at the beginning since the scheduler is stopped', () => {
|
||||
expect(component).toBeDefined();
|
||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
|
||||
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
|
||||
getSchedulerReq.flush(mockScheduler);
|
||||
|
||||
expect(component.scheduler).toEqual(mockScheduler);
|
||||
expect(component.scheduler.status).toEqual('STOPPED');
|
||||
expect(component.scheduler.status).toEqual(stoppedStatus);
|
||||
fixture.detectChanges();
|
||||
|
||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||
const schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
||||
const schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||
expect(schedulerBtnDe).toBeTruthy();
|
||||
|
||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
||||
@@ -56,13 +62,13 @@ describe('SchedulerControlComponent', () => {
|
||||
|
||||
it('should switch the button to pause when the scheduler is started', () => {
|
||||
expect(component).toBeDefined();
|
||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'STOPPED', []);
|
||||
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||
const mockScheduler = new Scheduler(schedulerName, schedulerId, stoppedStatus, []);
|
||||
getSchedulerReq.flush(mockScheduler);
|
||||
fixture.detectChanges();
|
||||
|
||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||
expect(schedulerBtnDe).toBeTruthy();
|
||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
||||
expect(playIconDe).toBeTruthy();
|
||||
@@ -72,7 +78,7 @@ describe('SchedulerControlComponent', () => {
|
||||
startSchedulerReq.flush(null);
|
||||
fixture.detectChanges();
|
||||
|
||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
||||
expect(pauseIconDe).toBeTruthy();
|
||||
|
||||
@@ -80,13 +86,13 @@ describe('SchedulerControlComponent', () => {
|
||||
|
||||
it('should switch the button to play when the scheduler is stopped', () => {
|
||||
expect(component).toBeDefined();
|
||||
const getSchedulerReq = httpTestingController.expectOne('/quartz-manager/scheduler');
|
||||
const mockScheduler = new Scheduler('test-scheduler', 'test-id', 'RUNNING', []);
|
||||
const getSchedulerReq = httpTestingController.expectOne(schedulerUrl);
|
||||
const mockScheduler = new Scheduler(schedulerName, schedulerId, 'RUNNING', []);
|
||||
getSchedulerReq.flush(mockScheduler);
|
||||
fixture.detectChanges();
|
||||
|
||||
const schedulerControlComponentDe: DebugElement = fixture.debugElement;
|
||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
||||
let schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||
expect(schedulerBtnDe).toBeTruthy();
|
||||
const pauseIconDe = schedulerBtnDe.query(By.css('.fa-pause'));
|
||||
expect(pauseIconDe).toBeTruthy();
|
||||
@@ -96,7 +102,7 @@ describe('SchedulerControlComponent', () => {
|
||||
startSchedulerReq.flush(null);
|
||||
fixture.detectChanges();
|
||||
|
||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css('#schedulerControllerBtn'));
|
||||
schedulerBtnDe = schedulerControlComponentDe.query(By.css(schedulerButtonSelector));
|
||||
const playIconDe = schedulerBtnDe.query(By.css('.fa-play'));
|
||||
expect(playIconDe).toBeTruthy();
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ import {MisfireInstruction} from '../../model/misfire-instruction.model';
|
||||
|
||||
describe('SimpleTriggerConfig', () => {
|
||||
|
||||
const submitButtonSelector = 'form button[color="primary"]';
|
||||
const repeatIntervalSelector = '#repeatInterval';
|
||||
const testTriggerName = 'test-trigger';
|
||||
const testJobName = 'TestJob';
|
||||
|
||||
let component: SimpleTriggerConfigComponent;
|
||||
let fixture: ComponentFixture<SimpleTriggerConfigComponent>;
|
||||
|
||||
@@ -91,16 +96,16 @@ describe('SimpleTriggerConfig', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const getJobsReq = httpTestingController.expectOne(`${CONTEXT_PATH}/jobs`);
|
||||
getJobsReq.flush(['TestJob']);
|
||||
getJobsReq.flush([testJobName]);
|
||||
|
||||
const componentDe: DebugElement = fixture.debugElement;
|
||||
|
||||
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
|
||||
const submitButton = componentDe.query(By.css(submitButtonSelector));
|
||||
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||
|
||||
setInputValue(componentDe, '#triggerName', 'test-trigger');
|
||||
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual('test-trigger');
|
||||
setInputValue(componentDe, '#triggerName', testTriggerName);
|
||||
expect(component.simpleTriggerReactiveForm.controls.triggerName.value).toEqual(testTriggerName);
|
||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||
setMatSelectValueByIndex(componentDe, '#misfireInstruction', 0);
|
||||
expect(component.simpleTriggerReactiveForm.controls.misfireInstruction.value).toEqual('MISFIRE_INSTRUCTION_FIRE_NOW');
|
||||
@@ -111,7 +116,7 @@ describe('SimpleTriggerConfig', () => {
|
||||
setInputValue(componentDe, '#repeatCount', '1000');
|
||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual('');
|
||||
|
||||
setInputValue(componentDe, '#repeatInterval', '2000');
|
||||
setInputValue(componentDe, repeatIntervalSelector, '2000');
|
||||
expect(submitButton.nativeElement.getAttribute('disabled')).toEqual(null);
|
||||
}
|
||||
|
||||
@@ -122,18 +127,18 @@ describe('SimpleTriggerConfig', () => {
|
||||
it('should emit an event when a new trigger is submitted', () => {
|
||||
const componentDe: DebugElement = fixture.debugElement;
|
||||
const mockTrigger = new Trigger();
|
||||
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
|
||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
|
||||
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
|
||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
|
||||
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
||||
|
||||
openFormAndFillAllMandatoryFields();
|
||||
|
||||
setInputValue(componentDe, '#repeatInterval', '2000');
|
||||
setInputValue(componentDe, repeatIntervalSelector, '2000');
|
||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(2000);
|
||||
setInputValue(componentDe, '#repeatCount', '100');
|
||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatCount).toEqual(100);
|
||||
|
||||
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
|
||||
const submitButton = componentDe.query(By.css(submitButtonSelector));
|
||||
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||
|
||||
let actualNewTrigger;
|
||||
@@ -141,28 +146,28 @@ describe('SimpleTriggerConfig', () => {
|
||||
|
||||
submitButton.nativeElement.click();
|
||||
|
||||
const postSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
|
||||
const postSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
|
||||
postSimpleTriggerReq.flush(mockTrigger);
|
||||
|
||||
expect(actualNewTrigger).toEqual(mockTrigger);
|
||||
});
|
||||
|
||||
it('should not emit an event when an existing trigger is edited', () => {
|
||||
const mockTriggerKey = new TriggerKey('test-trigger', null);
|
||||
const mockTriggerKey = new TriggerKey(testTriggerName, null);
|
||||
component.triggerKey = mockTriggerKey;
|
||||
fixture.detectChanges();
|
||||
|
||||
const mockTrigger = new SimpleTrigger();
|
||||
mockTrigger.triggerKeyDTO = new TriggerKey('test-trigger', null);
|
||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: 'TestJob', description: null};
|
||||
mockTrigger.triggerKeyDTO = new TriggerKey(testTriggerName, null);
|
||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
|
||||
mockTrigger.mayFireAgain = true;
|
||||
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
||||
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
|
||||
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
|
||||
getSimpleTriggerReq.flush(mockTrigger);
|
||||
|
||||
component.simpleTriggerReactiveForm.setValue({
|
||||
triggerName: 'test-trigger',
|
||||
jobClass: 'TestJob',
|
||||
triggerName: testTriggerName,
|
||||
jobClass: testJobName,
|
||||
triggerRecurrence: {
|
||||
repeatInterval: 2000,
|
||||
repeatCount: 100,
|
||||
@@ -178,10 +183,10 @@ describe('SimpleTriggerConfig', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const componentDe: DebugElement = fixture.debugElement;
|
||||
setInputValue(componentDe, '#repeatInterval', '4000');
|
||||
setInputValue(componentDe, repeatIntervalSelector, '4000');
|
||||
expect(component.simpleTriggerReactiveForm.controls.triggerRecurrence.value.repeatInterval).toEqual(4000);
|
||||
|
||||
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
|
||||
const submitButton = componentDe.query(By.css(submitButtonSelector));
|
||||
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||
|
||||
let actualNewTrigger;
|
||||
@@ -189,7 +194,7 @@ describe('SimpleTriggerConfig', () => {
|
||||
|
||||
submitButton.nativeElement.click();
|
||||
|
||||
const putSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/test-trigger`);
|
||||
const putSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
|
||||
putSimpleTriggerReq.flush(mockTrigger);
|
||||
|
||||
expect(actualNewTrigger).toBeUndefined();
|
||||
@@ -220,8 +225,34 @@ describe('SimpleTriggerConfig', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const componentDe: DebugElement = fixture.debugElement;
|
||||
const submitButton = componentDe.query(By.css('form button[color="primary"]'));
|
||||
const submitButton = componentDe.query(By.css(submitButtonSelector));
|
||||
expect(submitButton.nativeElement.textContent.trim()).toEqual('Submit');
|
||||
|
||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
|
||||
|
||||
});
|
||||
|
||||
it('should reset the form when a new trigger is selected', () => {
|
||||
const mockTriggerKey = new TriggerKey(testTriggerName, null);
|
||||
component.triggerKey = mockTriggerKey;
|
||||
|
||||
const mockTrigger = new SimpleTrigger();
|
||||
mockTrigger.triggerKeyDTO = mockTriggerKey;
|
||||
mockTrigger.jobDetailDTO = <JobDetail>{jobClassName: testJobName, description: null};
|
||||
mockTrigger.mayFireAgain = true;
|
||||
mockTrigger.misfireInstruction = MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW;
|
||||
|
||||
const getSimpleTriggerReq = httpTestingController.expectOne(`${CONTEXT_PATH}/simple-triggers/${testTriggerName}`);
|
||||
getSimpleTriggerReq.flush(mockTrigger);
|
||||
|
||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toEqual(testTriggerName);
|
||||
|
||||
component.triggerKey = null;
|
||||
|
||||
expect(component.simpleTriggerReactiveForm.value.triggerName).toBeNull();
|
||||
expect(component.simpleTriggerReactiveForm.value.jobClass).toBeNull();
|
||||
expect(component.shouldShowTheTriggerCardContent()).toBeTruthy();
|
||||
|
||||
});
|
||||
|
||||
it('should display the warning if there are no eligible jobs', () => {
|
||||
|
||||
@@ -34,9 +34,8 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
|
||||
scheduler: Scheduler;
|
||||
|
||||
triggerLoading = true;
|
||||
triggerLoading = false;
|
||||
|
||||
private fetchedTriggers = false;
|
||||
private triggerInProgress = false;
|
||||
|
||||
private selectedTriggerKey: TriggerKey;
|
||||
@@ -48,6 +47,9 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
@Output()
|
||||
onNewTrigger = new EventEmitter<SimpleTrigger>();
|
||||
|
||||
@Output()
|
||||
triggerFormOpenChange = new EventEmitter<boolean>();
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private schedulerService: SchedulerService,
|
||||
@@ -65,18 +67,37 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
|
||||
openTriggerForm() {
|
||||
this.enabledTriggerForm = true;
|
||||
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||
}
|
||||
|
||||
private closeTriggerForm() {
|
||||
this.enabledTriggerForm = false;
|
||||
this.triggerFormOpenChange.emit(this.enabledTriggerForm);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set triggerKey(triggerKey: TriggerKey) {
|
||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||
this.fetchSelectedTrigger();
|
||||
if (!triggerKey) {
|
||||
this.openNewTriggerForm();
|
||||
} else if (!this.selectedTriggerKey || this.selectedTriggerKey.name !== triggerKey.name) {
|
||||
this._resetTheTrigger();
|
||||
this.selectedTriggerKey = {...triggerKey} as TriggerKey;
|
||||
this.fetchSelectedTrigger();
|
||||
this.closeTriggerForm();
|
||||
}
|
||||
}
|
||||
|
||||
openNewTriggerForm() {
|
||||
this._resetTheTrigger();
|
||||
this.openTriggerForm();
|
||||
}
|
||||
|
||||
private _resetTheTrigger() {
|
||||
this.trigger = null;
|
||||
this.triggerInProgress = false;
|
||||
this.selectedTriggerKey = null;
|
||||
this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm());
|
||||
}
|
||||
|
||||
fetchSelectedTrigger = () => {
|
||||
this.triggerLoading = true;
|
||||
@@ -94,7 +115,11 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
existsATriggerInProgress = (): boolean => this.trigger && this.triggerInProgress;
|
||||
|
||||
onResetReactiveForm = () => {
|
||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
||||
if (this.trigger) {
|
||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
||||
} else {
|
||||
this.simpleTriggerReactiveForm.reset(new SimpleTriggerReactiveForm());
|
||||
}
|
||||
this.closeTriggerForm();
|
||||
};
|
||||
|
||||
@@ -103,13 +128,13 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
this.schedulerService.updateSimpleTriggerConfig : this.schedulerService.saveSimpleTriggerConfig;
|
||||
|
||||
const simpleTriggerCommand = this._fromReactiveFormToCommand();
|
||||
this.triggerLoading = true;
|
||||
schedulerServiceCall(simpleTriggerCommand)
|
||||
.subscribe((retTrigger: SimpleTrigger) => {
|
||||
this.trigger = retTrigger;
|
||||
|
||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(retTrigger));
|
||||
|
||||
this.fetchedTriggers = true;
|
||||
this.triggerInProgress = this.trigger.mayFireAgain;
|
||||
|
||||
if (schedulerServiceCall === this.schedulerService.saveSimpleTriggerConfig) {
|
||||
@@ -118,8 +143,13 @@ export class SimpleTriggerConfigComponent implements OnInit {
|
||||
|
||||
this.closeTriggerForm();
|
||||
}, error => {
|
||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
||||
});
|
||||
if (this.trigger) {
|
||||
this.simpleTriggerReactiveForm.setValue(this._fromTriggerToReactiveForm(this.trigger));
|
||||
}
|
||||
this.triggerLoading = false;
|
||||
}, () => {
|
||||
this.triggerLoading = false
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
<mat-card-content style="position: relative; height: 100%">
|
||||
<mat-nav-list style="overflow-y: auto; position: absolute; left: 0; right: 0; top: 0; bottom: 0; overflow: auto; height: calc(100% - 3em)">
|
||||
<mat-list-item *ngFor="let triggerKey of getTriggerKeyList()" class="triggerItemList"
|
||||
[ngClass]="{'selectedTrigger': selectedTrigger && selectedTrigger.name==triggerKey.name}">
|
||||
[ngClass]="{'selectedTrigger': selectedTrigger && selectedTrigger.name==triggerKey.name}"
|
||||
(click)="selectTrigger(triggerKey)"
|
||||
>
|
||||
<a matLine>{{ triggerKey.name }}</a>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
|
||||
@@ -81,16 +81,17 @@ export class TriggerListComponent implements OnInit {
|
||||
}
|
||||
|
||||
onNewTriggerBtnClicked() {
|
||||
if (this.getTriggerKeyList() && this.getTriggerKeyList().length > 0) {
|
||||
this.dialog.open(UnsupportedMultipleJobsDialog)
|
||||
} else {
|
||||
this.onNewTriggerClicked.emit();
|
||||
}
|
||||
this.onNewTriggerClicked.emit();
|
||||
// if (this.getTriggerKeyList() && this.getTriggerKeyList().length > 0) {
|
||||
// this.dialog.open(UnsupportedMultipleJobsDialog)
|
||||
// } else {
|
||||
// this.onNewTriggerClicked.emit();
|
||||
// }
|
||||
}
|
||||
|
||||
onNewTrigger(newTrigger: SimpleTrigger) {
|
||||
this.newTriggers = [newTrigger, ...this.newTriggers];
|
||||
this.selectedTrigger = newTrigger.triggerKeyDTO;
|
||||
this.selectTrigger(newTrigger.triggerKeyDTO);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
@@ -51,14 +51,14 @@ import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
@@ -68,7 +68,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* MATERIAL 2
|
||||
*/
|
||||
import 'hammerjs/hammer';
|
||||
|
||||
@@ -3,9 +3,8 @@ export * from './user.service';
|
||||
export * from './config.service';
|
||||
export * from './auth.service';
|
||||
export * from './scheduler.service';
|
||||
export * from './websocket.service';
|
||||
export * from './progress.websocket.service';
|
||||
export * from './logs.websocket.service';
|
||||
export * from './progress.rx-websocket.service';
|
||||
export * from './logs.rx-websocket.service';
|
||||
export * from './trigger.service'
|
||||
export * from './job.service'
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LogsRxWebsocketService } from './logs.rx-websocket.service';
|
||||
import {ApiService} from './api.service';
|
||||
import {RxStomp} from '@stomp/rx-stomp';
|
||||
import {jest} from '@jest/globals';
|
||||
|
||||
describe('LogsRxWebsocketService', () => {
|
||||
let service: LogsRxWebsocketService;
|
||||
let configureSpy;
|
||||
let activateSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
configureSpy = jest.spyOn(RxStomp.prototype, 'configure');
|
||||
activateSpy = jest.spyOn(RxStomp.prototype, 'activate').mockImplementation(() => undefined);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: ApiService, useValue: {getToken: () => 'test-token'}}
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(LogsRxWebsocketService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should configure rx-stomp with the logs websocket endpoint', () => {
|
||||
expect(configureSpy).toHaveBeenCalled();
|
||||
expect(activateSpy).toHaveBeenCalled();
|
||||
|
||||
const config = configureSpy.mock.calls[configureSpy.mock.calls.length - 1][0];
|
||||
expect(config.heartbeatIncoming).toEqual(0);
|
||||
expect(config.heartbeatOutgoing).toEqual(20000);
|
||||
expect(config.reconnectDelay).toEqual(200);
|
||||
expect(config.webSocketFactory.toString()).toContain('/logs?access_token=');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {RxStompService} from './rx-stomp.service';
|
||||
import {ApiService} from './api.service';
|
||||
import SockJS from 'sockjs-client';
|
||||
import {CONTEXT_PATH, getBaseUrl} from './config.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LogsRxWebsocketService extends RxStompService {
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
super({
|
||||
webSocketFactory: () => new SockJS(`${getBaseUrl()}${CONTEXT_PATH}/logs?access_token=${this.apiService.getToken()}`),
|
||||
heartbeatIncoming: 0,
|
||||
heartbeatOutgoing: 20000,
|
||||
reconnectDelay: 200,
|
||||
debug: (msg: string): void => {
|
||||
console.log(new Date(), msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {WebsocketService, ApiService, getBaseUrl, CONTEXT_PATH} from '.';
|
||||
import {SocketOption} from '../model/SocketOption.model';
|
||||
|
||||
@Injectable()
|
||||
export class LogsWebsocketService extends WebsocketService {
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
super(new SocketOption(getBaseUrl() + `${CONTEXT_PATH}/logs`, '/topic/logs', apiService.getToken))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {RxStomp} from '@stomp/rx-stomp';
|
||||
import {jest} from '@jest/globals';
|
||||
|
||||
import { ProgressRxWebsocketService } from './progress.rx-websocket.service';
|
||||
import {ApiService} from './api.service';
|
||||
|
||||
describe('ProgressRxWebsocketService', () => {
|
||||
let service: ProgressRxWebsocketService;
|
||||
let configureSpy;
|
||||
let activateSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
configureSpy = jest.spyOn(RxStomp.prototype, 'configure');
|
||||
activateSpy = jest.spyOn(RxStomp.prototype, 'activate').mockImplementation(() => undefined);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: ApiService, useValue: {getToken: () => 'test-token'}}
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(ProgressRxWebsocketService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should configure rx-stomp with the progress websocket endpoint', () => {
|
||||
expect(configureSpy).toHaveBeenCalled();
|
||||
expect(activateSpy).toHaveBeenCalled();
|
||||
|
||||
const config = configureSpy.mock.calls[configureSpy.mock.calls.length - 1][0];
|
||||
expect(config.heartbeatIncoming).toEqual(0);
|
||||
expect(config.heartbeatOutgoing).toEqual(20000);
|
||||
expect(config.reconnectDelay).toEqual(200);
|
||||
expect(config.webSocketFactory.toString()).toContain('/progress?access_token=');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {RxStompService} from './rx-stomp.service';
|
||||
import {ApiService} from './api.service';
|
||||
import SockJS from 'sockjs-client';
|
||||
import {CONTEXT_PATH, getBaseUrl} from './config.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ProgressRxWebsocketService extends RxStompService {
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
super({
|
||||
webSocketFactory: () => new SockJS(`${getBaseUrl()}${CONTEXT_PATH}/progress?access_token=${this.apiService.getToken()}`),
|
||||
heartbeatIncoming: 0,
|
||||
heartbeatOutgoing: 20000,
|
||||
reconnectDelay: 200,
|
||||
debug: (msg: string): void => {
|
||||
console.log(new Date(), msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {WebsocketService, ApiService, getBaseUrl, CONTEXT_PATH} from '.';
|
||||
import {SocketOption} from '../model/SocketOption.model';
|
||||
|
||||
@Injectable()
|
||||
export class ProgressWebsocketService extends WebsocketService {
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
super(new SocketOption(getBaseUrl() + `${CONTEXT_PATH}/progress`, '/topic/progress', apiService.getToken))
|
||||
}
|
||||
|
||||
}
|
||||
12
quartz-manager-frontend/src/app/services/rx-stomp.service.ts
Normal file
12
quartz-manager-frontend/src/app/services/rx-stomp.service.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {RxStomp} from '@stomp/rx-stomp';
|
||||
import {RxStompConfig} from '@stomp/rx-stomp/esm6/rx-stomp-config';
|
||||
|
||||
export class RxStompService extends RxStomp {
|
||||
|
||||
constructor(rxStompConfig: RxStompConfig) {
|
||||
super();
|
||||
super.configure(rxStompConfig);
|
||||
super.activate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export class UserService {
|
||||
this.currentUser = user;
|
||||
this.router.initialNavigation();
|
||||
}, err => {
|
||||
console.log(`error retrieving current user due to ` + JSON.stringify(err));
|
||||
console.log('error retrieving current user due to ' + JSON.stringify(err));
|
||||
const httpErrorResponse = err as HttpErrorResponse;
|
||||
if (httpErrorResponse.status === 404) {
|
||||
this.isAnAnonymousUser = true;
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import {Observable, Subscriber} from 'rxjs';
|
||||
import {SocketEndpoint} from '../model/SocketEndpoint.model'
|
||||
|
||||
|
||||
import Stomp from 'stompjs';
|
||||
import SockJS from 'sockjs-client';
|
||||
import {SocketOption} from '../model/SocketOption.model';
|
||||
|
||||
interface WebsocketSubscriber {
|
||||
index: number,
|
||||
observer: Subscriber<any>
|
||||
}
|
||||
|
||||
export interface QuartzManagerWebsocketMessage {
|
||||
type: string;
|
||||
message: any;
|
||||
headers: any;
|
||||
self: boolean;
|
||||
}
|
||||
|
||||
export class WebsocketService {
|
||||
|
||||
_options: SocketOption;
|
||||
|
||||
_socket: SocketEndpoint = new SocketEndpoint();
|
||||
|
||||
observableStompConnection: Observable<any>;
|
||||
subscribers: Array<WebsocketSubscriber> = [];
|
||||
subscriberIndex = 0;
|
||||
|
||||
_messageIds: Array<any> = [];
|
||||
|
||||
reconnectionPromise: any;
|
||||
|
||||
constructor(options: SocketOption) {
|
||||
this._options = options
|
||||
this.createObservableSocket();
|
||||
this.connect();
|
||||
}
|
||||
|
||||
getOptions = () => {
|
||||
}
|
||||
|
||||
private createObservableSocket = () => {
|
||||
this.observableStompConnection = new Observable((observer) => {
|
||||
const subscriberIndex = this.subscriberIndex++;
|
||||
this.addToSubscribers({index: subscriberIndex, observer});
|
||||
return () => this.removeFromSubscribers(subscriberIndex);
|
||||
});
|
||||
}
|
||||
|
||||
private addToSubscribers = (subscriber) => {
|
||||
this.subscribers.push(subscriber);
|
||||
}
|
||||
|
||||
private removeFromSubscribers = (index) => {
|
||||
this.subscribers = this.subscribers.filter(subscriber => subscriber.index !== index);
|
||||
}
|
||||
|
||||
getObservable = () => {
|
||||
return this.observableStompConnection;
|
||||
};
|
||||
|
||||
getMessage = function (data): QuartzManagerWebsocketMessage {
|
||||
const out: QuartzManagerWebsocketMessage = <QuartzManagerWebsocketMessage>{};
|
||||
out.type = 'SUCCESS';
|
||||
out.message = JSON.parse(data.body);
|
||||
out.headers = {};
|
||||
out.headers.messageId = data.headers['message-id'];
|
||||
|
||||
const messageIdIndex = this._messageIds.indexOf(out.headers.messageId);
|
||||
if (messageIdIndex > -1) {
|
||||
out.self = true;
|
||||
this._messageIds = this._messageIds.splice(messageIdIndex, 1);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
_socketListener = (frame) => {
|
||||
console.log('Connected: ' + frame);
|
||||
this._socket.stomp.subscribe(
|
||||
this._options.topicName,
|
||||
data => this.subscribers.forEach(subscriber => subscriber.observer.next(this.getMessage(data)))
|
||||
);
|
||||
}
|
||||
|
||||
_onSocketError = (errorMsg) => {
|
||||
const out: any = {};
|
||||
out.type = 'ERROR';
|
||||
out.message = errorMsg;
|
||||
this.subscribers.forEach(subscriber => subscriber.observer.error(out));
|
||||
this.scheduleReconnection();
|
||||
}
|
||||
|
||||
scheduleReconnection = () => {
|
||||
this.reconnectionPromise = setTimeout(() => {
|
||||
console.log('Socket reconnecting... (if it fails, next attempt in ' + this._options.reconnectionTimeout + ' msec)');
|
||||
this.connect();
|
||||
}, this._options.reconnectionTimeout);
|
||||
}
|
||||
|
||||
reconnectNow = function () {
|
||||
this._socket.stomp.disconnect();
|
||||
if (this.reconnectionPromise && this.reconnectionPromise.cancel) {
|
||||
this.reconnectionPromise.cancel();
|
||||
}
|
||||
this.connect();
|
||||
};
|
||||
|
||||
send = (message) => {
|
||||
const id = Math.floor(Math.random() * 1000000);
|
||||
this._socket.stomp.send(this._options.brokerName, {
|
||||
priority: 9
|
||||
}, JSON.stringify({
|
||||
message: message,
|
||||
id: id
|
||||
}));
|
||||
this._messageIds.push(id);
|
||||
};
|
||||
|
||||
connect = () => {
|
||||
const headers = {};
|
||||
|
||||
let socketUrl = this._options.socketUrl;
|
||||
if (this._options.getAccessToken()) {
|
||||
socketUrl += `?access_token=${this._options.getAccessToken()}`;
|
||||
}
|
||||
|
||||
this._socket.client = new SockJS(socketUrl);
|
||||
this._socket.stomp = Stomp.over(this._socket.client);
|
||||
this._socket.stomp.connect(headers, this._socketListener, this._onSocketError);
|
||||
this._socket.stomp.onclose = this.scheduleReconnection;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,18 +19,25 @@
|
||||
<div fxFlex="1 1 350px">
|
||||
<div fxLayout="row" fxFill>
|
||||
<div fxLayout="column" fxFill>
|
||||
<qrzmng-simple-trigger-config fxFill
|
||||
[triggerKey]="selectedTriggerKey"
|
||||
(onNewTrigger)="onNewTriggerCreated($event)">
|
||||
</qrzmng-simple-trigger-config>
|
||||
<qrzmng-simple-trigger-config fxFill
|
||||
[triggerKey]="selectedTriggerKey"
|
||||
(triggerFormOpenChange)="setNewTriggerFormOpened($event)"
|
||||
(onNewTrigger)="onNewTriggerCreated($event)">
|
||||
</qrzmng-simple-trigger-config>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div fxFlex="1 1 auto" style="margin-left: 20px;">
|
||||
<div fxFlex="1 1 auto" fxLayout="column" fxLayoutAlign="start stretch" fxLayoutGap="6px">
|
||||
<progress-panel></progress-panel>
|
||||
<logs-panel fxFlex="1 1 auto" fxFill></logs-panel>
|
||||
<progress-panel
|
||||
[triggerKey]=selectedTriggerKey
|
||||
>
|
||||
</progress-panel>
|
||||
<logs-panel fxFlex="1 1 auto" fxFill
|
||||
[triggerKey]=selectedTriggerKey
|
||||
>
|
||||
</logs-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {
|
||||
ConfigService,
|
||||
UserService
|
||||
} from '../../services';
|
||||
import {SimpleTrigger} from '../../model/simple-trigger.model';
|
||||
import {TriggerKey} from '../../model/triggerKey.model';
|
||||
import {SimpleTriggerConfigComponent} from '../../components/simple-trigger-config';
|
||||
@@ -32,15 +28,25 @@ export class ManagerComponent implements OnInit {
|
||||
}
|
||||
|
||||
onNewTriggerRequested() {
|
||||
this.triggerConfigComponent.openTriggerForm();
|
||||
this.selectedTriggerKey = null;
|
||||
this.newTriggerFormOpened = true;
|
||||
if (this.triggerConfigComponent) {
|
||||
this.triggerConfigComponent.openNewTriggerForm();
|
||||
}
|
||||
}
|
||||
|
||||
onNewTriggerCreated(newTrigger: SimpleTrigger) {
|
||||
this.triggerListComponent.onNewTrigger(newTrigger);
|
||||
this.newTriggerFormOpened = false;
|
||||
}
|
||||
|
||||
setSelectedTrigger(triggerKey: TriggerKey) {
|
||||
this.selectedTriggerKey = triggerKey;
|
||||
this.newTriggerFormOpened = false;
|
||||
}
|
||||
|
||||
setNewTriggerFormOpened(opened: boolean) {
|
||||
this.newTriggerFormOpened = opened;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
@@ -50,7 +50,7 @@ import 'core-js/es6/reflect';
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
@@ -59,7 +59,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
@@ -69,7 +69,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
|
||||
/***************************************************************************************************
|
||||
/** *************************************************************************************************
|
||||
* MATERIAL 2
|
||||
*/
|
||||
import 'hammerjs/hammer';
|
||||
|
||||
2
quartz-manager-frontend/src/typings.d.ts
vendored
2
quartz-manager-frontend/src/typings.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
/* SystemJS module definition */
|
||||
declare var module: NodeModule;
|
||||
declare let module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
||||
|
||||
1
quartz-manager-parent/.gitignore
vendored
1
quartz-manager-parent/.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
.classpath
|
||||
.project
|
||||
.idea
|
||||
/**/*.iml
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<properties>
|
||||
<java.version>9</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<org.projectlombok.version>1.18.30</org.projectlombok.version>
|
||||
<maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version>
|
||||
<maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
|
||||
<jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
|
||||
@@ -50,6 +51,7 @@
|
||||
<nexus-staging-maven-plugin.version>1.6.7</nexus-staging-maven-plugin.version>
|
||||
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
|
||||
<maven-gpg-plugin.version>3.0.1</maven-gpg-plugin.version>
|
||||
<sonar-maven-plugin.version>3.11.0.3922</sonar-maven-plugin.version>
|
||||
<sonar.organization>fabioformosa</sonar.organization>
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
<sonar.exclusions>
|
||||
@@ -78,27 +80,32 @@
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-common</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-api</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-security</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-persistence</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-starter-ui</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
@@ -133,6 +140,11 @@
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${maven-failsafe-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonarsource.scanner.maven</groupId>
|
||||
<artifactId>sonar-maven-plugin</artifactId>
|
||||
<version>${sonar-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
@@ -240,26 +252,25 @@
|
||||
</activation>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<id>maven-central-release</id>
|
||||
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/
|
||||
</url>
|
||||
<id>maven-central-release</id>
|
||||
<url>https://central.sonatype.com</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>${nexus-staging-maven-plugin.version}</version>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.7.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>ossrh</serverId>
|
||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
<publishingServerId>maven-central-release</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
<waitUntil>published</waitUntil>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
<artifactId>quartz-manager-common</artifactId>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quartz-manager-starter-api</artifactId>
|
||||
|
||||
@@ -55,7 +55,8 @@ public class SchedulerConfig {
|
||||
if (quartzProperties != null && quartzProperties.size() > 0)
|
||||
mergedProperties.putAll(quartzProperties);
|
||||
factory.setQuartzProperties(mergedProperties);
|
||||
factory.setAutoStartup(false);
|
||||
boolean isAutoStartup = mergedProperties.getProperty("org.quartz.scheduler.isAutoStartup") != null && mergedProperties.getProperty("org.quartz.scheduler.isAutoStartup").equals("true");
|
||||
factory.setAutoStartup(isAutoStartup);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package it.fabioformosa.quartzmanager.api.configuration;
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
@@ -14,14 +15,16 @@ public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.enableSimpleBroker("/topic");
|
||||
config.setApplicationDestinationPrefixes("/job");
|
||||
config.enableSimpleBroker("/topic"); //enable a simple memory-based message broker
|
||||
// on destinations prefixed with /topic
|
||||
config.setApplicationDestinationPrefixes("/job"); // it designates the prefix for messages
|
||||
// that are bound for methods annotated with @MessageMapping
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/quartz-manager/logs").setAllowedOrigins("/**").withSockJS();
|
||||
registry.addEndpoint("/quartz-manager/progress").setAllowedOrigins("/**").withSockJS();
|
||||
registry.addEndpoint(QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/logs").setAllowedOrigins("/**").withSockJS();
|
||||
registry.addEndpoint(QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/progress").setAllowedOrigins("/**").withSockJS();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import org.springframework.stereotype.Controller;
|
||||
@Controller
|
||||
public class WebsocketController {
|
||||
|
||||
@MessageMapping({ QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/logs", QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/progress" })
|
||||
@MessageMapping({
|
||||
QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/logs",
|
||||
QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/progress"
|
||||
})
|
||||
@SendTo("/topic/logs")
|
||||
public String subscribe() {
|
||||
return "subscribed";
|
||||
|
||||
@@ -2,6 +2,7 @@ package it.fabioformosa.quartzmanager.api.converters;
|
||||
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.SimpleScheduleBuilder;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.quartz.Trigger;
|
||||
@@ -19,6 +20,8 @@ public class SimpleTriggerCommandDTOToSimpleTrigger implements Converter<SimpleT
|
||||
if (triggerCommandDTO.getSimpleTriggerInputDTO().getEndDate() != null)
|
||||
triggerTriggerBuilder.endAt(triggerCommandDTO.getSimpleTriggerInputDTO().getEndDate());
|
||||
|
||||
if (triggerCommandDTO.getSimpleTriggerInputDTO().getJobDataMap() != null)
|
||||
triggerTriggerBuilder.usingJobData(new JobDataMap(triggerCommandDTO.getSimpleTriggerInputDTO().getJobDataMap()));
|
||||
|
||||
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
|
||||
if (triggerCommandDTO.getSimpleTriggerInputDTO().getRepeatInterval() != null)
|
||||
|
||||
@@ -14,6 +14,7 @@ public class SimpleTriggerToSimpleTriggerDTO extends TriggerToTriggerDTO<SimpleT
|
||||
target.setRepeatCount(source.getRepeatCount());
|
||||
target.setRepeatInterval(source.getRepeatInterval());
|
||||
target.setMisfireInstruction(source.getMisfireInstruction());
|
||||
target.setJobDataMap(source.getJobDataMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,8 +3,9 @@ package it.fabioformosa.quartzmanager.api.dto;
|
||||
import it.fabioformosa.quartzmanager.api.validators.ValidTriggerRepetition;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.constraints.Positive;
|
||||
import java.util.Map;
|
||||
|
||||
@ValidTriggerRepetition
|
||||
@SuperBuilder
|
||||
@@ -18,4 +19,7 @@ public class SimpleTriggerInputDTO extends TriggerCommandDTO implements TriggerR
|
||||
|
||||
@Positive
|
||||
private Long repeatInterval;
|
||||
|
||||
@Nullable
|
||||
private Map<String, ?> jobDataMap;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -23,4 +24,5 @@ public class TriggerDTO {
|
||||
private JobKeyDTO jobKeyDTO;
|
||||
private JobDetailDTO jobDetailDTO;
|
||||
private boolean mayFireAgain;
|
||||
private JobDataMap jobDataMap;
|
||||
}
|
||||
|
||||
@@ -38,11 +38,13 @@ public abstract class AbstractQuartzManagerJob implements Job {
|
||||
LogRecord logMsg = doIt(jobExecutionContext);
|
||||
log.info(logMsg.getMessage());
|
||||
|
||||
String triggerName = jobExecutionContext.getTrigger().getKey().getName();
|
||||
|
||||
logMsg.setThreadName(Thread.currentThread().getName());
|
||||
webSocketLogsNotifier.send(logMsg);
|
||||
webSocketLogsNotifier.send(triggerName, logMsg);
|
||||
|
||||
TriggerFiredBundleDTO triggerFiredBundleDTO = WebSocketProgressNotifier.buildTriggerFiredBundle(jobExecutionContext);
|
||||
webSocketProgressNotifier.send(triggerFiredBundleDTO);
|
||||
webSocketProgressNotifier.send(triggerName, triggerFiredBundleDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class WebSocketLogsNotifier implements WebhookSender<LogRecord> {
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
|
||||
@Override
|
||||
public void send(LogRecord logRecord) {
|
||||
messagingTemplate.convertAndSend(TOPIC_LOGS, logRecord);
|
||||
public void send(String triggerName, LogRecord logRecord) {
|
||||
messagingTemplate.convertAndSend(TOPIC_LOGS + "/" + triggerName, logRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ public class WebSocketProgressNotifier implements WebhookSender<TriggerFiredBund
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
|
||||
@Override
|
||||
public void send(TriggerFiredBundleDTO triggerFiredBundleDTO) {
|
||||
messagingTemplate.convertAndSend(TOPIC_PROGRESS, triggerFiredBundleDTO);
|
||||
public void send(String triggerName, TriggerFiredBundleDTO triggerFiredBundleDTO) {
|
||||
messagingTemplate.convertAndSend(TOPIC_PROGRESS + "/" + triggerName, triggerFiredBundleDTO);
|
||||
}
|
||||
|
||||
public static TriggerFiredBundleDTO buildTriggerFiredBundle(JobExecutionContext jobExecutionContext) {
|
||||
|
||||
@@ -9,6 +9,6 @@ package it.fabioformosa.quartzmanager.api.websockets;
|
||||
*/
|
||||
public interface WebhookSender<T> {
|
||||
|
||||
void send(T message);
|
||||
void send(String triggerName, T message);
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import java.util.Map;
|
||||
import static java.util.Map.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
|
||||
@@ -68,6 +69,10 @@ class SimpleTriggerControllerTest {
|
||||
}
|
||||
|
||||
private SimpleTriggerInputDTO buildACompleteSimpleTriggerCommandDTO() {
|
||||
Map<String, ?> triggerJobDataMap = Map.ofEntries(
|
||||
entry("customTriggerData1", "value1"),
|
||||
entry("customTriggerData2", "value2")
|
||||
);
|
||||
return SimpleTriggerInputDTO.builder()
|
||||
.jobClass("it.fabioformosa.quartzmanager.api.jobs.SampleJob")
|
||||
.startDate(new Date())
|
||||
@@ -75,6 +80,7 @@ class SimpleTriggerControllerTest {
|
||||
.misfireInstruction(MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW)
|
||||
.repeatCount(5)
|
||||
.repeatInterval(1000L * 60 * 60)
|
||||
.jobDataMap(triggerJobDataMap)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,23 @@ package it.fabioformosa.quartzmanager.api.controllers.utils;
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.common.utils.DateUtils;
|
||||
import it.fabioformosa.quartzmanager.api.dto.*;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.SimpleScheduleBuilder;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerBuilder;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
|
||||
public class TriggerUtils {
|
||||
|
||||
static public TriggerDTO getTriggerInstance(String triggerName){
|
||||
static public TriggerDTO getTriggerInstance(String triggerName) {
|
||||
return TriggerDTO.builder()
|
||||
.description("sample trigger")
|
||||
.endTime(DateUtils.addHoursToNow(2L))
|
||||
@@ -28,7 +39,7 @@ public class TriggerUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName, SimpleTriggerInputDTO simpleTriggerInputDTO){
|
||||
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName, SimpleTriggerInputDTO simpleTriggerInputDTO) {
|
||||
return SimpleTriggerDTO.builder()
|
||||
.description("simple trigger")
|
||||
.repeatCount(simpleTriggerInputDTO.getRepeatCount())
|
||||
@@ -48,10 +59,11 @@ public class TriggerUtils {
|
||||
.nextFireTime(DateUtils.addHoursToNow(1L))
|
||||
.priority(1)
|
||||
.startTime(DateUtils.fromLocalDateTimeToDate(LocalDateTime.now()))
|
||||
.jobDataMap(new JobDataMap(simpleTriggerInputDTO.getJobDataMap()))
|
||||
.build();
|
||||
}
|
||||
|
||||
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName){
|
||||
static public SimpleTriggerDTO getSimpleTriggerInstance(String triggerName) {
|
||||
return SimpleTriggerDTO.builder()
|
||||
.description("simple trigger")
|
||||
.repeatCount(2)
|
||||
@@ -71,6 +83,44 @@ public class TriggerUtils {
|
||||
.nextFireTime(DateUtils.addHoursToNow(1L))
|
||||
.priority(1)
|
||||
.startTime(DateUtils.fromLocalDateTimeToDate(LocalDateTime.now()))
|
||||
.jobDataMap(new JobDataMap(Map.ofEntries(entry("customTriggerData1", "value1"))))
|
||||
.build();
|
||||
}
|
||||
|
||||
static public SimpleTrigger buildSimpleTrigger() {
|
||||
TriggerBuilder<Trigger> triggerTriggerBuilder = TriggerBuilder.newTrigger();
|
||||
triggerTriggerBuilder.startAt(new Date());
|
||||
triggerTriggerBuilder.endAt(DateUtils.addHoursToNow(1));
|
||||
triggerTriggerBuilder.usingJobData(new JobDataMap(Map.ofEntries(entry("data", "value"))));
|
||||
|
||||
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
|
||||
scheduleBuilder.withIntervalInMilliseconds(1000);
|
||||
scheduleBuilder.withRepeatCount(1);
|
||||
scheduleBuilder.withMisfireHandlingInstructionFireNow();
|
||||
|
||||
return triggerTriggerBuilder.withSchedule(
|
||||
scheduleBuilder
|
||||
)
|
||||
.withIdentity("simpleTrigger").build();
|
||||
}
|
||||
|
||||
static public SimpleTriggerCommandDTO buildSimpleTriggerCommandDTO(String triggerName) throws ParseException {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date startDate = dateFormat.parse("2024-02-02");
|
||||
Date endDate = dateFormat.parse("2024-03-02");
|
||||
|
||||
SimpleTriggerInputDTO triggerInputDTO = SimpleTriggerInputDTO.builder()
|
||||
.misfireInstruction(MisfireInstruction.MISFIRE_INSTRUCTION_FIRE_NOW)
|
||||
.jobClass("sample.jobClass")
|
||||
.repeatCount(1)
|
||||
.repeatInterval(1000L)
|
||||
.startDate(startDate)
|
||||
.endDate(endDate)
|
||||
.jobDataMap(Map.ofEntries(entry("data", "value")))
|
||||
.build();
|
||||
return SimpleTriggerCommandDTO.builder()
|
||||
.triggerName(triggerName)
|
||||
.simpleTriggerInputDTO(triggerInputDTO)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package it.fabioformosa.quartzmanager.api.converters;
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.controllers.utils.TriggerUtils;
|
||||
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerCommandDTO;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@SpringBootTest
|
||||
class SimpleTriggerCommandDTOToSimpleTriggerTest {
|
||||
|
||||
@Autowired
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Order(1)
|
||||
@Test
|
||||
void givenSimpleTriggerCommandDTO_whenItIsConverted_thenASimpleTriggerIsReturned() throws ParseException {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date startDate = dateFormat.parse("2024-02-02");
|
||||
Date endDate = dateFormat.parse("2024-03-02");
|
||||
SimpleTriggerCommandDTO simpleTriggerCommandDTO = TriggerUtils.buildSimpleTriggerCommandDTO("mytrigger");
|
||||
SimpleTrigger simpleTrigger = conversionService.convert(simpleTriggerCommandDTO, SimpleTrigger.class);
|
||||
Assertions.assertThat(simpleTrigger).isNotNull();
|
||||
Assertions.assertThat(simpleTrigger.getRepeatCount()).isEqualTo(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getRepeatCount());
|
||||
Assertions.assertThat(simpleTrigger.getRepeatInterval()).isEqualTo(simpleTriggerCommandDTO.getSimpleTriggerInputDTO().getRepeatInterval());
|
||||
Assertions.assertThat(simpleTrigger.getJobDataMap()).containsEntry("data", "value");
|
||||
Assertions.assertThat(simpleTrigger.getStartTime()).isEqualTo(startDate);
|
||||
Assertions.assertThat(simpleTrigger.getEndTime()).isEqualTo(endDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package it.fabioformosa.quartzmanager.api.converters;
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.controllers.utils.TriggerUtils;
|
||||
import it.fabioformosa.quartzmanager.api.dto.SimpleTriggerDTO;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.quartz.SimpleTrigger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
||||
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@SpringBootTest
|
||||
class SimpleTriggerToSimpleTriggerDTOTest {
|
||||
|
||||
@Autowired
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Order(1)
|
||||
@Test
|
||||
void givenSimpleTrigger_whenItIsConverted_thenADtoIsReturned() {
|
||||
SimpleTrigger simpleTrigger = TriggerUtils.buildSimpleTrigger();
|
||||
SimpleTriggerDTO simpleTriggerDTO = conversionService.convert(simpleTrigger, SimpleTriggerDTO.class);
|
||||
Assertions.assertThat(simpleTriggerDTO).isNotNull();
|
||||
Assertions.assertThat(simpleTriggerDTO.getRepeatCount()).isEqualTo(simpleTrigger.getRepeatCount());
|
||||
Assertions.assertThat(simpleTriggerDTO.getRepeatInterval()).isEqualTo(simpleTrigger.getRepeatInterval());
|
||||
Assertions.assertThat(simpleTriggerDTO.getJobDataMap()).containsEntry("data", "value");
|
||||
Assertions.assertThat(simpleTriggerDTO.getStartTime()).isEqualTo(simpleTrigger.getStartTime());
|
||||
Assertions.assertThat(simpleTriggerDTO.getEndTime()).isEqualTo(simpleTrigger.getEndTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,26 @@ import it.fabioformosa.quartzmanager.api.dto.TriggerFiredBundleDTO;
|
||||
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
|
||||
import it.fabioformosa.quartzmanager.api.websockets.WebhookSender;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.quartz.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.ScheduleBuilder;
|
||||
import org.quartz.SimpleScheduleBuilder;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerBuilder;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SampleJobTest {
|
||||
|
||||
@InjectMocks
|
||||
@@ -24,14 +34,16 @@ class SampleJobTest {
|
||||
@Mock
|
||||
private WebhookSender<LogRecord> webSocketLogsNotifier;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
@Captor
|
||||
private ArgumentCaptor<LogRecord> logRecordCaptor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<TriggerFiredBundleDTO> triggerFiredBundleDTOCaptor;
|
||||
|
||||
@Test
|
||||
void givenASampleJob_whenTheJobIsExecuted_thenTheWebhookSendersAreCalled() {
|
||||
JobExecutionContext jobExecutionContext = Mockito.mock(JobExecutionContext.class);
|
||||
String triggerName = "test-trigger";
|
||||
|
||||
ScheduleBuilder schedulerBuilder = SimpleScheduleBuilder.simpleSchedule()
|
||||
.withRepeatCount(5)
|
||||
@@ -40,6 +52,7 @@ class SampleJobTest {
|
||||
.newJob(SampleJob.class).withIdentity(JobKey.jobKey("test-job"))
|
||||
.build();
|
||||
Trigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(triggerName)
|
||||
.forJob(jobDetail)
|
||||
.withSchedule(schedulerBuilder)
|
||||
.build();
|
||||
@@ -47,24 +60,23 @@ class SampleJobTest {
|
||||
Mockito.when(jobExecutionContext.getJobDetail()).thenReturn(jobDetail);
|
||||
|
||||
sampleJob.execute(jobExecutionContext);
|
||||
Mockito.verify(webSocketLogsNotifier).send(argThat(actualLogRecord -> {
|
||||
Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!");
|
||||
Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO);
|
||||
Assertions.assertThat(actualLogRecord.getDate()).isNotNull();
|
||||
Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull();
|
||||
return true;
|
||||
}));
|
||||
Mockito.verify(webSocketProgressNotifier).send(argThat(triggerFiredBundleDTO -> {
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job");
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6);
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName());
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getTimesTriggered()).isZero();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getNextFireTime()).isNull();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isZero();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getFinalFireTime()).isNotNull();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getPreviousFireTime()).isNull();
|
||||
return true;
|
||||
}));
|
||||
Mockito.verify(webSocketLogsNotifier).send(eq(triggerName), logRecordCaptor.capture());
|
||||
LogRecord actualLogRecord = logRecordCaptor.getValue();
|
||||
Assertions.assertThat(actualLogRecord.getMessage()).isEqualTo("Hello!");
|
||||
Assertions.assertThat(actualLogRecord.getType()).isEqualTo(LogRecord.LogType.INFO);
|
||||
Assertions.assertThat(actualLogRecord.getDate()).isNotNull();
|
||||
Assertions.assertThat(actualLogRecord.getThreadName()).isNotNull();
|
||||
|
||||
Mockito.verify(webSocketProgressNotifier).send(eq(triggerName), triggerFiredBundleDTOCaptor.capture());
|
||||
TriggerFiredBundleDTO triggerFiredBundleDTO = triggerFiredBundleDTOCaptor.getValue();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getJobKey()).isEqualTo("test-job");
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getRepeatCount()).isEqualTo(6);
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getJobClass()).isEqualTo(SampleJob.class.getName());
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getTimesTriggered()).isZero();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getNextFireTime()).isNull();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getPercentage()).isZero();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getFinalFireTime()).isNotNull();
|
||||
Assertions.assertThat(triggerFiredBundleDTO.getPreviousFireTime()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,13 +18,17 @@ import java.util.Date;
|
||||
@SpringBootTest
|
||||
class SimpleTriggerServiceIntegrationTest {
|
||||
|
||||
private static final String SAMPLE_JOB_CLASS = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
||||
private static final String SAMPLE_EXTRA_JOB_CLASS = "it.fabioformosa.samplepackage.SampleExtraJob";
|
||||
private static final String FIRST_TRIGGER_SUFFIX = "A";
|
||||
private static final String SECOND_TRIGGER_SUFFIX = "B";
|
||||
|
||||
@Autowired
|
||||
private SimpleTriggerService simpleTriggerService;
|
||||
|
||||
@Test
|
||||
void givenASimpleTriggerCommandDTOWithAllData_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
||||
String simpleTriggerTestName = "simpleTriggerWithAllData";
|
||||
String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
||||
Date startDate = new Date();
|
||||
Date endDate = DateUtils.addHoursToNow(5);
|
||||
int repeatCount = 3;
|
||||
@@ -41,7 +45,7 @@ class SimpleTriggerServiceIntegrationTest {
|
||||
.repeatCount(repeatCount)
|
||||
.repeatInterval(repeatInterval)
|
||||
.misfireInstruction(misfireInstructionFireNow)
|
||||
.jobClass(jobClass)
|
||||
.jobClass(SAMPLE_JOB_CLASS)
|
||||
.build())
|
||||
.build();
|
||||
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
||||
@@ -61,12 +65,11 @@ class SimpleTriggerServiceIntegrationTest {
|
||||
@Test
|
||||
void givenASimpleTriggerCommandDTOWithMissingOptionalField_whenANewSimpleTriggerIsScheduled_thenShouldGetATriggertDTO() throws SchedulerException, ClassNotFoundException {
|
||||
String simpleTriggerTestName = "simpleTriggerWithoutOptionalData";
|
||||
String jobClass = "it.fabioformosa.quartzmanager.api.jobs.SampleJob";
|
||||
|
||||
SimpleTriggerCommandDTO simpleTriggerCommand = SimpleTriggerCommandDTO.builder()
|
||||
.triggerName(simpleTriggerTestName)
|
||||
.simpleTriggerInputDTO(SimpleTriggerInputDTO.builder()
|
||||
.jobClass(jobClass)
|
||||
.jobClass(SAMPLE_JOB_CLASS)
|
||||
.build())
|
||||
.build();
|
||||
SimpleTriggerDTO simpleTriggerDTO = simpleTriggerService.scheduleSimpleTrigger(simpleTriggerCommand);
|
||||
@@ -81,4 +84,49 @@ class SimpleTriggerServiceIntegrationTest {
|
||||
Assertions.assertThat(simpleTriggerDTO.getRepeatInterval()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenTwoSimpleTriggerCommandDTOsForTheSameJob_whenScheduled_thenShouldCreateTwoTriggers() throws SchedulerException, ClassNotFoundException {
|
||||
String triggerNamePrefix = "sameJobTrigger" + System.nanoTime();
|
||||
|
||||
SimpleTriggerDTO firstTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||
buildSimpleTriggerCommand(triggerNamePrefix + FIRST_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||
);
|
||||
SimpleTriggerDTO secondTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||
buildSimpleTriggerCommand(triggerNamePrefix + SECOND_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||
);
|
||||
|
||||
Assertions.assertThat(firstTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + FIRST_TRIGGER_SUFFIX);
|
||||
Assertions.assertThat(secondTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + SECOND_TRIGGER_SUFFIX);
|
||||
Assertions.assertThat(firstTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||
Assertions.assertThat(secondTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||
Assertions.assertThat(firstTrigger.getJobKeyDTO().getName()).isNotEqualTo(secondTrigger.getJobKeyDTO().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenTwoSimpleTriggerCommandDTOsForDifferentJobs_whenScheduled_thenShouldCreateTwoTriggers() throws SchedulerException, ClassNotFoundException {
|
||||
String triggerNamePrefix = "differentJobTrigger" + System.nanoTime();
|
||||
|
||||
SimpleTriggerDTO firstTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||
buildSimpleTriggerCommand(triggerNamePrefix + FIRST_TRIGGER_SUFFIX, SAMPLE_JOB_CLASS)
|
||||
);
|
||||
SimpleTriggerDTO secondTrigger = simpleTriggerService.scheduleSimpleTrigger(
|
||||
buildSimpleTriggerCommand(triggerNamePrefix + SECOND_TRIGGER_SUFFIX, SAMPLE_EXTRA_JOB_CLASS)
|
||||
);
|
||||
|
||||
Assertions.assertThat(firstTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + FIRST_TRIGGER_SUFFIX);
|
||||
Assertions.assertThat(secondTrigger.getTriggerKeyDTO().getName()).isEqualTo(triggerNamePrefix + SECOND_TRIGGER_SUFFIX);
|
||||
Assertions.assertThat(firstTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_JOB_CLASS);
|
||||
Assertions.assertThat(secondTrigger.getJobDetailDTO().getJobClassName()).isEqualTo(SAMPLE_EXTRA_JOB_CLASS);
|
||||
}
|
||||
|
||||
private static SimpleTriggerCommandDTO buildSimpleTriggerCommand(String triggerName, String jobClass) {
|
||||
return SimpleTriggerCommandDTO.builder()
|
||||
.triggerName(triggerName)
|
||||
.simpleTriggerInputDTO(SimpleTriggerInputDTO.builder()
|
||||
.jobClass(jobClass)
|
||||
.startDate(DateUtils.addHoursToNow(1))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package it.fabioformosa.quartzmanager.api.websockets;
|
||||
|
||||
import it.fabioformosa.quartzmanager.api.dto.TriggerFiredBundleDTO;
|
||||
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
|
||||
import static org.mockito.MockitoAnnotations.openMocks;
|
||||
|
||||
class WebSocketNotifierTest {
|
||||
|
||||
@InjectMocks
|
||||
private WebSocketLogsNotifier webSocketLogsNotifier;
|
||||
|
||||
@InjectMocks
|
||||
private WebSocketProgressNotifier webSocketProgressNotifier;
|
||||
|
||||
@Mock
|
||||
private SimpMessageSendingOperations messagingTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenATriggerName_whenALogIsSent_thenShouldSendItToTheTriggerLogsTopic() {
|
||||
LogRecord logRecord = new LogRecord(LogRecord.LogType.INFO, "Hello!");
|
||||
|
||||
webSocketLogsNotifier.send("trigger-1", logRecord);
|
||||
|
||||
Mockito.verify(messagingTemplate).convertAndSend("/topic/logs/trigger-1", logRecord);
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenATriggerName_whenProgressIsSent_thenShouldSendItToTheTriggerProgressTopic() {
|
||||
TriggerFiredBundleDTO triggerFiredBundleDTO = new TriggerFiredBundleDTO();
|
||||
|
||||
webSocketProgressNotifier.send("trigger-1", triggerFiredBundleDTO);
|
||||
|
||||
Mockito.verify(messagingTemplate).convertAndSend("/topic/progress/trigger-1", triggerFiredBundleDTO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quartz-manager-starter-persistence</artifactId>
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.springframework.context.annotation.*;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:quartz-persistence.properties")
|
||||
public class PersistenceConfig {
|
||||
|
||||
@Value("${quartz-manager.persistence.quartz.datasource.url}")
|
||||
@@ -57,7 +56,7 @@ public class PersistenceConfig {
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public DataSource quartzManagerDatasource(PersistenceDatasourceProps persistenceDatasourceProps) {
|
||||
public DataSource quartzManagerDatasource() {
|
||||
return DataSourceBuilder.create()
|
||||
.url(quartzDatasourceUrl)
|
||||
.driverClassName("org.postgresql.Driver")
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
|
||||
|
||||
<includeAll path="./migrations" relativeToChangelogFile="true"/>
|
||||
<includeAll path="classpath:db/quartz-scheduler/migrations" relativeToChangelogFile="false" errorIfMissingOrEmpty="true"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quartz-manager-starter-security</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quartz-manager-starter-ui</artifactId>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: deploy.cloud.google.com/v1
|
||||
kind: Target
|
||||
metadata:
|
||||
name: dev
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: dev
|
||||
gke:
|
||||
cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: deploy.cloud.google.com/v1
|
||||
kind: DeliveryPipeline
|
||||
metadata:
|
||||
name: quartz-manager-pipeline
|
||||
labels:
|
||||
app: quartz-manager-standalone
|
||||
description: quartz-manager-standalone delivery pipeline
|
||||
serialPipeline:
|
||||
stages:
|
||||
- targetId: dev
|
||||
profiles:
|
||||
- dev
|
||||
- targetId: staging
|
||||
profiles:
|
||||
- staging
|
||||
- targetId: prod
|
||||
profiles:
|
||||
- prod
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: deploy.cloud.google.com/v1
|
||||
kind: Target
|
||||
metadata:
|
||||
name: prod
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: prod
|
||||
requireApproval: true
|
||||
gke:
|
||||
cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: deploy.cloud.google.com/v1
|
||||
kind: Target
|
||||
metadata:
|
||||
name: staging
|
||||
annotations: {}
|
||||
labels: {}
|
||||
description: staging
|
||||
requireApproval: true
|
||||
gke:
|
||||
cluster: projects/quartz-manager-test/locations/europe-west8-a/clusters/gke-cluster
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: v2
|
||||
name: quartz-manager-standalone
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.0.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "_HELM_APP_VERSION"
|
||||
@@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "quartzmanager-standalone.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "quartzmanager-standalone.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "quartzmanager-standalone.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "quartzmanager-standalone.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.labels" -}}
|
||||
helm.sh/chart: {{ include "quartzmanager-standalone.chart" . }}
|
||||
{{ include "quartzmanager-standalone.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "quartzmanager-standalone.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "quartzmanager-standalone.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "quartzmanager-standalone.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata:
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ .Chart.Name }}
|
||||
data:
|
||||
envName: {{ .Values.config.envName | quote }}
|
||||
@@ -0,0 +1,68 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "quartzmanager-standalone.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "quartzmanager-standalone.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
{{/* livenessProbe:*/}}
|
||||
{{/* initialDelaySeconds: 30*/}}
|
||||
{{/* timeoutSeconds: 10*/}}
|
||||
{{/* httpGet:*/}}
|
||||
{{/* path: /*/}}
|
||||
{{/* port: 8080*/}}
|
||||
{{/* readinessProbe:*/}}
|
||||
{{/* initialDelaySeconds: 30*/}}
|
||||
{{/* timeoutSeconds: 10*/}}
|
||||
{{/* httpGet:*/}}
|
||||
{{/* path: /*/}}
|
||||
{{/* port: 8080*/}}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "quartzmanager-standalone.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "quartzmanager-standalone.fullname" . }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "quartzmanager-standalone.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "quartzmanager-standalone.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "quartzmanager-standalone.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,83 @@
|
||||
# Default values for hello-world.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: "europe-west8-docker.pkg.dev/quartz-manager-test/quartz-manager/quartz-manager-standalone/quartz-manager-standalone"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: false
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
# targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
config:
|
||||
envName: NA
|
||||
@@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<groupId>it.fabioformosa.quartz-manager</groupId>
|
||||
<artifactId>quartz-manager-parent</artifactId>
|
||||
<version>4.0.8</version>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quartz-manager-web-showcase</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<packaging>war</packaging>
|
||||
<artifactId>quartz-manager-web-showcase</artifactId>
|
||||
|
||||
<name>Quartz Manager Web Showcase</name>
|
||||
<description>A webapp that imports Quartz Manager API lib and the frontend webjar</description>
|
||||
@@ -49,15 +49,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -123,30 +118,28 @@
|
||||
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>9</source>
|
||||
<target>9</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>9</source>
|
||||
<target>9</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package it.fabioformosa;
|
||||
|
||||
import lombok.Generated;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
/**
|
||||
* ServletInitializer needs to deploy quartz-manager into a servlet container as a war file
|
||||
*
|
||||
* @author Fabio Formosa
|
||||
*
|
||||
*/
|
||||
@Generated
|
||||
public class ServletInitializer extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(QuartzManagerDemoApplication.class);
|
||||
}
|
||||
|
||||
}
|
||||
46
skaffold.yaml
Normal file
46
skaffold.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
apiVersion: skaffold/v4beta7
|
||||
kind: Config
|
||||
build:
|
||||
tagPolicy:
|
||||
envTemplate:
|
||||
template: "_IMAGE_TAG_POLICY"
|
||||
artifacts:
|
||||
- image: quartz-manager-standalone
|
||||
context: ./
|
||||
profiles:
|
||||
- name: dev
|
||||
deploy:
|
||||
helm:
|
||||
releases:
|
||||
- name: quartzmanager-standalone
|
||||
createNamespace: true
|
||||
namespace: quartzmanager-dev
|
||||
chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm
|
||||
# valuesFiles:
|
||||
# - helm/envs/dev/values.yaml
|
||||
setValueTemplates:
|
||||
image.tag: "_IMAGE_TAG_POLICY"
|
||||
- name: staging
|
||||
deploy:
|
||||
helm:
|
||||
releases:
|
||||
- name: quartzmanager-standalone
|
||||
createNamespace: true
|
||||
namespace: quartzmanager-staging
|
||||
chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm
|
||||
# valuesFiles:
|
||||
# - helm/envs/dev/values.yaml
|
||||
setValueTemplates:
|
||||
image.tag: "_IMAGE_TAG_POLICY"
|
||||
- name: prod
|
||||
deploy:
|
||||
helm:
|
||||
releases:
|
||||
- name: quartzmanager-standalone
|
||||
createNamespace: true
|
||||
namespace: quartzmanager-prod
|
||||
chartPath: quartz-manager-parent/quartz-manager-web-showcase/helm
|
||||
# valuesFiles:
|
||||
# - helm/envs/dev/values.yaml
|
||||
setValueTemplates:
|
||||
image.tag: "_IMAGE_TAG_POLICY"
|
||||
Reference in New Issue
Block a user