Skip to content

Commit e1e4108

Browse files
committed
feat(ledger-browser): show fabric network structure
- Add `Discovery` tab to Fabric App for showing discovered fabric network components by MSP. - Update REAMDE. - Fix responsiveness of main App selection screen. Depends on #3837 Closes #3837 Signed-off-by: Michal Bajer <[email protected]>
1 parent 754b896 commit e1e4108

File tree

13 files changed

+502
-38
lines changed

13 files changed

+502
-38
lines changed

packages/cacti-ledger-browser/README.md

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
# `@hyperledger/cacti-ledger-browser`
22

3-
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.
3+
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.
44

55
## Summary
66

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

2320
## Getting Started
2421

25-
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.
22+
Clone the git repository on your local machine.
2623

27-
### Prerequisites using yarn
28-
29-
In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component:
30-
31-
```sh
32-
yarn run build
33-
```
34-
35-
### Alternative Prerequisites using npm
36-
37-
In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component:
38-
39-
```sh
40-
npm install
41-
```
42-
43-
### Usage
44-
45-
- Run Supabase instance (see documentation for detailed instructions). For development purposes, you can use our image located in `tools/docker/supabase-all-in-one`.
46-
- Run one or more persistence plugins:
47-
- [Ethereum](../cacti-plugin-persistence-ethereum)
48-
- [Fabric] (../cacti-plugin-persistence-fabric)
49-
- Edit Supabase configuration files, set correct supabase API URL and service_role key.
50-
- ./src/main/typescript/common/supabase-client.tsx
51-
- ./src/main/typescript/common/queries.ts
52-
- Execute `yarn run start` or `npm start` in this package directory.
53-
- The running application address: http://localhost:3001/ (can be changed in [Vite configuration](./vite.config.ts))
24+
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.
5425

5526
## Contributing
5627

packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Dashboard from "./pages/Dashboard/Dashboard";
44
import Blocks from "./pages/Blocks/Blocks";
55
import Transactions from "./pages/Transactions/Transactions";
66
import TransactionDetails from "./pages/TransactionDetails/TransactionDetails";
7+
import Discovery from "./pages/Discovery/Discovery";
78
import {
89
AppInstancePersistencePluginOptions,
910
AppDefinition,
@@ -56,11 +57,19 @@ const fabricBrowserAppDefinition: AppDefinition = {
5657
title: "Dashboard",
5758
url: "/",
5859
},
60+
{
61+
title: "Discovery",
62+
url: "/discovery",
63+
},
5964
],
6065
routes: [
6166
{
6267
element: <Dashboard />,
6368
},
69+
{
70+
path: "discovery",
71+
element: <Discovery />,
72+
},
6473
{
6574
path: "blocks",
6675
element: <Blocks />,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from "react";
2+
import { useQuery } from "@tanstack/react-query";
3+
import Typography from "@mui/material/Typography";
4+
import Box from "@mui/material/Box";
5+
import List from "@mui/material/List/List";
6+
import ListItem from "@mui/material/ListItem/ListItem";
7+
import ListItemButton from "@mui/material/ListItemButton/ListItemButton";
8+
import ListItemText from "@mui/material/ListItemText/ListItemText";
9+
import DoubleArrowIcon from "@mui/icons-material/DoubleArrow";
10+
import ListItemIcon from "@mui/material/ListItemIcon/ListItemIcon";
11+
import CircularProgress from "@mui/material/CircularProgress/CircularProgress";
12+
import Divider from "@mui/material/Divider/Divider";
13+
14+
import { DiscoveryMSP } from "../../supabase-types";
15+
import PageTitle from "../../../../components/ui/PageTitle";
16+
import { useNotification } from "../../../../common/context/NotificationContext";
17+
import { fabricDiscoveryMSPs } from "../../queries";
18+
import DiscoveryDetails from "./DiscoveryDetails";
19+
20+
function Discovery() {
21+
const { showNotification } = useNotification();
22+
const [selectedMSP, setSelectedMSP] = React.useState<
23+
DiscoveryMSP | undefined
24+
>(undefined);
25+
const { isError, isPending, data, error } = useQuery(fabricDiscoveryMSPs());
26+
27+
React.useEffect(() => {
28+
isError &&
29+
showNotification(`Could not fetch discovery MSPs: ${error}`, "error");
30+
}, [isError]);
31+
32+
const msps = data ?? [];
33+
34+
return (
35+
<Box>
36+
<PageTitle>Discovery</PageTitle>
37+
38+
<Box display="flex" gap="2rem">
39+
<Box flexGrow={1} maxWidth="300px">
40+
<Typography variant="h5" marginTop="2rem">
41+
Select MSP
42+
</Typography>
43+
<Box>
44+
{isPending && (
45+
<Box
46+
paddingY="1em"
47+
sx={{
48+
display: "flex",
49+
justifyContent: "center",
50+
width: "100%",
51+
}}
52+
>
53+
<CircularProgress />
54+
</Box>
55+
)}
56+
</Box>
57+
58+
{msps && (
59+
<List>
60+
{msps.map((msp) => (
61+
<ListItem disablePadding key={msp.id}>
62+
<ListItemButton
63+
onClick={() => setSelectedMSP(msp)}
64+
selected={msp.id === (selectedMSP?.id ?? "")}
65+
sx={(theme) => ({
66+
"&.Mui-selected": {
67+
backgroundColor: theme.palette.primary.main,
68+
color: "white",
69+
},
70+
})}
71+
>
72+
<ListItemIcon>
73+
<DoubleArrowIcon />
74+
</ListItemIcon>
75+
<ListItemText primary={msp.name} />
76+
</ListItemButton>
77+
</ListItem>
78+
))}
79+
</List>
80+
)}
81+
</Box>
82+
83+
<Divider orientation="vertical" flexItem />
84+
85+
<Box flexGrow={7}>{<DiscoveryDetails msp={selectedMSP} />}</Box>
86+
</Box>
87+
</Box>
88+
);
89+
}
90+
91+
export default Discovery;
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from "react";
2+
import { useQuery } from "@tanstack/react-query";
3+
import Box from "@mui/material/Box";
4+
import Typography from "@mui/material/Typography/Typography";
5+
import Card from "@mui/material/Card/Card";
6+
import CardContent from "@mui/material/CardContent/CardContent";
7+
import List from "@mui/material/List/List";
8+
import ListItem from "@mui/material/ListItem/ListItem";
9+
import ListItemText from "@mui/material/ListItemText/ListItemText";
10+
import CircularProgress from "@mui/material/CircularProgress/CircularProgress";
11+
12+
import { useNotification } from "../../../../common/context/NotificationContext";
13+
import { fabricDiscoveryNodes } from "../../queries";
14+
import { DiscoveryMSP } from "../../supabase-types";
15+
import PeerCardButton from "./PeerCardButton";
16+
import OrdererCardButton from "./OrdererCardButton";
17+
18+
function NothingSelectedMessage() {
19+
return (
20+
<Box display="flex" alignItems="center" sx={{ height: "100%" }}>
21+
<Typography>
22+
Select MSP on the left to display ledger components.
23+
</Typography>
24+
</Box>
25+
);
26+
}
27+
28+
function LoadingSpinner() {
29+
return (
30+
<Box display="flex" alignItems="center" sx={{ height: "100%" }}>
31+
<CircularProgress />
32+
</Box>
33+
);
34+
}
35+
36+
interface DiscoveryDetailsProps {
37+
msp: DiscoveryMSP | undefined;
38+
}
39+
40+
function DiscoveryDetails({ msp }: DiscoveryDetailsProps) {
41+
const { showNotification } = useNotification();
42+
const { isError, isPending, data, error } = useQuery(
43+
fabricDiscoveryNodes(msp?.id),
44+
);
45+
React.useEffect(() => {
46+
isError &&
47+
showNotification(
48+
`Could not fetch ledger components for MSP ${msp?.name ?? "(Unknown)"}: ${error}`,
49+
"error",
50+
);
51+
}, [isError]);
52+
53+
if (!msp) {
54+
return <NothingSelectedMessage />;
55+
}
56+
57+
if (isPending) {
58+
return <LoadingSpinner />;
59+
}
60+
61+
const mspOUString = JSON.parse(msp.organizational_unit_identifiers).join(
62+
", ",
63+
);
64+
65+
const peers = data?.peers ?? [];
66+
const orderers = data?.orderers ?? [];
67+
68+
return (
69+
<Box>
70+
<Card sx={{ width: "50%" }} variant="outlined">
71+
<CardContent>
72+
<Typography variant="h6">
73+
{msp.name} ({msp.mspid})
74+
</Typography>
75+
<List dense disablePadding>
76+
<ListItem>
77+
<ListItemText
78+
primary={mspOUString ? mspOUString : "-"}
79+
secondary={"Organizational Units"}
80+
/>
81+
</ListItem>
82+
<ListItem>
83+
<ListItemText
84+
primary={msp.admins ? msp.admins : "-"}
85+
secondary={"Admins"}
86+
/>
87+
</ListItem>
88+
</List>
89+
</CardContent>
90+
</Card>
91+
92+
<Typography variant="h6" marginY="1em">
93+
Peers
94+
</Typography>
95+
<Box
96+
display="grid"
97+
gap="2rem"
98+
gridTemplateColumns="repeat(auto-fit, minmax(300px, 400px))"
99+
>
100+
{peers.map((p) => (
101+
<PeerCardButton key={p.id} peer={p} />
102+
))}
103+
{peers.length === 0 && (
104+
<Typography>No peers defined for this MSP</Typography>
105+
)}
106+
</Box>
107+
108+
<Typography variant="h6" marginY="1em">
109+
Orderers
110+
</Typography>
111+
<Box
112+
display="grid"
113+
gap="2rem"
114+
gridTemplateColumns="repeat(auto-fit, minmax(300px, 400px))"
115+
>
116+
{orderers.map((o) => (
117+
<OrdererCardButton key={o.id} orderer={o} />
118+
))}
119+
{orderers.length === 0 && (
120+
<Typography>No orderers defined for this MSP</Typography>
121+
)}
122+
</Box>
123+
</Box>
124+
);
125+
}
126+
127+
export default DiscoveryDetails;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as React from "react";
2+
import Button from "@mui/material/Button";
3+
import Dialog from "@mui/material/Dialog";
4+
import DialogTitle from "@mui/material/DialogTitle";
5+
import DialogContent from "@mui/material/DialogContent";
6+
7+
import OrdererDetailsBox from "./OrdererDetailsBox";
8+
import { DiscoveryOrderer } from "../../supabase-types";
9+
10+
interface OrdererCardButtonProps {
11+
orderer: DiscoveryOrderer;
12+
}
13+
14+
export default function OrdererCardButton({ orderer }: OrdererCardButtonProps) {
15+
const [openDialog, setOpenDialog] = React.useState(false);
16+
17+
return (
18+
<>
19+
<Button
20+
style={{ textTransform: "none" }}
21+
variant="outlined"
22+
onClick={() => setOpenDialog(true)}
23+
>
24+
{orderer.name}
25+
</Button>
26+
<Dialog
27+
fullWidth
28+
maxWidth="sm"
29+
onClose={() => setOpenDialog(false)}
30+
open={openDialog}
31+
>
32+
<DialogTitle color="primary">Orderer Details</DialogTitle>
33+
<DialogContent>
34+
<OrdererDetailsBox orderer={orderer} />
35+
</DialogContent>
36+
</Dialog>
37+
</>
38+
);
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Box from "@mui/material/Box";
2+
import Typography from "@mui/material/Typography";
3+
import { styled } from "@mui/material/styles";
4+
import { DiscoveryOrderer } from "../../supabase-types";
5+
import StackedRowItems from "../../../../components/ui/StackedRowItems";
6+
7+
const ListHeaderTypography = styled(Typography)(({ theme }) => ({
8+
color: theme.palette.secondary.main,
9+
fontWeight: "bold",
10+
}));
11+
12+
export interface OrdererDetailsBoxProps {
13+
orderer: DiscoveryOrderer;
14+
}
15+
16+
export default function OrdererDetailsBox({ orderer }: OrdererDetailsBoxProps) {
17+
return (
18+
<Box>
19+
<StackedRowItems>
20+
<ListHeaderTypography>Name:</ListHeaderTypography>
21+
<Typography>{orderer.name}</Typography>
22+
</StackedRowItems>
23+
<StackedRowItems>
24+
<ListHeaderTypography>Host:</ListHeaderTypography>
25+
<Typography>{orderer.host}</Typography>
26+
</StackedRowItems>
27+
<StackedRowItems>
28+
<ListHeaderTypography>Port:</ListHeaderTypography>
29+
<Typography>{orderer.port}</Typography>
30+
</StackedRowItems>
31+
</Box>
32+
);
33+
}

0 commit comments

Comments
 (0)