Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ledger-browser): refactor eth dashboard page #3237

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 16 additions & 4 deletions packages/cacti-ledger-browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This component allows viewing ledger data in Supabase or other postgreSQL compat
## Remarks

- Plugin requires running Supabase or other database and persistence plugins in order to properly view ledger data.
- Currently, fabric and ethereum based ledgers are supported.
- Currently, fabric and ethereum based ledgers are supported.

## Getting Started

Expand All @@ -31,6 +31,7 @@ In the root of the project, execute the command to install and build the depende
```sh
yarn run build
```

### Alternative Prerequisites using npm

In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component:
Expand All @@ -40,14 +41,25 @@ npm install
```

### Usage

- Run Supabase instance (see documentation for detailed instructions). For development purposes, you can use our image located in `tools/docker/supabase-all-in-one`.
- Run one or more persistence plugins:
- [Ethereum](../cacti-plugin-persistence-ethereum)
- [Fabric] (../cacti-plugin-persistence-fabric)
- [Ethereum](../cacti-plugin-persistence-ethereum)
- [Fabric] (../cacti-plugin-persistence-fabric)
- Edit [Supabase configuration file](./src/supabase-client.tsx), set correct supabase API URL and service_role key.
- Execute `yarn run start` or `npm start` in this package directory.
- The running application address: http://localhost:3001/ (can be changed in [Vite configuration](./vite.config.ts))

#### Sample Data

- To preview the GUI without running the persistence plugins you can use historic sample data located at `packages/cacti-ledger-browser/src/test/sql/sample-data.sql`.
- Use `psql` tool to import it to your supabase postgres DB instance.
- example:

```bash
psql "postgres://postgres.DB_NAME:DB_PASS@aws-0-eu-central-1.pooler.supabase.com:5432/postgres" -f src/test/sql/sample-data.sql
```

## Contributing

We welcome contributions to Hyperledger Cacti in many forms, and there’s always plenty to do!
Expand All @@ -58,4 +70,4 @@ Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started.

This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file.

## Acknowledgments
## Acknowledgments
9 changes: 6 additions & 3 deletions packages/cacti-ledger-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@
"start": "vite"
},
"dependencies": {
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@emotion/react": "11.11.4",
"@emotion/styled": "11.11.5",
"@mui/icons-material": "5.15.10",
"@mui/material": "5.15.10",
"@mui/material": "5.15.15",
"@supabase/supabase-js": "1.35.6",
"@tanstack/react-query": "5.29.2",
"apexcharts": "3.45.2",
"localforage": "1.10.0",
"match-sorter": "6.3.3",
Expand All @@ -70,6 +71,8 @@
"web3": "4.1.1"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.28.11",
"@tanstack/react-query-devtools": "5.29.2",
"@types/react": "18.2.43",
"@types/react-dom": "18.2.17",
"@types/sort-by": "1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { useRoutes, BrowserRouter, RouteObject } from "react-router-dom";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
// import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

import { themeOptions } from "./theme";
import ContentLayout from "./components/Layout/ContentLayout";
import HeaderBar from "./components/Layout/HeaderBar";
import WelcomePage from "./components/WelcomePage";
import { AppConfig, AppListEntry } from "./common/types/app";
import { patchAppRoutePath } from "./common/utils";
import { NotificationProvider } from "./common/context/NotificationContext";

type AppConfigProps = {
appConfig: AppConfig[];
};

/**
* Get list of all apps from the config
*/
function getAppList(appConfig: AppConfig[]) {
const appList: AppListEntry[] = appConfig.map((app) => {
return {
path: app.path,
name: app.name,
};
});

appList.unshift({
path: "/",
name: "Home",
});

return appList;
}

/**
* Create header bar for each app based on app menuEntries field in config.
*/
function getHeaderBarRoutes(appConfig: AppConfig[]) {
const appList = getAppList(appConfig);

const headerRoutesConfig = appConfig.map((app) => {
return {
key: app.path,
path: `${app.path}/*`,
element: (
<HeaderBar
appList={appList}
path={app.path}
menuEntries={app.menuEntries}
/>
),
};
});
headerRoutesConfig.push({
key: "home",
path: `*`,
element: <HeaderBar appList={appList} />,
});
return useRoutes(headerRoutesConfig);
}

/**
* Create content routes
*/
function getContentRoutes(appConfig: AppConfig[]) {
const appRoutes: RouteObject[] = appConfig.map((app) => {
return {
key: app.path,
path: app.path,
children: app.routes.map((route) => {
return {
key: route.path,
path: patchAppRoutePath(app.path, route.path),
element: route.element,
children: route.children,
};
}),
};
});

// Include landing / welcome page
appRoutes.push({
index: true,
element: <WelcomePage />,
});

return useRoutes([
{
path: "/",
element: <ContentLayout />,
children: appRoutes,
},
]);
}

const App: React.FC<AppConfigProps> = ({ appConfig }) => {
const headerRoutes = getHeaderBarRoutes(appConfig);
const contentRoutes = getContentRoutes(appConfig);

return (
<div>
{headerRoutes}
{contentRoutes}
</div>
);
};

// MUI Theme
const theme = createTheme(themeOptions);

// React Query client
const queryClient = new QueryClient();

const CactiLedgerBrowserApp: React.FC<AppConfigProps> = ({ appConfig }) => {
return (
<BrowserRouter>
<ThemeProvider theme={theme}>
<QueryClientProvider client={queryClient}>
<NotificationProvider>
<CssBaseline />
<App appConfig={appConfig} />
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
</NotificationProvider>
</QueryClientProvider>
</ThemeProvider>
</BrowserRouter>
);
};

export default CactiLedgerBrowserApp;
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AppConfig } from "../../common/types/app";
import StatusPage from "./pages/status-page";

const appConfig = {
name: "Cacti",
url: "cacti",
const appConfig: AppConfig = {
name: "Status",
path: "/cacti",
menuEntries: [
{
title: "Plugin Status",
Expand All @@ -11,8 +12,7 @@ const appConfig = {
],
routes: [
{
path: "/",
component: StatusPage,
element: <StatusPage />,
},
],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,15 @@
import { useEffect, useState } from "react";
import { supabase } from "../../../common/supabase-client";
import CardWrapper from "../../../components/ui/CardWrapper";
import { useQuery } from "@tanstack/react-query";
import { persistencePluginStatusQuery } from "../queries";

function StatusPage() {
const [getPluginStatus, setPluginStatuse] = useState<unknown[]>([]);

const fetchPluginStatus = async () => {
try {
const { data, error } = await supabase.from("plugin_status").select();
if (error) {
throw new Error(
`Could not get plugin statuses from the DB: ${error.message}`,
);
}

if (data) {
setPluginStatuse(
data.map((p) => {
return {
...p,
is_schema_initialized: p.is_schema_initialized
? "Setup complete"
: "No schema",
};
}),
);
}
} catch (error) {
console.error("Error when fetching plugin statuses:", error);
}
};
const { isSuccess, isError, data, error } = useQuery(
persistencePluginStatusQuery(),
);

useEffect(() => {
fetchPluginStatus();
}, []);
if (isError) {
console.error("Data fetch error:", error);
}

return (
<div>
Expand All @@ -49,7 +25,18 @@ function StatusPage() {
],
} as any
}
data={getPluginStatus}
data={
isSuccess
? (data as any).map((p: any) => {
return {
...p,
is_schema_initialized: p.is_schema_initialized
? "Setup complete"
: "No schema",
};
})
: []
}
title={"Persistence Plugins"}
display={"All"}
trimmed={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { supabaseQueryTable } from "../../common/supabase-client";

export function persistencePluginStatusQuery() {
return supabaseQueryTable("plugin_status");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from "react";
import { ethereumAllBlocksQuery } from "../../queries";
import { blockColumnsConfig } from "./blockColumnsConfig";
import type { UITableListingPaginationActionProps } from "../../../../components/ui/UITableListing/UITableListingPaginationAction";
import UITableListing from "../../../../components/ui/UITableListing/UITableListing";

/**
* List of columns that can be rendered in a block list table
*/
export type BlockListColumn = keyof typeof blockColumnsConfig;

/**
* BlockList properties.
*/
export interface BlockListProps {
footerComponent: React.ComponentType<UITableListingPaginationActionProps>;
columns: BlockListColumn[];
rowsPerPage: number;
tableSize?: "small" | "medium";
}

/**
* BlockList - Show table with ethereum blocks.
*
* @param footerComponent component will be rendered in a footer of a transaction list table.
* @param columns list of columns to be rendered.
* @param rowsPerPage how many rows to show per page.
*/
const BlockList: React.FC<BlockListProps> = ({
footerComponent,
columns,
rowsPerPage,
tableSize,
}) => {
return (
<UITableListing
queryFunction={ethereumAllBlocksQuery}
label="block"
columnConfig={blockColumnsConfig}
footerComponent={footerComponent}
columns={columns}
rowsPerPage={rowsPerPage}
tableSize={tableSize}
/>
);
};

export default BlockList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Component user can select columns to be rendered in a table list.
* Possible fields and their configurations are defined in here.
*/
export const blockColumnsConfig = {
hash: {
name: "Hash",
field: "hash",
isLongString: true,
isUnique: true,
},
number: {
name: "Number",
field: "number",
},
createdAt: {
name: "Created At",
field: "created_at",
isDate: true,
},
txCount: {
name: "Transaction Count",
field: "number_of_tx",
},
};