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): rewrite fabric application #3320

Merged
merged 1 commit into from
Jul 24, 2024
Merged
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
1 change: 1 addition & 0 deletions packages/cacti-ledger-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@supabase/supabase-js": "1.35.6",
"@tanstack/react-query": "5.29.2",
"apexcharts": "3.45.2",
"buffer": "6.0.3",
"ethers": "6.12.1",
"react": "18.2.0",
"react-apexcharts": "1.4.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,14 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
import { SvgIconComponent } from "@mui/icons-material";
import ReceiptOutlinedIcon from "@mui/icons-material/ReceiptOutlined";
import HubIcon from "@mui/icons-material/Hub";

import PageTitle from "../../../../components/ui/PageTitle";
import TitleWithIcon from "../../../../components/ui/TitleWithIcon";
import TransactionSummary from "./TransactionSummary";
import BlockSummary from "./BlockSummary";

interface TitleWithIconProps {
icon: SvgIconComponent;
children: React.ReactNode;
}

const TitleWithIcon: React.FC<TitleWithIconProps> = ({
children,
icon: Icon,
}) => {
return (
<Box display="flex" alignItems="center" marginBottom={2}>
<Icon sx={{ fontSize: 35 }} color="primary" />
<Typography variant="h6" component="h3" marginLeft={1}>
{children}
</Typography>
</Box>
);
};

function Dashboard() {
return (
<Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from "react";
import { fabricAllBlocksQuery } 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 fabric 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={fabricAllBlocksQuery}
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,20 @@
/**
* Component user can select columns to be rendered in a table list.
* Possible fields and their configurations are defined in here.
*/
export const blockColumnsConfig = {
number: {
name: "Number",
field: "number",
},
hash: {
name: "Hash",
field: "hash",
isLongString: true,
isUnique: true,
},
txCount: {
name: "Transaction Count",
field: "transaction_count",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import { styled } from "@mui/material/styles";

import { FabricCertificate } from "../../fabric-supabase-types";
import StackedRowItems from "../ui/StackedRowItems";

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

const TextFieldDisabledBlackFont = styled(TextField)(() => ({
"& .MuiInputBase-input.Mui-disabled": {
WebkitTextFillColor: "black",
},
}));

function formatDateString(date: string | undefined) {
if (date) {
return new Date(date).toLocaleDateString();
}

return "-";
}

function formatCertificateAttr(attr: string | null | undefined) {
if (attr) {
return attr;
}

return "-";
}

function formatCertificateSubject(
certificate: FabricCertificate,
fieldPrefix: "issuer" | "subject",
) {
return (
<ul style={{ margin: 0 }}>
<li>
<StackedRowItems>
<ListHeaderTypography>Common Name:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_common_name`])}
</Typography>
</StackedRowItems>
</li>
<li>
<StackedRowItems>
<ListHeaderTypography>Organization:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_org`])}
</Typography>
</StackedRowItems>
</li>
<li>
<StackedRowItems>
<ListHeaderTypography>Organization Unit:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_org_unit`])}
</Typography>
</StackedRowItems>
</li>
<li>
<StackedRowItems>
<ListHeaderTypography>Country:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_country`])}
</Typography>
</StackedRowItems>
</li>
<li>
<StackedRowItems>
<ListHeaderTypography>Locality:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_locality`])}
</Typography>
</StackedRowItems>
</li>
<li>
<StackedRowItems>
<ListHeaderTypography>State:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate[`${fieldPrefix}_state`])}
</Typography>
</StackedRowItems>
</li>
</ul>
);
}

export interface CertificateDetailsBoxProps {
certificate: FabricCertificate | undefined;
}

/**
* Detailed information of provided fabric certificate.
* @param certificate: Fabric certificate from the DB.
*/
export default function CertificateDetailsBox({
certificate,
}: CertificateDetailsBoxProps) {
return (
<Box>
<StackedRowItems>
<ListHeaderTypography>Serial Number:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate?.serial_number)}
</Typography>
</StackedRowItems>
<StackedRowItems>
<ListHeaderTypography>Valid From:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(formatDateString(certificate?.valid_from))}
</Typography>
</StackedRowItems>
<StackedRowItems>
<ListHeaderTypography>Valid To:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(formatDateString(certificate?.valid_to))}
</Typography>
</StackedRowItems>
<StackedRowItems>
<ListHeaderTypography>Alt Name:</ListHeaderTypography>
<Typography>
{formatCertificateAttr(certificate?.subject_alt_name)}
</Typography>
</StackedRowItems>

{certificate ? (
<>
<ListHeaderTypography marginTop={2}>Subject:</ListHeaderTypography>
{formatCertificateSubject(certificate, "subject")}
</>
) : (
<StackedRowItems>
<ListHeaderTypography>Subject:</ListHeaderTypography>
<Typography>-</Typography>
</StackedRowItems>
)}

{certificate ? (
<>
<ListHeaderTypography marginTop={2}>Issuer:</ListHeaderTypography>
{formatCertificateSubject(certificate, "issuer")}
</>
) : (
<StackedRowItems>
<ListHeaderTypography>Issuer:</ListHeaderTypography>
<Typography>-</Typography>
</StackedRowItems>
)}

<ListHeaderTypography marginTop={2}>
Certificate (PEM):
</ListHeaderTypography>
<TextFieldDisabledBlackFont
disabled
fullWidth
rows={5}
multiline
size="small"
value={certificate?.pem ?? ""}
/>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from "react";
import { useQuery } from "@tanstack/react-query";
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 CertificateDetailsBox from "./CertificateDetailsBox";
import { fabricCertificate } from "../../queries";
import { useNotification } from "../../../../common/context/NotificationContext";

export interface CertificateDialogButtonProps {
certId: string | null;
}

/**
* Show a button with certificate common name and organization, that will
* open a dialog with full certificate details on click.
*
* @warn Fetches the certificate from DB when mounted.
*
* @param certId ID of the certificate in database.
*/
export default function CertificateDialogButton({
certId,
}: CertificateDialogButtonProps) {
const { showNotification } = useNotification();
const [openDialog, setOpenDialog] = React.useState(false);

const { isError, data, error } = useQuery({
...fabricCertificate(certId ?? ""),
enabled: !!certId,
});

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

let creatorName = "unknownName";
if (data?.subject_common_name) {
creatorName = data.subject_common_name;
}

let creatorOrg = "unknownOrg";
if (data?.subject_org) {
creatorOrg = data.subject_org;
} else if (data?.subject_org_unit) {
creatorOrg = data.subject_org_unit;
}

return (
<>
<Button
disabled={!data}
style={{ textTransform: "none" }}
variant="outlined"
onClick={() => setOpenDialog(true)}
>
{creatorName}@{creatorOrg}
</Button>
<Dialog onClose={() => setOpenDialog(false)} open={openDialog}>
<DialogTitle color="primary">Certificate Details</DialogTitle>
<DialogContent>
<CertificateDetailsBox certificate={data} />
</DialogContent>
</Dialog>
</>
);
}

This file was deleted.

Loading
Loading