├── .github └── workflows │ └── docker-image-push.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cctl-container ├── Dockerfile ├── Dockerfile.dev └── api │ ├── Cargo.toml │ ├── Rocket.toml │ ├── endpoints.md │ └── src │ ├── cache.rs │ ├── commands.json │ ├── main.rs │ └── utils.rs ├── docker-compose.yml ├── frontend ├── .gitignore ├── .prettierrc ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.test.tsx │ ├── App.tsx │ ├── assets │ │ └── logo.svg │ ├── casper-client.ts │ ├── components │ │ ├── atoms │ │ │ ├── format-json.tsx │ │ │ ├── settings-alert.tsx │ │ │ └── spinner-frame.tsx │ │ ├── molecules │ │ │ ├── account-modal.tsx │ │ │ ├── account-row-element.tsx │ │ │ ├── blocks-row-element.tsx │ │ │ ├── deploy-element.tsx │ │ │ ├── event-element.tsx │ │ │ ├── log-element.tsx │ │ │ ├── navbar-mobile.tsx │ │ │ ├── navbar-modal.tsx │ │ │ └── navbar-subbar.tsx │ │ ├── organisms │ │ │ ├── navbar.tsx │ │ │ ├── tab-about.tsx │ │ │ ├── tab-account.tsx │ │ │ ├── tab-advanced.tsx │ │ │ ├── tab-chain.tsx │ │ │ ├── tab-gas.tsx │ │ │ ├── tab-logging.tsx │ │ │ ├── tab-server.tsx │ │ │ └── tab-workspace.tsx │ │ └── pages │ │ │ ├── accounts.tsx │ │ │ ├── blocks.tsx │ │ │ ├── deploys.tsx │ │ │ ├── events.tsx │ │ │ ├── logs.tsx │ │ │ └── settings.tsx │ ├── constant.ts │ ├── context │ │ ├── IsNetworkLaunchedContext.tsx │ │ ├── IsNetworkRunningContext.tsx │ │ ├── NodeContext.tsx │ │ └── SearchContext.tsx │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── serviceWorker.ts │ ├── setupTests.ts │ ├── styles │ │ └── theme.ts │ └── test-utils.tsx └── tsconfig.json └── setup ├── docker-compose.yml ├── rebuild.ps1 └── rebuild.sh /.github/workflows/docker-image-push.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image Push 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-and-push: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check Out Code 12 | uses: actions/checkout@v3 13 | 14 | - name: Log in to Docker Hub 15 | uses: docker/login-action@v2 16 | with: 17 | username: ${{ secrets.DOCKER_USERNAME }} 18 | password: ${{ secrets.DOCKER_PASSWORD }} 19 | 20 | - name: Build and Push fondant-nctl-container Image 21 | uses: docker/build-push-action@v3 22 | with: 23 | context: ./cctl-container 24 | push: true 25 | tags: blockbites/fondant-cctl-container:latest 26 | 27 | - name: Build and Push fondant-frontend Image 28 | uses: docker/build-push-action@v3 29 | with: 30 | context: ./frontend 31 | push: true 32 | tags: blockbites/fondant-frontend:latest 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Electron-Forge 89 | out/ 90 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## Unreleased 9 | 10 | ### Added 11 | 12 | #### Frontend 13 | 14 | - [Added support for smaller screens](https://github.com/block-bites/fondant-app/pull/91) 15 | - [Added modal (popup) window for networks reset confirmation](https://github.com/block-bites/fondant-app/pull/77) 16 | - [Added preloaders for all pages, visual bugs fixed](https://github.com/block-bites/fondant-app/pull/80) 17 | - [Added loader to topbar start/pasue buttons](https://github.com/block-bites/fondant-app/issues/81) 18 | - Prepared frontend to handle CCTL 19 | 20 | #### Backend 21 | 22 | - Exchanged NCTL container to in-house created CCTL container 23 | - Discarded Lean API. Now the API uses rust rocket framework. 24 | - Merged API and CCTL containers. 25 | - Run endpoint that is compatible with all CCTL commands. 26 | 27 | ### Fixed 28 | 29 | #### Frontend 30 | 31 | - [Deploy, Event and Logs mapped elements are separated](https://github.com/block-bites/fondant-app/pull/78) 32 | - [Navbar menu is working correctly](https://github.com/block-bites/fondant-app/pull/64) 33 | - [Fixed access to all other subpages when network is not initialized](https://github.com/block-bites/fondant-app/issues/85) 34 | - [Fixed Start/Pause buttons](https://github.com/block-bites/fondant-app/issues/82) 35 | - Fixed pagination on all subpages 36 | 37 | ## [1.0.0] - 2024-01-15 38 | 39 | ### Added 40 | 41 | #### Frontend 42 | 43 | - Accounts page. Displays users private and public keys. 44 | - Logs, Event and Deploy pages presenting with JSON info on each coressponding topic. 45 | - Blocks page, displays basic information about block generated on the network. 46 | - Ability to change observed node. 47 | - Reset button for the network. 48 | 49 | #### Backend 50 | 51 | - Lean API that acts as a proxy for the original nctl container. 52 | - [Documentation](https://github.com/block-bites/fondant-app/blob/master/OpenAPI.yml) for the Lean API. 53 | - Flask API deployed on the nctl container to ease up interaction between Lean API and nctl. 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fondant: A Blockchain Application Suite 2 | 3 | Welcome to Fondant version 1.1! Fondant is a suite of tools the purpose of which is to help you develop and test your applications on the Casper blokchain. We provide all the functionality of CCTL packaged in a slick UI. The app runs on docker containers so you don't have to wander about cross-compatibility or demanding compilations. 4 | 5 | To get started install **docker**, clone the repo and build the images using the docker compomse file. 6 | ```git clone https://github.com/block-bites/fondant-app && cd fondant-app && docker-compose up --build``` 7 | 8 | 9 | ## Frontend Features 10 | 11 | Explore the user-friendly interface of Fondant's frontend: 12 | 13 | - **Accounts Page:** This section displays all user accounts, complete with their private and public keys. 14 | - **Blocks Section:** View all blockchain blocks arranged in chronological order. 15 | - **Deploys, Events, Logs:** Access detailed information about deploys, events, and logs for in-depth analysis. 16 | 17 | ## Backend Capabilities 18 | 19 | We've set up proxies for essential ports as backend endpoints. *Example: http://localhost/node-1/rpc*. These endpoints allow direct connection to the respective node's RPC port. 20 | Sample Commands: 21 | - To check the status of node 1: 22 | `casper-client get-node-status -n http://localhost/node-1/rpc` 23 | - To retrieve the latest block info from node 2: 24 | `casper-client get-block -n http://localhost/node-1/rpc` 25 | - To get the current state root hash from node 3: 26 | `casper-client get-state-root-hash -n http://localhost/node-1/rpc` (applicable after the first block emission) 27 | 28 | Currently, nodes 1 to 5 are supported, with plans to make this configurable in future updates. 29 | -------------------------------------------------------------------------------- /cctl-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 rust:buster as builder 2 | 3 | WORKDIR /usr/src/myapp 4 | 5 | COPY api/Cargo.toml ./ 6 | COPY api/Rocket.toml ./ 7 | 8 | RUN mkdir src && \ 9 | echo "fn main() {}" > src/main.rs && \ 10 | cargo build --release && \ 11 | rm -rf src 12 | 13 | COPY api/src ./src 14 | 15 | RUN cargo build --release 16 | 17 | FROM commondrum/cctl-fondant:latest 18 | 19 | USER root 20 | 21 | RUN apt-get update && \ 22 | apt-get install -y nginx && \ 23 | rm -rf /var/lib/apt/lists/* 24 | 25 | WORKDIR /usr/local/bin 26 | 27 | COPY --from=builder /usr/src/myapp/target/release/api ./ 28 | COPY api/src/commands.json ./ 29 | 30 | RUN chmod -R +x /home/cctl 31 | 32 | ENTRYPOINT ["/usr/local/bin/api"] 33 | 34 | EXPOSE 3001 80 35 | 36 | CMD ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /cctl-container/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM commondrum/cctl-fondant:latest 2 | 3 | USER root 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y nginx curl && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 10 | 11 | ENV PATH="/root/.cargo/bin:${PATH}" 12 | 13 | WORKDIR /usr/src/myapp 14 | 15 | COPY api/Cargo.toml ./ 16 | COPY api/Rocket.toml ./ 17 | COPY api/src ./src 18 | 19 | RUN cargo build --release 20 | 21 | WORKDIR /usr/local/bin 22 | 23 | RUN cp /usr/src/myapp/target/release/api ./ 24 | COPY api/src/commands.json ./ 25 | 26 | RUN chmod -R +x /home/cctl 27 | 28 | ENTRYPOINT ["/usr/local/bin/api"] 29 | 30 | EXPOSE 3001 80 31 | 32 | CMD ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /cctl-container/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rocket = { version = "0.5.0", features = ["json"] } 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0.115" 12 | regex = "1" 13 | tokio = { version = "1", features = ["full"] } 14 | sse-client = "1.1.1" 15 | lazy_static = "1.4.0" -------------------------------------------------------------------------------- /cctl-container/api/Rocket.toml: -------------------------------------------------------------------------------- 1 | [global.log] 2 | level = "normal" -------------------------------------------------------------------------------- /cctl-container/api/endpoints.md: -------------------------------------------------------------------------------- 1 | 2 | Health Check: 3 | Endpoint: GET /health 4 | Description: Check if the service is up and running. 5 | Response: Plain text message "Service is up and running". 6 | Run Command: 7 | Endpoint: POST /run/? 8 | Description: Execute a command with optional arguments. 9 | Parameters: 10 | command: The command to execute. 11 | args: Optional command arguments. 12 | Response: JSON object containing the command result or an error message. 13 | Get Events: 14 | Endpoint: GET /cache/events/ 15 | Description: Retrieve cached events for a specific node. 16 | Parameters: 17 | node_number: The node number. 18 | Response: JSON array of event strings or a 404 Not Found status if not available. 19 | Get Deploys: 20 | Endpoint: GET /cache/deploys/ 21 | Description: Retrieve cached deploys for a specific node. 22 | Parameters: 23 | node_number: The node number. 24 | Response: JSON array of deploy strings or a 404 Not Found status if not available. 25 | Search Events: 26 | Endpoint: GET /cache/events//search? 27 | Description: Search for events within the cached events for a specific node. 28 | Parameters: 29 | node_number: The node number. 30 | query: The search query. 31 | Response: JSON array of matching event strings or a 404 Not Found status if not available. 32 | Search Deploys: 33 | Endpoint: GET /cache/deploys//search? 34 | Description: Search for deploys within the cached deploys for a specific node. 35 | Parameters: 36 | node_number: The node number. 37 | query: The search query. 38 | Response: JSON array of matching deploy strings or a 404 Not Found status if not available. 39 | Launch Network: 40 | Endpoint: POST /launch 41 | Description: Launch the network. 42 | Response: JSON object indicating the success status and a message. 43 | Stop Network: 44 | Endpoint: POST /stop 45 | Description: Stop the network. 46 | Response: JSON object indicating the success status and a message. 47 | Start Network: 48 | Endpoint: POST /start 49 | Description: Start the network. 50 | Response: JSON object indicating the success status and a message. 51 | Get Network Status: 52 | Endpoint: GET /status 53 | Description: Get the current status of the network. 54 | Response: JSON object containing the success status and the current network status message. 55 | -------------------------------------------------------------------------------- /cctl-container/api/src/cache.rs: -------------------------------------------------------------------------------- 1 | extern crate sse_client; 2 | 3 | use std::collections::HashMap; 4 | use std::sync::{Arc, Mutex}; 5 | use sse_client::EventSource; 6 | 7 | pub struct SseCache { 8 | data: Arc>>>, 9 | capacity: i32, 10 | } 11 | 12 | impl SseCache { 13 | pub fn new(capacity: i32) -> SseCache { 14 | SseCache { 15 | data: Arc::new(Mutex::new(HashMap::new())), 16 | capacity: capacity, 17 | } 18 | } 19 | 20 | pub fn start_listening(&self, url: String) { 21 | let event_source = EventSource::new(&url).unwrap(); 22 | self.data.lock().unwrap().insert(url.clone(), Vec::new()); 23 | 24 | let data = Arc::clone(&self.data); 25 | let capacity = self.capacity; 26 | event_source.on_message(move |message| { 27 | let mut cache = data.lock().unwrap(); 28 | if let Some(messages) = cache.get_mut(&url) { 29 | messages.push(message.data.clone()); 30 | if messages.len() as i32 > capacity { 31 | messages.remove(0); 32 | } 33 | } 34 | }); 35 | 36 | event_source.add_event_listener("error", |error| { 37 | println!("Error {:?}", error); 38 | }); 39 | } 40 | 41 | pub fn get_data(&self, url: &String) -> Option> { 42 | self.data.lock().unwrap().get(url).cloned() 43 | } 44 | 45 | pub fn search(&self, url: &String, query: &str) -> Option> { 46 | if let Some(messages) = self.data.lock().unwrap().get(url) { 47 | let matching_messages: Vec = messages 48 | .iter() 49 | .filter(|message| message.contains(query)) 50 | .cloned() 51 | .collect(); 52 | if !matching_messages.is_empty() { 53 | return Some(matching_messages); 54 | } 55 | } 56 | None 57 | } 58 | } -------------------------------------------------------------------------------- /cctl-container/api/src/commands.json: -------------------------------------------------------------------------------- 1 | { 2 | "cctl-infra-bin-compile": "$CCTL/cmds/infra/bin/compile.sh", 3 | "cctl-infra-bin-compile-client": "$CCTL/cmds/infra/bin/compile_client.sh", 4 | "cctl-infra-bin-compile-contracts": "$CCTL/cmds/infra/bin/compile_contracts.sh", 5 | "cctl-infra-bin-compile-node": "$CCTL/cmds/infra/bin/compile_node.sh", 6 | "cctl-infra-bin-compile-node-launcher": "$CCTL/cmds/infra/bin/compile_node_launcher.sh", 7 | "cctl-infra-net-setup": "$CCTL/cmds/infra/net/setup.sh", 8 | "cctl-infra-net-start": "$CCTL/cmds/infra/net/start.sh", 9 | "cctl-infra-net-status": "$CCTL/cmds/infra/net/status.sh", 10 | "cctl-infra-net-stop": "$CCTL/cmds/infra/net/stop.sh", 11 | "cctl-infra-net-teardown": "$CCTL/cmds/infra/net/teardown.sh", 12 | "cctl-infra-node-clean": "$CCTL/cmds/infra/node/clean.sh", 13 | "cctl-infra-node-stop": "$CCTL/cmds/infra/node/stop.sh", 14 | "cctl-infra-node-restart": "$CCTL/cmds/infra/node/restart.sh", 15 | "cctl-infra-node-view-config": "$CCTL/cmds/infra/node/view_config.sh", 16 | "cctl-infra-node-view-error-log": "$CCTL/cmds/infra/node/view_log_stderr.sh", 17 | "cctl-infra-node-view-log": "$CCTL/cmds/infra/node/view_log_stdout.sh", 18 | "cctl-infra-node-view-metrics": "$CCTL/cmds/infra/node/view_metrics.sh", 19 | "cctl-infra-node-view-peers": "$CCTL/cmds/infra/node/view_peers.sh", 20 | "cctl-infra-node-view-peer-count": "$CCTL/cmds/infra/node/view_peer_count.sh", 21 | "cctl-infra-node-view-ports": "$CCTL/cmds/infra/node/view_ports.sh", 22 | "cctl-infra-node-view-rpc-endpoint": "$CCTL/cmds/infra/node/view_rpc_endpoint.sh", 23 | "cctl-infra-node-view-rpc-schema": "$CCTL/cmds/infra/node/view_rpc_schema.sh", 24 | "cctl-infra-node-view-status": "$CCTL/cmds/infra/node/view_status.sh", 25 | "cctl-infra-node-view-storage": "$CCTL/cmds/infra/node/view_storage.sh", 26 | "cctl-infra-node-write-rpc-schema": "$CCTL/cmds/infra/node/write_rpc_schema.sh", 27 | "cctl-chain-await-n-blocks": "$CCTL/cmds/chain/await/n_blocks.sh", 28 | "cctl-chain-await-n-eras": "$CCTL/cmds/chain/await/n_eras.sh", 29 | "cctl-chain-await-until-block-n": "$CCTL/cmds/chain/await/until_block_n.sh", 30 | "cctl-chain-await-until-era-n": "$CCTL/cmds/chain/await/until_era_n.sh", 31 | "cctl-chain-view-account": "$CCTL/cmds/chain/query/view_account.sh", 32 | "cctl-chain-view-account-address": "$CCTL/cmds/chain/query/view_account_address.sh", 33 | "cctl-chain-view-account-balance": "$CCTL/cmds/chain/query/view_account_balance.sh", 34 | "cctl-chain-view-account-balances": "$CCTL/cmds/chain/query/view_account_balances.sh", 35 | "cctl-chain-view-account-of-faucet": "$CCTL/cmds/chain/query/view_account_of_faucet.sh", 36 | "cctl-chain-view-account-of-user": "$CCTL/cmds/chain/query/view_account_of_user.sh", 37 | "cctl-chain-view-account-of-validator": "$CCTL/cmds/chain/query/view_account_of_validator.sh", 38 | "cctl-chain-view-auction-info": "$CCTL/cmds/chain/query/view_auction_info.sh", 39 | "cctl-chain-view-block": "$CCTL/cmds/chain/query/view_block.sh", 40 | "cctl-chain-view-block-transfers": "$CCTL/cmds/chain/query/view_block_transfers.sh", 41 | "cctl-chain-view-deploy": "$CCTL/cmds/chain/query/view_deploy.sh", 42 | "cctl-chain-view-era": "$CCTL/cmds/chain/query/view_era.sh", 43 | "cctl-chain-view-era-summary": "$CCTL/cmds/chain/query/view_era_summary.sh", 44 | "cctl-chain-view-genesis-accounts": "$CCTL/cmds/chain/query/view_genesis_accounts.sh", 45 | "cctl-chain-view-genesis-chainspec": "$CCTL/cmds/chain/query/view_genesis_chainspec.sh", 46 | "cctl-chain-view-height": "$CCTL/cmds/chain/query/view_height.sh", 47 | "cctl-chain-view-last-finalized-block": "$CCTL/cmds/chain/query/view_last_finalized_block.sh", 48 | "cctl-chain-view-state-root-hash": "$CCTL/cmds/chain/query/view_state_root_hash.sh", 49 | "cctl-chain-view-tip-info": "$CCTL/cmds/chain/query/view_tip_info.sh", 50 | "cctl-chain-view-validator-changes": "$CCTL/cmds/chain/query/view_validator_changes.sh", 51 | "cctl-tx-auction-activate-bid": "$CCTL/cmds/tx/auction/activate_bid.sh", 52 | "cctl-tx-auction-delegate": "$CCTL/cmds/tx/auction/delegate.sh", 53 | "cctl-tx-auction-add-bid": "$CCTL/cmds/tx/auction/add_bid.sh", 54 | "cctl-tx-auction-undelegate": "$CCTL/cmds/tx/auction/undelegate.sh", 55 | "cctl-tx-auction-withdraw-bid": "$CCTL/cmds/tx/auction/withdraw_bid.sh", 56 | "cctl-tx-mint-dispatch-transfer": "$CCTL/cmds/tx/mint/dispatch_transfer.sh", 57 | "cctl-tx-mint-dispatch-transfer-batch": "$CCTL/cmds/tx/mint/dispatch_transfer_batch.sh", 58 | "cctl-tx-mint-write-transfer-batch": "$CCTL/cmds/tx/mint/write_transfer_batch.sh" 59 | } 60 | -------------------------------------------------------------------------------- /cctl-container/api/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_use] extern crate rocket; 3 | 4 | use rocket::{get, launch, routes}; 5 | use rocket::http::Status; 6 | use rocket::serde::{Serialize, json::Json}; 7 | 8 | // Multithreading safety 9 | use lazy_static::lazy_static; 10 | use std::sync::Mutex; 11 | 12 | //For CORS 13 | use rocket::http::Header; 14 | use rocket::{Request, Response}; 15 | use rocket::fairing::{Fairing, Info, Kind}; 16 | 17 | // Filesystem for key reading. 18 | use std::fs; 19 | 20 | mod cache; 21 | mod utils; 22 | 23 | pub struct CORS; 24 | 25 | #[derive(Serialize)] 26 | struct ActivationResponse { 27 | success: bool, 28 | message: String, 29 | } 30 | 31 | lazy_static! { 32 | static ref CACHE: Mutex = Mutex::new(cache::SseCache::new(1000)); 33 | static ref NETWORK_STATUS: Mutex = Mutex::new("".to_string()); 34 | } 35 | 36 | #[post("/run/?")] 37 | fn run(command: String, args: Option>) -> Result, Status> { 38 | match utils::run_command(&command, args) { 39 | Ok(result) => Ok(Json(result)), 40 | Err(error) => { 41 | eprintln!("Command execution error: {}", error); 42 | Err(Status::InternalServerError) 43 | } 44 | } 45 | } 46 | 47 | // Init does not replace start, It is used to setup the netowrk, reverse proxy and sse caching. 48 | #[post("/init")] 49 | fn init() -> Result, Status> { 50 | let mut status = NETWORK_STATUS.lock().unwrap(); 51 | *status = "launching".to_string(); 52 | 53 | match utils::run_command("cctl-infra-net-setup", None) { 54 | Ok(_) => { 55 | *status = "launched".to_string(); 56 | } 57 | Err(_) => { 58 | *status = "stopped".to_string(); 59 | } 60 | } 61 | 62 | match utils::run_command("cctl-infra-net-start", None) { 63 | Ok(_) => { 64 | *status = "running".to_string(); 65 | } 66 | Err(_) => { 67 | *status = "stopped".to_string(); 68 | } 69 | } 70 | 71 | 72 | let parsed_ports = utils::parse_node_ports(); 73 | println!("{:?}", parsed_ports); 74 | 75 | utils::generate_nginx_config(&parsed_ports); 76 | utils::start_nginx(); 77 | 78 | let node_count = utils::count_running_nodes(); 79 | println!("{} nodes running.", node_count); 80 | 81 | for i in 1..node_count + 1{ 82 | let events = format!("http://localhost/node-{}/sse/events/main", i); 83 | CACHE.lock().unwrap().start_listening(events); 84 | 85 | let deploys = format!("http://localhost/node-{}/sse/events/deploys", i); 86 | CACHE.lock().unwrap().start_listening(deploys); 87 | } 88 | 89 | *status = "running".to_string(); 90 | Ok(Json(ActivationResponse { 91 | success: true, 92 | message: "Network is initilized and started.".to_string(), 93 | })) 94 | } 95 | 96 | #[post("/stop")] 97 | fn stop() -> Result, Status> { 98 | let mut status = NETWORK_STATUS.lock().unwrap(); 99 | *status = "stopping".to_string(); 100 | 101 | match utils::run_command("cctl-infra-net-stop", None) { 102 | Ok(_) => { 103 | *status = "stopped".to_string(); 104 | Ok(Json(ActivationResponse { 105 | success: true, 106 | message: "Network stopped successfully".to_string(), 107 | })) 108 | } 109 | Err(_) => { 110 | *status = "stopped".to_string(); 111 | Err(Status::InternalServerError) 112 | } 113 | } 114 | } 115 | 116 | #[post("/start")] 117 | fn start() -> Result, (Status, &'static str)> { 118 | let mut status = NETWORK_STATUS.lock().unwrap(); 119 | if *status == "" { 120 | return Err((Status::BadRequest, "You need to initialize the network before you can start it")); 121 | } 122 | if *status == "running" { 123 | return Err((Status::Conflict, "The network is already running")); 124 | } 125 | if *status == "starting" { 126 | return Err((Status::Conflict, "The network is already starting")); 127 | } 128 | *status = "starting".to_string(); 129 | match utils::run_command("cctl-infra-net-start", None) { 130 | Ok(_) => { 131 | *status = "running".to_string(); 132 | Ok(Json(ActivationResponse { 133 | success: true, 134 | message: "Network started successfully".to_string(), 135 | })) 136 | } 137 | Err(_) => { 138 | *status = "stopped".to_string(); 139 | Err((Status::InternalServerError, "Failed to start the network")) 140 | } 141 | } 142 | } 143 | 144 | #[get("/status")] 145 | fn status() -> Json { 146 | let status = NETWORK_STATUS.lock().unwrap(); 147 | Json(ActivationResponse { 148 | success: true, 149 | message: status.clone(), 150 | }) 151 | } 152 | 153 | #[get("/cache/events/")] 154 | fn get_events(node_number: i32) -> Option>> { 155 | let event_url = format!("http://localhost/node-{}/sse/events/main", node_number); 156 | let events = CACHE.lock().unwrap().get_data(&event_url).map(Json); 157 | events.map(|events| Json(events.0[1..].to_vec())) 158 | } 159 | 160 | #[get("/cache/deploys/")] 161 | fn get_deploys(node_number: i32) -> Option>> { 162 | let event_url = format!("http://localhost/node-{}/sse/events/main", node_number); 163 | let events = CACHE.lock().unwrap().get_data(&event_url).map(Json); 164 | //if string contains "Deploy" then add to the return list 165 | let deploys = events.map(|events| { 166 | let deploys: Vec = events.0[1..].iter().filter(|event| event.contains("Deploy")).cloned().collect(); 167 | Json(deploys) 168 | }); 169 | deploys 170 | } 171 | 172 | #[get("/cache/events//search?")] 173 | fn search_events(node_number: i32, query: &str) -> Option>> { 174 | let events = format!("http://localhost/node-{}/sse/events/main", node_number); 175 | CACHE.lock().unwrap().search(&events, query).map(Json) 176 | } 177 | 178 | #[get("/cache/deploys//search?")] 179 | fn search_deploys(node_number: i32, query: &str) -> Option>> { 180 | let deploys = format!("http://localhost/node-{}/sse/events/deploys", node_number); 181 | CACHE.lock().unwrap().search(&deploys, query).map(Json) 182 | } 183 | 184 | #[get("/users//private_key")] 185 | fn get_private_key(user_id: i32) -> Result, Status> { 186 | let secret_key_path = format!("/home/cctl/cctl/assets/users/user-{}/secret_key.pem", user_id); 187 | 188 | match fs::read_to_string(&secret_key_path) { 189 | Ok(secret_key) => Ok(Json(ActivationResponse { 190 | success: true, 191 | message: secret_key, 192 | })), 193 | Err(error) => { 194 | if error.kind() == std::io::ErrorKind::NotFound { 195 | Err(Status::NotFound) 196 | } else { 197 | Err(Status::InternalServerError) 198 | } 199 | } 200 | } 201 | } 202 | 203 | #[get("/users//public_key")] 204 | fn get_public_key(user_id: i32) -> Result, Status> { 205 | let public_key_path = format!("/home/cctl/cctl/assets/users/user-{}/public_key.pem", user_id); 206 | 207 | match fs::read_to_string(&public_key_path) { 208 | Ok(public_key) => Ok(Json(ActivationResponse { 209 | success: true, 210 | message: public_key, 211 | })), 212 | Err(error) => { 213 | if error.kind() == std::io::ErrorKind::NotFound { 214 | Err(Status::NotFound) 215 | } else { 216 | Err(Status::InternalServerError) 217 | } 218 | } 219 | } 220 | } 221 | 222 | #[get("/node_count")] 223 | fn node_count() -> Json { 224 | let node_count = utils::count_running_nodes(); 225 | Json(ActivationResponse { 226 | success: true, 227 | message: format!("There are {} nodes running", node_count), 228 | }) 229 | } 230 | 231 | 232 | 233 | #[rocket::async_trait] 234 | impl Fairing for CORS { 235 | fn info(&self) -> Info { 236 | Info { 237 | name: "Add CORS headers to responses", 238 | kind: Kind::Response 239 | } 240 | } 241 | 242 | async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { 243 | response.set_header(Header::new("Access-Control-Allow-Origin", "*")); 244 | response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, PATCH, OPTIONS")); 245 | response.set_header(Header::new("Access-Control-Allow-Headers", "*")); 246 | response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); 247 | } 248 | } 249 | 250 | #[launch] 251 | fn rocket() -> _ { 252 | rocket::build() 253 | .attach(CORS) 254 | .mount("/", routes![run, init, status, get_private_key, get_public_key, stop, start, get_events, get_deploys, search_events, search_deploys, node_count]) 255 | .configure(rocket::Config { 256 | address: "0.0.0.0".parse().unwrap(), 257 | port: 3001, 258 | ..rocket::Config::default() 259 | }) 260 | } 261 | -------------------------------------------------------------------------------- /cctl-container/api/src/utils.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::collections::HashMap; 3 | use std::fs; 4 | use std::process::{Command, Output}; 5 | use regex::Regex; 6 | use std::str; 7 | use std::fs::File; 8 | use std::io::Write; 9 | 10 | #[derive(Serialize, Debug)] 11 | pub struct CommandResult { 12 | pub status: String, 13 | pub stdout: String, 14 | pub stderr: String, 15 | } 16 | 17 | pub fn process_output(output: Output) -> Result { 18 | if output.status.success() { 19 | Ok(CommandResult { 20 | status: "success".to_string(), 21 | stdout: String::from_utf8_lossy(&output.stdout).to_string(), 22 | stderr: String::from_utf8_lossy(&output.stderr).to_string(), 23 | }) 24 | } else { 25 | Err(format!( 26 | "Command failed with status: {}\nStdout: {}\nStderr: {}", 27 | output.status, 28 | String::from_utf8_lossy(&output.stdout), 29 | String::from_utf8_lossy(&output.stderr) 30 | )) 31 | } 32 | } 33 | 34 | pub fn run_command(command: &str, args: Option>) -> Result { 35 | let json_file_path = "commands.json"; 36 | let file_contents = fs::read_to_string(json_file_path) 37 | .map_err(|e| format!("Failed to read JSON file: {}", e))?; 38 | let command_map: HashMap = serde_json::from_str(&file_contents) 39 | .map_err(|e| format!("Failed to parse JSON: {}", e))?; 40 | let mut final_command = command_map 41 | .get(command) 42 | .ok_or_else(|| format!("Command not found: {}", command))? 43 | .clone(); 44 | 45 | if let Some(ref args_vec) = args { 46 | final_command = format!("{} {}", final_command, args_vec.join(" ")); 47 | } 48 | 49 | println!("Running command: {}", final_command); 50 | 51 | let output = Command::new("bash") 52 | .arg("-c") 53 | .arg(&final_command) 54 | .output() 55 | .map_err(|e| format!("Failed to execute command: {}", e))?; 56 | 57 | process_output(output) 58 | } 59 | 60 | pub fn parse_node_ports() -> HashMap> { 61 | let command_output = run_command("cctl-infra-node-view-ports", None).unwrap(); 62 | let stdout = command_output.stdout; 63 | 64 | let line_regex = Regex::new(r"node-\d+ -> .*").unwrap(); 65 | let port_regex = Regex::new(r"node-(\d+) -> CONSENSUS @ (\d+) :: RPC @ (\d+) :: REST @ (\d+) :: SSE @ (\d+) :: SPECULATIVE_EXEC @ (\d+)").unwrap(); 66 | 67 | let mut node_service_ports = HashMap::new(); 68 | 69 | for line in stdout.lines() { 70 | if line_regex.is_match(line) { 71 | for cap in port_regex.captures_iter(line) { 72 | let node_name = format!("node-{}", &cap[1]); 73 | let services = vec![ 74 | ("CONSENSUS".to_string(), cap[2].parse::().unwrap()), 75 | ("RPC".to_string(), cap[3].parse::().unwrap()), 76 | ("REST".to_string(), cap[4].parse::().unwrap()), 77 | ("SSE".to_string(), cap[5].parse::().unwrap()), 78 | ]; 79 | node_service_ports.insert(node_name, services.into_iter().collect()); 80 | } 81 | } 82 | } 83 | 84 | 85 | node_service_ports 86 | } 87 | 88 | pub fn generate_nginx_config(node_service_ports: &HashMap>) { 89 | let port = match std::env::var("PROXY_PORT") { 90 | Ok(port) => port, 91 | Err(_) => "80".to_string(), 92 | }; 93 | 94 | let mut config = format!( 95 | "events {{ 96 | worker_connections 1024; 97 | }} 98 | http {{ 99 | server {{ 100 | listen {}; 101 | server_name localhost; 102 | 103 | ", port); 104 | 105 | for (node_name, services) in node_service_ports { 106 | for (service_name_unready, port) in services { 107 | let service_name = service_name_unready.to_lowercase(); 108 | 109 | let location_block = if service_name == "rpc" { 110 | format!(" location /{}/", node_name) 111 | } else { 112 | format!(" location /{}/{}/", node_name, service_name) 113 | }; 114 | 115 | let proxy_pass = format!("proxy_pass http://localhost:{}/;", port); 116 | 117 | let full_location_block = format!("{}{{ 118 | {} 119 | proxy_set_header Host $host; 120 | proxy_set_header X-Real-IP $remote_addr; 121 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 122 | proxy_set_header X-Forwarded-Proto $scheme; 123 | 124 | # Add CORS headers 125 | add_header 'Access-Control-Allow-Origin' '*' always; 126 | add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; 127 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; 128 | 129 | # Handle OPTIONS request 130 | if ($request_method = 'OPTIONS') {{ 131 | add_header 'Access-Control-Allow-Origin' '*'; 132 | add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; 133 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 134 | add_header 'Access-Control-Max-Age' 1728000; 135 | add_header 'Content-Type' 'text/plain; charset=utf-8'; 136 | add_header 'Content-Length' 0; 137 | return 204; 138 | }} 139 | }} 140 | ", location_block, proxy_pass); 141 | 142 | config.push_str(&full_location_block); 143 | } 144 | } 145 | 146 | config.push_str(" }\n}"); 147 | 148 | let mut file = File::create("/etc/nginx/nginx.conf").unwrap(); 149 | file.write_all(config.as_bytes()).unwrap(); 150 | } 151 | 152 | pub fn start_nginx() { 153 | let start_output = Command::new("service") 154 | .arg("nginx") 155 | .arg("start") 156 | .output() 157 | .expect("Failed to start Nginx"); 158 | 159 | if !start_output.status.success() { 160 | eprintln!("Failed to start Nginx: {}", String::from_utf8_lossy(&start_output.stderr)); 161 | } 162 | } 163 | 164 | pub fn count_running_nodes() -> i32 { 165 | let command_output = run_command("cctl-infra-net-status", None).unwrap(); 166 | let stdout = command_output.stdout; 167 | let mut running_nodes = 0; 168 | 169 | for line in stdout.lines() { 170 | if line.contains("RUNNING") { 171 | running_nodes += 1; 172 | } 173 | } 174 | 175 | running_nodes 176 | } 177 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | fondant-cctl-container: 5 | build: 6 | context: ./cctl-container 7 | dockerfile: Dockerfile.dev 8 | args: 9 | NODE_GITBRANCH: release-1.5.6 10 | CLIENT_GITBRANCH: release-2.0.0 11 | ports: 12 | - "3001:3001" 13 | - "80:80" 14 | environment: 15 | - PROXY_PORT=80 16 | networks: 17 | - fondant-network 18 | 19 | fondant-frontend: 20 | build: 21 | context: ./frontend 22 | dockerfile: Dockerfile.dev 23 | ports: 24 | - "3000:3000" 25 | networks: 26 | - fondant-network 27 | volumes: 28 | - ./frontend:/app 29 | - /app/node_modules 30 | 31 | networks: 32 | fondant-network: 33 | driver: bridge 34 | 35 | volumes: 36 | app-volume: 37 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": false, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | WORKDIR /app 3 | COPY . ./ 4 | RUN npm install 5 | COPY . ./ 6 | RUN npm run build 7 | RUN npm install -g serve 8 | COPY . ./ 9 | EXPOSE 3000 10 | CMD ["serve", "-s", "build", "-l", "3000"] 11 | -------------------------------------------------------------------------------- /frontend/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /app 4 | EXPOSE 3000 5 | 6 | CMD npm install && npm run start -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with 2 | [Create React App](https://github.com/facebook/create-react-app). 3 | 4 | ## Available Scripts 5 | 6 | In the project directory, you can run: 7 | 8 | ### `npm start` 9 | 10 | Runs the app in the development mode.
Open 11 | [http://localhost:3000](http://localhost:3000) to view it in the browser. 12 | 13 | The page will reload if you make edits.
You will also see any lint errors 14 | in the console. 15 | 16 | ### `npm test` 17 | 18 | Launches the test runner in the interactive watch mode.
See the section 19 | about 20 | [running tests](https://facebook.github.io/create-react-app/docs/running-tests) 21 | for more information. 22 | 23 | ### `npm run build` 24 | 25 | Builds the app for production to the `build` folder.
It correctly bundles 26 | React in production mode and optimizes the build for the best performance. 27 | 28 | The build is minified and the filenames include the hashes.
Your app is 29 | ready to be deployed! 30 | 31 | See the section about 32 | [deployment](https://facebook.github.io/create-react-app/docs/deployment) for 33 | more information. 34 | 35 | ### `npm run eject` 36 | 37 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 38 | 39 | If you aren’t satisfied with the build tool and configuration choices, you can 40 | `eject` at any time. This command will remove the single build dependency from 41 | your project. 42 | 43 | Instead, it will copy all the configuration files and the transitive 44 | dependencies (webpack, Babel, ESLint, etc) right into your project so you have 45 | full control over them. All of the commands except `eject` will still work, but 46 | they will point to the copied scripts so you can tweak them. At this point 47 | you’re on your own. 48 | 49 | You don’t have to ever use `eject`. The curated feature set is suitable for 50 | small and middle deployments, and you shouldn’t feel obligated to use this 51 | feature. However we understand that this tool wouldn’t be useful if you couldn’t 52 | customize it when you are ready for it. 53 | 54 | ## Learn More 55 | 56 | You can learn more in the 57 | [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 58 | 59 | To learn React, check out the [React documentation](https://reactjs.org/). 60 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chakra-ui/react": "^2.8.1", 7 | "@emotion/react": "^11.11.1", 8 | "@emotion/styled": "^11.11.0", 9 | "@fontsource-variable/inter": "^5.0.8", 10 | "@fontsource-variable/reem-kufi": "^5.0.16", 11 | "@fontsource/poppins": "^5.0.8", 12 | "@testing-library/jest-dom": "^5.17.0", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^14.5.1", 15 | "@types/jest": "^28.1.8", 16 | "@types/node": "^12.20.55", 17 | "@types/react": "^18.2.25", 18 | "@types/react-dom": "^18.2.7", 19 | "axios": "^0.24.0", 20 | "casper-js-sdk": "^2.15.4", 21 | "framer-motion": "^6.5.1", 22 | "hamburger-react": "^2.5.0", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-helmet-async": "^1.3.0", 26 | "react-icons": "^3.11.0", 27 | "react-router-dom": "^6.16.0", 28 | "react-scripts": "5.0.1", 29 | "typescript": "^4.9.5", 30 | "web-vitals": "^2.1.4" 31 | }, 32 | "scripts": { 33 | "start": "react-scripts start", 34 | "build": "react-scripts build", 35 | "test": "react-scripts test", 36 | "eject": "react-scripts eject" 37 | }, 38 | "eslintConfig": { 39 | "extends": "react-app" 40 | }, 41 | "browserslist": { 42 | "production": [ 43 | "chrome >= 67", 44 | "edge >= 79", 45 | "firefox >= 68", 46 | "opera >= 54", 47 | "safari >= 14" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | }, 55 | "devDependencies": { 56 | "prettier": "3.2.5" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/block-bites/fondant-app/b4a6083895fc778ce7fa187b6de94aaec9b2d373/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | Fondant 25 | 26 | 27 | 28 |
29 | 39 | 40 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/block-bites/fondant-app/b4a6083895fc778ce7fa187b6de94aaec9b2d373/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/block-bites/fondant-app/b4a6083895fc778ce7fa187b6de94aaec9b2d373/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { screen } from "@testing-library/react" 3 | import { render } from "./test-utils" 4 | import { App } from "./App" 5 | 6 | test("renders learn react link", () => { 7 | render() 8 | const linkElement = screen.getByText(/learn chakra/i) 9 | expect(linkElement).toBeInTheDocument() 10 | }) 11 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import { SearchProvider } from "./context/SearchContext" 3 | import { NodeProvider } from "./context/NodeContext" 4 | import { HelmetProvider } from "react-helmet-async" 5 | 6 | import { ChakraProvider } from "@chakra-ui/react" 7 | import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from "react-router-dom" 8 | import { fondantTheme } from "./styles/theme" 9 | 10 | import Navbar from "./components/organisms/navbar" 11 | import Accounts from "./components/pages/accounts" 12 | import Blocks from "./components/pages/blocks" 13 | import Logs from "./components/pages/logs" 14 | import Settings from "./components/pages/settings" 15 | import Events from "./components/pages/events" 16 | import Deploys from "./components/pages/deploys" 17 | import { NODE_URL_PORT } from "./constant" 18 | import { 19 | IsNetworkRunningProvider, 20 | useIsNetworkRunningContext, 21 | } from "./context/IsNetworkRunningContext" 22 | import { 23 | IsNetworkLaunchedProvider, 24 | useIsNetworkLaunchedContext, 25 | } from "./context/IsNetworkLaunchedContext" 26 | 27 | export const App = () => { 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | 47 | function AppContent() { 48 | const location = useLocation() 49 | const isSettingsPage = location.pathname === "/settings" 50 | const [screenWidth, setScreenWidth] = useState(0) 51 | const [isLaptop, setIsLaptop] = useState(false) 52 | const [isMobile, setIsMobile] = useState(false) 53 | const { setIsNetworkRunning } = useIsNetworkRunningContext() 54 | const { isNetworkLaunched, setIsNetworkLaunched } = useIsNetworkLaunchedContext() 55 | 56 | useEffect(() => { 57 | setIsLaptop(window.innerWidth >= 768 && window.innerWidth < 1024) 58 | setIsMobile(window.innerWidth < 768) 59 | }, [screenWidth]) 60 | 61 | useEffect(() => { 62 | checkStatus() 63 | function handleResize() { 64 | setScreenWidth(window.innerWidth) 65 | } 66 | window.addEventListener("resize", handleResize) 67 | return () => window.removeEventListener("resize", handleResize) 68 | // eslint-disable-next-line 69 | }, []) 70 | 71 | const checkStatus = async () => { 72 | try { 73 | const response = await fetch(`${NODE_URL_PORT}/status`) 74 | const resJson = await response.json() 75 | if (response.ok) { 76 | if (resJson.message === "") { 77 | console.log("Network status: NOT LAUNCHED") 78 | setIsNetworkLaunched(false) 79 | setIsNetworkRunning(false) 80 | } 81 | if (resJson.message === "running") { 82 | console.log("Network status: RUNNING") 83 | setIsNetworkLaunched(true) 84 | setIsNetworkRunning(true) 85 | } 86 | if (resJson.message === "stopped") { 87 | console.log("Network status: STOPPED") 88 | setIsNetworkLaunched(true) 89 | setIsNetworkRunning(false) 90 | } 91 | } 92 | } catch (error) { 93 | setIsNetworkRunning(false) 94 | setIsNetworkLaunched(false) 95 | console.error("Error fetching system status:", error) 96 | } 97 | } 98 | 99 | return ( 100 | <> 101 | {!isSettingsPage && } 102 | 103 | } /> 104 | } /> 105 | } /> 106 | } /> 107 | } /> 108 | } /> 109 | } /> 110 | 111 | 112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/casper-client.ts: -------------------------------------------------------------------------------- 1 | import { CasperServiceByJsonRPC } from "casper-js-sdk" 2 | import { DEFAULT_NODE_NUMBER, NODE_URL } from "./constant" 3 | 4 | class Client { 5 | nodeNum: number 6 | casperService: CasperServiceByJsonRPC 7 | 8 | constructor(nodeNum: number) { 9 | this.nodeNum = nodeNum 10 | this.casperService = new CasperServiceByJsonRPC(`${NODE_URL}/node-${this.nodeNum}/rpc`) 11 | } 12 | 13 | setClient(nodeNum: number) { 14 | this.nodeNum = nodeNum 15 | this.casperService = new CasperServiceByJsonRPC(`${NODE_URL}/node-${this.nodeNum}/rpc`) 16 | } 17 | } 18 | 19 | export const defaultClient = new Client(DEFAULT_NODE_NUMBER) 20 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/format-json.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const formatJson = (json: any, indent = 0, isFullDisplay = false) => { 4 | return Object.entries(json).map(([key, value], index) => { 5 | if (!isFullDisplay && index >= 3) return null 6 | 7 | let displayValue: React.ReactNode 8 | if (typeof value === "object" && value !== null) { 9 | displayValue = ( 10 |
11 | {formatJson(value, indent + 1, isFullDisplay)} 12 |
13 | ) 14 | } else { 15 | displayValue = typeof value === "string" ? `"${value}"` : JSON.stringify(value) 16 | } 17 | 18 | return ( 19 |
27 | 28 | {key} 29 | 30 | :{displayValue} 31 |
32 | ) 33 | }) 34 | } 35 | 36 | export default formatJson 37 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/settings-alert.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertIcon, AlertTitle, AlertDescription, Flex } from "@chakra-ui/react" 2 | 3 | interface ISettingsAlert { 4 | alertDesc: string 5 | } 6 | 7 | export default function SettingsAlert({ alertDesc }: ISettingsAlert) { 8 | return ( 9 | 10 | 11 | 12 | Alert 13 | {alertDesc} 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/components/atoms/spinner-frame.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Spinner } from "@chakra-ui/react" 2 | 3 | const SpinnerFrame = () => { 4 | return ( 5 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default SpinnerFrame 17 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/account-modal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Modal, 4 | ModalOverlay, 5 | ModalContent, 6 | ModalHeader, 7 | ModalCloseButton, 8 | ModalBody, 9 | ModalFooter, 10 | Box, 11 | Stack, 12 | Center, 13 | Text, 14 | } from "@chakra-ui/react" 15 | 16 | // Updated interface to include publicKey and privateKey 17 | interface IAccountModalProps { 18 | isOpen: boolean 19 | onClose: () => void 20 | publicKey: string 21 | privateKey: string 22 | } 23 | 24 | function AccountModal({ isOpen, onClose, publicKey, privateKey }: IAccountModalProps) { 25 | return ( 26 | 27 | 28 |
29 | 30 | 31 | Account Information 32 | 33 | 34 | 35 | 36 | 37 | 43 | PUBLIC KEY 44 | 45 | 51 | {publicKey} 52 | 53 | 54 | 55 | 61 | PRIVATE KEY 62 | 63 | 69 | {privateKey} 70 | 71 | 72 | 73 | 79 | Do not use this private key on a public blockchain; use it for 80 | development purposes only! 81 | 82 | 83 | 84 | 85 | 86 | 89 | 90 | 91 |
92 |
93 | ) 94 | } 95 | 96 | export default AccountModal 97 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/account-row-element.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { Center, Flex, HStack, Text, VStack } from "@chakra-ui/react" 3 | import { BiKey } from "react-icons/bi" 4 | import AccountModal from "./account-modal" // Assuming this is the modal component 5 | 6 | interface IAccountRowElemProps { 7 | publicKey: string 8 | privateKey: string 9 | } 10 | 11 | const AccountRowElement = ({ publicKey, privateKey }: IAccountRowElemProps) => { 12 | const [isModalOpen, setIsModalOpen] = useState(false) 13 | 14 | const handleOpenModal = () => { 15 | setIsModalOpen(true) 16 | } 17 | 18 | const handleCloseModal = () => { 19 | setIsModalOpen(false) 20 | } 21 | 22 | return ( 23 | <> 24 | 32 | 33 | 34 | Public Key 35 | 36 | 49 | {publicKey} 50 | 51 | 52 | 53 |
63 | 64 |
65 |
66 |
67 | {/* Modal will display the private key */} 68 | 74 | 75 | ) 76 | } 77 | 78 | export default AccountRowElement 79 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/blocks-row-element.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Flex, VStack, Text } from "@chakra-ui/react" 3 | 4 | interface BlockRowElementProps { 5 | height: number 6 | era: number 7 | deploys: number 8 | age: string 9 | blockHash: string 10 | } 11 | 12 | export default function BlockRowElement({ 13 | height, 14 | era, 15 | deploys, 16 | age, 17 | blockHash, 18 | }: BlockRowElementProps) { 19 | const fontSize = { 20 | base: "12px", 21 | sm: "15px", 22 | lg: "15px", 23 | xl: "15px", 24 | "2xl": "15px", 25 | } 26 | 27 | return ( 28 | 37 | 44 | 45 | HEIGHT 46 | 47 | 48 | {height.toString()} 49 | 50 | 51 | 56 | 57 | 62 | 63 | ERA 64 | 65 | 66 | {era.toString()} 67 | 68 | 69 | 70 | 71 | DEPLOYS 72 | 73 | 74 | {deploys.toString()} 75 | 76 | 77 | 78 | 79 | AGE 80 | 81 | 82 | {age} 83 | 84 | 85 | 86 | 92 | 93 | BLOCK HASH 94 | 95 | 110 | {blockHash} 111 | 112 | 113 | 114 | 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/deploy-element.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from "@chakra-ui/react" 2 | import formatJson from "../atoms/format-json" 3 | 4 | interface IDeployElementProps { 5 | onClick: () => void 6 | sameIndexes: boolean 7 | event: any 8 | } 9 | 10 | export default function DeployElement({ onClick, sameIndexes, event }: IDeployElementProps) { 11 | return ( 12 | 13 | 14 | 15 | 16 | {sameIndexes ? formatJson(event, 0, true) : formatJson(event, 0, false)} 17 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/event-element.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from "@chakra-ui/react" 2 | import formatJson from "../atoms/format-json" 3 | 4 | interface IEventElementProps { 5 | onClick: () => void 6 | event: any 7 | sameIndexes: boolean 8 | } 9 | 10 | export default function EventElement({ onClick, event, sameIndexes }: IEventElementProps) { 11 | return ( 12 | 13 | 14 | 15 | 16 | {sameIndexes ? formatJson(event, 0, true) : formatJson(event, 0, false)} 17 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/log-element.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text, Box } from "@chakra-ui/react" 2 | import formatJson from "../atoms/format-json" 3 | 4 | interface ILogElementProps { 5 | onClick: () => void 6 | log: { [key: string]: any } 7 | isLastElement: boolean 8 | sameIndexes: boolean 9 | } 10 | 11 | export default function LogElement({ onClick, log, isLastElement, sameIndexes }: ILogElementProps) { 12 | return ( 13 | 20 | 21 | 22 | 23 | {sameIndexes ? formatJson(log, 0, true) : formatJson(log, 0, false)} 24 | 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/navbar-mobile.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text } from "@chakra-ui/react" 2 | import { FaBell, FaRegFileCode } from "react-icons/fa" 3 | import { BiGridAlt } from "react-icons/bi" 4 | import { MdCloudUpload, MdSupervisorAccount } from "react-icons/md" 5 | import { Link } from "react-router-dom" 6 | 7 | interface INavbarMobProps { 8 | activePath: string 9 | setOpen: React.Dispatch> 10 | } 11 | 12 | const NavbarMobile: React.FC = ({ setOpen, activePath }) => { 13 | return ( 14 | 15 | 25 | setOpen(false)}> 26 | 33 | 34 | Accounts 35 | 36 | 37 | setOpen(false)}> 38 | 45 | 46 | Blocks 47 | 48 | 49 | setOpen(false)}> 50 | 57 | 58 | Deploys 59 | 60 | 61 | setOpen(false)}> 62 | 69 | 70 | Events 71 | 72 | 73 | setOpen(false)}> 74 | 81 | 82 | Logs 83 | 84 | 85 | 86 | 87 | ) 88 | } 89 | 90 | export default NavbarMobile 91 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/navbar-modal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Modal, 3 | ModalOverlay, 4 | ModalContent, 5 | ModalHeader, 6 | ModalCloseButton, 7 | ModalBody, 8 | ModalFooter, 9 | Box, 10 | Stack, 11 | Center, 12 | Text, 13 | Button, 14 | } from "@chakra-ui/react" 15 | 16 | interface INavbarModalProps { 17 | isOpen: boolean 18 | onClose: () => void 19 | handleReset: () => void 20 | } 21 | 22 | export default function NavbarModal({ isOpen, onClose, handleReset }: INavbarModalProps) { 23 | const handleAgreeClick = () => { 24 | handleReset() 25 | onClose() 26 | } 27 | 28 | return ( 29 | 30 | 31 |
32 | 33 | 34 | Warning 35 | 36 | 37 | 38 | 39 | 40 | 46 | Are you sure you want to reset the network? 47 | 48 | 49 | 50 | 56 | This will remove all your deploys! 57 | 58 | 59 | 60 | 61 | 62 | 65 | 68 | 69 | 70 |
71 |
72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /frontend/src/components/molecules/navbar-subbar.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, HStack, Select, Spinner, Text } from "@chakra-ui/react" 2 | import { NUM_OF_NODES_CONSIDERED_RUNNING, NODE_URL_PORT } from "../../constant" 3 | import { useNodeContext } from "../../context/NodeContext" 4 | import React, { useEffect, useState } from "react" 5 | import { defaultClient } from "../../casper-client" 6 | import { GetStatusResult } from "casper-js-sdk" 7 | import { useIsNetworkRunningContext } from "../../context/IsNetworkRunningContext" 8 | import { useIsNetworkLaunchedContext } from "../../context/IsNetworkLaunchedContext" 9 | 10 | interface NavbarSubbarProps { 11 | isMobile: boolean 12 | } 13 | 14 | const NavbarSubbar: React.FC = ({ isMobile }) => { 15 | const { isNetworkRunning, setIsNetworkRunning } = useIsNetworkRunningContext() 16 | const { isNetworkLaunched, setIsNetworkLaunched } = useIsNetworkLaunchedContext() 17 | const { nodeNumber, setNodeNumber } = useNodeContext() 18 | const [currentBlock, setCurrentBlock] = useState(0) 19 | const [uptime, setUptime] = useState("") 20 | const [isLaunching, setIsLaunching] = useState(false) 21 | const [isNetworkStarting, setIsNetworkStarting] = useState(false) 22 | const [isNetworkStopping, setIsNetworkStopping] = useState(false) 23 | 24 | useEffect(() => { 25 | if (isNetworkRunning) { 26 | fetchUptime() 27 | } 28 | const intervalId = setInterval(() => { 29 | if (isNetworkRunning) { 30 | fetchUptime() 31 | } 32 | }, 1000) // 1 seconds interval 33 | return () => clearInterval(intervalId) 34 | }, [isNetworkRunning]) 35 | 36 | // Need to optimize to not fetch every 1sec 37 | const fetchUptime = async () => { 38 | try { 39 | const response: GetStatusResult = await defaultClient.casperService.getStatus() 40 | setUptime(response.uptime.split(" ").slice(0, -1).join(" ")) 41 | } catch (error) { 42 | // Handle network errors here 43 | console.error("Network error:", error) 44 | } 45 | } 46 | 47 | useEffect(() => { 48 | if (isNetworkRunning) { 49 | fetchLatestBlock() 50 | } 51 | const intervalId = setInterval(() => { 52 | if (isNetworkRunning) { 53 | fetchLatestBlock() 54 | } 55 | }, 5 * 1000) // 10 seconds interval 56 | return () => clearInterval(intervalId) 57 | }, [isNetworkRunning]) 58 | 59 | //Launching network 60 | const handleLaunch = async () => { 61 | setIsLaunching(true) 62 | try { 63 | const response = await fetch(`${NODE_URL_PORT}/init`, { 64 | method: "POST", 65 | }) 66 | if (response.ok) { 67 | setIsNetworkLaunched(true) 68 | setIsNetworkRunning(true) 69 | setIsLaunching(false) 70 | console.log("Launched successfully") 71 | } else { 72 | console.error("Server error:", response.status) 73 | // Handle server errors here 74 | } 75 | } catch (error) { 76 | console.error("Network error:", error) 77 | // Handle network errors here 78 | } 79 | } 80 | 81 | //Start network 82 | const handleStart = async () => { 83 | setIsNetworkStarting(true) 84 | try { 85 | const response = await fetch(`${NODE_URL_PORT}/start`, { 86 | method: "POST", 87 | }) 88 | if (response.ok) { 89 | setIsNetworkRunning(true) 90 | setIsNetworkStarting(false) 91 | console.log("System started successfully") 92 | } 93 | } catch (error) { 94 | console.error("Error starting the system:", error) 95 | } 96 | } 97 | 98 | //Stop network 99 | const handleStop = async () => { 100 | setIsNetworkStopping(true) 101 | try { 102 | const response = await fetch(`${NODE_URL_PORT}/stop`, { 103 | method: "POST", 104 | }) 105 | if (response.ok) { 106 | setIsNetworkRunning(false) 107 | setIsNetworkStopping(false) 108 | console.log("System stopped successfully") 109 | } 110 | } catch (error) { 111 | console.error("Error stopping the system:", error) 112 | } 113 | } 114 | 115 | //Get block height 116 | const fetchLatestBlock = async () => { 117 | try { 118 | const latestBlockInfo = await defaultClient.casperService.getLatestBlockInfo() 119 | if (latestBlockInfo && latestBlockInfo.block) { 120 | setCurrentBlock(latestBlockInfo.block.header.height) 121 | } 122 | } catch (error) { 123 | console.error("Error fetching latest block info:", error) 124 | } 125 | } 126 | 127 | //Dropdown nodes to choose 128 | const nodeOptions = [] 129 | for (let i = 1; i <= NUM_OF_NODES_CONSIDERED_RUNNING; i++) { 130 | nodeOptions.push( 131 | 134 | ) 135 | } 136 | 137 | // Uptime displaying 138 | const uptimeDisplayingMode = () => { 139 | if (isLaunching || isNetworkStarting) { 140 | return "Loading..." 141 | } else if ((isNetworkLaunched && !isNetworkRunning) || !isNetworkLaunched) { 142 | return "---" 143 | } else { 144 | return uptime 145 | } 146 | } 147 | 148 | return ( 149 | 159 | 165 | 166 | 173 | 174 | {isMobile ? "CURR." : "CURRENT"} BLOCK 175 | 176 | 177 | {currentBlock} 178 | 179 | 180 | 185 | 186 | UPTIME 187 | 188 | 189 | {uptimeDisplayingMode()} 190 | 191 | 192 | 193 | 194 | 195 | {/* Conditionally render Start/Stop buttons based on isNetworkRunning */} 196 | {isNetworkLaunched ? null : ( 197 | 213 | )} 214 | {!isNetworkRunning && isNetworkLaunched ? ( 215 | 231 | ) : null} 232 | {isNetworkRunning && isNetworkLaunched ? ( 233 | 249 | ) : null} 250 | 263 | 264 | 265 | 266 | ) 267 | } 268 | 269 | export default NavbarSubbar 270 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { useLocation, Location } from "react-router-dom" 3 | import { Link } from "react-router-dom" 4 | import Hamburger from "hamburger-react" 5 | import { HStack, Tab, Tabs, TabList, Flex, Icon, Text, Image } from "@chakra-ui/react" 6 | import { FaBell, FaRegFileCode } from "react-icons/fa" 7 | import { BiGridAlt } from "react-icons/bi" 8 | import { MdCloudUpload, MdSupervisorAccount } from "react-icons/md" 9 | import NavbarMobile from "../molecules/navbar-mobile" 10 | 11 | import Logo from "../../assets/logo.svg" 12 | import NavbarSubbar from "../molecules/navbar-subbar" 13 | import { useIsNetworkLaunchedContext } from "../../context/IsNetworkLaunchedContext" 14 | 15 | interface NavbarProps { 16 | isLaptop: boolean 17 | isMobile: boolean 18 | } 19 | 20 | const Navbar: React.FC = ({ isLaptop, isMobile }) => { 21 | const location: Location = useLocation() 22 | const [activePath, setActivePath] = useState(location.pathname) 23 | const [open, setOpen] = useState(false) 24 | const { isNetworkLaunched } = useIsNetworkLaunchedContext() 25 | 26 | useEffect(() => { 27 | setActivePath(location.pathname) 28 | }, [location]) 29 | 30 | useEffect(() => { 31 | if (open) { 32 | setOpen(false) 33 | } 34 | // eslint-disable-next-line 35 | }, [isMobile]) 36 | 37 | return ( 38 | 39 | 46 | 52 | 53 | 54 | 55 | 56 | Fondant 57 | 58 | 59 | 60 | 61 | {isMobile ? ( 62 | 63 | 66 | isNetworkLaunched ? setOpen((prev) => !prev) : null 67 | } 68 | size={35} 69 | color={isNetworkLaunched ? "white" : "grey"} 70 | rounded 71 | /> 72 | {open ? ( 73 | 74 | ) : null} 75 | 76 | ) : ( 77 | 81 | 82 | 92 | 93 | Accounts 94 | 95 | 96 | 97 | 109 | 110 | Blocks 111 | 112 | 113 | 114 | 128 | 129 | Deploys 130 | 131 | 132 | 133 | 145 | 146 | Events 147 | 148 | 149 | 150 | 160 | 161 | Logs 162 | 163 | 164 | 165 | )} 166 | 167 | 168 | 169 | 170 | 171 | ) 172 | } 173 | 174 | export default Navbar 175 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-about.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text } from "@chakra-ui/react" 2 | 3 | export default function AboutTab() { 4 | return ( 5 | 6 | 7 | About 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-account.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text } from "@chakra-ui/react" 2 | 3 | export default function AccountTab() { 4 | return ( 5 | 6 | 7 | Account 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-advanced.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text } from "@chakra-ui/react" 2 | 3 | export default function AbvancedTab() { 4 | return ( 5 | 6 | 7 | Advanced 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-chain.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text } from "@chakra-ui/react" 2 | 3 | export default function ChainTab() { 4 | return ( 5 | 6 | 7 | Chain 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-gas.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Input, Text, Select } from "@chakra-ui/react" 2 | import SettingsAlert from "../atoms/settings-alert" 3 | 4 | export default function GasTab() { 5 | return ( 6 | 7 | 8 | 14 | Gas 15 | 16 | 17 | 18 | 19 | The price of each unit of gas, in WEI. Leave blank for default. 20 | 21 | 22 | 23 | 24 | Hardfork 25 | 26 | 27 | 32 | 33 | The price of each unit of gas, in WEI. Leave blank for default. 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-logging.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text, Switch } from "@chakra-ui/react" 2 | 3 | export default function LoggingTab() { 4 | return ( 5 | 6 | 7 | Logging 8 | 9 | 10 | 11 | 12 | 13 | Output Logs to file 14 | 15 | 16 | 17 | 18 | Save Logs to file 19 | 20 | 21 | 22 | 23 | 24 | Verbose Logs 25 | 26 | 27 | 28 | 29 | Increase the log output 30 | 31 | 32 | 33 | 34 | 35 | Analytics 36 | 37 | 38 | 39 | 40 | Google Analytics 41 | 42 | 43 | 44 | 45 | We use Google Analytics to track Fondant usage. This information tracking 15 46 | anymous. get o not track chsons ty da ntifiable information, account data or 47 | private keys. Note: This setting is global and will persist between 48 | workspaces. 49 | 50 | 51 | 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-server.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text, Select, Input, Switch, Divider } from "@chakra-ui/react" 2 | 3 | export default function ServerTab() { 4 | return ( 5 | 6 | 7 | Server 8 | 9 | 10 | 11 | 12 | 17 | 18 | The server will accept RPC connections on the following host and port. 19 | 20 | 21 | 30 | 39 | 40 | 41 | 42 | 43 | 50 | Automine 51 | 52 | 53 | 54 | 55 | Process transactions instantaneously. 56 | 57 | 58 | 59 | 60 | 67 | Error on transaction failure 68 | 69 | 70 | 71 | 72 | failures wact only all detectable era the statubled, a in action transaction 73 | receipt. Disabling this feature will make Ganache handle transaction 74 | failures like other Ethereum clients. 75 | 76 | 77 | 78 | 79 | 85 | Chain Forking 86 | 87 | 94 | Forking is Disabled 95 | 96 | 97 | 98 | 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /frontend/src/components/organisms/tab-workspace.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Flex, Input, Text, Textarea } from "@chakra-ui/react" 2 | 3 | export default function WorkspaceTab() { 4 | return ( 5 | 6 | 7 | Workspace 8 | 9 | 19 | 20 | A friendly name for this workspace. 21 | 22 |