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

+ + + + + +
+ +
+ + + + +
+
+ + +
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 + + + + + + + + + + + + +

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/encumbrance-pawn-shop/README.md b/kotlin-samples/encumbrance-pawn-shop/README.md index 2e0af81..05ccfce 100644 --- a/kotlin-samples/encumbrance-pawn-shop/README.md +++ b/kotlin-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/kotlin-samples/encumbrance-pawn-shop/build.gradle b/kotlin-samples/encumbrance-pawn-shop/build.gradle index ea4f634..1c6eb2e 100644 --- a/kotlin-samples/encumbrance-pawn-shop/build.gradle +++ b/kotlin-samples/encumbrance-pawn-shop/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-encumbrance-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/encumbrance-pawn-shop/config/combined-worker-compose.yaml b/kotlin-samples/encumbrance-pawn-shop/config/combined-worker-compose.yaml new file mode 100644 index 0000000..da2495d --- /dev/null +++ b/kotlin-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/kotlin-samples/encumbrance-pawn-shop/contracts/build.gradle b/kotlin-samples/encumbrance-pawn-shop/contracts/build.gradle index da26473..c1f6ed6 100644 --- a/kotlin-samples/encumbrance-pawn-shop/contracts/build.gradle +++ b/kotlin-samples/encumbrance-pawn-shop/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/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/Jenkinsfile b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/Jenkinsfile new file mode 100644 index 0000000..2108886 --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/Jenkinsfile @@ -0,0 +1,11 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaPipeline( + nexusAppId: 'com.corda.CSDE-kotlin.5.0', + publishRepoPrefix: '', + slimBuild: true, + runUnitTests: false, + dedicatedJobForSnykDelta: false, + slackChannel: '#corda-corda5-dev-ex-build-notifications', + gitHubComments: false + ) diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/nightly/JenkinsfileSnykScan b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/nightly/JenkinsfileSnykScan new file mode 100644 index 0000000..fc2b1ee --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.ci/nightly/JenkinsfileSnykScan @@ -0,0 +1,6 @@ +@Library('corda-shared-build-pipeline-steps@5.0') _ + +cordaSnykScanPipeline ( + snykTokenId: 'r3-snyk-corda5', + snykAdditionalCommands: "--all-sub-projects -d" +) diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.gitignore b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.gitignore new file mode 100644 index 0000000..d2879c4 --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.gitignore @@ -0,0 +1,86 @@ + +# Eclipse, ctags, Mac metadata, log files +.classpath +.project +.settings +tags +.DS_Store +*.log +*.orig + +# Created by .ignore support plugin (hsz.mobi) + +.gradle +local.properties +.gradletasknamecache + +# General build files +**/build/* + +lib/quasar.jar + +**/logs/* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/*.xml +.idea/.name +.idea/copyright +.idea/inspectionProfiles +.idea/libraries +.idea/shelf +.idea/dataSources +.idea/markdown-navigator +.idea/runConfigurations +.idea/dictionaries + + +# Include the -parameters compiler option by default in IntelliJ required for serialization. +!.idea/codeStyleSettings.xml + + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +**/out/ +/classes/ + + + +# vim +*.swp +*.swn +*.swo + + + +# Directory generated during Resolve and TestOSGi gradle tasks +bnd/ + +# Ignore Gradle build output directory +build +/.idea/codeStyles/codeStyleConfig.xml +/.idea/codeStyles/Project.xml + + + +# Ignore Visual studio directory +bin/ + + + +*.cpi +*.cpb +*.cpk +workspace/** + +# ingore temporary data files +*.dat \ No newline at end of file diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.snyk b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.snyk new file mode 100644 index 0000000..a4b3e0e --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/.snyk @@ -0,0 +1,14 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.25.0 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744: + - '*': + reason: >- + This vulnerability relates to information exposure via creation of + temporary files (via Kotlin functions) with insecure permissions. + Corda does not use any of the vulnerable functions so it is not + susceptible to this vulnerability + expires: 2023-10-19T17:08:41.029Z + created: 2023-02-02T17:08:41.032Z +patch: {} \ No newline at end of file diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/README.md b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/README.md new file mode 100644 index 0000000..40258b4 --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/README.md @@ -0,0 +1,89 @@ +# C5-Obligation-CorDapp + +This app is our signature CorDapp that we use to show the main functionalities of Corda, which are creating a digital asset, +updating the digital asset, and transferring the digital asset. This app depicts a simple use +case of money borrowing between two parties. In the app, the borrowed asset is called the `IOUState` (I-owe-you) +and it is quantifiable. + +In this app you can: +1. Create a new IOUState with a counterparty. `IOUIssueFlow` +2. List out the IOU entries you had. `ListIOUFlow` +3. Settle(pau back) the IOUState `IOUSettleFlow` +4. Lender transfer the debt to a different person `IOUTransferFlow` + +### 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 + 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 + + + +### Running the app + +In Corda 5, flows will be triggered via `POST /flow/{holdingidentityshorthash}` and flow result will need to be view at `GET /flow/{holdingidentityshorthash}/{clientrequestid}` +* holdingidentityshorthash: the id of the network participants, ie Bob, Alice, Charlie. You can view all the short hashes of the network member with another gradle task called `ListVNodes` +* clientrequestid: the id you specify in the flow requestBody when you trigger a flow. + +#### Step 1: Create IOUState between two parties +Pick a VNode identity to initiate the IOU creation, and get its short hash. (Let's pick Alice. Don't pick Bob because Bob is the person who alice will borrow from). + +Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body: +``` +{ + "clientRequestId": "createiou-1", + "flowClassName": "com.r3.developers.samples.obligation.workflows.IOUIssueFlow", + "requestBody": { + "amount":"20", + "lender":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB" + } +} +``` + +After trigger the create-IOUflow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and client request id to view the flow result + +#### Step 2: List created IOU state +In order to continue the app logics, we would need the IOU ID. This step will bring out all the IOU entries this entity (Alice) has. +Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body: +``` +{ + "clientRequestId": "list-1", + "flowClassName": "com.r3.developers.samples.obligation.workflows.ListIOUFlow", + "requestBody": {} +} +``` +After trigger the list-IOUs flow, again, we need to hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and check the result. Let's record that id. + + +#### Step 3: Partially settle the IOU with `IOUSettleFlow` +In this step, we will partially settle the IOU with some amount. +Goto `POST /flow/{holdingidentityshorthash}`, enter the identity short hash and request body. Note that the settle action can only be initiated by the borrower of the IOU +``` +{ + "clientRequestId": "settleiou-1", + "flowClassName": "com.r3.developers.samples.obligation.workflows.IOUSettleFlow", + "requestBody": { + "amountSettle":"10", + "iouID":" ** fill in id **" + } +} +``` +And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the required fields. + +#### Step 4: Lastly, the lender of the IOU can transfer the IOU to a different owner. +Note this transfer action can only be initiated by the lender of the IOU. We will have Bob transfer his IOU to Charlie. +We will now take Bob's shorthash and enter the following request Body. +``` +{ + "clientRequestId": "transferiou-1", + "flowClassName": "com.r3.developers.samples.obligation.workflows.IOUTransferFlow", + "requestBody": { + "newLender":"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB", + "iouID":" ** fill in id **" + } +} +``` +And as for the result, you need to go to the Get API again and enter the short hash and client request ID. + +Thus, we have concluded a full run through of the obligation app. diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/build.gradle b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/build.gradle new file mode 100644 index 0000000..210776a --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/build.gradle @@ -0,0 +1,88 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +import static org.gradle.api.JavaVersion.VERSION_17 + +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' + + def javaVersion = VERSION_17 + + // 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" + } + + // Declare the set of Kotlin compiler options we need to build a CorDapp. + tasks.withType(KotlinCompile).configureEach { + kotlinOptions { + allWarningsAsErrors = false + + // Specify the version of Kotlin that we are that we will be developing. + languageVersion = '1.7' + // Specify the Kotlin libraries that code is compatible with + apiVersion = '1.7' + // Note that we Need to use a version of Kotlin that will be compatible with the Corda API. + // Currently that is developed in Kotlin 1.7 so picking the same version ensures compatibility with that. + + // Specify the version of Java to target. + jvmTarget = javaVersion + + // Needed for reflection to work correctly. + javaParameters = true + + // -Xjvm-default determines how Kotlin supports default methods. + // JetBrains currently recommends developers use -Xjvm-default=all + // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/ + freeCompilerArgs += [ + "-Xjvm-default=all" + ] + } + } + + repositories { + // All dependencies are held in Maven Central + mavenLocal() + mavenCentral() + } + + tasks.withType(Test).configureEach { + useJUnitPlatform() + } + +} + +publishing { + publications { + maven(MavenPublication) { + artifactId "corda-dev-template-kotlin-sample" + groupId project.group + artifact jar + } + } +} \ No newline at end of file diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradle.properties b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradle.properties new file mode 100644 index 0000000..2549e5f --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradle.properties @@ -0,0 +1,73 @@ +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.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.2.0.0 + +# Specify the version of the cordapp-cpb and cordapp-cpk plugins +cordaPluginsVersion=7.0.4 + +# 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 + +# Version of Kotlin to use. +# We recommend using a version close to that used by Corda-API. +kotlinVersion = 1.7.21 + +# Do not use default dependencies. +kotlin.stdlib.default.dependency=false + +# Test Tooling Dependency Versions +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 + +# 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/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew new file mode 100644 index 0000000..65dcd68 --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew.bat b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/settings.gradle b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/settings.gradle new file mode 100644 index 0000000..69e0c8f --- /dev/null +++ b/kotlin-samples/encumbrance-pawn-shop/corda5-obligation-cordapp/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + // Declare the repositories where plugins are stored. + repositories { + gradlePluginPortal() + mavenCentral() + mavenLocal() + } + + // The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version, + // Corda API version, and Kotlin version. + plugins { + 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 '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 = 'cordapp-obligation-kotlin' +include ':workflows' +include ':contracts' \ No newline at end of file diff --git a/kotlin-samples/encumbrance-pawn-shop/gradle.properties b/kotlin-samples/encumbrance-pawn-shop/gradle.properties index 282b05a..2549e5f 100644 --- a/kotlin-samples/encumbrance-pawn-shop/gradle.properties +++ b/kotlin-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/kotlin-samples/encumbrance-pawn-shop/settings.gradle b/kotlin-samples/encumbrance-pawn-shop/settings.gradle index 28784d3..b7ead52 100644 --- a/kotlin-samples/encumbrance-pawn-shop/settings.gradle +++ b/kotlin-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-kotlin' include ':workflows' -include ':contracts' - +include ':contracts' \ No newline at end of file diff --git a/kotlin-samples/encumbrance-pawn-shop/workflows/build.gradle b/kotlin-samples/encumbrance-pawn-shop/workflows/build.gradle index c1cb258..c8bde66 100644 --- a/kotlin-samples/encumbrance-pawn-shop/workflows/build.gradle +++ b/kotlin-samples/encumbrance-pawn-shop/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/oracle-foreign-exchange/FlowManagementUI/Dockerfile b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/Dockerfile new file mode 100644 index 0000000..016bc21 --- /dev/null +++ b/kotlin-samples/oracle-foreign-exchange/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/oracle-foreign-exchange/FlowManagementUI/README.md b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/README.md new file mode 100644 index 0000000..fffff29 --- /dev/null +++ b/kotlin-samples/oracle-foreign-exchange/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/oracle-foreign-exchange/FlowManagementUI/app.py b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/app.py new file mode 100644 index 0000000..5d3e3f6 --- /dev/null +++ b/kotlin-samples/oracle-foreign-exchange/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/oracle-foreign-exchange/FlowManagementUI/requirements.txt b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/requirements.txt new file mode 100644 index 0000000..8ab6294 --- /dev/null +++ b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/requirements.txt @@ -0,0 +1 @@ +flask \ No newline at end of file diff --git a/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/Scripts/script.js b/kotlin-samples/oracle-foreign-exchange/FlowManagementUI/static/Scripts/script.js new file mode 100644 index 0000000..8fd4ec0 --- /dev/null +++ b/kotlin-samples/oracle-foreign-exchange/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/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))