From bdc5cd79be7a3d1d0130bb2471cd787503b93731 Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Tue, 13 Aug 2024 16:38:16 +0200 Subject: [PATCH] docs(ledger-browser): add initial GUI documentation and fix some bugs - Add new documentation section to project docs. New pages can be found under `Cactus` -> `Ledger Browser` category. Documentation include GUI setup instructions, application overviews, developer guide and tutorial on developing new app plugin for the GUI. - Do some minor quality of life improvements and bug fixes. - Add missing GUI database schema file. - Remove dead code from GUI that still used `getAppList`. - Add documentation links to the GUI. For now, most links are empty and they will be set once this PR is merged and the docs URLs are confirmed. - Add sample tutorial application (the same that is created step-by-step in the tutorial in the documentation) - Improve plugin app URL regex to allow more valid paths. - Expose ethereum and fabric schemas in supabase-all-in-one - Extend persistence plugin init SQL with code for exposing custom schemas. - Improve supabase-all-in-one readme documentation. - Fix persistence sample setup scripts ports so that both scripts can be run at the same time. Depends on #3448 Depends on #3449 Signed-off-by: Michal Bajer --- .../developer-guide/architecture.md | 143 ++++++++ ...ture_cacti_ledger_browser_architecture.png | Bin 0 -> 41856 bytes .../developer-guide/overview.md | 18 + .../developer-guide/tutorial.md | 329 ++++++++++++++++++ .../images/supabase-credentials.png | Bin 0 -> 70637 bytes docs/docs/cactus/ledger-browser/overview.md | 13 + .../plugin-apps/app-patterns.md | 14 + .../plugin-apps/ethereum-browser.md | 95 +++++ .../plugin-apps/fabric-browser.md | 92 +++++ .../images/sample_arch_persistence_app.png | Bin 0 -> 32667 bytes docs/docs/cactus/ledger-browser/setup.md | 175 ++++++++++ docs/mkdocs.yml | 11 + packages/cacti-ledger-browser/package.json | 14 +- .../src/main/sql/schema.sql | 52 +++ .../main/typescript/CactiLedgerBrowserApp.tsx | 29 +- .../ERC20BalanceHistoryChart.tsx | 2 +- .../src/main/typescript/apps/eth/index.tsx | 4 +- .../src/main/typescript/apps/fabric/index.tsx | 4 +- .../typescript/apps/tutorial-app/hooks.tsx | 9 + .../typescript/apps/tutorial-app/index.tsx | 72 ++++ .../apps/tutorial-app/pages/DataFetch.tsx | 44 +++ .../apps/tutorial-app/pages/Home.tsx | 13 + .../src/main/typescript/common/config.tsx | 2 + .../src/main/typescript/common/types/app.ts | 87 +++++ .../src/main/typescript/common/utils.ts | 18 + .../components/AppSetupForms/AppSetupForm.tsx | 5 +- .../components/Layout/HeaderBar.tsx | 29 +- .../pages/add-new-app/SelectAppView.tsx | 31 +- packages/cacti-ledger-browser/tsconfig.json | 1 + .../src/main/sql/schema.sql | 8 + .../typescript/manual/common-setup-methods.ts | 2 + .../manual/complete-sample-scenario.ts | 2 +- .../src/main/sql/schema.sql | 8 + .../typescript/manual/common-setup-methods.ts | 2 + .../manual/complete-sample-scenario.ts | 2 +- .../test/typescript/manual/sample-setup.ts | 2 +- tools/docker/supabase-all-in-one/Dockerfile | 1 + tools/docker/supabase-all-in-one/README.md | 38 +- yarn.lock | 1 + 39 files changed, 1310 insertions(+), 62 deletions(-) create mode 100644 docs/docs/cactus/ledger-browser/developer-guide/architecture.md create mode 100644 docs/docs/cactus/ledger-browser/developer-guide/images/architecture_cacti_ledger_browser_architecture.png create mode 100644 docs/docs/cactus/ledger-browser/developer-guide/overview.md create mode 100644 docs/docs/cactus/ledger-browser/developer-guide/tutorial.md create mode 100644 docs/docs/cactus/ledger-browser/images/supabase-credentials.png create mode 100644 docs/docs/cactus/ledger-browser/overview.md create mode 100644 docs/docs/cactus/ledger-browser/plugin-apps/app-patterns.md create mode 100644 docs/docs/cactus/ledger-browser/plugin-apps/ethereum-browser.md create mode 100644 docs/docs/cactus/ledger-browser/plugin-apps/fabric-browser.md create mode 100644 docs/docs/cactus/ledger-browser/plugin-apps/images/sample_arch_persistence_app.png create mode 100644 docs/docs/cactus/ledger-browser/setup.md create mode 100644 packages/cacti-ledger-browser/src/main/sql/schema.sql create mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/hooks.tsx create mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/index.tsx create mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/DataFetch.tsx create mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/Home.tsx diff --git a/docs/docs/cactus/ledger-browser/developer-guide/architecture.md b/docs/docs/cactus/ledger-browser/developer-guide/architecture.md new file mode 100644 index 0000000000..740c1a982d --- /dev/null +++ b/docs/docs/cactus/ledger-browser/developer-guide/architecture.md @@ -0,0 +1,143 @@ +# Architecture + +## Components + +### AppDefinition + +Each application must define an `AppDefinition`. This includes essential information like the app's name, category, and default settings shown to the user during app creation (such as description, path, and custom options). The most critical part of the `AppDefinition` is the `createAppInstance` factory method. This method accepts a `GuiAppConfig` (which contains dynamic app settings configured by the user and stored in a database) and returns an `AppInstance` object. + +#### Interface + +```typescript +interface AppDefinition { + /** + * Application name as shown to the user + */ + appName: string; + + /** + * Application category, the user can filter using it. + * If there's no matching category for your app consider adding a new one! + */ + category: string; + + /** + * Full URL to a setup guide, it will be displayed to the user on app configuration page. + */ + appSetupGuideURL?: string; + + /** + * Full URL to app documentation page + */ + appDocumentationURL?: string; + + /** + * Default value for instance name that user can set to uniquely identify this ap instance. + */ + defaultInstanceName: string; + + /** + * Default value for app description. + */ + defaultDescription: string; + + /** + * Default path under which app routes will be mounted (must be path with `/`, like `/eth`) + */ + defaultPath: string; + + /** + * Default custom, app-specific options in JSON format. This will change between applications. + */ + defaultOptions: unknown; + + /** + * Factory method for creating application instance object from configuration stored in a database. + */ + createAppInstance: CreateAppInstanceFactoryType; +} +``` + +### AppInstance + +An `AppInstance` represents the runtime configuration of an app. It holds all the details required by `Ledger Browser` to render the app correctly. Key components include `menuEntries` (links displayed in the top header bar) and `routes` that adhere to [react-router-dom](https://reactrouter.com/en/main) specifications. + +```typescript +interface AppInstance { + /** + * Unique database ID of this app instance. + */ + id: string; + + /** + * Name of the application (can be same as appName in app definition) + */ + appName: string; + + /** + * Instance name (set by the user) + */ + instanceName: string; + + /** + * Instance description (set by the user) + */ + description: string | undefined; + + /** + * Path under which app routes will be mounted (must be path with `/`, like `/eth`) + */ + path: string; + + /** + * Custom, app-specific options in JSON format. This will change between applications. + */ + options: T; + + /** + * List on titles and URL of menu entries to be added to the top bar (used to navigate within an app) + */ + menuEntries: AppInstanceMenuEntry[]; + + /** + * `react-router-dom` compatible list of this application routes. + */ + routes: RouteObject[]; + + /** + * Method for retriving application status details. + */ + useAppStatus: () => GetStatusResponse; + + /** + * Status component showed when user opens a app status pop up window. + */ + StatusComponent: React.ReactElement; + + /** + * Full URL to a setup guide, it will be displayed to the user on app configuration page. + */ + appSetupGuideURL?: string; + + /** + * Full URL to app documentation page + */ + appDocumentationURL?: string; +} +``` + +### AppConfig + +To make an application available to the `Ledger Browser`, it must be added to the main `AppConfig` mapping (located in `common/config.tsx`). When creating a new app, ensure it is included here, and assign it a unique ID within the map. + +### ConfiguredApps (GuiAppConfig) + +`ConfiguredApps` refers to the dynamic settings for apps as configured by the user and stored in a database. This data is maintained in the `public.gui_app_config` table within the GUI's PostgreSQL instance. + +### AppInstance List + +During startup, the application reads dynamic app configurations from the database (`GuiAppConfig`), retrieves the corresponding `AppDefinition` from the main `AppConfig`, and uses the `createAppInstance` method to generate the final `AppInstance`. This instance is then added to a list, with its routes mounted under the specified path. + +## Diagram + +![Ledger Browser Architecture Diagram](images/architecture_cacti_ledger_browser_architecture.png) \ No newline at end of file diff --git a/docs/docs/cactus/ledger-browser/developer-guide/images/architecture_cacti_ledger_browser_architecture.png b/docs/docs/cactus/ledger-browser/developer-guide/images/architecture_cacti_ledger_browser_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..46b157212e535d645be28d9d6fcfb9678addec4c GIT binary patch literal 41856 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 z�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 { + return { + isPending: false, + isInitialized: true, + status: { + severity: "success", + message: "Mocked response!", + }, + }; + }, + StatusComponent: ( + + Everything is OK (we hope) + + ), + appSetupGuideURL: myTutorialAppDefinition.appSetupGuideURL, + appDocumentationURL: myTutorialAppDefinition.appDocumentationURL, + }; + }, +}; + +export default myTutorialAppDefinition; +``` + +Add the newly created application to the main `AppConfig` mapping. + +```shell +code ../../common/config.tsx +``` + +```typescript +import myTutorialAppDefinition from "../apps/my-tutorial-app"; +import { AppDefinition } from "./types/app"; + +const config = new Map([ + // ... + + // Add new application + ["myTutorialApplication", myTutorialAppDefinition], +]); + +export default config; +``` + +(Re)start the `Ledger Browser` application and add the newly created app by clicking the `Add Application` card on the main page. Select the `Sample App` category and choose `My Tutorial App`. On the `App Specific Setup` page, enter your name and click `Save`. The application configuration should be stored in the database, and the new application should appear on the main page. Clicking the `Status` button should display the hardcoded response defined in `StatusComponent`, and opening the app should navigate to [http://localhost:3001/my-tutorial](http://localhost:3001/my-tutorial), where nothing will be shown since no pages have been created yet. + +## Add the Home page + +Now it's time to add an actual page to our application. Our Home page will display a simple "Hello World" greeting using the name provided in the app configuration. To begin, create a new page component. Note that we're using the `PageTitle` component from the common UI components to maintain visual consistency with the rest of the application. + +```shell +mkdir pages +code pages/Home.tsx +``` + +```typescript +import Box from "@mui/material/Box"; +import PageTitle from "../../../components/ui/PageTitle"; + +export default function Home() { + return ( + + Hello World! + + ); +} +``` + +Next, we will add our Home page to the `AppDefinition` by including it in the routes and menu entries. + +```typescript +// ... +import Home from "./pages/Home"; + + +const myTutorialAppDefinition: AppDefinition = { + // ... + + createAppInstance(app: GuiAppConfig) { + // ... + return { + // ... + menuEntries: [ + { + title: "Home", + url: "/", + }, + ], + routes: [ + { + element: , + }, + ], + // ... + }; + }, +}; +``` + +Navigating to [http://localhost:3001/my-tutorial](http://localhost:3001/my-tutorial) should now display our "Hello World" message! To show a more personalized greeting, we need to access the custom app options stored in the app's React Router outer context. To simplify the retrieval of these options, we'll create a custom hook. + +```shell +code hooks.tsx +``` + +```typescript +import { useOutletContext } from "react-router-dom"; + +type TutorialOptionsType = { + name: string; +}; + +export function useAppOptions() { + return useOutletContext(); +} +``` + +Now we can use our custom hook to retrieve the name and display it to the user. + +```typescript +import Box from "@mui/material/Box"; +import PageTitle from "../../../components/ui/PageTitle"; +import { useAppOptions } from "../hooks"; + +export default function Home() { + const appOptions = useAppOptions(); + + return ( + + Hello {appOptions.name}! + + ); +} +``` + +The home page should now display a personalized greeting. + +## Data fetching and displaying notifications + +To illustrate how to fetch data from an external server, we'll create another page. We will use [restful-api.dev](https://restful-api.dev/) as a dummy backend for our requests. Begin by creating a new page called `DataFetch`. + +```shell +code pages/DataFetch.tsx +``` + +```typescript +import Box from "@mui/material/Box"; +import PageTitle from "../../../components/ui/PageTitle"; + +export default function DataFetch() { + return ( + + Data Fetch Sample + + ); +} +``` + +Next, add the new `DataFetch` page to the `AppDefinition`. + +```typescript +// ... +import DataFetch from "./pages/DataFetch"; + + +const myTutorialAppDefinition: AppDefinition = { + // ... + + createAppInstance(app: GuiAppConfig) { + // ... + return { + // ... + menuEntries: [ + { + title: "Home", + url: "/", + }, + { + title: "Data Fetch", + url: "/data-fetch", + }, + ], + routes: [ + { + element: , + }, + { + path: "data-fetch", + element: , + }, + ], + // ... + }; + }, +}; +``` + +Ensure that the header bar now includes a link to "Data Fetch" and that it correctly navigates to the newly created page at [http://localhost:3001/my-tutorial/data-fetch](http://localhost:3001/my-tutorial/data-fetch). Next, we'll use [react-query](https://tanstack.com/query/v3) to fetch data from the test REST server and display it on the page. + +```typescript +import axios from "axios"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import PageTitle from "../../../components/ui/PageTitle"; +import { useQuery } from "@tanstack/react-query"; + +/** + * Simple method for fetching test data from restful-api.dev + */ +async function fetchSampleData() { + const response = await axios.get("https://api.restful-api.dev/objects/7"); + return response.data; +} + +export default function DataFetch() { + const { data } = useQuery({ + queryKey: ["sampleFetch"], + queryFn: fetchSampleData, + }); + + return ( + + Data Fetch Sample + + + Fetched object: {data?.name ?? ""} + + + ); +} +``` + +You should see some object name displayed after the `Fetched object` text. Next, we'll display a success notification when our query finishes using the `useEffect` React hook. + +```typescript +import React from "react"; +import { useNotification } from "../../../common/context/NotificationContext"; +// ... + +export default function DataFetch() { + const { showNotification } = useNotification(); + const { data, isPending } = useQuery({ + queryKey: ["sampleFetch"], + queryFn: fetchSampleData, + }); + + React.useEffect(() => { + !isPending && + data && + showNotification(`Fetched data: ${data.name}`, "success"); + }, [data, isPending]); + + // ... +} +``` + +Try refreshing the page, and you should see a green notification in the bottom left corner with the fetched data name. Lastly, we'll handle query failures by displaying an error notification in such cases. + +```typescript +const { data, isPending, isError, error } = useQuery({ + queryKey: ["sampleFetch"], + queryFn: fetchSampleData, +}); + +React.useEffect(() => { + isError && showNotification(`Could not fetch sample data: ${error}`, "error"); +}, [isError]); +``` + +This concludes the tutorial. You should now have a basic understanding of how to add a new application to the Ledger Browser. If you have any questions, please reach out for support on the Hyperledger Discord Cacti channel. The final tutorial application can be found in `apps/tutorial-app`. + +## Future improvements + +- Extend the Data Fetch page with sub-pages to demonstrate how to use child routes. +- Implement a simple status hook and component. diff --git a/docs/docs/cactus/ledger-browser/images/supabase-credentials.png b/docs/docs/cactus/ledger-browser/images/supabase-credentials.png new file mode 100644 index 0000000000000000000000000000000000000000..4582aa041fe3e4f26228cfc82a9bd1adb89a8c91 GIT binary patch literal 70637 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

8SW}w(K^o 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¨1wRIYQL|nmcZ?n)2D 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 literal 0 HcmV?d00001 diff --git a/docs/docs/cactus/ledger-browser/setup.md b/docs/docs/cactus/ledger-browser/setup.md new file mode 100644 index 0000000000..0f8729909a --- /dev/null +++ b/docs/docs/cactus/ledger-browser/setup.md @@ -0,0 +1,175 @@ +# Setup + +## Clone the repository + +Clone the [cacti git repository](https://github.com/hyperledger/cacti) on your local machine. Follow these instructions to get a copy of the project up and running for development and testing purposes. + +```sh +git clone https://github.com/hyperledger/cacti.git +cd cacti/packages/cacti-ledger-browser/ +``` + +## App Prerequisites + +### Install Node + +NodeJS v18 or later is required (we recommend using the Node Version Manager (nvm) if available for your OS). + +```sh +nvm install 18 +nvm use 18 +``` + +### Install Yarn + +Run the following command from within the project directory: + +```sh +npm run enable-corepack +``` + +### Install Docker + +Ensure Docker Engine is installed and running. You can verify this by running `docker ps -aq`. Follow the installation instructions for Docker [here](https://docs.docker.com/engine/install/ubuntu/). + +### Install required packages + +You can run the following command from either the root of the project or the `cacti-ledger-browser` folder: + +```sh +yarn install +``` + +## Setup Supabase + +### Create Instance + +You can start a local instance of Supabase to be used as the GUI backend using our Cacti `supabase-all-in-one` image. Alternatively, follow the [Supabase Self-Hosting guide](https://supabase.com/docs/guides/self-hosting/docker). + +#### Using Self-Hosting + +1. Open a new console window in the root of the Cacti project. +2. Build the `cactus-supabase-all-in-one` image: + + ```sh + docker build ./tools/docker/supabase-all-in-one -t cactus-supabase-all-in-one + ``` + +3. Run the container (detached): + + ```sh + docker run --name supabase_all_in_one_gui \ + --detach \ + --privileged \ + -p 8000:8000 \ + -p 5432:5432 \ + cactus-supabase-all-in-one + ``` + +4. Copy and save the `SERVICE_ROLE_KEY` (our `API Key`) from the Supabase environment, it will be needed later: + + ```sh + docker exec -ti supabase_all_in_one_gui cat /home/supabase/docker/.env | grep SERVICE_ROLE_KEY + ``` + +5. Open the Supabase dashboard by navigating to http://localhost:8000/ +6. Use the following credentials to log in: + + - **Username**: `supabase` + - **Password**: `this_password_is_insecure_and_should_be_updated` + +#### Using Supabase Cloud + +Supabase provides a free tier that can be used for development and small projects. + +1. Open the dashboard https://supabase.com/dashboard/projects +1. You'll be asked to log in or sign up if you haven't done that already. You can also use your GitHub account to quickly create the account. +1. After a successful login, you'll see a list of your projects (possibly empty). Click the `New project` button on the top of the page to create a new project. Select a default organization (or create a new one if needed). +1. Set any project name and secure database password (remember or save it, it will be needed when setting up some plugin apps). Select a region close to your location. +1. Wait for the project to set up. +1. You can see connection details on the new project home page. Save the `Project URL` and `API Key`, they will be used later on. + +![Supabase Cloud credentials window](images/supabase-credentials.png) + +### Setup DB Schema + +1. Navigate to `SQL Editor` using the navigation panel on the left. Copy the content of `packages/cacti-ledger-browser/src/main/sql/schema.sql` into the editor and run the query (`Run` button or `Ctrl + Enter`). +1. Navigate to `Table Editor` and verify that tables were created in the `public` schema. + +Alternatively, you can use the `psql` CLI tool: + +```shell +psql "__CONNECTION_STRING_TO_DB__" -f packages/cacti-ledger-browser/src/main/sql/schema.sql +``` + +## Start the application + +### Set the environment variables + +1. Copy `packages/cacti-ledger-browser/.env.template` to `packages/cacti-ledger-browser/.env`. +2. Edit the newly created `.env` file (`vim packages/cacti-ledger-browser/.env` or similar). +3. Set `VITE_SUPABASE_URL` to: + - **Self Hosting**: `http://localhost:8000`. + - **Supabase Cloud**: `Project URL` from the config page. +4. Set `VITE_SUPABASE_KEY` to: + - **Self Hosting**: `SERVICE_ROLE_KEY` from `.env` file withing the container. + - **Supabase Cloud**: `API Key` from the config page. +5. Leave `VITE_SUPABASE_SCHEMA` as `public`. + +If there are any connection errors to Supabase when running the app, double-check if the data in this file was filled correctly! + +### Development build + +```sh +yarn run start +``` + +The server will run on http://localhost:3001/ + +### Production build + +```sh +yarn run build +yarn run serve +``` + +The server will run on http://localhost:4173/ + +## Use application + +By default, there will be no configured apps. Click the `Add Application` action card to open the wizard that will guide you through the process. + +### Adding Applications + +#### Select Group + +All (pluggable) applications are divided into groups for better organization. + +#### Select Application + +Select the application you want to add. Use the `Setup Guide` button to the right of a given app to open its documentation in a separate card. If there's no documentation for a given plugin, the button is grayed out. + +#### Common Setup + +These are fields common for all plugin apps. + +- **Instance Name**: Used to uniquely identify the given application. +- **Description**: Provides more context to the app user. +- **Path**: Path under which the application routes will be mounted. Example: an app with path `/eth` will be available under `http://localhost:3001/eth/`. **Must be unique!** + +#### App Specific Setup + +- Each app may require its own configuration (e.g., access to the database where data is stored, authentication keys, Cacti connector endpoint, etc.). +- These options can be configured using JSON format. +- Each app can define its own custom format, so this will vary from app to app. Consult the application documentation for more details. + +### App Dashboard + +On the main page, you can see the list of currently configured applications. Each app has two health indicators: + +- **Initialized**: Indicates whether the plugin and all infrastructure required by it were created successfully. +- **Status**: Indicates if all components are running. + +You can open the detailed status page by clicking the `Status` button on the app card (this may vary between different plugin apps). You can open the settings page by clicking on the `Configure` button. Clicking on the card itself will navigate to the plugin application. + +At any time, you can navigate back to this page by clicking the app grid icon on the far left of the top navigation bar. You can open the documentation for a given app by clicking the far right question mark button on the navigation bar. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b365ccd1b2..0cf4f603d3 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -114,6 +114,17 @@ nav: - Test Api Client: cactus/packages/cactus-test-api-client.md - Test CMD Api Server: cactus/packages/cactus-test-cmd-api-server.md - Test Tooling: cactus/packages/cactus-test-tooling.md + - Ledger Browser: + - Overview: cactus/ledger-browser/overview.md + - Setup: cactus/ledger-browser/setup.md + - Plugin Apps: + - App Patterns: cactus/ledger-browser/plugin-apps/app-patterns.md + - Ethereum Browser: cactus/ledger-browser/plugin-apps/ethereum-browser.md + - Hyperledger Fabric Browser: cactus/ledger-browser/plugin-apps/fabric-browser.md + - Developer Guide: + - Overview: cactus/ledger-browser/developer-guide/overview.md + - Architecture: cactus/ledger-browser/developer-guide/architecture.md + - Tutorial: cactus/ledger-browser/developer-guide/tutorial.md - Ledger Support: - Overview: cactus/support.md - Hyperledger Besu: cactus/support/besu.md diff --git a/packages/cacti-ledger-browser/package.json b/packages/cacti-ledger-browser/package.json index 5c1a6999ae..57ea068267 100644 --- a/packages/cacti-ledger-browser/package.json +++ b/packages/cacti-ledger-browser/package.json @@ -1,6 +1,7 @@ { "name": "@hyperledger/cacti-ledger-browser", "version": "2.0.0-rc.3", + "private": true, "description": "Cacti GUI for visualizing ledger data build on react.", "keywords": [ "Hyperledger", @@ -41,11 +42,12 @@ } ], "scripts": { - "build": "yarn run build:prod:frontend", + "build": "yarn run tsc && yarn run build:prod:frontend", "build:dev:frontend": "vite build --mode=development", "build:prod:frontend": "vite build", "serve": "vite preview", - "start": "vite" + "start": "vite", + "tsc": "tsc --build --verbose" }, "dependencies": { "@emotion/react": "11.11.4", @@ -56,6 +58,7 @@ "@supabase/supabase-js": "1.35.6", "@tanstack/react-query": "5.29.2", "apexcharts": "3.45.2", + "axios": "1.7.2", "buffer": "6.0.3", "ethers": "6.12.1", "react": "18.2.0", @@ -72,5 +75,12 @@ "@vitejs/plugin-react": "4.2.1", "typescript": "5.5.2", "vite": "5.1.7" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + }, + "publishConfig": { + "access": "public" } } diff --git a/packages/cacti-ledger-browser/src/main/sql/schema.sql b/packages/cacti-ledger-browser/src/main/sql/schema.sql new file mode 100644 index 0000000000..03915a4276 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/sql/schema.sql @@ -0,0 +1,52 @@ +ALTER SCHEMA extensions OWNER TO postgres; +ALTER SCHEMA public OWNER TO postgres; + +-- Table: public.plugin_status +-- DROP TABLE IF EXISTS public.plugin_status; + +CREATE TABLE IF NOT EXISTS public.plugin_status +( + name text COLLATE pg_catalog."default" NOT NULL, + last_instance_id text COLLATE pg_catalog."default" NOT NULL, + is_schema_initialized boolean NOT NULL, + created_at timestamp with time zone NOT NULL DEFAULT now(), + last_connected_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT plugin_status_pkey PRIMARY KEY (name), + CONSTRAINT plugin_status_name_key UNIQUE (name) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS public.plugin_status + OWNER to postgres; + +GRANT ALL ON TABLE public.plugin_status TO anon; +GRANT ALL ON TABLE public.plugin_status TO authenticated; +GRANT ALL ON TABLE public.plugin_status TO postgres; +GRANT ALL ON TABLE public.plugin_status TO service_role; + + +-- Table: public.gui_app_config +-- DROP TABLE IF EXISTS public.gui_app_config; + +CREATE TABLE public.gui_app_config ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + app_id text NOT NULL, + instance_name text NOT NULL, + description text NOT NULL, + path text NOT NULL, + options json, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT gui_app_config_pkey PRIMARY KEY (id), + CONSTRAINT gui_app_config_path_key UNIQUE (path) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS public.gui_app_config + OWNER to postgres; + +GRANT ALL ON TABLE public.gui_app_config TO anon; +GRANT ALL ON TABLE public.gui_app_config TO authenticated; +GRANT ALL ON TABLE public.gui_app_config TO postgres; +GRANT ALL ON TABLE public.gui_app_config TO service_role; diff --git a/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx b/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx index 2be409ec8e..7923d037d4 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx @@ -18,47 +18,26 @@ import { themeOptions } from "./theme"; import ContentLayout from "./components/Layout/ContentLayout"; import HeaderBar from "./components/Layout/HeaderBar"; import HomePage from "./pages/home/HomePage"; -import { AppInstance, AppListEntry } from "./common/types/app"; +import { AppInstance } from "./common/types/app"; import { patchAppRoutePath } from "./common/utils"; import { NotificationProvider } from "./common/context/NotificationContext"; import { guiAppConfig } from "./common/queries"; import createApplications from "./common/createApplications"; import ConnectionFailedDialog from "./components/ConnectionFailedDialog/ConnectionFailedDialog"; -/** - * Get list of all apps from the config - */ -function getAppList(appConfig: AppInstance[]) { - const appList: AppListEntry[] = appConfig.map((app) => { - return { - path: app.path, - name: app.appName, - }; - }); - - appList.unshift({ - path: "/", - name: "Home", - }); - - return appList; -} - /** * Create header bar for each app based on app menuEntries field in config. */ function getHeaderBarRoutes(appConfig: AppInstance[]) { - const appList = getAppList(appConfig); - const headerRoutesConfig = appConfig.map((app) => { return { key: app.path, path: `${app.path}/*`, element: ( ), }; @@ -66,7 +45,9 @@ function getHeaderBarRoutes(appConfig: AppInstance[]) { headerRoutesConfig.push({ key: "home", path: `*`, - element: , + element: ( + + ), }); return useRoutes(headerRoutesConfig); } diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx index ab092df29e..b6a537cac6 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx @@ -57,7 +57,7 @@ export default function ERC20BalanceHistoryChart({ colors: [theme.palette.primary.main], xaxis: { type: "datetime", - categories: data?.map((txn) => new Date(txn.created_at).getTime()), + categories: data?.map((txn) => txn.created_at), labels: { format: "dd-MM-yyyy h:mm", }, diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx index 78fd712e40..1a21893187 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx @@ -42,7 +42,7 @@ const ethBrowserAppDefinition: AppDefinition = { return { id: app.id, - appName: "Ethereum Browser", + appName: ethBrowserAppDefinition.appName, instanceName: app.instance_name, description: app.description, path: app.path, @@ -78,6 +78,8 @@ const ethBrowserAppDefinition: AppDefinition = { StatusComponent: ( ), + appSetupGuideURL: ethBrowserAppDefinition.appSetupGuideURL, + appDocumentationURL: ethBrowserAppDefinition.appDocumentationURL, }; }, }; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx index 7f4ede78fa..f2c30200e4 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx @@ -44,7 +44,7 @@ const fabricBrowserAppDefinition: AppDefinition = { return { id: app.id, - appName: "Hyperledger Fabric Browser", + appName: fabricBrowserAppDefinition.appName, instanceName: app.instance_name, description: app.description, path: app.path, @@ -86,6 +86,8 @@ const fabricBrowserAppDefinition: AppDefinition = { StatusComponent: ( ), + appSetupGuideURL: fabricBrowserAppDefinition.appSetupGuideURL, + appDocumentationURL: fabricBrowserAppDefinition.appDocumentationURL, }; }, }; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/hooks.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/hooks.tsx new file mode 100644 index 0000000000..b714847175 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/hooks.tsx @@ -0,0 +1,9 @@ +import { useOutletContext } from "react-router-dom"; + +type TutorialOptionsType = { + name: string; +}; + +export function useAppOptions() { + return useOutletContext(); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/index.tsx new file mode 100644 index 0000000000..7fe747cab6 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/index.tsx @@ -0,0 +1,72 @@ +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { AppDefinition } from "../../common/types/app"; +import { GuiAppConfig } from "../../common/supabase-types"; +import { AppCategory } from "../../common/app-category"; +import Home from "./pages/Home"; +import DataFetch from "./pages/DataFetch"; + +const tutorialAppDefinition: AppDefinition = { + appName: "Tutorial App", + category: AppCategory.SampleApp, + defaultInstanceName: "My App", + defaultDescription: "This is a tutorial application.", + defaultPath: "/tutorial", + defaultOptions: { + // Our app will use this variable to display a greeting later on + name: "Cacti", + }, + + createAppInstance(app: GuiAppConfig) { + if (!app.options || !app.options.name) { + throw new Error(`Missing 'name' in received GuiAppConfig options!`); + } + + return { + id: app.id, + appName: tutorialAppDefinition.appName, + instanceName: app.instance_name, + description: app.description, + path: app.path, + options: app.options, + menuEntries: [ + { + title: "Home", + url: "/", + }, + { + title: "Data Fetch", + url: "/data-fetch", + }, + ], + routes: [ + { + element: , + }, + { + path: "data-fetch", + element: , + }, + ], + useAppStatus: () => { + return { + isPending: false, + isInitialized: true, + status: { + severity: "success", + message: "Mocked response!", + }, + }; + }, + StatusComponent: ( + + Everything is OK (we hope) + + ), + appSetupGuideURL: tutorialAppDefinition.appSetupGuideURL, + appDocumentationURL: tutorialAppDefinition.appDocumentationURL, + }; + }, +}; + +export default tutorialAppDefinition; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/DataFetch.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/DataFetch.tsx new file mode 100644 index 0000000000..49b47fc0d2 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/DataFetch.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import axios from "axios"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useQuery } from "@tanstack/react-query"; +import PageTitle from "../../../components/ui/PageTitle"; +import { useNotification } from "../../../common/context/NotificationContext"; + +/** + * Simple method for fetching test data from restful-api.dev + */ +async function fetchSampleData() { + const response = await axios.get("https://api.restful-api.dev/objects/7"); + return response.data; +} + +export default function DataFetch() { + const { showNotification } = useNotification(); + const { data, isPending, isError, error } = useQuery({ + queryKey: ["sampleFetch"], + queryFn: fetchSampleData, + }); + + React.useEffect(() => { + isError && + showNotification(`Could not fetch sample data: ${error}`, "error"); + }, [isError]); + + React.useEffect(() => { + !isPending && + data && + showNotification(`Fetched data: ${data.name}`, "success"); + }, [data, isPending]); + + return ( + + Data Fetch Sample + + + Fetched object: {data?.name ?? ""} + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/Home.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/Home.tsx new file mode 100644 index 0000000000..27c5df281b --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/tutorial-app/pages/Home.tsx @@ -0,0 +1,13 @@ +import Box from "@mui/material/Box"; +import PageTitle from "../../../components/ui/PageTitle"; +import { useAppOptions } from "../hooks"; + +export default function Home() { + const appOptions = useAppOptions(); + + return ( + + Hello {appOptions.name}! + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx b/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx index 983a8cbcac..28c633a61a 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx @@ -1,10 +1,12 @@ import ethBrowserAppDefinition from "../apps/eth"; import fabricBrowserAppDefinition from "../apps/fabric"; +import tutorialAppDefinition from "../apps/tutorial-app"; import { AppDefinition } from "./types/app"; const config = new Map([ ["ethereumPersistenceBrowser", ethBrowserAppDefinition], ["fabricPersistenceBrowser", fabricBrowserAppDefinition], + ["tutorialApplication", tutorialAppDefinition], ]); export default config; diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts b/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts index 7da191ab78..136850ad37 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts +++ b/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts @@ -30,27 +30,114 @@ export interface AppInstancePersistencePluginOptions { } export interface AppInstance { + /** + * Unique database ID of this app instance. + */ id: string; + + /** + * Name of the application (can be same as appName in app definition) + */ appName: string; + + /** + * Instance name (set by the user) + */ instanceName: string; + + /** + * Instance description (set by the user) + */ description: string | undefined; + + /** + * Path under which app routes will be mounted (must be path with `/`, like `/eth`) + */ path: string; + + /** + * Custom, app-specific options in JSON format. This will change between applications. + */ options: T; + + /** + * List on titles and URL of menu entries to be added to the top bar (used to navigate within an app) + */ menuEntries: AppInstanceMenuEntry[]; + + /** + * `react-router-dom` compatible list of this application routes. + */ routes: RouteObject[]; + + /** + * Method for retriving application status details. + */ useAppStatus: () => GetStatusResponse; + + /** + * Status component showed when user opens a app status pop up window. + */ StatusComponent: React.ReactElement; + + /** + * Full URL to a setup guide, it will be displayed to the user on app configuration page. + */ + appSetupGuideURL?: string; + + /** + * Full URL to app documentation page + */ + appDocumentationURL?: string; } export type CreateAppInstanceFactoryType = (app: GuiAppConfig) => AppInstance; export interface AppDefinition { + /** + * Application name as shown to the user + */ appName: string; + + /** + * Application category, the user can filter using it. + * If there's no matching category for your app consider adding a new one! + */ category: string; + + /** + * Full URL to a setup guide, it will be displayed to the user on app configuration page. + */ + appSetupGuideURL?: string; + + /** + * Full URL to app documentation page + */ + appDocumentationURL?: string; + + /** + * Default value for instance name that user can set to uniquely identify this ap instance. + */ defaultInstanceName: string; + + /** + * Default value for app description. + */ defaultDescription: string; + + /** + * Default path under which app routes will be mounted (must be path with `/`, like `/eth`) + */ defaultPath: string; + + /** + * Default custom, app-specific options in JSON format. This will change between applications. + */ defaultOptions: unknown; + + /** + * Factory method for creating application instance object from configuration stored in a database. + */ createAppInstance: CreateAppInstanceFactoryType; } diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/utils.ts b/packages/cacti-ledger-browser/src/main/typescript/common/utils.ts index 109187a799..6a2d38b171 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/common/utils.ts +++ b/packages/cacti-ledger-browser/src/main/typescript/common/utils.ts @@ -15,3 +15,21 @@ export function patchAppRoutePath(appPath: string, routePath?: string) { return appPath; } } + +/** + * Returns true if provided url is defined and valid, returns false and + * writes error to `console.error` otherwise. + */ +export function isValidUrl(urlString?: string) { + if (!urlString) { + return false; + } + + try { + new URL(urlString); + return true; + } catch (e) { + console.error(`Invalid URL provided: ${urlString}, error: ${e}`); + return false; + } +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/AppSetupForms/AppSetupForm.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/AppSetupForms/AppSetupForm.tsx index a93788bed5..38cf082aa6 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/components/AppSetupForms/AppSetupForm.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/components/AppSetupForms/AppSetupForm.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import Box from "@mui/material/Box"; import TextField from "@mui/material/TextField"; +const pathCheckRegex = /^\/[a-zA-Z][a-zA-Z0-9]*$/; const emptyFormHelperText = "Field can't be empty"; const regularPathHelperText = "Path under which the plugin will be available, must be unique withing GUI."; @@ -30,9 +31,7 @@ export default function AppSetupForm({ const isInstanceNameEmptyError = !!!commonSetupValues.instanceName; const isDescriptionEmptyError = !!!commonSetupValues.description; const isPathEmptyError = !!!commonSetupValues.path; - const isPathInvalidError = !( - commonSetupValues.path.startsWith("/") && commonSetupValues.path.length > 1 - ); + const isPathInvalidError = !pathCheckRegex.test(commonSetupValues.path); let pathHelperText = regularPathHelperText; if (isPathEmptyError) { pathHelperText = emptyFormHelperText; diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx index 14b1b6c4d9..513ce600fe 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx @@ -5,18 +5,23 @@ import Box from "@mui/material/Box"; import Toolbar from "@mui/material/Toolbar"; import IconButton from "@mui/material/IconButton"; import AppsIcon from "@mui/icons-material/Apps"; +import HelpIcon from "@mui/icons-material/Help"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; -import { AppInstanceMenuEntry, AppListEntry } from "../../common/types/app"; -import { patchAppRoutePath } from "../../common/utils"; +import { AppInstanceMenuEntry } from "../../common/types/app"; +import { isValidUrl, patchAppRoutePath } from "../../common/utils"; type HeaderBarProps = { - appList: AppListEntry[]; path?: string; menuEntries?: AppInstanceMenuEntry[]; + appDocumentationURL?: string; }; -const HeaderBar: React.FC = ({ path, menuEntries }) => { +export default function HeaderBarProps({ + path, + menuEntries, + appDocumentationURL, +}: HeaderBarProps) { return ( @@ -48,9 +53,19 @@ const HeaderBar: React.FC = ({ path, menuEntries }) => { ))} )} + + {isValidUrl(appDocumentationURL) && ( + window.open(appDocumentationURL, "_blank")} + > + + + )} ); -}; - -export default HeaderBar; +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/pages/add-new-app/SelectAppView.tsx b/packages/cacti-ledger-browser/src/main/typescript/pages/add-new-app/SelectAppView.tsx index 42663bed25..94cefbd85a 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/pages/add-new-app/SelectAppView.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/pages/add-new-app/SelectAppView.tsx @@ -6,9 +6,11 @@ import ListItemText from "@mui/material/ListItemText"; import ListItemAvatar from "@mui/material/ListItemAvatar"; import Avatar from "@mui/material/Avatar"; import ListItemButton from "@mui/material/ListItemButton"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import config from "../../common/config"; import { AppCategory, getAppCategoryConfig } from "../../common/app-category"; +import { isValidUrl } from "../../common/utils"; export interface SelectAppViewProps { appCategory: string; @@ -36,15 +38,26 @@ export default function SelectAppView({ {apps.map(([appId, app]) => { return ( - handleAppSelected(appId)}> - - {categoryConfig.icon} - - - + + handleAppSelected(appId)}> + + {categoryConfig.icon} + + + + + ); })} diff --git a/packages/cacti-ledger-browser/tsconfig.json b/packages/cacti-ledger-browser/tsconfig.json index 6fdc580da1..bacee55f67 100644 --- a/packages/cacti-ledger-browser/tsconfig.json +++ b/packages/cacti-ledger-browser/tsconfig.json @@ -10,6 +10,7 @@ "skipLibCheck": true, "noEmit": true, "jsx": "react-jsx", + "tsBuildInfoFile": "../../.build-cache/cacti-ledger-browser.tsbuildinfo", "plugins": [ { "name": "typescript-plugin-css-modules" diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql index 206c5120da..85fea39df9 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql +++ b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql @@ -359,3 +359,11 @@ ALTER FUNCTION ethereum.get_missing_blocks_in_range(integer, integer) OWNER TO postgres; GRANT EXECUTE ON PROCEDURE ethereum.update_issued_erc721_tokens(numeric) TO public; + +GRANT USAGE ON SCHEMA ethereum TO anon, authenticated, service_role; +GRANT ALL ON ALL TABLES IN SCHEMA ethereum TO anon, authenticated, service_role; +GRANT ALL ON ALL ROUTINES IN SCHEMA ethereum TO anon, authenticated, service_role; +GRANT ALL ON ALL SEQUENCES IN SCHEMA ethereum TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA ethereum GRANT ALL ON TABLES TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA ethereum GRANT ALL ON ROUTINES TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA ethereum GRANT ALL ON SEQUENCES TO anon, authenticated, service_role; diff --git a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/common-setup-methods.ts b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/common-setup-methods.ts index 24d8790caa..1eb2307f4d 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/common-setup-methods.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/common-setup-methods.ts @@ -102,6 +102,8 @@ export async function setupApiServer(port: number, rpcApiWsHost: string) { cactusApiServerOptions.apiCorsDomainCsv = "*"; cactusApiServerOptions.apiTlsEnabled = false; cactusApiServerOptions.apiPort = port; + cactusApiServerOptions.grpcPort = port + 1; + cactusApiServerOptions.crpcPort = port + 2; const config = await configService.newExampleConfigConvict( cactusApiServerOptions, ); diff --git a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/complete-sample-scenario.ts b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/complete-sample-scenario.ts index 6777699ba1..5393d65e12 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/complete-sample-scenario.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/manual/complete-sample-scenario.ts @@ -207,7 +207,7 @@ async function main() { // Set up the ApiServer with Ethereum Connector and Ethereum Persistence plugins. // It returns the persistence plugin, which we can use to run monitoring operations. - const persistence = await setupApiServer(9782, rpcApiWsHost); + const persistence = await setupApiServer(9530, rpcApiWsHost); console.log("Environment is running..."); // Deploy an ERC721 contract to our test ledger and mint some tokens, diff --git a/packages/cactus-plugin-persistence-fabric/src/main/sql/schema.sql b/packages/cactus-plugin-persistence-fabric/src/main/sql/schema.sql index 8b59c85e43..56930d05aa 100644 --- a/packages/cactus-plugin-persistence-fabric/src/main/sql/schema.sql +++ b/packages/cactus-plugin-persistence-fabric/src/main/sql/schema.sql @@ -223,3 +223,11 @@ ALTER TABLE ONLY fabric.transaction ALTER TABLE ONLY fabric.transaction ADD CONSTRAINT transaction_block_number_fkey FOREIGN KEY (block_number) REFERENCES fabric.block(number); + +GRANT USAGE ON SCHEMA fabric TO anon, authenticated, service_role; +GRANT ALL ON ALL TABLES IN SCHEMA fabric TO anon, authenticated, service_role; +GRANT ALL ON ALL ROUTINES IN SCHEMA fabric TO anon, authenticated, service_role; +GRANT ALL ON ALL SEQUENCES IN SCHEMA fabric TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA fabric GRANT ALL ON TABLES TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA fabric GRANT ALL ON ROUTINES TO anon, authenticated, service_role; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA fabric GRANT ALL ON SEQUENCES TO anon, authenticated, service_role; diff --git a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/common-setup-methods.ts b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/common-setup-methods.ts index eabbfe999a..2432e12bcc 100644 --- a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/common-setup-methods.ts +++ b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/common-setup-methods.ts @@ -142,6 +142,8 @@ export async function setupApiServer( cactusApiServerOptions.apiCorsDomainCsv = "*"; cactusApiServerOptions.apiTlsEnabled = false; cactusApiServerOptions.apiPort = port; + cactusApiServerOptions.grpcPort = port + 1; + cactusApiServerOptions.crpcPort = port + 2; const config = await configService.newExampleConfigConvict( cactusApiServerOptions, ); diff --git a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/complete-sample-scenario.ts b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/complete-sample-scenario.ts index 78a582fdac..56cb9c84a9 100644 --- a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/complete-sample-scenario.ts +++ b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/complete-sample-scenario.ts @@ -146,7 +146,7 @@ async function main() { // Set up the ApiServer with Fabric Connector and Fabric Persistence plugins. // It returns the persistence plugin, which we can use to run monitoring operations. const { persistence, apiClient, signingCredential } = await setupApiServer( - 9781, // run at that port + 9950, // run at that port ledgerChannelName, connectionProfile, userIdentity, diff --git a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/sample-setup.ts b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/sample-setup.ts index 28aa4029e8..143a74b81f 100644 --- a/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/sample-setup.ts +++ b/packages/cactus-plugin-persistence-fabric/src/test/typescript/manual/sample-setup.ts @@ -69,7 +69,7 @@ async function main() { // Set up the ApiServer with Fabric Connector and Fabric Persistence plugins. // It returns the persistence plugin, which we can use to run monitoring operations. const { persistence } = await setupApiServer( - 9781, // run at that port + 9930, // run at that port FABRIC_CHANNEL_NAME, connectionProfile, userIdentity as any, diff --git a/tools/docker/supabase-all-in-one/Dockerfile b/tools/docker/supabase-all-in-one/Dockerfile index 9d968dc8b1..955b2ba879 100644 --- a/tools/docker/supabase-all-in-one/Dockerfile +++ b/tools/docker/supabase-all-in-one/Dockerfile @@ -37,6 +37,7 @@ FROM docker:25.0.5-dind ARG freeze_tmp_dir ENV FREEZE_TMP_DIR=$freeze_tmp_dir ENV APP_ROOT="/home/supabase/docker" +ENV PGRST_DB_SCHEMAS="public, storage, graphql_public, fabric, ethereum" # Install package dependencies RUN apk update \ diff --git a/tools/docker/supabase-all-in-one/README.md b/tools/docker/supabase-all-in-one/README.md index 32f6799fa2..1bceb5c3f3 100644 --- a/tools/docker/supabase-all-in-one/README.md +++ b/tools/docker/supabase-all-in-one/README.md @@ -5,17 +5,7 @@ An all in one supabase image that can be used as Cactus GUI backend. - This docker image is for `testing` and `development` only. - **Do NOT use in production!** -## Usage - -### Dashboard credentials: - -- http://127.0.0.1:8000/ -- Username: supabase -- Password: this_password_is_insecure_and_should_be_updated - -### Postgress ccredentials: - -- Password: `your-super-secret-and-long-postgres-password` +## Running ### Docker Compose @@ -45,3 +35,29 @@ docker run --name supabase_all_in_one_gui \ -p 5432:5432 \ cactus-supabase-all-in-one ``` + +## Usage + +Supabase dashboard is available under http://localhost:8000/. Use the following credentials to access it: +- **Username**: `supabase` +- **Password**: `this_password_is_insecure_and_should_be_updated` + +### Postgres access + +Use the `psql` tool, database password is: `your-super-secret-and-long-postgres-password` + +```sh +psql -h 127.0.0.1 -p 5432 -d postgres -U postgres +``` + +#### Connection string + +```sh +postgresql://postgres:your-super-secret-and-long-postgres-password@127.0.0.1:5432/postgres +``` + +### API Key + +```sh +docker exec -ti supabase_all_in_one_gui cat /home/supabase/docker/.env | grep SERVICE_ROLE_KEY +``` diff --git a/yarn.lock b/yarn.lock index 1bd0cf1679..35024b0af3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9075,6 +9075,7 @@ __metadata: "@types/react-dom": "npm:18.2.17" "@vitejs/plugin-react": "npm:4.2.1" apexcharts: "npm:3.45.2" + axios: "npm:1.7.2" buffer: "npm:6.0.3" ethers: "npm:6.12.1" react: "npm:18.2.0"