From bcb5d58d186d36f99d37ea36fc0c7fc155a6cbde Mon Sep 17 00:00:00 2001
From: parisyup <66366646+parisyup@users.noreply.github.com>
Date: Tue, 30 Apr 2024 16:19:15 +0400
Subject: [PATCH] bump to 5.2
---
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../corda5-negotiation-cordapp/README.md | 14 +-
.../corda5-negotiation-cordapp/build.gradle | 48 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 22 +-
.../gradle.properties | 55 ++-
.../settings.gradle | 5 +-
.../workflows/build.gradle | 28 +-
.../workflows/util/ListProposal.java | 3 +-
.../negotiation/workflows/util/ListTrade.java | 45 +++
.../workflows/util/ListTradeArgs.java | 37 ++
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../corda5-obligation-cordapp/README.md | 2 +-
.../corda5-obligation-cordapp/build.gradle | 48 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 22 +-
.../gradle.properties | 55 ++-
.../corda5-obligation-cordapp/settings.gradle | 15 +-
.../workflows/build.gradle | 30 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
java-samples/encumbrance-pawn-shop/README.md | 2 +-
.../encumbrance-pawn-shop/build.gradle | 140 ++++----
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 22 +-
.../encumbrance-pawn-shop/gradle.properties | 56 ++-
.../encumbrance-pawn-shop/settings.gradle | 5 +-
.../workflows/build.gradle | 30 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../persistence-carinsurance/README.md | 2 +-
.../persistence-carinsurance/build.gradle | 141 ++++----
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 21 +-
.../gradle.properties | 56 ++-
.../persistence-carinsurance/settings.gradle | 5 +-
.../workflows/build.gradle | 30 +-
.../workflows/InsuranceClaimFlow.java | 3 +-
.../InsuranceClaimFlowResponder.java | 4 +-
.../workflows/IssueInsuranceFlow.java | 3 +-
.../workflows/IssueInsuranceResponder.java | 4 +-
.../ping-pong/FlowManagementUI/Dockerfile | 5 +
.../ping-pong/FlowManagementUI/README.md | 84 +++++
.../ping-pong/FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
java-samples/ping-pong/README.md | 2 +-
java-samples/ping-pong/build.gradle | 47 +--
.../config/combined-worker-compose.yaml | 87 +++++
java-samples/ping-pong/gradle.properties | 55 ++-
java-samples/ping-pong/settings.gradle | 14 +-
java-samples/ping-pong/workflows/build.gradle | 32 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../referencestates-sanctionsbody/README.md | 2 +-
.../build.gradle | 141 ++++----
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 21 +-
.../gradle.properties | 56 ++-
.../settings.gradle | 5 +-
.../workflows/build.gradle | 30 +-
.../shinny-tokens/FlowManagementUI/Dockerfile | 5 +
.../shinny-tokens/FlowManagementUI/README.md | 84 +++++
.../shinny-tokens/FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
java-samples/shinny-tokens/README.md | 2 +-
java-samples/shinny-tokens/build.gradle | 140 ++++----
.../config/combined-worker-compose.yaml | 87 +++++
.../shinny-tokens/contracts/build.gradle | 22 +-
java-samples/shinny-tokens/gradle.properties | 56 ++-
java-samples/shinny-tokens/settings.gradle | 5 +-
.../shinny-tokens/workflows/build.gradle | 30 +-
.../tokens/workflows/BurnGoldTokenFlow.java | 3 +-
.../workflows/TransferGoldTokenFlow.java | 3 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../corda5-obligation-cordapp/README.md | 2 +-
.../corda5-obligation-cordapp/build.gradle | 40 ++-
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 22 +-
.../gradle.properties | 56 ++-
.../corda5-obligation-cordapp/settings.gradle | 7 +-
.../workflows/build.gradle | 31 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../encumbrance-pawn-shop/README.md | 2 +-
.../encumbrance-pawn-shop/build.gradle | 50 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 14 +-
.../corda5-obligation-cordapp/.ci/Jenkinsfile | 11 +
.../.ci/nightly/JenkinsfileSnykScan | 6 +
.../corda5-obligation-cordapp/.gitignore | 86 +++++
.../corda5-obligation-cordapp/.snyk | 14 +
.../corda5-obligation-cordapp/README.md | 89 +++++
.../corda5-obligation-cordapp/build.gradle | 88 +++++
.../gradle.properties | 73 ++++
.../corda5-obligation-cordapp/gradlew | 244 +++++++++++++
.../corda5-obligation-cordapp/gradlew.bat | 92 +++++
.../corda5-obligation-cordapp/settings.gradle | 25 ++
.../encumbrance-pawn-shop/gradle.properties | 56 ++-
.../encumbrance-pawn-shop/settings.gradle | 5 +-
.../workflows/build.gradle | 24 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../oracle-foreign-exchange/README.md | 2 +-
.../oracle-foreign-exchange/build.gradle | 40 ++-
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 16 +-
.../oracle-foreign-exchange/gradle.properties | 56 ++-
.../oracle-foreign-exchange/settings.gradle | 5 +-
.../workflows/build.gradle | 26 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../persistence-carinsurance/README.md | 2 +-
.../persistence-carinsurance/build.gradle | 50 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 14 +-
.../gradle.properties | 56 ++-
.../persistence-carinsurance/settings.gradle | 5 +-
.../workflows/build.gradle | 24 +-
.../persistence/workflows/InsuranceClaim.kt | 6 +-
.../persistence/workflows/IssueInsurance.kt | 5 +-
.../ping-pong/FlowManagementUI/Dockerfile | 5 +
.../ping-pong/FlowManagementUI/README.md | 84 +++++
.../ping-pong/FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
kotlin-samples/ping-pong/README.md | 2 +-
kotlin-samples/ping-pong/build.gradle | 50 +--
.../config/combined-worker-compose.yaml | 87 +++++
kotlin-samples/ping-pong/gradle.properties | 56 ++-
kotlin-samples/ping-pong/settings.gradle | 5 +-
.../ping-pong/workflows/build.gradle | 2 +-
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../referencestates-sanctionsbody/README.md | 2 +-
.../build.gradle | 50 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../contracts/build.gradle | 14 +-
.../gradle.properties | 56 ++-
.../settings.gradle | 5 +-
.../workflows/build.gradle | 24 +-
.../shinny-tokens/FlowManagementUI/Dockerfile | 5 +
.../shinny-tokens/FlowManagementUI/README.md | 84 +++++
.../shinny-tokens/FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
kotlin-samples/shinny-tokens/README.md | 2 +-
kotlin-samples/shinny-tokens/build.gradle | 50 +--
.../config/combined-worker-compose.yaml | 87 +++++
.../shinny-tokens/contracts/build.gradle | 14 +-
.../shinny-tokens/gradle.properties | 56 ++-
kotlin-samples/shinny-tokens/settings.gradle | 5 +-
.../shinny-tokens/workflows/build.gradle | 24 +-
.../tokens/workflows/BurnGoldTokenFlow.kt | 2 +-
.../tokens/workflows/TransferGoldTokenFlow.kt | 2 +-
217 files changed, 13647 insertions(+), 937 deletions(-)
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/Dockerfile
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/README.md
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/app.py
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/requirements.txt
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/corda5-negotiation-cordapp/FlowManagementUI/templates/index.html
create mode 100644 java-samples/corda5-negotiation-cordapp/config/combined-worker-compose.yaml
create mode 100644 java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTrade.java
create mode 100644 java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTradeArgs.java
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
create mode 100644 java-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
create mode 100644 java-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/README.md
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/app.py
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
create mode 100644 java-samples/persistence-carinsurance/config/combined-worker-compose.yaml
create mode 100644 java-samples/ping-pong/FlowManagementUI/Dockerfile
create mode 100644 java-samples/ping-pong/FlowManagementUI/README.md
create mode 100644 java-samples/ping-pong/FlowManagementUI/app.py
create mode 100644 java-samples/ping-pong/FlowManagementUI/requirements.txt
create mode 100644 java-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/ping-pong/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/ping-pong/FlowManagementUI/templates/index.html
create mode 100644 java-samples/ping-pong/config/combined-worker-compose.yaml
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
create mode 100644 java-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/Dockerfile
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/README.md
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/app.py
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/requirements.txt
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/shinny-tokens/FlowManagementUI/templates/index.html
create mode 100644 java-samples/shinny-tokens/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/Jenkinsfile
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/nightly/JenkinsfileSnykScan
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.gitignore
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.snyk
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/README.md
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/build.gradle
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradle.properties
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew.bat
create mode 100644 kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/settings.gradle
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/README.md
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/app.py
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/oracle-foreign-exchange/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/oracle-foreign-exchange/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/README.md
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/app.py
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/persistence-carinsurance/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/README.md
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/app.py
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/ping-pong/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/ping-pong/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/Dockerfile
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/README.md
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/app.py
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/requirements.txt
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/static/css/main.css
create mode 100644 kotlin-samples/shinny-tokens/FlowManagementUI/templates/index.html
create mode 100644 kotlin-samples/shinny-tokens/config/combined-worker-compose.yaml
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/Dockerfile b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/README.md b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/app.py b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/requirements.txt b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/Scripts/script.js b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/css/main.css b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/corda5-negotiation-cordapp/FlowManagementUI/templates/index.html b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/corda5-negotiation-cordapp/README.md b/java-samples/corda5-negotiation-cordapp/README.md
index 4541221..40e6502 100644
--- a/java-samples/corda5-negotiation-cordapp/README.md
+++ b/java-samples/corda5-negotiation-cordapp/README.md
@@ -29,7 +29,7 @@ In the `AcceptanceFlow.java`, we receive the modified ProposalState and it's con
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload.
@@ -103,6 +103,18 @@ And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/
Thus, we have concluded a full run through of the Negotiation app.
+#### Step 5: list the trade from Alice
+Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
+```
+{
+ "clientRequestId": "list-2",
+ "flowClassName": "com.r3.developers.samples.negotiation.workflows.util.ListTrade",
+ "requestBody": {}
+}
+```
+After trigger the Trade Proposal, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and client request id ("list-2" in the case above) to view the flow result.
+
+
### App Diagrams
Below are the app diagrams which are useful for the visual understanding.
diff --git a/java-samples/corda5-negotiation-cordapp/build.gradle b/java-samples/corda5-negotiation-cordapp/build.gradle
index 3fa6225..eb0a9bd 100644
--- a/java-samples/corda5-negotiation-cordapp/build.gradle
+++ b/java-samples/corda5-negotiation-cordapp/build.gradle
@@ -1,4 +1,5 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,34 +7,41 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'net.corda.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
// Declare the set of Java compiler options we need to build a CorDapp.
tasks.withType(JavaCompile) {
// -parameters - Needed for reflection and serialization to work correctly.
@@ -57,11 +65,9 @@ allprojects {
publishing {
publications {
maven(MavenPublication) {
- artifactId "corda5-negotiation-cordapp"
+ artifactId "corda-dev-template-java-sample"
groupId project.group
artifact jar
+ }
}
-
- }
}
-
diff --git a/java-samples/corda5-negotiation-cordapp/config/combined-worker-compose.yaml b/java-samples/corda5-negotiation-cordapp/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/contracts/build.gradle b/java-samples/corda5-negotiation-cordapp/contracts/build.gradle
index 52ca6f0..513a070 100644
--- a/java-samples/corda5-negotiation-cordapp/contracts/build.gradle
+++ b/java-samples/corda5-negotiation-cordapp/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,22 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -73,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/gradle.properties b/java-samples/corda5-negotiation-cordapp/gradle.properties
index 5aa4933..2549e5f 100644
--- a/java-samples/corda5-negotiation-cordapp/gradle.properties
+++ b/java-samples/corda5-negotiation-cordapp/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,12 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
-# R3 internal repository
-# Use this version when pointing to artefacts in artifactory that have not been published to S3
-artifactoryContextUrl=https://software.r3.com/artifactory
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/settings.gradle b/java-samples/corda5-negotiation-cordapp/settings.gradle
index d039a95..9b75799 100644
--- a/java-samples/corda5-negotiation-cordapp/settings.gradle
+++ b/java-samples/corda5-negotiation-cordapp/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-negotiation-java'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/workflows/build.gradle b/java-samples/corda5-negotiation-cordapp/workflows/build.gradle
index 9cb4157..748dd3d 100644
--- a/java-samples/corda5-negotiation-cordapp/workflows/build.gradle
+++ b/java-samples/corda5-negotiation-cordapp/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -26,6 +34,10 @@ dependencies {
// Alternatively we can explicitly specify all our Corda-API dependencies:
cordaProvided 'net.corda:corda-base'
+ cordaProvided 'net.corda:corda-application'
+ cordaProvided 'net.corda:corda-crypto'
+ cordaProvided 'net.corda:corda-membership'
+ // cordaProvided 'net.corda:corda-persistence'
cordaProvided 'net.corda:corda-serialization'
cordaProvided 'net.corda:corda-ledger-utxo'
cordaProvided 'net.corda:corda-ledger-consensual'
@@ -38,15 +50,15 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -65,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListProposal.java b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListProposal.java
index 41b7465..384b2d4 100644
--- a/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListProposal.java
+++ b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListProposal.java
@@ -9,6 +9,7 @@
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import org.jetbrains.annotations.NotNull;
+import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
@@ -26,7 +27,7 @@ public class ListProposal implements ClientStartableFlow {
@Override
public String call(@NotNull ClientRequestBody requestBody) {
// Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
- List> states = utxoLedgerService.findUnconsumedStatesByType(Proposal.class);
+ List> states = utxoLedgerService.findUnconsumedStatesByExactType(Proposal.class, 100, Instant.now()).getResults();
List results = states.stream().map(stateAndRef ->
new ListProposalArgs(
stateAndRef.getState().getContractState().getProposalID(),
diff --git a/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTrade.java b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTrade.java
new file mode 100644
index 0000000..233df2b
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTrade.java
@@ -0,0 +1,45 @@
+package com.r3.developers.samples.negotiation.workflows.util;
+
+import com.r3.developers.samples.negotiation.Trade;
+import net.corda.v5.application.flows.ClientRequestBody;
+import net.corda.v5.application.flows.ClientStartableFlow;
+import net.corda.v5.application.flows.CordaInject;
+import net.corda.v5.application.marshalling.JsonMarshallingService;
+import net.corda.v5.ledger.utxo.StateAndRef;
+import net.corda.v5.ledger.utxo.UtxoLedgerService;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ListTrade implements ClientStartableFlow {
+
+ // Injects the JsonMarshallingService to read and populate JSON parameters.
+ @CordaInject
+ public JsonMarshallingService jsonMarshallingService;
+
+ // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
+ @CordaInject
+ public UtxoLedgerService utxoLedgerService;
+
+ @NotNull
+ @Override
+ public String call(@NotNull ClientRequestBody requestBody) {
+ // Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
+ List> states = utxoLedgerService.findUnconsumedStatesByExactType(Trade.class, 100, Instant.now()).getResults();
+ List results = states.stream().map(stateAndRef ->
+ new ListTradeArgs(
+ stateAndRef.getState().getContractState().getProposalID(),
+ stateAndRef.getState().getContractState().getAmount(),
+ stateAndRef.getState().getContractState().getBuyer().toString(),
+ stateAndRef.getState().getContractState().getSeller().toString()
+
+ )
+ ).collect(Collectors.toList());
+
+ // Uses the JsonMarshallingService's format() function to serialize the DTO to Json.
+ return jsonMarshallingService.format(results);
+
+ }
+}
diff --git a/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTradeArgs.java b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTradeArgs.java
new file mode 100644
index 0000000..d426c62
--- /dev/null
+++ b/java-samples/corda5-negotiation-cordapp/workflows/src/main/java/com/r3/developers/samples/negotiation/workflows/util/ListTradeArgs.java
@@ -0,0 +1,37 @@
+package com.r3.developers.samples.negotiation.workflows.util;
+
+import java.util.UUID;
+
+public class ListTradeArgs {
+
+ private UUID proposalID;
+ private int amount;
+ private String buyer;
+ private String seller;
+ public ListTradeArgs() {
+ }
+
+ public ListTradeArgs(UUID proposalID, int amount, String buyer, String seller) {
+ this.proposalID = proposalID;
+ this.amount = amount;
+ this.buyer = buyer;
+ this.seller = seller;
+ }
+
+ public UUID getProposalID() {
+ return proposalID;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public String getBuyer() {
+ return buyer;
+ }
+
+ public String getSeller() {
+ return seller;
+ }
+
+}
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile b/java-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/README.md b/java-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/app.py b/java-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt b/java-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js b/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css b/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html b/java-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/corda5-obligation-cordapp/README.md b/java-samples/corda5-obligation-cordapp/README.md
index 24a030c..14d97b4 100644
--- a/java-samples/corda5-obligation-cordapp/README.md
+++ b/java-samples/corda5-obligation-cordapp/README.md
@@ -14,7 +14,7 @@ In this app you can:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/corda5-obligation-cordapp/build.gradle b/java-samples/corda5-obligation-cordapp/build.gradle
index f184ad9..eb0a9bd 100644
--- a/java-samples/corda5-obligation-cordapp/build.gradle
+++ b/java-samples/corda5-obligation-cordapp/build.gradle
@@ -1,4 +1,5 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,34 +7,41 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'net.corda.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
// Declare the set of Java compiler options we need to build a CorDapp.
tasks.withType(JavaCompile) {
// -parameters - Needed for reflection and serialization to work correctly.
@@ -57,11 +65,9 @@ allprojects {
publishing {
publications {
maven(MavenPublication) {
- artifactId "corda5-obligation-cordapp"
+ artifactId "corda-dev-template-java-sample"
groupId project.group
artifact jar
+ }
}
-
- }
}
-
diff --git a/java-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml b/java-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/contracts/build.gradle b/java-samples/corda5-obligation-cordapp/contracts/build.gradle
index 52ca6f0..513a070 100644
--- a/java-samples/corda5-obligation-cordapp/contracts/build.gradle
+++ b/java-samples/corda5-obligation-cordapp/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,22 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -73,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/gradle.properties b/java-samples/corda5-obligation-cordapp/gradle.properties
index 5aa4933..2549e5f 100644
--- a/java-samples/corda5-obligation-cordapp/gradle.properties
+++ b/java-samples/corda5-obligation-cordapp/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,12 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
-# R3 internal repository
-# Use this version when pointing to artefacts in artifactory that have not been published to S3
-artifactoryContextUrl=https://software.r3.com/artifactory
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/settings.gradle b/java-samples/corda5-obligation-cordapp/settings.gradle
index 012fd56..b8e61e0 100644
--- a/java-samples/corda5-obligation-cordapp/settings.gradle
+++ b/java-samples/corda5-obligation-cordapp/settings.gradle
@@ -4,16 +4,6 @@ pluginManagement {
gradlePluginPortal()
mavenCentral()
mavenLocal()
- maven {
- url = "$artifactoryContextUrl/corda-os-maven"
- authentication {
- basic(BasicAuthentication)
- }
- credentials {
- username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME')
- password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD')
- }
- }
}
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
@@ -22,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-obligation-java'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/corda5-obligation-cordapp/workflows/build.gradle b/java-samples/corda5-obligation-cordapp/workflows/build.gradle
index d3d21e5..748dd3d 100644
--- a/java-samples/corda5-obligation-cordapp/workflows/build.gradle
+++ b/java-samples/corda5-obligation-cordapp/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -40,23 +48,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -75,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile b/java-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/README.md b/java-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/app.py b/java-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt b/java-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js b/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css b/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html b/java-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/encumbrance-pawn-shop/README.md b/java-samples/encumbrance-pawn-shop/README.md
index 2e0af81..05ccfce 100644
--- a/java-samples/encumbrance-pawn-shop/README.md
+++ b/java-samples/encumbrance-pawn-shop/README.md
@@ -30,7 +30,7 @@ and cannot be transferred to another party till the loan has been repaid.
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/encumbrance-pawn-shop/build.gradle b/java-samples/encumbrance-pawn-shop/build.gradle
index 1795e1a..eb0a9bd 100644
--- a/java-samples/encumbrance-pawn-shop/build.gradle
+++ b/java-samples/encumbrance-pawn-shop/build.gradle
@@ -1,67 +1,73 @@
-import static org.gradle.api.JavaVersion.VERSION_11
-
-plugins {
- id 'org.jetbrains.kotlin.jvm'
- id 'net.corda.cordapp.cordapp-configuration'
- id 'org.jetbrains.kotlin.plugin.jpa'
- id 'java'
- id 'maven-publish'
- id 'net.corda.plugins.csde'
-}
-
-allprojects {
- group 'com.r3.developers.samples'
- version '1.0-SNAPSHOT'
-
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
- cordaClusterURL = "https://localhost:8888"
- networkConfigFile = "config/static-network-config.json"
- r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
- cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
- cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
- }
-
- // Declare the set of Java compiler options we need to build a CorDapp.
- tasks.withType(JavaCompile) {
- // -parameters - Needed for reflection and serialization to work correctly.
- options.compilerArgs += [
- "-parameters"
- ]
- }
-
- repositories {
- // All dependencies are held in Maven Central
- mavenLocal()
- mavenCentral()
- }
-
- tasks.withType(Test).configureEach {
- useJUnitPlatform()
- }
-
-}
-
-publishing {
- publications {
- maven(MavenPublication) {
- artifactId "corda5-encumbrance-cordapp"
- groupId project.group
- artifact jar
- }
-
- }
-}
-
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'net.corda.cordapp.cordapp-configuration'
+ id 'org.jetbrains.kotlin.plugin.jpa'
+ id 'java'
+ id 'maven-publish'
+ id 'net.corda.gradle.plugin'
+}
+
+allprojects {
+ group 'com.r3.developers.cordapptemplate'
+ version '1.0-SNAPSHOT'
+
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
+ cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
+ networkConfigFile = "config/static-network-config.json"
+ r3RootCertFile = "config/r3-ca-key.pem"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
+ cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
+ cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
+ }
+
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
+ // Declare the set of Java compiler options we need to build a CorDapp.
+ tasks.withType(JavaCompile) {
+ // -parameters - Needed for reflection and serialization to work correctly.
+ options.compilerArgs += [
+ "-parameters"
+ ]
+ }
+
+ repositories {
+ // All dependencies are held in Maven Central
+ mavenLocal()
+ mavenCentral()
+ }
+
+ tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+ }
+
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-java-sample"
+ groupId project.group
+ artifact jar
+ }
+ }
+}
diff --git a/java-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml b/java-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/contracts/build.gradle b/java-samples/encumbrance-pawn-shop/contracts/build.gradle
index ff1e610..513a070 100644
--- a/java-samples/encumbrance-pawn-shop/contracts/build.gradle
+++ b/java-samples/encumbrance-pawn-shop/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,22 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional used by tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -73,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/gradle.properties b/java-samples/encumbrance-pawn-shop/gradle.properties
index 282b05a..2549e5f 100644
--- a/java-samples/encumbrance-pawn-shop/gradle.properties
+++ b/java-samples/encumbrance-pawn-shop/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/settings.gradle b/java-samples/encumbrance-pawn-shop/settings.gradle
index 0fb3179..e365e25 100644
--- a/java-samples/encumbrance-pawn-shop/settings.gradle
+++ b/java-samples/encumbrance-pawn-shop/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-encumbrance-cordapp-java'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/encumbrance-pawn-shop/workflows/build.gradle b/java-samples/encumbrance-pawn-shop/workflows/build.gradle
index d3d21e5..748dd3d 100644
--- a/java-samples/encumbrance-pawn-shop/workflows/build.gradle
+++ b/java-samples/encumbrance-pawn-shop/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -40,23 +48,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -75,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/Dockerfile b/java-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/README.md b/java-samples/persistence-carinsurance/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/app.py b/java-samples/persistence-carinsurance/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/requirements.txt b/java-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js b/java-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css b/java-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/persistence-carinsurance/FlowManagementUI/templates/index.html b/java-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/persistence-carinsurance/README.md b/java-samples/persistence-carinsurance/README.md
index 26bc20e..e0ba2f8 100644
--- a/java-samples/persistence-carinsurance/README.md
+++ b/java-samples/persistence-carinsurance/README.md
@@ -31,7 +31,7 @@ There are two flow in this CorDapp:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/persistence-carinsurance/build.gradle b/java-samples/persistence-carinsurance/build.gradle
index 82559bb..eb0a9bd 100644
--- a/java-samples/persistence-carinsurance/build.gradle
+++ b/java-samples/persistence-carinsurance/build.gradle
@@ -1,68 +1,73 @@
-import static org.gradle.api.JavaVersion.VERSION_11
-
-plugins {
- id 'org.jetbrains.kotlin.jvm'
- id 'net.corda.cordapp.cordapp-configuration'
- id 'org.jetbrains.kotlin.plugin.jpa'
- id 'java'
- id 'maven-publish'
- id 'net.corda.plugins.csde'
-}
-
-allprojects {
- group 'com.r3.developers.samples'
- version '1.0-SNAPSHOT'
-
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
- cordaClusterURL = "https://localhost:8888"
- networkConfigFile = "config/static-network-config.json"
- r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
- cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
- cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
- }
-
- // Declare the set of Java compiler options we need to build a CorDapp.
- tasks.withType(JavaCompile) {
- // -parameters - Needed for reflection and serialization to work correctly.
- options.compilerArgs += [
- "-parameters"
- ]
- }
-
- repositories {
- // All dependencies are held in Maven Central
- mavenLocal()
- mavenCentral()
-
- }
-
- tasks.withType(Test).configureEach {
- useJUnitPlatform()
- }
-
-}
-
-publishing {
- publications {
- maven(MavenPublication) {
- artifactId "persistence-java-sample"
- groupId project.group
- artifact jar
- }
-
- }
-}
-
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'net.corda.cordapp.cordapp-configuration'
+ id 'org.jetbrains.kotlin.plugin.jpa'
+ id 'java'
+ id 'maven-publish'
+ id 'net.corda.gradle.plugin'
+}
+
+allprojects {
+ group 'com.r3.developers.cordapptemplate'
+ version '1.0-SNAPSHOT'
+
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
+ cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
+ networkConfigFile = "config/static-network-config.json"
+ r3RootCertFile = "config/r3-ca-key.pem"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
+ cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
+ cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
+ }
+
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
+ // Declare the set of Java compiler options we need to build a CorDapp.
+ tasks.withType(JavaCompile) {
+ // -parameters - Needed for reflection and serialization to work correctly.
+ options.compilerArgs += [
+ "-parameters"
+ ]
+ }
+
+ repositories {
+ // All dependencies are held in Maven Central
+ mavenLocal()
+ mavenCentral()
+ }
+
+ tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+ }
+
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-java-sample"
+ groupId project.group
+ artifact jar
+ }
+ }
+}
diff --git a/java-samples/persistence-carinsurance/config/combined-worker-compose.yaml b/java-samples/persistence-carinsurance/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/persistence-carinsurance/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/contracts/build.gradle b/java-samples/persistence-carinsurance/contracts/build.gradle
index d42f5bc..513a070 100644
--- a/java-samples/persistence-carinsurance/contracts/build.gradle
+++ b/java-samples/persistence-carinsurance/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,21 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -72,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/gradle.properties b/java-samples/persistence-carinsurance/gradle.properties
index 282b05a..2549e5f 100644
--- a/java-samples/persistence-carinsurance/gradle.properties
+++ b/java-samples/persistence-carinsurance/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/settings.gradle b/java-samples/persistence-carinsurance/settings.gradle
index 2d0c4cc..7b5f380 100644
--- a/java-samples/persistence-carinsurance/settings.gradle
+++ b/java-samples/persistence-carinsurance/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'persistence-carinsurance'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/workflows/build.gradle b/java-samples/persistence-carinsurance/workflows/build.gradle
index d3d21e5..748dd3d 100644
--- a/java-samples/persistence-carinsurance/workflows/build.gradle
+++ b/java-samples/persistence-carinsurance/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -40,23 +48,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -75,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlow.java b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlow.java
index 0f40196..34d429d 100644
--- a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlow.java
+++ b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlow.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import java.util.stream.Collectors;
@InitiatingFlow(protocol = "add-claim")
@@ -157,7 +158,7 @@ private PersistentInsurance persistInsurance(InsuranceState insuranceState, List
persistentClaims
);
- persistenceService.persist(persistentInsurance);
+ persistenceService.persist(UUID.randomUUID().toString(), persistentInsurance);
return persistentInsurance;
}
}
diff --git a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlowResponder.java b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlowResponder.java
index 9f2ec5b..7ac1e00 100644
--- a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlowResponder.java
+++ b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/InsuranceClaimFlowResponder.java
@@ -10,6 +10,8 @@
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import org.jetbrains.annotations.NotNull;
+import java.util.UUID;
+
@InitiatedBy(protocol = "add-claim")
public class InsuranceClaimFlowResponder implements ResponderFlow {
@CordaInject
@@ -21,7 +23,7 @@ public class InsuranceClaimFlowResponder implements ResponderFlow {
@Suspendable
public void call(@NotNull FlowSession session) {
PersistentInsurance persistentInsurance = session.receive(PersistentInsurance.class);
- persistenceService.persist(persistentInsurance);
+ persistenceService.persist(UUID.randomUUID().toString(), persistentInsurance);
utxoLedgerService.receiveFinality(session, transaction -> {});
}
}
diff --git a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceFlow.java b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceFlow.java
index 666be52..70ea572 100644
--- a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceFlow.java
+++ b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceFlow.java
@@ -31,6 +31,7 @@
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
+import java.util.UUID;
@InitiatingFlow(protocol = "issue-insurance")
public class IssueInsuranceFlow implements ClientStartableFlow {
@@ -109,7 +110,7 @@ private PersistentInsurance persistInsurance(InsuranceState insuranceState){
),
Collections.emptyList()
);
- persistenceService.persist(persistentInsurance);
+ persistenceService.persist(UUID.randomUUID().toString(), persistentInsurance);
return persistentInsurance;
}
diff --git a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceResponder.java b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceResponder.java
index f553e9c..f0cc636 100644
--- a/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceResponder.java
+++ b/java-samples/persistence-carinsurance/workflows/src/main/java/com/r3/developers/samples/persistence/workflows/IssueInsuranceResponder.java
@@ -10,6 +10,8 @@
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import org.jetbrains.annotations.NotNull;
+import java.util.UUID;
+
@InitiatedBy(protocol = "issue-insurance")
public class IssueInsuranceResponder implements ResponderFlow {
@@ -23,7 +25,7 @@ public class IssueInsuranceResponder implements ResponderFlow {
@Suspendable
public void call(@NotNull FlowSession session) {
PersistentInsurance persistentInsurance = session.receive(PersistentInsurance.class);
- persistenceService.persist(persistentInsurance);
+ persistenceService.persist(UUID.randomUUID().toString(), persistentInsurance);
utxoLedgerService.receiveFinality(session, transaction -> {});
}
}
diff --git a/java-samples/ping-pong/FlowManagementUI/Dockerfile b/java-samples/ping-pong/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/ping-pong/FlowManagementUI/README.md b/java-samples/ping-pong/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/ping-pong/FlowManagementUI/app.py b/java-samples/ping-pong/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/ping-pong/FlowManagementUI/requirements.txt b/java-samples/ping-pong/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/ping-pong/FlowManagementUI/static/Scripts/script.js b/java-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/ping-pong/FlowManagementUI/static/css/main.css b/java-samples/ping-pong/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/ping-pong/FlowManagementUI/templates/index.html b/java-samples/ping-pong/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/ping-pong/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/ping-pong/README.md b/java-samples/ping-pong/README.md
index 91bcf7a..180d114 100644
--- a/java-samples/ping-pong/README.md
+++ b/java-samples/ping-pong/README.md
@@ -25,7 +25,7 @@ For development environment setup, please refer to: [Setup Guide](https://docs.r
1. We will begin our test deployment with clicking the `startCorda`.
`./gradlew startCorda` run this from the Intellij terminal
This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#.
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/.
You can test out some functions to check connectivity.(GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI,
the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/ping-pong/build.gradle b/java-samples/ping-pong/build.gradle
index 3354108..eb0a9bd 100644
--- a/java-samples/ping-pong/build.gradle
+++ b/java-samples/ping-pong/build.gradle
@@ -1,4 +1,5 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,34 +7,41 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.csdetemplate'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
// Declare the set of Java compiler options we need to build a CorDapp.
tasks.withType(JavaCompile) {
// -parameters - Needed for reflection and serialization to work correctly.
@@ -57,10 +65,9 @@ allprojects {
publishing {
publications {
maven(MavenPublication) {
- artifactId "corda-CSDE-java-sample"
+ artifactId "corda-dev-template-java-sample"
groupId project.group
artifact jar
}
-
}
-}
\ No newline at end of file
+}
diff --git a/java-samples/ping-pong/config/combined-worker-compose.yaml b/java-samples/ping-pong/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/ping-pong/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/ping-pong/gradle.properties b/java-samples/ping-pong/gradle.properties
index 5aa4933..2549e5f 100644
--- a/java-samples/ping-pong/gradle.properties
+++ b/java-samples/ping-pong/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,12 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
-# R3 internal repository
-# Use this version when pointing to artefacts in artifactory that have not been published to S3
-artifactoryContextUrl=https://software.r3.com/artifactory
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/ping-pong/settings.gradle b/java-samples/ping-pong/settings.gradle
index 7d679c8..212b313 100644
--- a/java-samples/ping-pong/settings.gradle
+++ b/java-samples/ping-pong/settings.gradle
@@ -4,16 +4,6 @@ pluginManagement {
gradlePluginPortal()
mavenCentral()
mavenLocal()
- maven {
- url = "$artifactoryContextUrl/corda-os-maven"
- authentication {
- basic(BasicAuthentication)
- }
- credentials {
- username = settings.ext.find('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME')
- password = settings.ext.find('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD')
- }
- }
}
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
@@ -22,14 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'ping-pong'
include ':workflows'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/ping-pong/workflows/build.gradle b/java-samples/ping-pong/workflows/build.gradle
index 0b02763..731a846 100644
--- a/java-samples/ping-pong/workflows/build.gradle
+++ b/java-samples/ping-pong/workflows/build.gradle
@@ -12,7 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
- // From other subprojects:
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
@@ -39,23 +47,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -74,16 +76,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile b/java-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/README.md b/java-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/app.py b/java-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt b/java-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js b/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css b/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html b/java-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/referencestates-sanctionsbody/README.md b/java-samples/referencestates-sanctionsbody/README.md
index 98735b8..d92e8a7 100644
--- a/java-samples/referencestates-sanctionsbody/README.md
+++ b/java-samples/referencestates-sanctionsbody/README.md
@@ -11,7 +11,7 @@ This CorDapp allows two nodes to enter into an IOU agreement, but enforces that
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/referencestates-sanctionsbody/build.gradle b/java-samples/referencestates-sanctionsbody/build.gradle
index 41efc29..eb0a9bd 100644
--- a/java-samples/referencestates-sanctionsbody/build.gradle
+++ b/java-samples/referencestates-sanctionsbody/build.gradle
@@ -1,68 +1,73 @@
-import static org.gradle.api.JavaVersion.VERSION_11
-
-plugins {
- id 'org.jetbrains.kotlin.jvm'
- id 'net.corda.cordapp.cordapp-configuration'
- id 'org.jetbrains.kotlin.plugin.jpa'
- id 'java'
- id 'maven-publish'
- id 'net.corda.plugins.csde'
-}
-
-allprojects {
- group 'com.r3.developers.csdetemplate'
- version '1.0-SNAPSHOT'
-
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
- cordaClusterURL = "https://localhost:8888"
- networkConfigFile = "config/static-network-config.json"
- r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
- cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
- cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
- }
-
- // Declare the set of Java compiler options we need to build a CorDapp.
- tasks.withType(JavaCompile) {
- // -parameters - Needed for reflection and serialization to work correctly.
- options.compilerArgs += [
- "-parameters"
- ]
- }
-
- repositories {
- // All dependencies are held in Maven Central
- mavenLocal()
- mavenCentral()
-
- }
-
- tasks.withType(Test).configureEach {
- useJUnitPlatform()
- }
-
-}
-
-publishing {
- publications {
- maven(MavenPublication) {
- artifactId "corda-CSDE-java-sample"
- groupId project.group
- artifact jar
- }
-
- }
-}
-
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'net.corda.cordapp.cordapp-configuration'
+ id 'org.jetbrains.kotlin.plugin.jpa'
+ id 'java'
+ id 'maven-publish'
+ id 'net.corda.gradle.plugin'
+}
+
+allprojects {
+ group 'com.r3.developers.cordapptemplate'
+ version '1.0-SNAPSHOT'
+
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
+ cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
+ networkConfigFile = "config/static-network-config.json"
+ r3RootCertFile = "config/r3-ca-key.pem"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
+ cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
+ cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
+ }
+
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
+ // Declare the set of Java compiler options we need to build a CorDapp.
+ tasks.withType(JavaCompile) {
+ // -parameters - Needed for reflection and serialization to work correctly.
+ options.compilerArgs += [
+ "-parameters"
+ ]
+ }
+
+ repositories {
+ // All dependencies are held in Maven Central
+ mavenLocal()
+ mavenCentral()
+ }
+
+ tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+ }
+
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-java-sample"
+ groupId project.group
+ artifact jar
+ }
+ }
+}
diff --git a/java-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml b/java-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/contracts/build.gradle b/java-samples/referencestates-sanctionsbody/contracts/build.gradle
index d42f5bc..513a070 100644
--- a/java-samples/referencestates-sanctionsbody/contracts/build.gradle
+++ b/java-samples/referencestates-sanctionsbody/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,21 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -72,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/gradle.properties b/java-samples/referencestates-sanctionsbody/gradle.properties
index 282b05a..2549e5f 100644
--- a/java-samples/referencestates-sanctionsbody/gradle.properties
+++ b/java-samples/referencestates-sanctionsbody/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/settings.gradle b/java-samples/referencestates-sanctionsbody/settings.gradle
index 0354064..a9ea7cf 100644
--- a/java-samples/referencestates-sanctionsbody/settings.gradle
+++ b/java-samples/referencestates-sanctionsbody/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'referencestates-sanctionsbody'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/referencestates-sanctionsbody/workflows/build.gradle b/java-samples/referencestates-sanctionsbody/workflows/build.gradle
index d3d21e5..748dd3d 100644
--- a/java-samples/referencestates-sanctionsbody/workflows/build.gradle
+++ b/java-samples/referencestates-sanctionsbody/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -40,23 +48,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -75,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/FlowManagementUI/Dockerfile b/java-samples/shinny-tokens/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/FlowManagementUI/README.md b/java-samples/shinny-tokens/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/shinny-tokens/FlowManagementUI/app.py b/java-samples/shinny-tokens/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/shinny-tokens/FlowManagementUI/requirements.txt b/java-samples/shinny-tokens/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js b/java-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/FlowManagementUI/static/css/main.css b/java-samples/shinny-tokens/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/shinny-tokens/FlowManagementUI/templates/index.html b/java-samples/shinny-tokens/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/shinny-tokens/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/shinny-tokens/README.md b/java-samples/shinny-tokens/README.md
index a086b2c..340135f 100644
--- a/java-samples/shinny-tokens/README.md
+++ b/java-samples/shinny-tokens/README.md
@@ -21,7 +21,7 @@ In this app you can:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some of the
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some of the
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/java-samples/shinny-tokens/build.gradle b/java-samples/shinny-tokens/build.gradle
index 73ac398..eb0a9bd 100644
--- a/java-samples/shinny-tokens/build.gradle
+++ b/java-samples/shinny-tokens/build.gradle
@@ -1,67 +1,73 @@
-import static org.gradle.api.JavaVersion.VERSION_11
-
-plugins {
- id 'org.jetbrains.kotlin.jvm'
- id 'net.corda.cordapp.cordapp-configuration'
- id 'org.jetbrains.kotlin.plugin.jpa'
- id 'java'
- id 'maven-publish'
- id 'net.corda.plugins.csde'
-}
-
-allprojects {
- group 'com.r3.developers.samples'
- version '1.0-SNAPSHOT'
-
- def javaVersion = VERSION_11
-
- // Configure the CSDE
- csde {
- cordaClusterURL = "https://localhost:8888"
- networkConfigFile = "config/static-network-config.json"
- r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
- cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
- cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
- }
-
- // Declare the set of Java compiler options we need to build a CorDapp.
- tasks.withType(JavaCompile) {
- // -parameters - Needed for reflection and serialization to work correctly.
- options.compilerArgs += [
- "-parameters"
- ]
- }
-
- repositories {
- // All dependencies are held in Maven Central
- mavenLocal()
- mavenCentral()
- }
-
- tasks.withType(Test).configureEach {
- useJUnitPlatform()
- }
-
-}
-
-publishing {
- publications {
- maven(MavenPublication) {
- artifactId "shinny-tokens-java-sample"
- groupId project.group
- artifact jar
- }
-
- }
-}
-
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'net.corda.cordapp.cordapp-configuration'
+ id 'org.jetbrains.kotlin.plugin.jpa'
+ id 'java'
+ id 'maven-publish'
+ id 'net.corda.gradle.plugin'
+}
+
+allprojects {
+ group 'com.r3.developers.cordapptemplate'
+ version '1.0-SNAPSHOT'
+
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
+ cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
+ networkConfigFile = "config/static-network-config.json"
+ r3RootCertFile = "config/r3-ca-key.pem"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
+ cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
+ cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
+ }
+
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
+ // Declare the set of Java compiler options we need to build a CorDapp.
+ tasks.withType(JavaCompile) {
+ // -parameters - Needed for reflection and serialization to work correctly.
+ options.compilerArgs += [
+ "-parameters"
+ ]
+ }
+
+ repositories {
+ // All dependencies are held in Maven Central
+ mavenLocal()
+ mavenCentral()
+ }
+
+ tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+ }
+
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-java-sample"
+ groupId project.group
+ artifact jar
+ }
+ }
+}
diff --git a/java-samples/shinny-tokens/config/combined-worker-compose.yaml b/java-samples/shinny-tokens/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/shinny-tokens/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/contracts/build.gradle b/java-samples/shinny-tokens/contracts/build.gradle
index ff1e610..513a070 100644
--- a/java-samples/shinny-tokens/contracts/build.gradle
+++ b/java-samples/shinny-tokens/contracts/build.gradle
@@ -1,4 +1,3 @@
-
plugins {
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
// These extend existing build environment so that CPB and CPK files can be built.
@@ -39,22 +38,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional used by tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
}
// The CordApp section.
@@ -73,16 +67,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/gradle.properties b/java-samples/shinny-tokens/gradle.properties
index 282b05a..2549e5f 100644
--- a/java-samples/shinny-tokens/gradle.properties
+++ b/java-samples/shinny-tokens/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/settings.gradle b/java-samples/shinny-tokens/settings.gradle
index 7d71042..38bf074 100644
--- a/java-samples/shinny-tokens/settings.gradle
+++ b/java-samples/shinny-tokens/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'shinny-tokens'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/workflows/build.gradle b/java-samples/shinny-tokens/workflows/build.gradle
index d3d21e5..748dd3d 100644
--- a/java-samples/shinny-tokens/workflows/build.gradle
+++ b/java-samples/shinny-tokens/workflows/build.gradle
@@ -12,6 +12,14 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
// From other subprojects:
cordapp project(':contracts')
@@ -40,23 +48,17 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
}
// The CordApp section.
@@ -75,16 +77,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.java b/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.java
index fd9aaee..6c34200 100644
--- a/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.java
+++ b/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.java
@@ -33,6 +33,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
@@ -94,7 +95,7 @@ public String call(@NotNull ClientRequestBody requestBody) {
// tryClaim will check in the vault if there are tokens which can satisfy the expected amount.
// If yes all the fungible tokens are returned back.
// Remaining change will be returned back to the sender.
- tokenClaim = tokenSelection.tryClaim(tokenClaimCriteria);
+ tokenClaim = tokenSelection.tryClaim(UUID.randomUUID().toString(), tokenClaimCriteria);
if(tokenClaim == null) {
log.info("No tokens found for" + jsonMarshallingService.format(tokenClaimCriteria));
diff --git a/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.java b/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.java
index 78adf0b..02b26eb 100644
--- a/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.java
+++ b/java-samples/shinny-tokens/workflows/src/main/java/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
@@ -105,7 +106,7 @@ public String call( ClientRequestBody requestBody) {
// tryClaim will check in the vault if there are tokens which can satisfy the expected amount.
// If yes all the fungible tokens are returned back.
// Remaining change will be returned back to the sender.
- tokenClaim = tokenSelection.tryClaim(tokenClaimCriteria);
+ tokenClaim = tokenSelection.tryClaim(UUID.randomUUID().toString(), tokenClaimCriteria);
if(tokenClaim == null) {
log.info("No tokens found for" + jsonMarshallingService.format(tokenClaimCriteria));
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/README.md b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/app.py b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/corda5-obligation-cordapp/README.md b/kotlin-samples/corda5-obligation-cordapp/README.md
index 40258b4..91a5d82 100644
--- a/kotlin-samples/corda5-obligation-cordapp/README.md
+++ b/kotlin-samples/corda5-obligation-cordapp/README.md
@@ -14,7 +14,7 @@ In this app you can:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/kotlin-samples/corda5-obligation-cordapp/build.gradle b/kotlin-samples/corda5-obligation-cordapp/build.gradle
index 2b6c5a3..1c6eb2e 100644
--- a/kotlin-samples/corda5-obligation-cordapp/build.gradle
+++ b/kotlin-samples/corda5-obligation-cordapp/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.csdetemplate'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -76,7 +80,7 @@ allprojects {
publishing {
publications {
maven(MavenPublication) {
- artifactId "corda-CSDE-kotlin-sample"
+ artifactId "corda-dev-template-kotlin-sample"
groupId project.group
artifact jar
}
diff --git a/kotlin-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml b/kotlin-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/corda5-obligation-cordapp/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/contracts/build.gradle b/kotlin-samples/corda5-obligation-cordapp/contracts/build.gradle
index 52ca6f0..c1f6ed6 100644
--- a/kotlin-samples/corda5-obligation-cordapp/contracts/build.gradle
+++ b/kotlin-samples/corda5-obligation-cordapp/contracts/build.gradle
@@ -39,22 +39,18 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // This are shared so should be here.
- // Dependencies Required By Test Tooling
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing-kotlin:$contractTestingVersion"
}
// The CordApp section.
@@ -73,16 +69,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/gradle.properties b/kotlin-samples/corda5-obligation-cordapp/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/corda5-obligation-cordapp/gradle.properties
+++ b/kotlin-samples/corda5-obligation-cordapp/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/settings.gradle b/kotlin-samples/corda5-obligation-cordapp/settings.gradle
index a4d3510..69e0c8f 100644
--- a/kotlin-samples/corda5-obligation-cordapp/settings.gradle
+++ b/kotlin-samples/corda5-obligation-cordapp/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
-rootProject.name = 'corda5-obligation-kotlin'
+rootProject.name = 'cordapp-obligation-kotlin'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/corda5-obligation-cordapp/workflows/build.gradle b/kotlin-samples/corda5-obligation-cordapp/workflows/build.gradle
index 4196b47..c8bde66 100644
--- a/kotlin-samples/corda5-obligation-cordapp/workflows/build.gradle
+++ b/kotlin-samples/corda5-obligation-cordapp/workflows/build.gradle
@@ -12,6 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
// From other subprojects:
cordapp project(':contracts')
@@ -40,22 +49,18 @@ dependencies {
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
cordaProvided 'org.slf4j:slf4j-api'
- // Dependencies Required By Simulator Tests
- // Todo: these are commented out as the simulator UTXO work has not been merged into Gecko yet.
-// testImplementation "net.corda:corda-simulator-api:$simulatorVersion"
-// testRuntimeOnly "net.corda:corda-simulator-runtime:$simulatorVersion"
-
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
+ testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
}
// The CordApp section.
@@ -74,16 +79,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/README.md b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/app.py b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/encumbrance-pawn-shop/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/css/main.css b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/templates/index.html b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/oracle-foreign-exchange/README.md b/kotlin-samples/oracle-foreign-exchange/README.md
index 5de3330..74d6cd9 100644
--- a/kotlin-samples/oracle-foreign-exchange/README.md
+++ b/kotlin-samples/oracle-foreign-exchange/README.md
@@ -28,7 +28,7 @@ so that every node updates their record to include the new foreign exchange tran
## Usage
### Setting Up
1. Start the sandbox environment by clicking the Gradle task `Tasks > csde-cordapp > startCorda`.
- A successful deployment will allow you to open the REST APIs: https://localhost:8888/api/v1/swagger
+ A successful deployment will allow you to open the REST APIs: https://localhost:8888/api/v5_2/swagger#/
2. Deploy the CorDapp by clicking `Tasks > csde-cordapp > 5-vNodesSetup`. When successful, you should be able to see the
CPI metadata of the CorDapp you deployed by calling the `GET /cpi/` endpoint.
3. Take note of the identity short hash of the `Alice` member node by either:
diff --git a/kotlin-samples/oracle-foreign-exchange/build.gradle b/kotlin-samples/oracle-foreign-exchange/build.gradle
index af75bfa..210776a 100644
--- a/kotlin-samples/oracle-foreign-exchange/build.gradle
+++ b/kotlin-samples/oracle-foreign-exchange/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -76,7 +80,7 @@ allprojects {
publishing {
publications {
maven(MavenPublication) {
- artifactId "corda-oracle-foreign-exchange-kotlin-sample"
+ artifactId "corda-dev-template-kotlin-sample"
groupId project.group
artifact jar
}
diff --git a/kotlin-samples/oracle-foreign-exchange/config/combined-worker-compose.yaml b/kotlin-samples/oracle-foreign-exchange/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/oracle-foreign-exchange/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/oracle-foreign-exchange/contracts/build.gradle b/kotlin-samples/oracle-foreign-exchange/contracts/build.gradle
index 37cb3dd..c1f6ed6 100644
--- a/kotlin-samples/oracle-foreign-exchange/contracts/build.gradle
+++ b/kotlin-samples/oracle-foreign-exchange/contracts/build.gradle
@@ -41,14 +41,16 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing-kotlin:$contractTestingVersion"
}
// The CordApp section.
@@ -67,16 +69,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/oracle-foreign-exchange/gradle.properties b/kotlin-samples/oracle-foreign-exchange/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/oracle-foreign-exchange/gradle.properties
+++ b/kotlin-samples/oracle-foreign-exchange/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/oracle-foreign-exchange/settings.gradle b/kotlin-samples/oracle-foreign-exchange/settings.gradle
index ce7b7f6..5555de6 100644
--- a/kotlin-samples/oracle-foreign-exchange/settings.gradle
+++ b/kotlin-samples/oracle-foreign-exchange/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-oracle-foreign-exchange-kotlin'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/oracle-foreign-exchange/workflows/build.gradle b/kotlin-samples/oracle-foreign-exchange/workflows/build.gradle
index ddc1790..c8bde66 100644
--- a/kotlin-samples/oracle-foreign-exchange/workflows/build.gradle
+++ b/kotlin-samples/oracle-foreign-exchange/workflows/build.gradle
@@ -12,6 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
// From other subprojects:
cordapp project(':contracts')
@@ -42,15 +51,16 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- // Optional but used by exmaple tests.
+ // Optional but used by example tests.
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
+ testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
}
// The CordApp section.
@@ -69,16 +79,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/Dockerfile b/kotlin-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/README.md b/kotlin-samples/persistence-carinsurance/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/app.py b/kotlin-samples/persistence-carinsurance/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/requirements.txt b/kotlin-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css b/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/persistence-carinsurance/FlowManagementUI/templates/index.html b/kotlin-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/persistence-carinsurance/README.md b/kotlin-samples/persistence-carinsurance/README.md
index 125c6f4..1e5a59c 100644
--- a/kotlin-samples/persistence-carinsurance/README.md
+++ b/kotlin-samples/persistence-carinsurance/README.md
@@ -31,7 +31,7 @@ There are two flow in this CorDapp:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/kotlin-samples/persistence-carinsurance/build.gradle b/kotlin-samples/persistence-carinsurance/build.gradle
index 3e33578..1c6eb2e 100644
--- a/kotlin-samples/persistence-carinsurance/build.gradle
+++ b/kotlin-samples/persistence-carinsurance/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -75,10 +79,10 @@ allprojects {
publishing {
publications {
- maven(MavenPublication) {
- artifactId "corda5-persistence-cordapp"
- groupId project.group
- artifact jar
- }
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-kotlin-sample"
+ groupId project.group
+ artifact jar
+ }
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/config/combined-worker-compose.yaml b/kotlin-samples/persistence-carinsurance/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/persistence-carinsurance/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/contracts/build.gradle b/kotlin-samples/persistence-carinsurance/contracts/build.gradle
index da26473..c1f6ed6 100644
--- a/kotlin-samples/persistence-carinsurance/contracts/build.gradle
+++ b/kotlin-samples/persistence-carinsurance/contracts/build.gradle
@@ -41,7 +41,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -49,6 +49,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing-kotlin:$contractTestingVersion"
}
// The CordApp section.
@@ -67,16 +69,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/gradle.properties b/kotlin-samples/persistence-carinsurance/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/persistence-carinsurance/gradle.properties
+++ b/kotlin-samples/persistence-carinsurance/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/settings.gradle b/kotlin-samples/persistence-carinsurance/settings.gradle
index 85048c8..ed14feb 100644
--- a/kotlin-samples/persistence-carinsurance/settings.gradle
+++ b/kotlin-samples/persistence-carinsurance/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-persistence-carinsurance-kotlin'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/workflows/build.gradle b/kotlin-samples/persistence-carinsurance/workflows/build.gradle
index c1cb258..c8bde66 100644
--- a/kotlin-samples/persistence-carinsurance/workflows/build.gradle
+++ b/kotlin-samples/persistence-carinsurance/workflows/build.gradle
@@ -12,6 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
// From other subprojects:
cordapp project(':contracts')
@@ -42,7 +51,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -50,7 +59,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
+ testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
}
// The CordApp section.
@@ -69,16 +79,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/InsuranceClaim.kt b/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/InsuranceClaim.kt
index d950ca3..4a51d0c 100644
--- a/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/InsuranceClaim.kt
+++ b/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/InsuranceClaim.kt
@@ -19,6 +19,8 @@ import net.corda.v5.ledger.utxo.UtxoLedgerService
import org.slf4j.LoggerFactory
import java.time.Duration
import java.time.Instant
+import java.util.*
+import kotlin.collections.ArrayList
@InitiatingFlow(protocol = "add-claim")
class InsuranceClaimFlow : ClientStartableFlow {
@@ -155,7 +157,7 @@ class InsuranceClaimFlow : ClientStartableFlow {
persistentClaims
)
- persistentService.persist(persistentInsurance)
+ persistentService.persist(UUID.randomUUID().toString(), persistentInsurance)
return persistentInsurance;
}
@@ -185,7 +187,7 @@ class InsuranceClaimFlowResponder: ResponderFlow {
@Suspendable
override fun call(session: FlowSession) {
val persistentInsurance = session.receive(PersistentInsurance::class.java)
- persistentService.persist(persistentInsurance)
+ persistentService.persist(UUID.randomUUID().toString(), persistentInsurance)
ledgerService.receiveFinality(session, {})
}
diff --git a/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/IssueInsurance.kt b/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/IssueInsurance.kt
index f5fde45..e48edb3 100644
--- a/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/IssueInsurance.kt
+++ b/kotlin-samples/persistence-carinsurance/workflows/src/main/kotlin/com/r3/developers/samples/persistence/workflows/IssueInsurance.kt
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
+import java.util.*
@InitiatingFlow(protocol = "issue-insurance")
class IssueInsuranceFlow : ClientStartableFlow {
@@ -143,7 +144,7 @@ class IssueInsuranceFlow : ClientStartableFlow {
),
emptyList()
)
- persistentService.persist(persistentInsurance)
+ persistentService.persist(UUID.randomUUID().toString(), persistentInsurance)
return persistentInsurance
}
@@ -182,7 +183,7 @@ class IssueInsuranceResponder: ResponderFlow{
@Suspendable
override fun call(session: FlowSession) {
val persistentInsurance = session.receive(PersistentInsurance::class.java)
- persistentService.persist(persistentInsurance)
+ persistentService.persist(UUID.randomUUID().toString(), persistentInsurance)
ledgerService.receiveFinality(session, {})
}
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/Dockerfile b/kotlin-samples/ping-pong/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/README.md b/kotlin-samples/ping-pong/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/app.py b/kotlin-samples/ping-pong/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/requirements.txt b/kotlin-samples/ping-pong/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/static/css/main.css b/kotlin-samples/ping-pong/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/ping-pong/FlowManagementUI/templates/index.html b/kotlin-samples/ping-pong/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/ping-pong/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/ping-pong/README.md b/kotlin-samples/ping-pong/README.md
index 890c670..865ad6b 100644
--- a/kotlin-samples/ping-pong/README.md
+++ b/kotlin-samples/ping-pong/README.md
@@ -25,7 +25,7 @@ For development environment setup, please refer to: [Setup Guide](https://docs.r
1. We will begin our test deployment with clicking the `startCorda`.
`./gradlew startCorda` run this from the Intellij terminal
This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#.
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/.
You can test out some functions to check connectivity.(GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI,
the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/kotlin-samples/ping-pong/build.gradle b/kotlin-samples/ping-pong/build.gradle
index 3bde776..1c6eb2e 100644
--- a/kotlin-samples/ping-pong/build.gradle
+++ b/kotlin-samples/ping-pong/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -75,10 +79,10 @@ allprojects {
publishing {
publications {
- maven(MavenPublication) {
- artifactId "corda5-pingpong-cordapp"
- groupId project.group
- artifact jar
- }
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-kotlin-sample"
+ groupId project.group
+ artifact jar
+ }
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/config/combined-worker-compose.yaml b/kotlin-samples/ping-pong/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/ping-pong/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/gradle.properties b/kotlin-samples/ping-pong/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/ping-pong/gradle.properties
+++ b/kotlin-samples/ping-pong/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/settings.gradle b/kotlin-samples/ping-pong/settings.gradle
index 8576e56..b3ee327 100644
--- a/kotlin-samples/ping-pong/settings.gradle
+++ b/kotlin-samples/ping-pong/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-pingpong-sample'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/ping-pong/workflows/build.gradle b/kotlin-samples/ping-pong/workflows/build.gradle
index 3dbbce5..11dcb14 100644
--- a/kotlin-samples/ping-pong/workflows/build.gradle
+++ b/kotlin-samples/ping-pong/workflows/build.gradle
@@ -95,4 +95,4 @@ compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/README.md b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/app.py b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/referencestates-sanctionsbody/README.md b/kotlin-samples/referencestates-sanctionsbody/README.md
index 98735b8..d92e8a7 100644
--- a/kotlin-samples/referencestates-sanctionsbody/README.md
+++ b/kotlin-samples/referencestates-sanctionsbody/README.md
@@ -11,7 +11,7 @@ This CorDapp allows two nodes to enter into an IOU agreement, but enforces that
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/kotlin-samples/referencestates-sanctionsbody/build.gradle b/kotlin-samples/referencestates-sanctionsbody/build.gradle
index e71924e..1c6eb2e 100644
--- a/kotlin-samples/referencestates-sanctionsbody/build.gradle
+++ b/kotlin-samples/referencestates-sanctionsbody/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -75,10 +79,10 @@ allprojects {
publishing {
publications {
- maven(MavenPublication) {
- artifactId "corda5-referencestate-sample"
- groupId project.group
- artifact jar
- }
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-kotlin-sample"
+ groupId project.group
+ artifact jar
+ }
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml b/kotlin-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/referencestates-sanctionsbody/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/contracts/build.gradle b/kotlin-samples/referencestates-sanctionsbody/contracts/build.gradle
index da26473..c1f6ed6 100644
--- a/kotlin-samples/referencestates-sanctionsbody/contracts/build.gradle
+++ b/kotlin-samples/referencestates-sanctionsbody/contracts/build.gradle
@@ -41,7 +41,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -49,6 +49,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing-kotlin:$contractTestingVersion"
}
// The CordApp section.
@@ -67,16 +69,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/gradle.properties b/kotlin-samples/referencestates-sanctionsbody/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/referencestates-sanctionsbody/gradle.properties
+++ b/kotlin-samples/referencestates-sanctionsbody/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/settings.gradle b/kotlin-samples/referencestates-sanctionsbody/settings.gradle
index 65690be..4826e0d 100644
--- a/kotlin-samples/referencestates-sanctionsbody/settings.gradle
+++ b/kotlin-samples/referencestates-sanctionsbody/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-referencestate-sample'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/referencestates-sanctionsbody/workflows/build.gradle b/kotlin-samples/referencestates-sanctionsbody/workflows/build.gradle
index c1cb258..c8bde66 100644
--- a/kotlin-samples/referencestates-sanctionsbody/workflows/build.gradle
+++ b/kotlin-samples/referencestates-sanctionsbody/workflows/build.gradle
@@ -12,6 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
// From other subprojects:
cordapp project(':contracts')
@@ -42,7 +51,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -50,7 +59,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
+ testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
}
// The CordApp section.
@@ -69,16 +79,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/Dockerfile b/kotlin-samples/shinny-tokens/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/README.md b/kotlin-samples/shinny-tokens/FlowManagementUI/README.md
new file mode 100644
index 0000000..6befec1
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/app.py b/kotlin-samples/shinny-tokens/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/requirements.txt b/kotlin-samples/shinny-tokens/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/static/css/main.css b/kotlin-samples/shinny-tokens/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/kotlin-samples/shinny-tokens/FlowManagementUI/templates/index.html b/kotlin-samples/shinny-tokens/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/kotlin-samples/shinny-tokens/README.md b/kotlin-samples/shinny-tokens/README.md
index 72cc4e6..f03aeea 100644
--- a/kotlin-samples/shinny-tokens/README.md
+++ b/kotlin-samples/shinny-tokens/README.md
@@ -21,7 +21,7 @@ In this app you can:
### Setting up
1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
- A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v1/swagger#. You can test out some of the
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#/. You can test out some of the
functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
2. We will now deploy the cordapp with a click of `5-vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
diff --git a/kotlin-samples/shinny-tokens/build.gradle b/kotlin-samples/shinny-tokens/build.gradle
index feb78e5..1c6eb2e 100644
--- a/kotlin-samples/shinny-tokens/build.gradle
+++ b/kotlin-samples/shinny-tokens/build.gradle
@@ -1,4 +1,6 @@
-import static org.gradle.api.JavaVersion.VERSION_11
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static org.gradle.api.JavaVersion.VERSION_17
plugins {
id 'org.jetbrains.kotlin.jvm'
@@ -6,36 +8,38 @@ plugins {
id 'org.jetbrains.kotlin.plugin.jpa'
id 'java'
id 'maven-publish'
- id 'net.corda.plugins.csde'
+ id 'net.corda.gradle.plugin'
}
allprojects {
- group 'com.r3.developers.samples'
+ group 'com.r3.developers.cordapptemplate'
version '1.0-SNAPSHOT'
- def javaVersion = VERSION_11
+ def javaVersion = VERSION_17
- // Configure the CSDE
- csde {
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
networkConfigFile = "config/static-network-config.json"
r3RootCertFile = "config/r3-ca-key.pem"
- corDappCpiName = "MyCorDapp"
- notaryCpiName = "NotaryServer"
- cordaRpcUser = "admin"
- cordaRpcPasswd ="admin"
- workflowsModuleName = workflowsModule
- csdeWorkspaceDir = "workspace"
- notaryVersion = cordaNotaryPluginsVersion
- combinedWorkerVersion = combinedWorkerJarVersion
- postgresJdbcVersion = "42.4.3"
- cordaDbContainerName = "CSDEpostgresql"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
}
// Declare the set of Kotlin compiler options we need to build a CorDapp.
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = false
@@ -75,10 +79,10 @@ allprojects {
publishing {
publications {
- maven(MavenPublication) {
- artifactId "corda5-token-cordapp"
- groupId project.group
- artifact jar
- }
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-kotlin-sample"
+ groupId project.group
+ artifact jar
+ }
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/config/combined-worker-compose.yaml b/kotlin-samples/shinny-tokens/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/kotlin-samples/shinny-tokens/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/contracts/build.gradle b/kotlin-samples/shinny-tokens/contracts/build.gradle
index da26473..c1f6ed6 100644
--- a/kotlin-samples/shinny-tokens/contracts/build.gradle
+++ b/kotlin-samples/shinny-tokens/contracts/build.gradle
@@ -41,7 +41,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -49,6 +49,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing-kotlin:$contractTestingVersion"
}
// The CordApp section.
@@ -67,16 +69,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
contract {
- name "ContractsModuleNameHere"
+ name contractsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/gradle.properties b/kotlin-samples/shinny-tokens/gradle.properties
index 282b05a..2549e5f 100644
--- a/kotlin-samples/shinny-tokens/gradle.properties
+++ b/kotlin-samples/shinny-tokens/gradle.properties
@@ -2,24 +2,38 @@ kotlin.code.style=official
# Specify the version of the Corda-API to use.
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
-cordaApiVersion=5.0.0.765
+cordaApiVersion=5.2.0.52
# Specify the version of the notary plugins to use.
# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
-cordaNotaryPluginsVersion=5.0.0.0
-
-# Specify the version of the Combined Worker to use
-combinedWorkerJarVersion=5.0.0.0
+cordaNotaryPluginsVersion=5.2.0.0
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
-cordaPluginsVersion=7.0.3
+cordaPluginsVersion=7.0.4
-# Specify the version of the CSDE gradle plugin to use
-csdePluginVersion=1.1.0
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
workflowsModule=workflows
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
# For the time being this just needs to be set to a dummy value.
platformVersion = 999
@@ -31,7 +45,29 @@ kotlinVersion = 1.7.21
kotlin.stdlib.default.dependency=false
# Test Tooling Dependency Versions
-junitVersion = 5.8.2
+junitVersion = 5.10.0
mockitoKotlinVersion=4.0.0
mockitoVersion=4.6.1
-hamcrestVersion=2.2
\ No newline at end of file
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/settings.gradle b/kotlin-samples/shinny-tokens/settings.gradle
index 9c835cf..3110f05 100644
--- a/kotlin-samples/shinny-tokens/settings.gradle
+++ b/kotlin-samples/shinny-tokens/settings.gradle
@@ -12,15 +12,14 @@ pluginManagement {
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
- id 'net.corda.plugins.csde' version csdePluginVersion
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
+ id 'net.corda.gradle.plugin' version cordaGradlePluginVersion
}
}
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
rootProject.name = 'corda5-token-sample-kotlin'
include ':workflows'
-include ':contracts'
-
+include ':contracts'
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/workflows/build.gradle b/kotlin-samples/shinny-tokens/workflows/build.gradle
index c1cb258..c8bde66 100644
--- a/kotlin-samples/shinny-tokens/workflows/build.gradle
+++ b/kotlin-samples/shinny-tokens/workflows/build.gradle
@@ -12,6 +12,15 @@ plugins {
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
dependencies {
+ constraints {
+ testImplementation('org.slf4j:slf4j-api') {
+ version {
+ // Corda cannot use SLF4J 2.x yet.
+ strictly '1.7.36'
+ }
+ }
+ }
+
// From other subprojects:
cordapp project(':contracts')
@@ -42,7 +51,7 @@ dependencies {
// 3rd party libraries
// Required
- testImplementation "org.slf4j:slf4j-simple:2.0.0"
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
@@ -50,7 +59,8 @@ dependencies {
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
-
+ testImplementation "org.assertj:assertj-core:$assertjVersion"
+ testImplementation "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"
}
// The CordApp section.
@@ -69,16 +79,20 @@ cordapp {
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
// Declares the type and metadata of the CPK (this CPB has one CPK).
workflow {
- name "WorkflowsModuleNameHere"
+ name workflowsModule
versionId 1
- vendor "VendorNameHere"
+ licence cordappLicense
+ vendor cordappVendorName
}
}
+// Use the name of the workflow module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.workflow.name.isPresent() ? cordapp.workflow.name.get() : workflowsModule
+
publishing {
publications {
maven(MavenPublication) {
from components.cordapp
}
}
-}
+}
\ No newline at end of file
diff --git a/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.kt b/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.kt
index bf3af86..b36a144 100644
--- a/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.kt
+++ b/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/BurnGoldTokenFlow.kt
@@ -88,7 +88,7 @@ class BurnGoldTokenFlow : ClientStartableFlow{
// tryClaim will check in the vault if there are tokens which can satisfy the expected amount.
// If yes all the fungible tokens are returned back.
// Remaining change will be returned back to the sender.
- tokenClaim = tokenSelection.tryClaim(tokenClaimCriteria)
+ tokenClaim = tokenSelection.tryClaim(UUID.randomUUID().toString(), tokenClaimCriteria)
if (tokenClaim == null) {
diff --git a/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.kt b/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.kt
index 467b40f..08e5aee 100644
--- a/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.kt
+++ b/kotlin-samples/shinny-tokens/workflows/src/main/kotlin/com/r3/developers/samples/tokens/workflows/TransferGoldTokenFlow.kt
@@ -96,7 +96,7 @@ class TransferGoldTokenFlow : ClientStartableFlow{
// tryClaim will check in the vault if there are tokens which can satisfy the expected amount.
// If yes all the fungible tokens are returned back.
// Remaining change will be returned back to the sender.
- tokenClaim = tokenSelection.tryClaim(tokenClaimCriteria)
+ tokenClaim = tokenSelection.tryClaim(UUID.randomUUID().toString(), tokenClaimCriteria)
if (tokenClaim == null) {
log.info("No tokens found for" + jsonMarshallingService.format(tokenClaimCriteria))