Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 3 additions & 32 deletions packages/cacti-ledger-browser/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# `@hyperledger/cacti-ledger-browser`

This component allows viewing ledger data in Supabase or other postgreSQL compatible database. The data is fed to supabase by persistence plugins for each ledgers.
This component allows viewing ledger data in Supabase or other PostgreSQL compatible database. The data is fed to supabase by persistence plugins for each ledgers.

## Summary

- [`@hyperledger/cacti-ledger-browser`](#hyperledgercacti-gui-tx-viewer)
- [Summary](#summary)
- [Remarks](#remarks)
- [Getting Started](#getting-started)
- [Prerequisites using yarn](#prerequisites-using-yarn)
- [Alternative Prerequisites using npm](#alternative-prerequisites-using-npm)
- [Usage](#usage)
- [Contributing](#contributing)
- [License](#license)
- [Acknowledgments](#acknowledgments)
Expand All @@ -22,35 +19,9 @@ This component allows viewing ledger data in Supabase or other postgreSQL compat

## Getting Started

Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on your local machine for development and testing purposes.
Clone the git repository on your local machine.

### Prerequisites using yarn

In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component:

```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:

```sh
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)
- Edit Supabase configuration files, set correct supabase API URL and service_role key.
- ./src/main/typescript/common/supabase-client.tsx
- ./src/main/typescript/common/queries.ts
- 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))
See [docs/docs/cactus/ledger-browser/setup.md](../../docs/docs/cactus/ledger-browser/setup.md) for detailed information on how to setup and use this package.

## Contributing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Dashboard from "./pages/Dashboard/Dashboard";
import Blocks from "./pages/Blocks/Blocks";
import Transactions from "./pages/Transactions/Transactions";
import TransactionDetails from "./pages/TransactionDetails/TransactionDetails";
import Discovery from "./pages/Discovery/Discovery";
import {
AppInstancePersistencePluginOptions,
AppDefinition,
Expand Down Expand Up @@ -56,11 +57,19 @@ const fabricBrowserAppDefinition: AppDefinition = {
title: "Dashboard",
url: "/",
},
{
title: "Discovery",
url: "/discovery",
},
],
routes: [
{
element: <Dashboard />,
},
{
path: "discovery",
element: <Discovery />,
},
{
path: "blocks",
element: <Blocks />,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from "react";
import { useQuery } from "@tanstack/react-query";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import List from "@mui/material/List/List";
import ListItem from "@mui/material/ListItem/ListItem";
import ListItemButton from "@mui/material/ListItemButton/ListItemButton";
import ListItemText from "@mui/material/ListItemText/ListItemText";
import DoubleArrowIcon from "@mui/icons-material/DoubleArrow";
import ListItemIcon from "@mui/material/ListItemIcon/ListItemIcon";
import CircularProgress from "@mui/material/CircularProgress/CircularProgress";
import Divider from "@mui/material/Divider/Divider";

import { DiscoveryMSP } from "../../supabase-types";
import PageTitle from "../../../../components/ui/PageTitle";
import { useNotification } from "../../../../common/context/NotificationContext";
import { fabricDiscoveryMSPs } from "../../queries";
import DiscoveryDetails from "./DiscoveryDetails";

function Discovery() {
const { showNotification } = useNotification();
const [selectedMSP, setSelectedMSP] = React.useState<
DiscoveryMSP | undefined
>(undefined);
const { isError, isPending, data, error } = useQuery(fabricDiscoveryMSPs());

React.useEffect(() => {
isError &&
showNotification(`Could not fetch discovery MSPs: ${error}`, "error");
}, [isError]);

const msps = data ?? [];

return (
<Box>
<PageTitle>Discovery</PageTitle>

<Box display="flex" gap="2rem">
<Box flexGrow={1} maxWidth="300px">
<Typography variant="h5" marginTop="2rem">
Select MSP
</Typography>
<Box>
{isPending && (
<Box
paddingY="1em"
sx={{
display: "flex",
justifyContent: "center",
width: "100%",
}}
>
<CircularProgress />
</Box>
)}
</Box>

{msps && (
<List>
{msps.map((msp) => (
<ListItem disablePadding key={msp.id}>
<ListItemButton
onClick={() => setSelectedMSP(msp)}
selected={msp.id === (selectedMSP?.id ?? "")}
sx={(theme) => ({
"&.Mui-selected": {
backgroundColor: theme.palette.primary.main,
color: "white",
},
})}
>
<ListItemIcon>
<DoubleArrowIcon />
</ListItemIcon>
<ListItemText primary={msp.name} />
</ListItemButton>
</ListItem>
))}
</List>
)}
</Box>

<Divider orientation="vertical" flexItem />

<Box flexGrow={7}>{<DiscoveryDetails msp={selectedMSP} />}</Box>
</Box>
</Box>
);
}

export default Discovery;
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from "react";
import { useQuery } from "@tanstack/react-query";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography/Typography";
import Card from "@mui/material/Card/Card";
import CardContent from "@mui/material/CardContent/CardContent";
import List from "@mui/material/List/List";
import ListItem from "@mui/material/ListItem/ListItem";
import ListItemText from "@mui/material/ListItemText/ListItemText";
import CircularProgress from "@mui/material/CircularProgress/CircularProgress";

import { useNotification } from "../../../../common/context/NotificationContext";
import { fabricDiscoveryNodes } from "../../queries";
import { DiscoveryMSP } from "../../supabase-types";
import PeerCardButton from "./PeerCardButton";
import OrdererCardButton from "./OrdererCardButton";

function NothingSelectedMessage() {
return (
<Box display="flex" alignItems="center" sx={{ height: "100%" }}>
<Typography>
Select MSP on the left to display ledger components.
</Typography>
</Box>
);
}

function LoadingSpinner() {
return (
<Box display="flex" alignItems="center" sx={{ height: "100%" }}>
<CircularProgress />
</Box>
);
}

interface DiscoveryDetailsProps {
msp: DiscoveryMSP | undefined;
}

function DiscoveryDetails({ msp }: DiscoveryDetailsProps) {
const { showNotification } = useNotification();
const { isError, isPending, data, error } = useQuery(
fabricDiscoveryNodes(msp?.id),
);
React.useEffect(() => {
isError &&
showNotification(
`Could not fetch ledger components for MSP ${msp?.name ?? "(Unknown)"}: ${error}`,
"error",
);
}, [isError]);

if (!msp) {
return <NothingSelectedMessage />;
}

if (isPending) {
return <LoadingSpinner />;
}

const mspOUString = JSON.parse(msp.organizational_unit_identifiers).join(
", ",
);

const peers = data?.peers ?? [];
const orderers = data?.orderers ?? [];

return (
<Box>
<Card sx={{ width: "50%" }} variant="outlined">
<CardContent>
<Typography variant="h6">
{msp.name} ({msp.mspid})
</Typography>
<List dense disablePadding>
<ListItem>
<ListItemText
primary={mspOUString ? mspOUString : "-"}
secondary={"Organizational Units"}
/>
</ListItem>
<ListItem>
<ListItemText
primary={msp.admins ? msp.admins : "-"}
secondary={"Admins"}
/>
</ListItem>
</List>
</CardContent>
</Card>

<Typography variant="h6" marginY="1em">
Peers
</Typography>
<Box
display="grid"
gap="2rem"
gridTemplateColumns="repeat(auto-fit, minmax(300px, 400px))"
>
{peers.map((p) => (
<PeerCardButton key={p.id} peer={p} />
))}
{peers.length === 0 && (
<Typography>No peers defined for this MSP</Typography>
)}
</Box>

<Typography variant="h6" marginY="1em">
Orderers
</Typography>
<Box
display="grid"
gap="2rem"
gridTemplateColumns="repeat(auto-fit, minmax(300px, 400px))"
>
{orderers.map((o) => (
<OrdererCardButton key={o.id} orderer={o} />
))}
{orderers.length === 0 && (
<Typography>No orderers defined for this MSP</Typography>
)}
</Box>
</Box>
);
}

export default DiscoveryDetails;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";

import OrdererDetailsBox from "./OrdererDetailsBox";
import { DiscoveryOrderer } from "../../supabase-types";

interface OrdererCardButtonProps {
orderer: DiscoveryOrderer;
}

export default function OrdererCardButton({ orderer }: OrdererCardButtonProps) {
const [openDialog, setOpenDialog] = React.useState(false);

return (
<>
<Button
style={{ textTransform: "none" }}
variant="outlined"
onClick={() => setOpenDialog(true)}
>
{orderer.name}
</Button>
<Dialog
fullWidth
maxWidth="sm"
onClose={() => setOpenDialog(false)}
open={openDialog}
>
<DialogTitle color="primary">Orderer Details</DialogTitle>
<DialogContent>
<OrdererDetailsBox orderer={orderer} />
</DialogContent>
</Dialog>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { styled } from "@mui/material/styles";
import { DiscoveryOrderer } from "../../supabase-types";
import StackedRowItems from "../../../../components/ui/StackedRowItems";

const ListHeaderTypography = styled(Typography)(({ theme }) => ({
color: theme.palette.secondary.main,
fontWeight: "bold",
}));

export interface OrdererDetailsBoxProps {
orderer: DiscoveryOrderer;
}

export default function OrdererDetailsBox({ orderer }: OrdererDetailsBoxProps) {
return (
<Box>
<StackedRowItems>
<ListHeaderTypography>Name:</ListHeaderTypography>
<Typography>{orderer.name}</Typography>
</StackedRowItems>
<StackedRowItems>
<ListHeaderTypography>Host:</ListHeaderTypography>
<Typography>{orderer.host}</Typography>
</StackedRowItems>
<StackedRowItems>
<ListHeaderTypography>Port:</ListHeaderTypography>
<Typography>{orderer.port}</Typography>
</StackedRowItems>
</Box>
);
}
Loading
Loading