From f81fc9969fdca63d2e36c8a3c4f5adcfd7d5beb7 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 7 Apr 2022 16:57:29 -0400 Subject: [PATCH 1/7] [misc-tweaks] group page and identity slides Signed-off-by: David Echelberger --- .../Accordions/MessageDataAccordion.tsx | 27 --- src/components/Buttons/IdentityButton.tsx | 39 ++++ src/components/Lists/EventList.tsx | 21 +- src/components/Lists/GroupList.tsx | 64 ++++++ src/components/Lists/IdentityList.tsx | 104 +++++++++ src/components/Lists/VerifiersList.tsx | 37 +++ src/components/Navigation/OffChainNav.tsx | 6 + src/components/Slides/GroupSlide.tsx | 62 +++++ src/components/Slides/IdentitySlide.tsx | 83 +++++++ src/components/Stacks/MemberStack.tsx | 80 +++++++ src/components/Viewers/FFJsonViewer.tsx | 18 +- src/interfaces/api.ts | 34 ++- src/interfaces/constants.ts | 2 + src/interfaces/filters.ts | 9 + src/interfaces/navigation.ts | 21 +- src/pages/Network/views/Identities.tsx | 24 +- src/pages/Network/views/Nodes.tsx | 37 +++ src/pages/Network/views/Organizations.tsx | 23 ++ src/pages/Off-Chain/Routes.tsx | 5 + src/pages/Off-Chain/views/Groups.tsx | 213 ++++++++++++++++++ src/translations/en.json | 22 +- 21 files changed, 871 insertions(+), 60 deletions(-) create mode 100644 src/components/Buttons/IdentityButton.tsx create mode 100644 src/components/Lists/GroupList.tsx create mode 100644 src/components/Lists/IdentityList.tsx create mode 100644 src/components/Lists/VerifiersList.tsx create mode 100644 src/components/Slides/GroupSlide.tsx create mode 100644 src/components/Slides/IdentitySlide.tsx create mode 100644 src/components/Stacks/MemberStack.tsx create mode 100644 src/pages/Off-Chain/views/Groups.tsx diff --git a/src/components/Accordions/MessageDataAccordion.tsx b/src/components/Accordions/MessageDataAccordion.tsx index 620087da..5c68adf2 100644 --- a/src/components/Accordions/MessageDataAccordion.tsx +++ b/src/components/Accordions/MessageDataAccordion.tsx @@ -6,8 +6,6 @@ import { AccordionSummary, Grid, IconButton, - Modal, - Paper, } from '@mui/material'; import { useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -37,7 +35,6 @@ export const MessageDataAccordion: React.FC = ({ const { t } = useTranslation(); const navigate = useNavigate(); const [expanded, setExpanded] = useState(isOpen); - const [openDataModal, setOpenDataModal] = useState(false); const accInfo: IDataWithHeader[] = [ { @@ -122,30 +119,6 @@ export const MessageDataAccordion: React.FC = ({ - setOpenDataModal(false)} - sx={{ wordWrap: 'break-word' }} - > - - - - ); }; - -const modalStyle = { - position: 'absolute' as const, - overflow: 'auto', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '40%', - height: '50%', - backgroundColor: 'background.paper', - border: '2px solid #000', - boxShadow: 24, - p: 4, - wordWrap: 'break-word', -}; diff --git a/src/components/Buttons/IdentityButton.tsx b/src/components/Buttons/IdentityButton.tsx new file mode 100644 index 00000000..3da079ae --- /dev/null +++ b/src/components/Buttons/IdentityButton.tsx @@ -0,0 +1,39 @@ +import { Launch } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ApplicationContext } from '../../contexts/ApplicationContext'; +import { FF_NAV_PATHS } from '../../interfaces'; + +type Props = { + did?: string; + nodeID?: string; +}; + +const getDIDNavURL = (did: string, ns: string) => { + if (did.startsWith('did:firefly:org')) { + return FF_NAV_PATHS.networkOrgsPath(ns, did); + } else if (did.startsWith('did:firefly:node')) { + return FF_NAV_PATHS.networkNodesPath(ns, did); + } + + return FF_NAV_PATHS.networkIdentitiesPath(ns, did); +}; + +export const IdentityButton: React.FC = ({ did, nodeID }) => { + const { selectedNamespace } = useContext(ApplicationContext); + const navigate = useNavigate(); + + return ( + { + e.stopPropagation(); + did + ? navigate(getDIDNavURL(did, selectedNamespace)) + : navigate(FF_NAV_PATHS.networkNodesPath(selectedNamespace, nodeID)); + }} + > + + + ); +}; diff --git a/src/components/Lists/EventList.tsx b/src/components/Lists/EventList.tsx index b5131edc..2a4ade7b 100644 --- a/src/components/Lists/EventList.tsx +++ b/src/components/Lists/EventList.tsx @@ -1,7 +1,7 @@ import { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ApplicationContext } from '../../contexts/ApplicationContext'; -import { IEvent } from '../../interfaces'; +import { FF_EVENTS_CATEGORY_MAP, IEvent } from '../../interfaces'; import { IDataListItem } from '../../interfaces/lists'; import { FFCopyButton } from '../Buttons/CopyButton'; import { TxButton } from '../Buttons/TxButton'; @@ -25,10 +25,22 @@ export const EventList: React.FC = ({ event, showTxLink = true }) => { if (event) { setDataList([ { - label: t('id'), + label: t('eventID'), value: , button: , }, + { + label: t(FF_EVENTS_CATEGORY_MAP[event.type].referenceIDName), + value: , + button: ( + <> + {FF_EVENTS_CATEGORY_MAP[event.type].referenceIDButton( + event.reference + )} + + + ), + }, { label: t('transactionID'), value: , @@ -41,11 +53,6 @@ export const EventList: React.FC = ({ event, showTxLink = true }) => { ), }, - { - label: t('referenceID'), - value: , - button: , - }, { label: t('created'), value: , diff --git a/src/components/Lists/GroupList.tsx b/src/components/Lists/GroupList.tsx new file mode 100644 index 00000000..1290b5e0 --- /dev/null +++ b/src/components/Lists/GroupList.tsx @@ -0,0 +1,64 @@ +import { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ApplicationContext } from '../../contexts/ApplicationContext'; +import { IGroup } from '../../interfaces'; +import { IDataListItem } from '../../interfaces/lists'; +import { FFCopyButton } from '../Buttons/CopyButton'; +import { MsgButton } from '../Buttons/MsgButton'; +import { FFListItem } from './FFListItem'; +import { FFListText } from './FFListText'; +import { FFListTimestamp } from './FFListTimestamp'; +import { FFSkeletonList } from './FFSkeletonList'; + +interface Props { + group?: IGroup; +} + +export const GroupList: React.FC = ({ group }) => { + const { selectedNamespace } = useContext(ApplicationContext); + const { t } = useTranslation(); + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + if (group) { + setDataList([ + { + label: t('groupHash'), + value: , + button: , + }, + { + label: t('numberOfMembers'), + value: ( + + ), + }, + { + label: t('messageID'), + value: , + button: ( + <> + + + + ), + }, + { + label: t('created'), + value: , + }, + ]); + } + }, [group]); + + return ( + <> + {dataList.map((d, idx) => ( + + ))} + + ); +}; diff --git a/src/components/Lists/IdentityList.tsx b/src/components/Lists/IdentityList.tsx new file mode 100644 index 00000000..250b69ed --- /dev/null +++ b/src/components/Lists/IdentityList.tsx @@ -0,0 +1,104 @@ +import { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ApplicationContext } from '../../contexts/ApplicationContext'; +import { IIdentity } from '../../interfaces'; +import { IDataListItem } from '../../interfaces/lists'; +import { FFCopyButton } from '../Buttons/CopyButton'; +import { MsgButton } from '../Buttons/MsgButton'; +import { FFListItem } from './FFListItem'; +import { FFListText } from './FFListText'; +import { FFListTimestamp } from './FFListTimestamp'; +import { FFSkeletonList } from './FFSkeletonList'; + +interface Props { + identity?: IIdentity; +} + +export const IdentityList: React.FC = ({ identity }) => { + const { selectedNamespace } = useContext(ApplicationContext); + const { t } = useTranslation(); + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + if (identity) { + setDataList([ + { + label: t('did'), + value: , + button: , + }, + { + label: t('id'), + value: , + button: , + }, + { + label: t('type'), + value: , + button: , + }, + { + label: t('parent'), + value: identity.parent ? ( + + ) : ( + + ), + button: identity.parent ? ( + + ) : ( + <> + ), + }, + { + label: t('messageClaim'), + value: , + button: ( + <> + + + + ), + }, + { + label: t('messageVerification'), + value: identity.messages.verification ? ( + + ) : ( + + ), + button: identity.messages.verification ? ( + <> + + + + ) : ( + <> + ), + }, + { + label: t('updated'), + value: , + }, + { + label: t('created'), + value: , + }, + ]); + } + }, [identity]); + + return ( + <> + {dataList.map((d, idx) => ( + + ))} + + ); +}; diff --git a/src/components/Lists/VerifiersList.tsx b/src/components/Lists/VerifiersList.tsx new file mode 100644 index 00000000..364f1dbc --- /dev/null +++ b/src/components/Lists/VerifiersList.tsx @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react'; +import { IVerifier } from '../../interfaces'; +import { IDataListItem } from '../../interfaces/lists'; +import { FFCopyButton } from '../Buttons/CopyButton'; +import { FFListItem } from './FFListItem'; +import { FFListText } from './FFListText'; +import { FFSkeletonList } from './FFSkeletonList'; + +interface Props { + verifiers?: IVerifier[]; +} + +export const VerifiersList: React.FC = ({ verifiers }) => { + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + if (verifiers) { + setDataList( + verifiers.map(({ type, value }) => { + return { + label: type, + value: , + button: , + }; + }) + ); + } + }, [verifiers]); + + return ( + <> + {dataList.map((d, idx) => ( + + ))} + + ); +}; diff --git a/src/components/Navigation/OffChainNav.tsx b/src/components/Navigation/OffChainNav.tsx index 6d3e952b..c327a648 100644 --- a/src/components/Navigation/OffChainNav.tsx +++ b/src/components/Navigation/OffChainNav.tsx @@ -31,6 +31,7 @@ export const OffChainNav = () => { const offchainPath = FF_NAV_PATHS.offchainPath(selectedNamespace); const messagesPath = FF_NAV_PATHS.offchainMessagesPath(selectedNamespace); const dataPath = FF_NAV_PATHS.offchainDataPath(selectedNamespace); + const groupsPath = FF_NAV_PATHS.offchainGroupsPath(selectedNamespace); const datatypesPath = FF_NAV_PATHS.offchainDatatypesPath(selectedNamespace); const navItems: INavItem[] = [ @@ -49,6 +50,11 @@ export const OffChainNav = () => { action: () => navigate(dataPath), itemIsActive: pathname === dataPath, }, + { + name: t('groups'), + action: () => navigate(groupsPath), + itemIsActive: pathname === groupsPath, + }, { name: t('datatypes'), action: () => navigate(datatypesPath), diff --git a/src/components/Slides/GroupSlide.tsx b/src/components/Slides/GroupSlide.tsx new file mode 100644 index 00000000..baaae843 --- /dev/null +++ b/src/components/Slides/GroupSlide.tsx @@ -0,0 +1,62 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { IGroup } from '../../interfaces'; +import { DEFAULT_PADDING } from '../../theme'; +import { getShortHash } from '../../utils'; +import { GroupList } from '../Lists/GroupList'; +import { MemberStack } from '../Stacks/MemberStack'; +import { DisplaySlide } from './DisplaySlide'; +import { SlideHeader } from './SlideHeader'; +import { SlideSectionHeader } from './SlideSectionHeader'; + +interface Props { + group: IGroup; + open: boolean; + onClose: () => void; +} + +export const GroupSlide: React.FC = ({ group, open, onClose }) => { + const { t } = useTranslation(); + + return ( + <> + + + {/* Header */} + + {/* Group list */} + + + + {/* Members */} + + + {group.members.map((m, idx) => ( + + ))} + + + + + ); +}; diff --git a/src/components/Slides/IdentitySlide.tsx b/src/components/Slides/IdentitySlide.tsx new file mode 100644 index 00000000..0e4a5d99 --- /dev/null +++ b/src/components/Slides/IdentitySlide.tsx @@ -0,0 +1,83 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { SnackbarContext } from '../../contexts/SnackbarContext'; +import { FF_Paths, IIdentity } from '../../interfaces'; +import { DEFAULT_PADDING } from '../../theme'; +import { fetchCatcher } from '../../utils'; +import { IdentityList } from '../Lists/IdentityList'; +import { VerifiersList } from '../Lists/VerifiersList'; +import { DisplaySlide } from './DisplaySlide'; +import { SlideHeader } from './SlideHeader'; +import { SlideSectionHeader } from './SlideSectionHeader'; + +interface Props { + did: string; + open: boolean; + onClose: () => void; +} + +export const IdentitySlide: React.FC = ({ did, open, onClose }) => { + const { reportFetchError } = useContext(SnackbarContext); + const { t } = useTranslation(); + const [identity, setIdentity] = useState(); + + const [isMounted, setIsMounted] = useState(false); + useEffect(() => { + setIsMounted(true); + return () => { + setIsMounted(false); + }; + }, []); + + useEffect(() => { + isMounted && + fetchCatcher( + `${FF_Paths.apiPrefix}${FF_Paths.networkIdentitiesByDID( + did + )}?fetchverifiers` + ) + .then((identity: IIdentity) => { + isMounted && setIdentity(identity); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [isMounted]); + + return ( + <> + + + {/* Header */} + + {/* Group list */} + + + + {/* Verifiers */} + + + + + + + + ); +}; diff --git a/src/components/Stacks/MemberStack.tsx b/src/components/Stacks/MemberStack.tsx new file mode 100644 index 00000000..1d1f6e9b --- /dev/null +++ b/src/components/Stacks/MemberStack.tsx @@ -0,0 +1,80 @@ +import { Box, Grid, Paper, Stack } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { IGroupMember } from '../../interfaces'; +import { + DEFAULT_BORDER_RADIUS, + DEFAULT_PADDING, + DEFAULT_SPACING, +} from '../../theme'; +import { FFAccordionText } from '../Accordions/FFAccordionText'; +import { IdentityButton } from '../Buttons/IdentityButton'; +import { HashPopover } from '../Popovers/HashPopover'; + +interface Props { + member: IGroupMember; + isOpen?: boolean; +} + +export const MemberStack: React.FC = ({ member }) => { + const { t } = useTranslation(); + + return ( + + + + + {/* Identity */} + + + + + + + + + + {/* Node */} + + + + + + + + + + + + + + ); +}; diff --git a/src/components/Viewers/FFJsonViewer.tsx b/src/components/Viewers/FFJsonViewer.tsx index ec70f38c..f56843c4 100644 --- a/src/components/Viewers/FFJsonViewer.tsx +++ b/src/components/Viewers/FFJsonViewer.tsx @@ -1,6 +1,6 @@ -import { InputAdornment, TextField, useTheme } from '@mui/material'; +import { useTheme } from '@mui/material'; import ReactJson from 'react-json-view'; -import { FFCopyButton } from '../Buttons/CopyButton'; +import { FFTextField } from '../Inputs/FFTextField'; interface Props { json: object | string; @@ -37,18 +37,6 @@ export const FFJsonViewer: React.FC = ({ json }) => { name={false} /> ) : ( - - {typeof json === 'string' && } - - ), - }} - fullWidth - /> + ); }; diff --git a/src/interfaces/api.ts b/src/interfaces/api.ts index 9ef40643..060f1108 100644 --- a/src/interfaces/api.ts +++ b/src/interfaces/api.ts @@ -133,20 +133,35 @@ export interface IGenericPagedResponse { total: number; } +export interface IGroup { + namespace: string; + name: string; + members: IGroupMember[]; + message: string; + hash: string; + created: string; +} + +export interface IGroupMember { + identity: string; + node: string; +} + export interface IIdentity { id: string; did: string; type: string; - parent?: string; namespace: string; + parent?: string; name: string; messages: { claim: string; - verification: string | null; - update: string | null; + verification: null | string; + update: null | string; }; created: string; updated: string; + verifiers: IVerifier[]; } export interface IMessage { @@ -189,6 +204,7 @@ export type IMessageTransaction = ITransaction; export interface IMetric { count: string; + isCapped: boolean; timestamp: string; types: IMetricType[]; } @@ -309,6 +325,13 @@ export interface IPagedFireFlyApiResponse { total: number; } +export interface IPagedGroupResponse { + pageParam: number; + count: number; + items: IGroup[]; + total: number; +} + export interface IPagedIdentityResponse { pageParam: number; count: number; @@ -511,3 +534,8 @@ export interface ITxStatus { }; }[]; } + +export interface IVerifier { + type: string; + value: string; +} diff --git a/src/interfaces/constants.ts b/src/interfaces/constants.ts index a79b88a1..43406f04 100644 --- a/src/interfaces/constants.ts +++ b/src/interfaces/constants.ts @@ -113,6 +113,8 @@ export const FF_Paths = { `/transactions/${txId}/operations`, transactionByIdStatus: (txId: string) => `/transactions/${txId}/status`, // Network + networkIdentities: '/network/identities', + networkIdentitiesByDID: (did: string) => `/network/identities/${did}`, networkNodes: '/network/nodes', networkNodeById: (id: string) => `/network/nodes/${id}`, networkNodeSelf: '/network/self', diff --git a/src/interfaces/filters.ts b/src/interfaces/filters.ts index 6e7aee75..d168972e 100644 --- a/src/interfaces/filters.ts +++ b/src/interfaces/filters.ts @@ -88,6 +88,15 @@ export const EventFilters = [ 'created', ]; +export const GroupFilters = [ + 'hash', + 'message', + 'namespace', + 'description', + 'ledger', + 'created', +]; + export const IdentityFilters = [ 'id', 'did', diff --git a/src/interfaces/navigation.ts b/src/interfaces/navigation.ts index 39b77a36..b67f7025 100644 --- a/src/interfaces/navigation.ts +++ b/src/interfaces/navigation.ts @@ -32,6 +32,7 @@ export const DATATYPES_PATH = 'datatypes'; export const DOCS_PATH = 'https://hyperledger.github.io/firefly/'; export const EVENTS_PATH = 'events'; export const FILE_EXPLORER_PATH = 'fileExplorer'; +export const GROUPS_PATH = 'groups'; export const HOME_PATH = 'home'; export const IDENTITIES_PATH = 'identities'; export const INTERFACES_PATH = 'interfaces'; @@ -107,6 +108,8 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${DATATYPES_PATH}${ datatypeID ? `?filters=id==${datatypeID}&slide=${datatypeID}` : '' }`, + offchainGroupsPath: (ns: string) => + `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${GROUPS_PATH}`, // Tokens tokensPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}`, tokensTransfersPath: (ns: string, poolID?: string) => @@ -131,12 +134,18 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${BALANCES_PATH}?filters=pool==${poolID}`, // Network networkPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}`, - networkOrgsPath: (ns: string) => - `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${ORGANIZATIONS_PATH}`, - networkNodesPath: (ns: string) => - `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${NODES_PATH}`, - networkIdentitiesPath: (ns: string) => - `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${IDENTITIES_PATH}`, + networkOrgsPath: (ns: string, slideID?: string) => + `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${ORGANIZATIONS_PATH}${ + slideID ? '?slide=' + slideID : '' + }`, + networkNodesPath: (ns: string, slideID?: string) => + `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${NODES_PATH}${ + slideID ? '?slide=' + slideID : '' + }`, + networkIdentitiesPath: (ns: string, slideID?: string) => + `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${IDENTITIES_PATH}${ + slideID ? '?slide=' + slideID : '' + }`, // My Node myNodePath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${MY_NODES_PATH}`, myNodeSubscriptionsPath: (ns: string) => diff --git a/src/pages/Network/views/Identities.tsx b/src/pages/Network/views/Identities.tsx index 6681b90e..8dcd1f62 100644 --- a/src/pages/Network/views/Identities.tsx +++ b/src/pages/Network/views/Identities.tsx @@ -22,11 +22,13 @@ import { FilterModal } from '../../../components/Filters/FilterModal'; import { Header } from '../../../components/Header'; import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { IdentitySlide } from '../../../components/Slides/IdentitySlide'; import { FFTableText } from '../../../components/Tables/FFTableText'; import { DataTable } from '../../../components/Tables/Table'; import { ApplicationContext } from '../../../contexts/ApplicationContext'; import { DateFilterContext } from '../../../contexts/DateFilterContext'; import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; import { SnackbarContext } from '../../../contexts/SnackbarContext'; import { FF_Paths, @@ -45,12 +47,14 @@ export const NetworkIdentities: () => JSX.Element = () => { const { dateFilter } = useContext(DateFilterContext); const { filterAnchor, setFilterAnchor, filterString } = useContext(FilterContext); + const { setSlideSearchParam, slideID } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); const { t } = useTranslation(); const [isMounted, setIsMounted] = useState(false); const [identities, setIdentities] = useState(); const [identitiesTotal, setIdentitiesTotal] = useState(0); + const [viewIdentity, setViewIdentity] = useState(); const [currentPage, setCurrentPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); @@ -61,6 +65,10 @@ export const NetworkIdentities: () => JSX.Element = () => { }; }, []); + useEffect(() => { + isMounted && slideID && setViewIdentity(slideID); + }, [slideID, isMounted]); + // Identities useEffect(() => { isMounted && @@ -128,6 +136,10 @@ export const NetworkIdentities: () => JSX.Element = () => { ), }, ], + onClick: () => { + setViewIdentity(id.did); + setSlideSearchParam(id.did); + }, }; }); @@ -164,7 +176,7 @@ export const NetworkIdentities: () => JSX.Element = () => { records={idRecords} columnHeaders={idColHeaders} paginate={true} - emptyStateText={t('noIdentitiesToDisplay')} + emptyStateText={t('noIdentitiesInNs')} dataTotal={identitiesTotal} currentPage={currentPage} rowsPerPage={rowsPerPage} @@ -180,6 +192,16 @@ export const NetworkIdentities: () => JSX.Element = () => { fields={IdentityFilters} /> )} + {viewIdentity && ( + { + setViewIdentity(undefined); + setSlideSearchParam(null); + }} + /> + )} ); }; diff --git a/src/pages/Network/views/Nodes.tsx b/src/pages/Network/views/Nodes.tsx index a99055c7..3ba9a67a 100644 --- a/src/pages/Network/views/Nodes.tsx +++ b/src/pages/Network/views/Nodes.tsx @@ -22,15 +22,18 @@ import { FilterModal } from '../../../components/Filters/FilterModal'; import { Header } from '../../../components/Header'; import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { IdentitySlide } from '../../../components/Slides/IdentitySlide'; import { FFTableText } from '../../../components/Tables/FFTableText'; import { DataTable } from '../../../components/Tables/Table'; import { ApplicationContext } from '../../../contexts/ApplicationContext'; import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; import { SnackbarContext } from '../../../contexts/SnackbarContext'; import { FF_Paths, IDataTableRecord, IdentityFilters, + IIdentity, INode, IPagedNodeResponse, } from '../../../interfaces'; @@ -41,6 +44,7 @@ export const NetworkNodes: () => JSX.Element = () => { const { nodeName } = useContext(ApplicationContext); const { filterAnchor, setFilterAnchor, filterString } = useContext(FilterContext); + const { setSlideSearchParam, slideID } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); const { t } = useTranslation(); const [isMounted, setIsMounted] = useState(false); @@ -48,6 +52,7 @@ export const NetworkNodes: () => JSX.Element = () => { const [nodes, setNodes] = useState(); // Node total const [nodeTotal, setNodeTotal] = useState(0); + const [viewIdentity, setViewIdentity] = useState(); const [currentPage, setCurrentPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); @@ -58,6 +63,24 @@ export const NetworkNodes: () => JSX.Element = () => { }; }, []); + useEffect(() => { + if (isMounted && slideID) { + if (slideID.startsWith('did:firefly')) { + setViewIdentity(slideID); + } else { + fetchCatcher( + `${FF_Paths.apiPrefix}/${FF_Paths.networkNodeById(slideID)}` + ) + .then((nodeRes: IIdentity) => { + setViewIdentity(nodeRes.did); + }) + .catch((err) => { + reportFetchError(err); + }); + } + } + }, [slideID, isMounted]); + // Nodes useEffect(() => { isMounted && @@ -121,6 +144,10 @@ export const NetworkNodes: () => JSX.Element = () => { ), }, ], + onClick: () => { + setViewIdentity(node.did); + setSlideSearchParam(node.did); + }, }; }); @@ -172,6 +199,16 @@ export const NetworkNodes: () => JSX.Element = () => { fields={IdentityFilters} /> )} + {viewIdentity && ( + { + setViewIdentity(undefined); + setSlideSearchParam(null); + }} + /> + )} ); }; diff --git a/src/pages/Network/views/Organizations.tsx b/src/pages/Network/views/Organizations.tsx index a90a51da..d8812cb3 100644 --- a/src/pages/Network/views/Organizations.tsx +++ b/src/pages/Network/views/Organizations.tsx @@ -22,10 +22,12 @@ import { FilterModal } from '../../../components/Filters/FilterModal'; import { Header } from '../../../components/Header'; import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { IdentitySlide } from '../../../components/Slides/IdentitySlide'; import { FFTableText } from '../../../components/Tables/FFTableText'; import { DataTable } from '../../../components/Tables/Table'; import { ApplicationContext } from '../../../contexts/ApplicationContext'; import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; import { SnackbarContext } from '../../../contexts/SnackbarContext'; import { FF_Paths, @@ -41,6 +43,7 @@ export const NetworkOrganizations: () => JSX.Element = () => { const { orgName } = useContext(ApplicationContext); const { filterAnchor, setFilterAnchor, filterString } = useContext(FilterContext); + const { setSlideSearchParam, slideID } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); const { t } = useTranslation(); const [isMounted, setIsMounted] = useState(false); @@ -49,6 +52,8 @@ export const NetworkOrganizations: () => JSX.Element = () => { const [orgs, setOrgs] = useState(); // Org total const [orgTotal, setOrgTotal] = useState(0); + + const [viewIdentity, setViewIdentity] = useState(); const [currentPage, setCurrentPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); @@ -59,6 +64,10 @@ export const NetworkOrganizations: () => JSX.Element = () => { }; }, []); + useEffect(() => { + isMounted && slideID && setViewIdentity(slideID); + }, [slideID, isMounted]); + // Organizations useEffect(() => { isMounted && @@ -125,6 +134,10 @@ export const NetworkOrganizations: () => JSX.Element = () => { ), }, ], + onClick: () => { + setViewIdentity(org.did); + setSlideSearchParam(org.did); + }, }; }); @@ -176,6 +189,16 @@ export const NetworkOrganizations: () => JSX.Element = () => { fields={IdentityFilters} /> )} + {viewIdentity && ( + { + setViewIdentity(undefined); + setSlideSearchParam(null); + }} + /> + )} ); }; diff --git a/src/pages/Off-Chain/Routes.tsx b/src/pages/Off-Chain/Routes.tsx index 29ba4727..f1a3037d 100644 --- a/src/pages/Off-Chain/Routes.tsx +++ b/src/pages/Off-Chain/Routes.tsx @@ -3,6 +3,7 @@ import { NAMESPACES_PATH } from '../../interfaces'; import { OffChainDashboard } from './views/Dashboard'; import { OffChainData } from './views/Data'; import { OffChainDataTypes } from './views/DataTypes'; +import { OffChainGroups } from './views/Groups'; import { OffChainMessages } from './views/Messages'; export const OffChainRoutes: RouteObject = { @@ -21,6 +22,10 @@ export const OffChainRoutes: RouteObject = { path: 'data', element: , }, + { + path: 'groups', + element: , + }, { path: 'datatypes', element: , diff --git a/src/pages/Off-Chain/views/Groups.tsx b/src/pages/Off-Chain/views/Groups.tsx new file mode 100644 index 00000000..9dfee76b --- /dev/null +++ b/src/pages/Off-Chain/views/Groups.tsx @@ -0,0 +1,213 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FilterButton } from '../../../components/Filters/FilterButton'; +import { FilterModal } from '../../../components/Filters/FilterModal'; +import { Header } from '../../../components/Header'; +import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; +import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { GroupSlide } from '../../../components/Slides/GroupSlide'; +import { FFTableText } from '../../../components/Tables/FFTableText'; +import { DataTable } from '../../../components/Tables/Table'; +import { ApplicationContext } from '../../../contexts/ApplicationContext'; +import { DateFilterContext } from '../../../contexts/DateFilterContext'; +import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; +import { SnackbarContext } from '../../../contexts/SnackbarContext'; +import { + FF_Paths, + GroupFilters, + IDataTableRecord, + IGroup, + IPagedGroupResponse, +} from '../../../interfaces'; +import { DEFAULT_PADDING, DEFAULT_PAGE_LIMITS } from '../../../theme'; +import { fetchCatcher, getFFTime } from '../../../utils'; +import { hasOffchainEvent } from '../../../utils/wsEvents'; + +export const OffChainGroups: () => JSX.Element = () => { + const { newEvents, lastRefreshTime, clearNewEvents, selectedNamespace } = + useContext(ApplicationContext); + const { dateFilter } = useContext(DateFilterContext); + const { filterAnchor, setFilterAnchor, filterString } = + useContext(FilterContext); + const { slideID, setSlideSearchParam } = useContext(SlideContext); + const { reportFetchError } = useContext(SnackbarContext); + const { t } = useTranslation(); + const [isMounted, setIsMounted] = useState(false); + + // Data + const [groups, setGroups] = useState(); + // Data total + const [groupTotal, setGroupTotal] = useState(0); + const [viewGroup, setViewGroup] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); + + useEffect(() => { + setIsMounted(true); + return () => { + setIsMounted(false); + }; + }, []); + + useEffect(() => { + isMounted && + slideID && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.groupsById( + slideID + )}` + ) + .then((groupRes: IGroup) => { + setViewGroup(groupRes); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [slideID, isMounted]); + + // Group + useEffect(() => { + isMounted && + dateFilter && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${ + FF_Paths.groups + }?limit=${rowsPerPage}&count&skip=${rowsPerPage * currentPage}${ + dateFilter.filterString + }${filterString ?? ''}` + ) + .then((groupRes: IPagedGroupResponse) => { + if (isMounted) { + setGroups(groupRes.items); + setGroupTotal(groupRes.total); + } + }) + .catch((err) => { + reportFetchError(err); + }); + }, [ + rowsPerPage, + currentPage, + selectedNamespace, + dateFilter, + filterString, + lastRefreshTime, + isMounted, + ]); + + const groupColHeaders = [ + t('groupHash'), + t('name'), + t('numberOfMembers'), + t('created'), + ]; + + const groupRecords: IDataTableRecord[] | undefined = groups?.map( + (g, idx) => ({ + key: idx.toString(), + columns: [ + { + value: , + }, + { + value: g.name.length ? ( + + ) : ( + + ), + }, + { + value: ( + + ), + }, + { + value: , + }, + ], + onClick: () => { + setViewGroup(g); + setSlideSearchParam(g.hash); + }, + }) + ); + + return ( + <> +
+ + + ) => + setFilterAnchor(e.currentTarget) + } + /> + } + /> + + setCurrentPage(currentPage) + } + onHandleRowsPerPage={(rowsPerPage: number) => + setRowsPerPage(rowsPerPage) + } + stickyHeader={true} + minHeight="300px" + maxHeight="calc(100vh - 340px)" + records={groupRecords} + columnHeaders={groupColHeaders} + paginate={true} + emptyStateText={t('noGroupsToDisplay')} + dataTotal={groupTotal} + currentPage={currentPage} + rowsPerPage={rowsPerPage} + /> + + + {filterAnchor && ( + { + setFilterAnchor(null); + }} + fields={GroupFilters} + /> + )} + {viewGroup && ( + { + setViewGroup(undefined); + setSlideSearchParam(null); + }} + /> + )} + + ); +}; diff --git a/src/translations/en.json b/src/translations/en.json index e7ee679b..c8bfbe94 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -8,7 +8,9 @@ "address": "Address", "amount": "Amount", "api": "API", + "apiID": "API ID", "apis": "APIs", + "approvalID": "Approval ID", "approvals": "Approvals", "author": "Author", "authorKey": "Author Key", @@ -22,6 +24,7 @@ "blockchain": "Blockchain", "blockchainBatchPin": "Blockchain Batch Pin", "blockchainEvent": "Blockchain Event", + "blockchainEventID": "Blockchain Event ID", "blockchainEventReceived": "Blockchain Event Received", "blockchainEvents": "Blockchain Events", "blockchainId": "Blockchain ID", @@ -33,6 +36,7 @@ "broadcast": "Broadcast", "burn": "Burn", "cancel": "Cancel", + "cappedInfo": "Bar is capped", "caseSensitive": "Case sensitive", "certificate": "Certificate", "clear": "Clear", @@ -61,6 +65,7 @@ "dataHash": "Data Hash", "datatype": "Datatype", "datatypeConfirmed": "Datatype Confirmed", + "datatypeID": "Datatype ID", "datatypes": "Datatypes", "datatypeValue": "Datatype Value", "dataValue": "Data Value", @@ -91,13 +96,18 @@ "from": "From", "greaterThan": "Greater than", "greaterThanOrEqual": "Greater than or equal", + "group": "Group", "groupConfirmed": "Group Confirmed", + "groupHash": "Group Hash", "groupInit": "Group Init", + "groupMembers": "Group Members", + "groups": "Groups", "hash": "Hash", "id": "ID", "identities": "Identities", "identity": "Identity", "identityConfirmed": "Identity Confirmed", + "identityID": "Identity ID", "identityUpdated": "Identity Updated", "in": "In", "info": "Info", @@ -121,12 +131,14 @@ "location": "Location", "matches": "Matches", "message": "Message", + "messageClaim": "Message Claim", "messageConfirmed": "Message Confirmed", "messageData": "Message Data", "messageID": "Message ID", "messageRejected": "Message Rejected", "messages": "Messages", "messageTransaction": "Message Transaction", + "messageVerification": "Message Verification", "mint": "Mint", "myNode": "My Node", "myRecentTransactions": "My Recently Submitted Transactions", @@ -159,13 +171,16 @@ "noEvents": "No Events", "noEventsToDisplay": "No Events to Display", "noFromAccount": "No From Account", - "noIdentitiesToDisplay": "No Identities to Display", + "noGroupsToDisplay": "No Groups to Display", + "noIdentitiesInNs": "No Identities in Namespace", "noInterfacesToDisplay": "No Interfaces to Display", "noListenersToDisplay": "No Listeners to Display", "noMessageInTransfer": "No Message In Transfer", "noMessages": "No Messages", "noMessagesToDisplay": "No Messages to Display", + "noMessageVerification": "No Message Verification", "noMoreEvents": "No More Events", + "noNameSpecified": "No Name Specified", "none": "None", "noNodesToDisplay": "No Nodes to Display", "noOperations": "No Operations", @@ -192,7 +207,9 @@ "noTransactions": "No Transactions", "noTransactionsToDisplay": "No Transactions to Display", "noTransfers": "No Transfers", + "nsID": "Namespace ID", "nullAddress": "0x0000000000000000000000000000000000000000", + "numberOfMembers": "Number of Members", "offChain": "Off-Chain", "openApi": "Open API", "operation": "Operation", @@ -245,6 +262,7 @@ "sharedStorageUploadBatch": "Shared Storage Upload Batch", "sharedStorageUploadBlob": "Shared Storage Upload Blob", "signingKey": "Signing Key", + "someCappedInfo": "Some bars are capped for performance reasons", "source": "Source", "standard": "Standard", "startsWith": "Starts with", @@ -286,6 +304,7 @@ "transactionType": "Transaction Type", "transfer": "Transfer", "transferBroadcast": "Transfer Broadcast", + "transferID": "Transfer ID", "transferPrivate": "Transfer Private", "transfers": "Transfers", "transfersInPool": "Transfers In Pool", @@ -299,6 +318,7 @@ "uri": "URI", "validator": "Validator", "value": "Value", + "verifiers": "Verifiers", "version": "Version", "yourNode": "Your Node", "yourOrg": "Your Org" From 9cb525e27e7a8b9f4ddc556183ac96aaf5a110f7 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 7 Apr 2022 18:12:33 -0400 Subject: [PATCH 2/7] [misc-tweaks] adding isCapped field to histogram Signed-off-by: David Echelberger --- src/components/Charts/Histogram.tsx | 19 ++++-- src/interfaces/enums/blockchainEventTypes.ts | 1 + .../enums/{eventTypes.ts => eventTypes.tsx} | 66 +++++++++++++++++++ src/interfaces/enums/messageTypes.ts | 1 + src/interfaces/enums/operationTypes.ts | 1 + src/interfaces/enums/transactionTypes.ts | 1 + src/interfaces/enums/transferTypes.tsx | 1 + src/translations/en.json | 3 +- src/utils/charts.ts | 4 +- .../histograms/blockchainEventHistogram.ts | 2 + src/utils/histograms/eventHistogram.ts | 2 + src/utils/histograms/messageHistogram.ts | 2 + src/utils/histograms/operationHistogram.ts | 2 + src/utils/histograms/transactionHistogram.ts | 2 + src/utils/histograms/transferHistogram.ts | 2 + 15 files changed, 100 insertions(+), 9 deletions(-) rename src/interfaces/enums/{eventTypes.ts => eventTypes.tsx} (79%) diff --git a/src/components/Charts/Histogram.tsx b/src/components/Charts/Histogram.tsx index 9f8f8e93..7978fc1e 100644 --- a/src/components/Charts/Histogram.tsx +++ b/src/components/Charts/Histogram.tsx @@ -21,6 +21,9 @@ interface Props { isLoading: boolean; } +export const getIsCappedColor = (lightMode: boolean) => + lightMode ? '#000000' : '#FFFFFF'; + export const Histogram: React.FC = ({ colors, data, @@ -35,6 +38,8 @@ export const Histogram: React.FC = ({ const theme = useTheme(); const [xAxisValues, setXAxisValues] = useState<(string | number)[]>([]); + colors.push(getIsCappedColor(theme.palette.mode === 'light')); + useEffect(() => { if (data) { // Show x axis every other tick @@ -94,7 +99,7 @@ export const Histogram: React.FC = ({ anchor: 'bottom', direction: 'row', justify: false, - translateX: 20, + translateX: 0, translateY: 50, itemsSpacing: 2, itemWidth: 100, @@ -129,10 +134,10 @@ export const Histogram: React.FC = ({ }, }, }} - tooltip={({ data }) => { + tooltip={({ data }, idx) => { return ( = ({ > {keys.map((key, idx) => { return ( - - {`${key.toUpperCase()}: ${data[key] ?? 0}`} - + key !== 'isCapped' && ( + + {`${key.toUpperCase()}: ${data[key] ?? 0}`} + + ) ); })} diff --git a/src/interfaces/enums/blockchainEventTypes.ts b/src/interfaces/enums/blockchainEventTypes.ts index ee679bdb..8a8c682d 100644 --- a/src/interfaces/enums/blockchainEventTypes.ts +++ b/src/interfaces/enums/blockchainEventTypes.ts @@ -3,6 +3,7 @@ import { FFColors } from '../../theme'; export interface IHistBlockchainEventBucket { [BlockchainEventCategoryEnum.BLOCKCHAINEVENT]: number; + isCapped: number; } export interface IHistBlockchainEventTimeMap { diff --git a/src/interfaces/enums/eventTypes.ts b/src/interfaces/enums/eventTypes.tsx similarity index 79% rename from src/interfaces/enums/eventTypes.ts rename to src/interfaces/enums/eventTypes.tsx index 8a7b7263..d9c8d216 100644 --- a/src/interfaces/enums/eventTypes.ts +++ b/src/interfaces/enums/eventTypes.tsx @@ -1,4 +1,7 @@ import { t } from 'i18next'; +import { MsgButton } from '../../components/Buttons/MsgButton'; +import { PoolButton } from '../../components/Buttons/PoolButton'; +import { TxButton } from '../../components/Buttons/TxButton'; import { FFColors } from '../../theme'; import { getShortHash } from '../../utils'; import { IEvent } from '../api'; @@ -11,6 +14,7 @@ export interface IHistEventBucket { [EventCategoryEnum.BLOCKCHAIN]: number; [EventCategoryEnum.MESSAGES]: number; [EventCategoryEnum.TOKENS]: number; + isCapped: number; } export interface IHistEventTimeMap { @@ -50,6 +54,8 @@ interface IEventCategory { enrichedEventKey: string; enrichedEventString: (key: any) => string; nicename: string; + referenceIDName: string; + referenceIDButton: (refID: string) => JSX.Element; } export const getEnrichedEventText = (event: IEvent) => { @@ -75,6 +81,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Protocol ID=' + getShortHash(event.blockchainevent?.protocolId) : '' }`, + referenceIDName: 'blockchainEventID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.CONTRACT_API_CONFIRMED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -87,6 +97,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Address=' + getShortHash(event.contractAPI?.location?.address) : '' }`, + referenceIDName: 'apiID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.CONTRACT_INTERFACE_CONFIRMED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -99,6 +113,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Version=' + event.contractInterface?.version : '' }`, + referenceIDName: 'interfaceID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.DATATYPE_CONFIRMED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -113,6 +131,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Validator=' + event.datatype?.validator : '' }`, + referenceIDName: 'datatypeID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.IDENTITY_CONFIRMED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -123,6 +145,10 @@ export const FF_EVENTS_CATEGORY_MAP: { `${event.identity?.name}${ event.identity?.did ? ', DID=' + event.identity?.did : '' }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, + referenceIDName: 'identityID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.IDENTITY_UPDATED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -133,6 +159,10 @@ export const FF_EVENTS_CATEGORY_MAP: { `${event.identity?.name}${ event.identity?.did ? ', DID=' + event.identity?.did : '' }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, + referenceIDName: 'identityID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.NS_CONFIRMED]: { category: EventCategoryEnum.BLOCKCHAIN, @@ -145,6 +175,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Type=' + event.namespaceDetails?.type : '' }`, + referenceIDName: 'nsID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, // Message Events [FF_EVENTS.MSG_CONFIRMED]: { @@ -159,6 +193,10 @@ export const FF_EVENTS_CATEGORY_MAP: { }]${ event.message?.header.tag ? ', Tag=' + event.message?.header.tag : '' }`, + referenceIDName: 'messageID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.MSG_REJECTED]: { category: EventCategoryEnum.MESSAGES, @@ -172,6 +210,10 @@ export const FF_EVENTS_CATEGORY_MAP: { }]${ event.message?.header.tag ? ', Tag=' + event.message?.header.tag : '' }`, + referenceIDName: 'messageID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.TX_SUBMITTED]: { category: EventCategoryEnum.MESSAGES, @@ -186,6 +228,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ?.map((bid) => getShortHash(bid)) .join(', ')}]` : t('transactionSubmitted'), + referenceIDName: 'transactionID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, // Token Events [FF_EVENTS.TOKEN_POOL_CONFIRMED]: { @@ -199,6 +245,10 @@ export const FF_EVENTS_CATEGORY_MAP: { ? ', Connector=' + event.tokenPool?.connector : '' }${event.tokenPool?.type ? ', Type=' + event.tokenPool?.type : ''}`, + referenceIDName: 'poolID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.TOKEN_APPROVAL_CONFIRMED]: { category: EventCategoryEnum.TOKENS, @@ -209,6 +259,10 @@ export const FF_EVENTS_CATEGORY_MAP: { `Key=${getShortHash( event.tokenApproval?.key ?? '' )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, + referenceIDName: 'approvalID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.TOKEN_APPROVAL_OP_FAILED]: { category: EventCategoryEnum.TOKENS, @@ -219,6 +273,10 @@ export const FF_EVENTS_CATEGORY_MAP: { `Key=${getShortHash( event.tokenApproval?.key ?? '' )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, + referenceIDName: 'approvalID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.TOKEN_TRANSFER_CONFIRMED]: { category: EventCategoryEnum.TOKENS, @@ -242,6 +300,10 @@ export const FF_EVENTS_CATEGORY_MAP: { }, Amount=${event.tokenTransfer?.amount ?? ''}, Signer=${getShortHash( event.tokenTransfer?.key ?? '' )}`, + referenceIDName: 'transferID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, [FF_EVENTS.TOKEN_TRANSFER_FAILED]: { category: EventCategoryEnum.TOKENS, @@ -250,5 +312,9 @@ export const FF_EVENTS_CATEGORY_MAP: { enrichedEventKey: 'tokenTransfer', enrichedEventString: (event: IEvent): string => `${t('event')} ID=${event.id}`, + referenceIDName: 'transferID', + referenceIDButton: (refID: string): JSX.Element => ( + + ), }, }; diff --git a/src/interfaces/enums/messageTypes.ts b/src/interfaces/enums/messageTypes.ts index e2848474..4d7c6137 100644 --- a/src/interfaces/enums/messageTypes.ts +++ b/src/interfaces/enums/messageTypes.ts @@ -21,6 +21,7 @@ export interface IHistMsgBucket { [MsgCategoryEnum.BROADCAST]: number; [MsgCategoryEnum.DEFINITON]: number; [MsgCategoryEnum.PRIVATE]: number; + isCapped: number; } export interface IHistMsgTimeMap { diff --git a/src/interfaces/enums/operationTypes.ts b/src/interfaces/enums/operationTypes.ts index 79f85e01..f571bc9a 100644 --- a/src/interfaces/enums/operationTypes.ts +++ b/src/interfaces/enums/operationTypes.ts @@ -21,6 +21,7 @@ export interface IHistOpBucket { [OpCategoryEnum.BLOCKCHAIN]: number; [OpCategoryEnum.MESSAGES]: number; [OpCategoryEnum.TOKENS]: number; + isCapped: number; } export interface IHistOpTimeMap { diff --git a/src/interfaces/enums/transactionTypes.ts b/src/interfaces/enums/transactionTypes.ts index 2a682610..b3799211 100644 --- a/src/interfaces/enums/transactionTypes.ts +++ b/src/interfaces/enums/transactionTypes.ts @@ -21,6 +21,7 @@ export interface IHistTxBucket { [TxCategoryEnum.BLOCKCHAIN]: number; [TxCategoryEnum.MESSAGES]: number; [TxCategoryEnum.TOKENS]: number; + isCapped: number; } export interface IHistTxTimeMap { diff --git a/src/interfaces/enums/transferTypes.tsx b/src/interfaces/enums/transferTypes.tsx index 0b540acc..922907e0 100644 --- a/src/interfaces/enums/transferTypes.tsx +++ b/src/interfaces/enums/transferTypes.tsx @@ -24,6 +24,7 @@ export interface IHistTransferBucket { [TransferCategoryEnum.MINT]: number; [TransferCategoryEnum.BURN]: number; [TransferCategoryEnum.TRANSFER]: number; + isCapped: number; } export interface IHistTransferTimeMap { diff --git a/src/translations/en.json b/src/translations/en.json index c8bfbe94..c4591cb8 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -36,7 +36,6 @@ "broadcast": "Broadcast", "burn": "Burn", "cancel": "Cancel", - "cappedInfo": "Bar is capped", "caseSensitive": "Case sensitive", "certificate": "Certificate", "clear": "Clear", @@ -115,6 +114,7 @@ "inputAndOutput": "Input and Output", "interfaceID": "Interface ID", "interfaces": "Interfaces", + "isCapped": "Is Capped", "joined": "Joined", "key": "Key", "last1Hour": "Last 1 Hour", @@ -262,7 +262,6 @@ "sharedStorageUploadBatch": "Shared Storage Upload Batch", "sharedStorageUploadBlob": "Shared Storage Upload Blob", "signingKey": "Signing Key", - "someCappedInfo": "Some bars are capped for performance reasons", "source": "Source", "standard": "Standard", "startsWith": "Starts with", diff --git a/src/utils/charts.ts b/src/utils/charts.ts index 83fc9367..a605864c 100644 --- a/src/utils/charts.ts +++ b/src/utils/charts.ts @@ -20,5 +20,7 @@ export const makeColorArray = (map: { [key: string]: IBlockchainCategory }) => { }; export const makeKeyArray = (map: { [key: string]: IBlockchainCategory }) => { - return Array.from(new Set(Object.keys(map).map((k) => map[k]?.category))); + return Array.from( + new Set(Object.keys(map).map((k) => map[k]?.category)).add('isCapped') + ); }; diff --git a/src/utils/histograms/blockchainEventHistogram.ts b/src/utils/histograms/blockchainEventHistogram.ts index f925ee83..11aa57b0 100644 --- a/src/utils/histograms/blockchainEventHistogram.ts +++ b/src/utils/histograms/blockchainEventHistogram.ts @@ -12,10 +12,12 @@ export const makeBlockchainEventHistogram = ( histList.map((hist) => { timeMap[hist.timestamp] = { [BlockchainEventCategoryEnum.BLOCKCHAINEVENT]: 0, + isCapped: 0, }; timeMap[hist.timestamp][BlockchainEventCategoryEnum.BLOCKCHAINEVENT] = timeMap[hist.timestamp][BlockchainEventCategoryEnum.BLOCKCHAINEVENT] + +hist.count; + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); const finalHistogram: BarDatum[] = []; diff --git a/src/utils/histograms/eventHistogram.ts b/src/utils/histograms/eventHistogram.ts index 2880caea..dc5e71f8 100644 --- a/src/utils/histograms/eventHistogram.ts +++ b/src/utils/histograms/eventHistogram.ts @@ -14,6 +14,7 @@ export const makeEventHistogram = (histList: IMetric[]): BarDatum[] => { [EventCategoryEnum.BLOCKCHAIN]: 0, [EventCategoryEnum.MESSAGES]: 0, [EventCategoryEnum.TOKENS]: 0, + isCapped: 0, }; hist.types.map((type) => { switch (FF_EVENTS_CATEGORY_MAP[type.type as FF_EVENTS]?.category) { @@ -33,6 +34,7 @@ export const makeEventHistogram = (histList: IMetric[]): BarDatum[] => { timeMap[hist.timestamp][EventCategoryEnum.TOKENS] + +type.count; break; } + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); }); diff --git a/src/utils/histograms/messageHistogram.ts b/src/utils/histograms/messageHistogram.ts index 11416d2c..2d2327a0 100644 --- a/src/utils/histograms/messageHistogram.ts +++ b/src/utils/histograms/messageHistogram.ts @@ -14,6 +14,7 @@ export const makeMsgHistogram = (histList: IMetric[]): BarDatum[] => { [MsgCategoryEnum.BROADCAST]: 0, [MsgCategoryEnum.DEFINITON]: 0, [MsgCategoryEnum.PRIVATE]: 0, + isCapped: 0, }; hist.types.map((type) => { switch (FF_MESSAGES_CATEGORY_MAP[type.type as FF_MESSAGES]?.category) { @@ -33,6 +34,7 @@ export const makeMsgHistogram = (histList: IMetric[]): BarDatum[] => { timeMap[hist.timestamp][MsgCategoryEnum.PRIVATE] + +type.count; break; } + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); }); diff --git a/src/utils/histograms/operationHistogram.ts b/src/utils/histograms/operationHistogram.ts index 46890aab..c1c00bb9 100644 --- a/src/utils/histograms/operationHistogram.ts +++ b/src/utils/histograms/operationHistogram.ts @@ -14,6 +14,7 @@ export const makeOperationHistogram = (histList: IMetric[]): BarDatum[] => { [OpCategoryEnum.BLOCKCHAIN]: 0, [OpCategoryEnum.MESSAGES]: 0, [OpCategoryEnum.TOKENS]: 0, + isCapped: 0, }; hist.types.map((type) => { switch (FF_OP_CATEGORY_MAP[type.type as FF_OPS]?.category) { @@ -33,6 +34,7 @@ export const makeOperationHistogram = (histList: IMetric[]): BarDatum[] => { timeMap[hist.timestamp][OpCategoryEnum.TOKENS] + +type.count; break; } + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); }); diff --git a/src/utils/histograms/transactionHistogram.ts b/src/utils/histograms/transactionHistogram.ts index 1bb87c4e..c3f2f401 100644 --- a/src/utils/histograms/transactionHistogram.ts +++ b/src/utils/histograms/transactionHistogram.ts @@ -14,6 +14,7 @@ export const makeTxHistogram = (histList: IMetric[]): BarDatum[] => { [TxCategoryEnum.BLOCKCHAIN]: 0, [TxCategoryEnum.MESSAGES]: 0, [TxCategoryEnum.TOKENS]: 0, + isCapped: 0, }; hist.types.map((type) => { switch (FF_TX_CATEGORY_MAP[type.type as FF_TX]?.category) { @@ -33,6 +34,7 @@ export const makeTxHistogram = (histList: IMetric[]): BarDatum[] => { timeMap[hist.timestamp][TxCategoryEnum.TOKENS] + +type.count; break; } + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); }); diff --git a/src/utils/histograms/transferHistogram.ts b/src/utils/histograms/transferHistogram.ts index 8cff0436..5f39a1f7 100644 --- a/src/utils/histograms/transferHistogram.ts +++ b/src/utils/histograms/transferHistogram.ts @@ -14,6 +14,7 @@ export const makeTransferHistogram = (histList: IMetric[]): BarDatum[] => { [TransferCategoryEnum.MINT]: 0, [TransferCategoryEnum.BURN]: 0, [TransferCategoryEnum.TRANSFER]: 0, + isCapped: 0, }; hist.types.map((type) => { switch (FF_TRANSFER_CATEGORY_MAP[type.type as FF_TRANSFERS]?.category) { @@ -34,6 +35,7 @@ export const makeTransferHistogram = (histList: IMetric[]): BarDatum[] => { +type.count; break; } + hist.isCapped && (timeMap[hist.timestamp].isCapped = 1); }); }); From 0e83a1d4c8129b9b1faad04767c4ace375851989 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 7 Apr 2022 19:41:28 -0400 Subject: [PATCH 3/7] [misc-tweaks] approvals and namespaces page Signed-off-by: David Echelberger --- .../Accordions/MessageAccordion.tsx | 2 +- src/components/Lists/ApprovalList.tsx | 80 +++++++ src/components/Lists/EventList.tsx | 1 + src/components/Lists/NamespaceList.tsx | 71 ++++++ src/components/Navigation/NetworkNav.tsx | 6 + src/components/Navigation/TokensNav.tsx | 6 + src/components/Slides/ApprovalSlide.tsx | 53 +++++ src/components/Slides/NamespaceSlide.tsx | 49 ++++ src/interfaces/api.ts | 16 +- src/interfaces/filters.ts | 25 ++ src/interfaces/navigation.ts | 11 +- src/pages/Network/Routes.tsx | 5 + src/pages/Tokens/Routes.tsx | 5 + src/pages/Tokens/views/Approvals.tsx | 225 ++++++++++++++++++ src/pages/Tokens/views/Balances.tsx | 2 +- src/translations/en.json | 9 + src/utils/wsEvents.tsx | 6 + 17 files changed, 568 insertions(+), 4 deletions(-) create mode 100644 src/components/Lists/ApprovalList.tsx create mode 100644 src/components/Lists/NamespaceList.tsx create mode 100644 src/components/Slides/ApprovalSlide.tsx create mode 100644 src/components/Slides/NamespaceSlide.tsx create mode 100644 src/pages/Tokens/views/Approvals.tsx diff --git a/src/components/Accordions/MessageAccordion.tsx b/src/components/Accordions/MessageAccordion.tsx index 82cb565a..05feec0b 100644 --- a/src/components/Accordions/MessageAccordion.tsx +++ b/src/components/Accordions/MessageAccordion.tsx @@ -87,7 +87,7 @@ export const MessageAccordion: React.FC = ({ /> - + {accInfo.map((info, idx) => ( = ({ approval }) => { + const { t } = useTranslation(); + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + approval && + setDataList([ + { + label: t('id'), + value: , + button: , + }, + { + label: t('signingKey'), + value: , + button: , + }, + { + label: t('operator'), + value: , + button: , + }, + { + label: t('pool'), + value: , + button: ( + <> + + + + ), + }, + { + label: t('protocolID'), + value: , + button: , + }, + { + label: t('approved?'), + value: ( + + ), + }, + { + label: t('created'), + value: , + }, + ]); + }, [approval]); + + return ( + <> + {dataList.map((d, idx) => ( + + ))} + + ); +}; diff --git a/src/components/Lists/EventList.tsx b/src/components/Lists/EventList.tsx index 2a4ade7b..798b521b 100644 --- a/src/components/Lists/EventList.tsx +++ b/src/components/Lists/EventList.tsx @@ -35,6 +35,7 @@ export const EventList: React.FC = ({ event, showTxLink = true }) => { button: ( <> {FF_EVENTS_CATEGORY_MAP[event.type].referenceIDButton( + selectedNamespace, event.reference )} diff --git a/src/components/Lists/NamespaceList.tsx b/src/components/Lists/NamespaceList.tsx new file mode 100644 index 00000000..6ec36b9a --- /dev/null +++ b/src/components/Lists/NamespaceList.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { INamespace } from '../../interfaces'; +import { IDataListItem } from '../../interfaces/lists'; +import { FFCopyButton } from '../Buttons/CopyButton'; +import { MsgButton } from '../Buttons/MsgButton'; +import { FFListItem } from './FFListItem'; +import { FFListText } from './FFListText'; +import { FFListTimestamp } from './FFListTimestamp'; +import { FFSkeletonList } from './FFSkeletonList'; + +interface Props { + ns?: INamespace; +} + +export const NamespaceList: React.FC = ({ ns }) => { + const { t } = useTranslation(); + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + ns && + setDataList([ + { + label: t('id'), + value: , + button: , + }, + { + label: t('description'), + value: ( + + ), + }, + { + label: t('type'), + value: , + }, + { + label: t('messageID'), + value: ns.message ? ( + + ) : ( + + ), + button: ns.message ? ( + <> + + + + ) : ( + <> + ), + }, + { + label: t('created'), + value: , + }, + ]); + }, [ns]); + + return ( + <> + {dataList.map((d, idx) => ( + + ))} + + ); +}; diff --git a/src/components/Navigation/NetworkNav.tsx b/src/components/Navigation/NetworkNav.tsx index 2823dc33..a242d7c8 100644 --- a/src/components/Navigation/NetworkNav.tsx +++ b/src/components/Navigation/NetworkNav.tsx @@ -32,6 +32,7 @@ export const NetworkNav = () => { const orgPath = FF_NAV_PATHS.networkOrgsPath(selectedNamespace); const nodePath = FF_NAV_PATHS.networkNodesPath(selectedNamespace); const identitiesPath = FF_NAV_PATHS.networkIdentitiesPath(selectedNamespace); + const namespacesPath = FF_NAV_PATHS.networkNamespacesPath(selectedNamespace); const navItems: INavItem[] = [ { @@ -54,6 +55,11 @@ export const NetworkNav = () => { action: () => navigate(identitiesPath), itemIsActive: pathname === identitiesPath, }, + { + name: t('namespaces'), + action: () => navigate(namespacesPath), + itemIsActive: pathname === namespacesPath, + }, ]; return ( diff --git a/src/components/Navigation/TokensNav.tsx b/src/components/Navigation/TokensNav.tsx index 059597a6..0aac4d94 100644 --- a/src/components/Navigation/TokensNav.tsx +++ b/src/components/Navigation/TokensNav.tsx @@ -32,6 +32,7 @@ export const TokensNav = () => { const transfersPath = FF_NAV_PATHS.tokensTransfersPath(selectedNamespace); const poolsPath = FF_NAV_PATHS.tokensPoolsPath(selectedNamespace); const balancesPath = FF_NAV_PATHS.tokensBalancesPath(selectedNamespace); + const approvalsPath = FF_NAV_PATHS.tokensApprovalsPath(selectedNamespace); const navItems: INavItem[] = [ { @@ -54,6 +55,11 @@ export const TokensNav = () => { action: () => navigate(balancesPath), itemIsActive: pathname === balancesPath, }, + { + name: t('approvals'), + action: () => navigate(approvalsPath), + itemIsActive: pathname === approvalsPath, + }, ]; return ( diff --git a/src/components/Slides/ApprovalSlide.tsx b/src/components/Slides/ApprovalSlide.tsx new file mode 100644 index 00000000..16d62382 --- /dev/null +++ b/src/components/Slides/ApprovalSlide.tsx @@ -0,0 +1,53 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ITokenApproval } from '../../interfaces'; +import { DEFAULT_PADDING } from '../../theme'; +import { getShortHash } from '../../utils'; +import { ApprovalList } from '../Lists/ApprovalList'; +import { DisplaySlide } from './DisplaySlide'; +import { SlideHeader } from './SlideHeader'; + +interface Props { + approval: ITokenApproval; + open: boolean; + onClose: () => void; +} + +export const ApprovalSlide: React.FC = ({ approval, open, onClose }) => { + const { t } = useTranslation(); + + return ( + <> + + + {/* Header */} + + {/* Data list */} + + + + + + + ); +}; diff --git a/src/components/Slides/NamespaceSlide.tsx b/src/components/Slides/NamespaceSlide.tsx new file mode 100644 index 00000000..338451bf --- /dev/null +++ b/src/components/Slides/NamespaceSlide.tsx @@ -0,0 +1,49 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { INamespace } from '../../interfaces'; +import { DEFAULT_PADDING } from '../../theme'; +import { NamespaceList } from '../Lists/NamespaceList'; +import { DisplaySlide } from './DisplaySlide'; +import { SlideHeader } from './SlideHeader'; + +interface Props { + ns: INamespace; + open: boolean; + onClose: () => void; +} + +export const NamespaceSlide: React.FC = ({ ns, open, onClose }) => { + const { t } = useTranslation(); + + return ( + <> + + + {/* Title */} + + {/* Namespace list */} + + + + + + + ); +}; diff --git a/src/interfaces/api.ts b/src/interfaces/api.ts index 060f1108..97495861 100644 --- a/src/interfaces/api.ts +++ b/src/interfaces/api.ts @@ -216,11 +216,11 @@ export interface IMetricType { export interface INamespace { id: string; + message?: string; name: string; description: string; type: string; created: string; - confirmed: string; } export interface INode { @@ -346,6 +346,13 @@ export interface IPagedMessageResponse { total: number; } +export interface IPagesNamespaceResponse { + pageParam: number; + count: number; + items: INamespace[]; + total: number; +} + export interface IPagedNodeResponse { pageParam: number; count: number; @@ -374,6 +381,13 @@ export interface IPagedSubscriptionsResponse { total: number; } +export interface IPagedTokenApprovalResponse { + pageParam: number; + count: number; + items: ITokenApproval[]; + total: number; +} + export interface IPagedTokenBalanceResponse { pageParam: number; count: number; diff --git a/src/interfaces/filters.ts b/src/interfaces/filters.ts index d168972e..76432db2 100644 --- a/src/interfaces/filters.ts +++ b/src/interfaces/filters.ts @@ -31,6 +31,21 @@ export enum TimeFilterEnum { export const ApiFilters = ['id', 'name', 'interface']; +export const ApprovalFilters = [ + 'localid', + 'pool', + 'connector', + 'namespace', + 'key', + 'operator', + 'approved', + 'protocolid', + 'created', + 'tx.type', + 'tx.id', + 'blockchainevent', +]; + export const BalanceFilters = [ 'pool', 'tokenindex', @@ -133,6 +148,16 @@ export const MessageFilters = [ 'batch', ]; +export const NamespaceFilters = [ + 'id', + 'message', + 'type', + 'name', + 'description', + 'created', + 'confirmed', +]; + export const OperationFilters = [ 'id', 'tx', diff --git a/src/interfaces/navigation.ts b/src/interfaces/navigation.ts index b67f7025..f5aa9ad6 100644 --- a/src/interfaces/navigation.ts +++ b/src/interfaces/navigation.ts @@ -25,6 +25,7 @@ export interface INavItem { export const ACTIVITY_PATH = 'activity'; export const APIS_PATH = 'apis'; +export const APPROVALS_PATH = 'approvals'; export const BALANCES_PATH = 'balances'; export const BLOCKCHAIN_PATH = 'blockchain'; export const DATA_PATH = 'data'; @@ -120,7 +121,7 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${OPERATIONS_PATH}?filters=error=!=&filters=type==${FF_OPS.TOKEN_TRANSFER}`, tokensTransfersPathLocalID: (ns: string, localID?: string) => `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${TRANSFERS_PATH}${ - localID ? `?filters=localid==${localID}` : '' + localID ? `?slide=${localID}` : '' }`, tokensTransfersPathByKeyAndPool: (ns: string, key: string, pool: string) => `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${TRANSFERS_PATH}${`?filters=key==${key}&filters=pool==${pool}`}`, @@ -132,6 +133,10 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${BALANCES_PATH}`, tokensBalancesPathByPool: (ns: string, poolID: string) => `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${BALANCES_PATH}?filters=pool==${poolID}`, + tokensApprovalsPath: (ns: string, approvalID?: string) => + `/${NAMESPACES_PATH}/${ns}/${TOKENS_PATH}/${APPROVALS_PATH}${ + approvalID ? '?slide=' + approvalID : '' + }`, // Network networkPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}`, networkOrgsPath: (ns: string, slideID?: string) => @@ -146,6 +151,10 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${IDENTITIES_PATH}${ slideID ? '?slide=' + slideID : '' }`, + networkNamespacesPath: (ns: string, slideID?: string) => + `/${NAMESPACES_PATH}/${ns}/${NETWORK_PATH}/${NAMESPACES_PATH}${ + slideID ? '?slide=' + slideID : '' + }`, // My Node myNodePath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${MY_NODES_PATH}`, myNodeSubscriptionsPath: (ns: string) => diff --git a/src/pages/Network/Routes.tsx b/src/pages/Network/Routes.tsx index 5bdbbdd4..e44f7d91 100644 --- a/src/pages/Network/Routes.tsx +++ b/src/pages/Network/Routes.tsx @@ -2,6 +2,7 @@ import { RouteObject } from 'react-router-dom'; import { NAMESPACES_PATH } from '../../interfaces'; import { NetworkMapDashboard } from './views/Dashboard'; import { NetworkIdentities } from './views/Identities'; +import { NetworkNamespaces } from './views/Namespaces'; import { NetworkNodes } from './views/Nodes'; import { NetworkOrganizations } from './views/Organizations'; @@ -25,5 +26,9 @@ export const NetworkRoutes: RouteObject = { path: 'identities', element: , }, + { + path: 'namespaces', + element: , + }, ], }; diff --git a/src/pages/Tokens/Routes.tsx b/src/pages/Tokens/Routes.tsx index 3d79e125..fb60c831 100644 --- a/src/pages/Tokens/Routes.tsx +++ b/src/pages/Tokens/Routes.tsx @@ -1,5 +1,6 @@ import { RouteObject } from 'react-router-dom'; import { NAMESPACES_PATH } from '../../interfaces'; +import { TokensApprovals } from './views/Approvals'; import { TokensBalances } from './views/Balances'; import { TokensDashboard } from './views/Dashboard'; import { PoolDetails } from './views/PoolDetails'; @@ -30,5 +31,9 @@ export const TokensRoutes: RouteObject = { path: 'balances', element: , }, + { + path: 'approvals', + element: , + }, ], }; diff --git a/src/pages/Tokens/views/Approvals.tsx b/src/pages/Tokens/views/Approvals.tsx new file mode 100644 index 00000000..8412241d --- /dev/null +++ b/src/pages/Tokens/views/Approvals.tsx @@ -0,0 +1,225 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FilterButton } from '../../../components/Filters/FilterButton'; +import { FilterModal } from '../../../components/Filters/FilterModal'; +import { Header } from '../../../components/Header'; +import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; +import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { ApprovalSlide } from '../../../components/Slides/ApprovalSlide'; +import { FFTableText } from '../../../components/Tables/FFTableText'; +import { DataTable } from '../../../components/Tables/Table'; +import { ApplicationContext } from '../../../contexts/ApplicationContext'; +import { DateFilterContext } from '../../../contexts/DateFilterContext'; +import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; +import { SnackbarContext } from '../../../contexts/SnackbarContext'; +import { + ApprovalFilters, + FF_Paths, + IDataTableRecord, + IPagedTokenApprovalResponse, + ITokenApproval, +} from '../../../interfaces'; +import { DEFAULT_PADDING, DEFAULT_PAGE_LIMITS } from '../../../theme'; +import { fetchCatcher, getFFTime } from '../../../utils'; +import { hasApprovalEvent } from '../../../utils/wsEvents'; + +export const TokensApprovals: () => JSX.Element = () => { + const { newEvents, lastRefreshTime, clearNewEvents, selectedNamespace } = + useContext(ApplicationContext); + const { dateFilter } = useContext(DateFilterContext); + const { filterAnchor, setFilterAnchor, filterString } = + useContext(FilterContext); + const { slideID, setSlideSearchParam } = useContext(SlideContext); + const { reportFetchError } = useContext(SnackbarContext); + const { t } = useTranslation(); + const [isMounted, setIsMounted] = useState(false); + // Token approvals + const [tokenApprovals, setTokenApprovals] = useState(); + // Token approvals totals + const [tokenApprovalsTotal, setTokenApprovalsTotal] = useState(0); + const [viewApproval, setViewApproval] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); + + useEffect(() => { + setIsMounted(true); + return () => { + setIsMounted(false); + }; + }, []); + + useEffect(() => { + isMounted && + slideID && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.tokenApprovals}?localid=${slideID}` + ) + .then((approvalRes: ITokenApproval[]) => { + isMounted && + approvalRes.length === 1 && + setViewApproval(approvalRes[0]); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [slideID, isMounted]); + + // Token approvals + useEffect(() => { + isMounted && + dateFilter && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${ + FF_Paths.tokenApprovals + }?limit=${rowsPerPage}&count&skip=${rowsPerPage * currentPage}${ + dateFilter.filterString + }${filterString ?? ''}` + ) + .then((tokenApprovalRes: IPagedTokenApprovalResponse) => { + setTokenApprovals(tokenApprovalRes.items); + setTokenApprovalsTotal(tokenApprovalRes.total); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [ + rowsPerPage, + currentPage, + selectedNamespace, + dateFilter, + filterString, + lastRefreshTime, + isMounted, + ]); + + const tokenApprovalColHeaders = [ + t('id'), + t('signingKey'), + t('operator'), + t('pool'), + t('protocolID'), + t('approved?'), + t('created'), + ]; + const tokenApprovalRecords: IDataTableRecord[] | undefined = + tokenApprovals?.map((approval, idx) => ({ + key: idx.toString(), + columns: [ + { + value: , + }, + { + value: , + }, + { + value: , + }, + { + value: , + }, + { + value: , + }, + { + value: ( + + ), + }, + { + value: ( + + ), + }, + ], + onClick: () => { + setViewApproval(approval); + setSlideSearchParam(approval.localId); + }, + })); + + return ( + <> +
+ + + ) => + setFilterAnchor(e.currentTarget) + } + /> + } + /> + + setCurrentPage(currentPage) + } + onHandleRowsPerPage={(rowsPerPage: number) => + setRowsPerPage(rowsPerPage) + } + stickyHeader={true} + minHeight="300px" + maxHeight="calc(100vh - 340px)" + records={tokenApprovalRecords} + columnHeaders={tokenApprovalColHeaders} + paginate={true} + emptyStateText={t('noTokenApprovalsToDisplay')} + dataTotal={tokenApprovalsTotal} + currentPage={currentPage} + rowsPerPage={rowsPerPage} + /> + + + {filterAnchor && ( + { + setFilterAnchor(null); + }} + fields={ApprovalFilters} + /> + )} + {viewApproval && ( + { + setViewApproval(undefined); + setSlideSearchParam(null); + }} + /> + )} + + ); +}; diff --git a/src/pages/Tokens/views/Balances.tsx b/src/pages/Tokens/views/Balances.tsx index 54ce810c..f7d62e0c 100644 --- a/src/pages/Tokens/views/Balances.tsx +++ b/src/pages/Tokens/views/Balances.tsx @@ -127,7 +127,7 @@ export const TokensBalances: () => JSX.Element = () => { t('pool'), t('uri'), t('connector'), - t('updates'), + t('updated'), ]; const tokenBalanceRecords: IDataTableRecord[] | undefined = tokenBalances?.map((balance, idx) => ({ diff --git a/src/translations/en.json b/src/translations/en.json index c4591cb8..55501b68 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -10,8 +10,10 @@ "api": "API", "apiID": "API ID", "apis": "APIs", + "approvalFor": "Approval For:", "approvalID": "Approval ID", "approvals": "Approvals", + "approved?": "Approved?", "author": "Author", "authorKey": "Author Key", "balance": "Balance", @@ -146,9 +148,11 @@ "nameOrID": "Name/ID", "namespace": "Namespace", "namespaceConfirmed": "Namespace Confirmed", + "namespaces": "Namespaces", "network": "Network", "networkMap": "Network Map", "newEvents": "New Events", + "no": "No", "noAccounts": "No Accounts", "noActivity": "No Activity", "noAddressForListener": "No Address for Listener", @@ -166,6 +170,7 @@ "nodeID": "Node ID", "nodeName": "Node Name", "nodes": "Nodes", + "noDescription": "No Description", "noDescriptionForInterface": "No Description for Interface", "noDescriptionForListener": "No Description for Listener", "noEvents": "No Events", @@ -175,11 +180,13 @@ "noIdentitiesInNs": "No Identities in Namespace", "noInterfacesToDisplay": "No Interfaces to Display", "noListenersToDisplay": "No Listeners to Display", + "noMessageID": "No Message ID", "noMessageInTransfer": "No Message In Transfer", "noMessages": "No Messages", "noMessagesToDisplay": "No Messages to Display", "noMessageVerification": "No Message Verification", "noMoreEvents": "No More Events", + "noNamespaces": "No Namespaces", "noNameSpecified": "No Name Specified", "none": "None", "noNodesToDisplay": "No Nodes to Display", @@ -197,6 +204,7 @@ "notMatches": "Does not match", "noToAccount": "No To Account", "noTokenAccounts": "No Token Accounts", + "noTokenApprovalsToDisplay": "No Token Approvals to Display", "noTokenBalancesToDisplay": "No Token Balances to Display", "noTokenPools": "No Token Pools", "noTokenPoolsToDisplay": "No Token Pools to Display", @@ -319,6 +327,7 @@ "value": "Value", "verifiers": "Verifiers", "version": "Version", + "yes": "Yes", "yourNode": "Your Node", "yourOrg": "Your Org" } diff --git a/src/utils/wsEvents.tsx b/src/utils/wsEvents.tsx index 3d3029e2..44833837 100644 --- a/src/utils/wsEvents.tsx +++ b/src/utils/wsEvents.tsx @@ -75,6 +75,12 @@ export const hasTransferEvent = (events: INewEventSet) => { events[FF_EVENTS.TOKEN_TRANSFER_FAILED] ); }; +export const hasApprovalEvent = (events: INewEventSet) => { + return ( + events[FF_EVENTS.TOKEN_APPROVAL_CONFIRMED] || + events[FF_EVENTS.TOKEN_APPROVAL_OP_FAILED] + ); +}; export const hasTxEvent = (events: INewEventSet) => { return events[FF_EVENTS.TX_SUBMITTED]; From 95859ccdc4b78c9abed8bf769c1e428010881a4e Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 7 Apr 2022 19:44:28 -0400 Subject: [PATCH 4/7] [misc-tweaks] event reference linking and adding namespace page Signed-off-by: David Echelberger --- .../Accordions/MessageAccordion.tsx | 1 - .../Buttons/EventReferenceButton.tsx | 25 ++ src/components/Lists/ApprovalList.tsx | 6 +- src/components/Lists/NamespaceList.tsx | 6 +- src/interfaces/enums/eventTypes.tsx | 95 +++++--- src/pages/Network/views/Identities.tsx | 18 +- src/pages/Network/views/Namespaces.tsx | 213 ++++++++++++++++++ 7 files changed, 324 insertions(+), 40 deletions(-) create mode 100644 src/components/Buttons/EventReferenceButton.tsx create mode 100644 src/pages/Network/views/Namespaces.tsx diff --git a/src/components/Accordions/MessageAccordion.tsx b/src/components/Accordions/MessageAccordion.tsx index 05feec0b..62e1a3db 100644 --- a/src/components/Accordions/MessageAccordion.tsx +++ b/src/components/Accordions/MessageAccordion.tsx @@ -14,7 +14,6 @@ import { IDataWithHeader, IMessage, } from '../../interfaces'; -import { DEFAULT_PADDING } from '../../theme'; import { getFFTime } from '../../utils'; import { LaunchButton } from '../Buttons/LaunchButton'; import { MsgStatusChip } from '../Chips/MsgStatusChip'; diff --git a/src/components/Buttons/EventReferenceButton.tsx b/src/components/Buttons/EventReferenceButton.tsx new file mode 100644 index 00000000..3d8ee87a --- /dev/null +++ b/src/components/Buttons/EventReferenceButton.tsx @@ -0,0 +1,25 @@ +import LaunchIcon from '@mui/icons-material/Launch'; +import { IconButton } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { FFColors } from '../../theme'; + +interface Props { + link: string; + small?: boolean; +} + +export const EventReferenceButton: React.FC = ({ + link, + small = false, +}) => { + const navigate = useNavigate(); + return ( + navigate(link)} + > + + + ); +}; diff --git a/src/components/Lists/ApprovalList.tsx b/src/components/Lists/ApprovalList.tsx index 4fade45a..c8e023a4 100644 --- a/src/components/Lists/ApprovalList.tsx +++ b/src/components/Lists/ApprovalList.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { ApplicationContext } from '../../contexts/ApplicationContext'; import { ITokenApproval } from '../../interfaces'; import { IDataListItem } from '../../interfaces/lists'; import { FFCopyButton } from '../Buttons/CopyButton'; @@ -14,6 +15,7 @@ interface Props { } export const ApprovalList: React.FC = ({ approval }) => { + const { selectedNamespace } = useContext(ApplicationContext); const { t } = useTranslation(); const [dataList, setDataList] = useState(FFSkeletonList); @@ -40,7 +42,7 @@ export const ApprovalList: React.FC = ({ approval }) => { value: , button: ( <> - + ), diff --git a/src/components/Lists/NamespaceList.tsx b/src/components/Lists/NamespaceList.tsx index 6ec36b9a..917835d4 100644 --- a/src/components/Lists/NamespaceList.tsx +++ b/src/components/Lists/NamespaceList.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { ApplicationContext } from '../../contexts/ApplicationContext'; import { INamespace } from '../../interfaces'; import { IDataListItem } from '../../interfaces/lists'; import { FFCopyButton } from '../Buttons/CopyButton'; @@ -14,6 +15,7 @@ interface Props { } export const NamespaceList: React.FC = ({ ns }) => { + const { selectedNamespace } = useContext(ApplicationContext); const { t } = useTranslation(); const [dataList, setDataList] = useState(FFSkeletonList); @@ -47,7 +49,7 @@ export const NamespaceList: React.FC = ({ ns }) => { ), button: ns.message ? ( <> - + ) : ( diff --git a/src/interfaces/enums/eventTypes.tsx b/src/interfaces/enums/eventTypes.tsx index d9c8d216..dd45e6ed 100644 --- a/src/interfaces/enums/eventTypes.tsx +++ b/src/interfaces/enums/eventTypes.tsx @@ -1,10 +1,9 @@ import { t } from 'i18next'; -import { MsgButton } from '../../components/Buttons/MsgButton'; -import { PoolButton } from '../../components/Buttons/PoolButton'; -import { TxButton } from '../../components/Buttons/TxButton'; +import { EventReferenceButton } from '../../components/Buttons/EventReferenceButton'; import { FFColors } from '../../theme'; import { getShortHash } from '../../utils'; import { IEvent } from '../api'; +import { FF_NAV_PATHS } from '../navigation'; export type INewEventSet = { [key in FF_EVENTS]: boolean; @@ -55,7 +54,7 @@ interface IEventCategory { enrichedEventString: (key: any) => string; nicename: string; referenceIDName: string; - referenceIDButton: (refID: string) => JSX.Element; + referenceIDButton: (ns: string, refID: string) => JSX.Element; } export const getEnrichedEventText = (event: IEvent) => { @@ -82,8 +81,10 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }`, referenceIDName: 'blockchainEventID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.CONTRACT_API_CONFIRMED]: { @@ -98,8 +99,8 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }`, referenceIDName: 'apiID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.CONTRACT_INTERFACE_CONFIRMED]: { @@ -114,8 +115,10 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }`, referenceIDName: 'interfaceID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.DATATYPE_CONFIRMED]: { @@ -132,8 +135,10 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }`, referenceIDName: 'datatypeID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.IDENTITY_CONFIRMED]: { @@ -146,8 +151,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.identity?.did ? ', DID=' + event.identity?.did : '' }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, referenceIDName: 'identityID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.IDENTITY_UPDATED]: { @@ -160,8 +167,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.identity?.did ? ', DID=' + event.identity?.did : '' }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, referenceIDName: 'identityID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.NS_CONFIRMED]: { @@ -176,8 +185,10 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }`, referenceIDName: 'nsID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, // Message Events @@ -194,8 +205,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.message?.header.tag ? ', Tag=' + event.message?.header.tag : '' }`, referenceIDName: 'messageID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.MSG_REJECTED]: { @@ -211,8 +224,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.message?.header.tag ? ', Tag=' + event.message?.header.tag : '' }`, referenceIDName: 'messageID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.TX_SUBMITTED]: { @@ -229,8 +244,10 @@ export const FF_EVENTS_CATEGORY_MAP: { .join(', ')}]` : t('transactionSubmitted'), referenceIDName: 'transactionID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, // Token Events @@ -246,8 +263,10 @@ export const FF_EVENTS_CATEGORY_MAP: { : '' }${event.tokenPool?.type ? ', Type=' + event.tokenPool?.type : ''}`, referenceIDName: 'poolID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.TOKEN_APPROVAL_CONFIRMED]: { @@ -260,8 +279,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.tokenApproval?.key ?? '' )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, referenceIDName: 'approvalID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.TOKEN_APPROVAL_OP_FAILED]: { @@ -274,8 +295,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.tokenApproval?.key ?? '' )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, referenceIDName: 'approvalID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.TOKEN_TRANSFER_CONFIRMED]: { @@ -301,8 +324,10 @@ export const FF_EVENTS_CATEGORY_MAP: { event.tokenTransfer?.key ?? '' )}`, referenceIDName: 'transferID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, [FF_EVENTS.TOKEN_TRANSFER_FAILED]: { @@ -313,8 +338,10 @@ export const FF_EVENTS_CATEGORY_MAP: { enrichedEventString: (event: IEvent): string => `${t('event')} ID=${event.id}`, referenceIDName: 'transferID', - referenceIDButton: (refID: string): JSX.Element => ( - + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + ), }, }; diff --git a/src/pages/Network/views/Identities.tsx b/src/pages/Network/views/Identities.tsx index 8dcd1f62..638a9c7f 100644 --- a/src/pages/Network/views/Identities.tsx +++ b/src/pages/Network/views/Identities.tsx @@ -66,7 +66,23 @@ export const NetworkIdentities: () => JSX.Element = () => { }, []); useEffect(() => { - isMounted && slideID && setViewIdentity(slideID); + if (isMounted && slideID) { + if (slideID.startsWith('did:firefly')) { + setViewIdentity(slideID); + } else { + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.identitiesById( + slideID + )}` + ) + .then((idRes: IIdentity) => { + setViewIdentity(idRes.did); + }) + .catch((err) => { + reportFetchError(err); + }); + } + } }, [slideID, isMounted]); // Identities diff --git a/src/pages/Network/views/Namespaces.tsx b/src/pages/Network/views/Namespaces.tsx new file mode 100644 index 00000000..e909fe00 --- /dev/null +++ b/src/pages/Network/views/Namespaces.tsx @@ -0,0 +1,213 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FilterButton } from '../../../components/Filters/FilterButton'; +import { FilterModal } from '../../../components/Filters/FilterModal'; +import { Header } from '../../../components/Header'; +import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; +import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { NamespaceSlide } from '../../../components/Slides/NamespaceSlide'; +import { FFTableText } from '../../../components/Tables/FFTableText'; +import { DataTable } from '../../../components/Tables/Table'; +import { ApplicationContext } from '../../../contexts/ApplicationContext'; +import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; +import { SnackbarContext } from '../../../contexts/SnackbarContext'; +import { + FF_Paths, + IDataTableRecord, + INamespace, + IPagesNamespaceResponse, + NamespaceFilters, +} from '../../../interfaces'; +import { DEFAULT_PADDING, DEFAULT_PAGE_LIMITS } from '../../../theme'; +import { fetchCatcher, getFFTime } from '../../../utils'; +import { hasIdentityEvent } from '../../../utils/wsEvents'; + +export const NetworkNamespaces: () => JSX.Element = () => { + const { newEvents, lastRefreshTime, clearNewEvents, selectedNamespace } = + useContext(ApplicationContext); + const { filterAnchor, setFilterAnchor, filterString } = + useContext(FilterContext); + const { setSlideSearchParam, slideID } = useContext(SlideContext); + const { reportFetchError } = useContext(SnackbarContext); + const { t } = useTranslation(); + const [isMounted, setIsMounted] = useState(false); + + const [namespaces, setNamespaces] = useState(); + const [nsTotal, setNsTotal] = useState(0); + const [viewNs, setViewNs] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); + + useEffect(() => { + setIsMounted(true); + return () => { + setIsMounted(false); + }; + }, []); + + useEffect(() => { + isMounted && slideID; + fetchCatcher(`${FF_Paths.nsPrefix}?id=${slideID}`) + .then((nsRes: INamespace[]) => { + isMounted && nsRes.length === 1 && setViewNs(nsRes[0]); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [slideID, isMounted]); + + // Namespaces + useEffect(() => { + isMounted && + fetchCatcher( + `${FF_Paths.nsPrefix}?limit=${rowsPerPage}&count&skip=${ + rowsPerPage * currentPage + }${filterString ?? ''}&sort=created` + ) + .then((nsRes: IPagesNamespaceResponse) => { + if (isMounted) { + setNamespaces(nsRes.items); + setNsTotal(nsRes.total); + } + }) + .catch((err) => { + reportFetchError(err); + }); + }, [ + rowsPerPage, + currentPage, + selectedNamespace, + filterString, + lastRefreshTime, + isMounted, + ]); + + const nsColHeaders = [ + t('name'), + t('description'), + t('type'), + t('id'), + t('message'), + t('created'), + ]; + const nsRecords: IDataTableRecord[] | undefined = namespaces?.map((ns) => { + return { + key: ns.id, + columns: [ + { + value: , + }, + { + value: ( + + ), + }, + { + value: , + }, + { + value: , + }, + { + value: ns.message ? ( + + ) : ( + + ), + }, + { + value: ( + + ), + }, + ], + onClick: () => { + setViewNs(ns); + setSlideSearchParam(ns.id); + }, + }; + }); + + return ( + <> +
+ + + ) => + setFilterAnchor(e.currentTarget) + } + /> + } + /> + + setCurrentPage(currentPage) + } + onHandleRowsPerPage={(rowsPerPage: number) => + setRowsPerPage(rowsPerPage) + } + stickyHeader={true} + minHeight="300px" + maxHeight="calc(100vh - 340px)" + records={nsRecords} + columnHeaders={nsColHeaders} + paginate={true} + emptyStateText={t('noNamespaces')} + dataTotal={nsTotal} + currentPage={currentPage} + rowsPerPage={rowsPerPage} + /> + + + {filterAnchor && ( + { + setFilterAnchor(null); + }} + fields={NamespaceFilters} + /> + )} + {viewNs && ( + { + setViewNs(undefined); + setSlideSearchParam(null); + }} + /> + )} + + ); +}; From 66d198ef4d2acbc88dedcc6cedf5ad8e654da92d Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 7 Apr 2022 23:11:09 -0400 Subject: [PATCH 5/7] [misc-tweaks] adding batches page and linking from transaction slides Signed-off-by: David Echelberger --- .../Accordions/JsonViewerAccordion.tsx | 13 + src/components/Buttons/DownloadJsonButton.tsx | 24 ++ src/components/Buttons/IdentityButton.tsx | 2 + src/components/Lists/BatchList.tsx | 93 +++++++ src/components/Lists/PoolList.tsx | 4 +- src/components/Lists/TransferList.tsx | 4 +- src/components/Lists/TxList.tsx | 21 +- src/components/Navigation/OffChainNav.tsx | 12 +- src/components/Slides/BatchSlide.tsx | 61 +++++ src/components/Slides/TransactionSlide.tsx | 9 +- src/components/Slides/TransferSlide.tsx | 4 +- src/components/Viewers/FFJsonViewer.tsx | 8 +- src/interfaces/api.ts | 49 +++- .../{txStatusTypes.ts => txStatusTypes.tsx} | 42 ++- src/interfaces/filters.ts | 16 ++ src/interfaces/navigation.ts | 9 +- .../Activity/views/TransactionDetails.tsx | 4 +- src/pages/Off-Chain/Routes.tsx | 9 +- src/pages/Off-Chain/views/Batches.tsx | 239 ++++++++++++++++++ src/translations/en.json | 3 + src/utils/files.ts | 19 ++ 21 files changed, 588 insertions(+), 57 deletions(-) create mode 100644 src/components/Buttons/DownloadJsonButton.tsx create mode 100644 src/components/Lists/BatchList.tsx create mode 100644 src/components/Slides/BatchSlide.tsx rename src/interfaces/enums/{txStatusTypes.ts => txStatusTypes.tsx} (53%) create mode 100644 src/pages/Off-Chain/views/Batches.tsx diff --git a/src/components/Accordions/JsonViewerAccordion.tsx b/src/components/Accordions/JsonViewerAccordion.tsx index eee92184..6f11ebe5 100644 --- a/src/components/Accordions/JsonViewerAccordion.tsx +++ b/src/components/Accordions/JsonViewerAccordion.tsx @@ -6,6 +6,7 @@ import { Grid, } from '@mui/material'; import { useState } from 'react'; +import { DownloadJsonButton } from '../Buttons/DownloadJsonButton'; import { FFJsonViewer } from '../Viewers/FFJsonViewer'; import { FFAccordionHeader } from './FFAccordionHeader'; import { FFAccordionText } from './FFAccordionText'; @@ -14,12 +15,14 @@ interface Props { header: string; json: object; isOpen?: boolean; + filename?: string; } export const JsonViewAccordion: React.FC = ({ header, json, isOpen = false, + filename, }) => { const [expanded, setExpanded] = useState(isOpen); @@ -34,6 +37,16 @@ export const JsonViewAccordion: React.FC = ({ leftContent={ } + rightContent={ + filename ? ( + + ) : ( + <> + ) + } /> diff --git a/src/components/Buttons/DownloadJsonButton.tsx b/src/components/Buttons/DownloadJsonButton.tsx new file mode 100644 index 00000000..0264895e --- /dev/null +++ b/src/components/Buttons/DownloadJsonButton.tsx @@ -0,0 +1,24 @@ +import { Download } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; +import { downloadJsonString } from '../../utils'; + +interface Props { + filename: string; + jsonString: string; +} + +export const DownloadJsonButton: React.FC = ({ + filename, + jsonString, +}) => { + return ( + { + e.stopPropagation(); + downloadJsonString(jsonString, filename); + }} + > + + + ); +}; diff --git a/src/components/Buttons/IdentityButton.tsx b/src/components/Buttons/IdentityButton.tsx index 3da079ae..9935b44e 100644 --- a/src/components/Buttons/IdentityButton.tsx +++ b/src/components/Buttons/IdentityButton.tsx @@ -4,6 +4,7 @@ import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { ApplicationContext } from '../../contexts/ApplicationContext'; import { FF_NAV_PATHS } from '../../interfaces'; +import { FFColors } from '../../theme'; type Props = { did?: string; @@ -26,6 +27,7 @@ export const IdentityButton: React.FC = ({ did, nodeID }) => { return ( { e.stopPropagation(); did diff --git a/src/components/Lists/BatchList.tsx b/src/components/Lists/BatchList.tsx new file mode 100644 index 00000000..6249ac13 --- /dev/null +++ b/src/components/Lists/BatchList.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { IBatch } from '../../interfaces'; +import { IDataListItem } from '../../interfaces/lists'; +import { FFCopyButton } from '../Buttons/CopyButton'; +import { IdentityButton } from '../Buttons/IdentityButton'; +import { FFListItem } from './FFListItem'; +import { FFListText } from './FFListText'; +import { FFListTimestamp } from './FFListTimestamp'; +import { FFSkeletonList } from './FFSkeletonList'; + +interface Props { + batch?: IBatch; +} + +export const BatchList: React.FC = ({ batch }) => { + const { t } = useTranslation(); + const [dataList, setDataList] = useState(FFSkeletonList); + + useEffect(() => { + batch && + setDataList([ + { + label: t('id'), + value: , + button: , + }, + { + label: t('type'), + value: , + }, + { + label: t('hash'), + value: , + button: , + }, + { + label: t('node'), + value: , + button: ( + <> + + + + ), + }, + { + label: batch.group ? t('group') : '', + value: batch.group ? ( + + ) : ( + <> + ), + button: batch.group ? : <>, + }, + { + label: t('author'), + value: , + button: ( + <> + + + + ), + }, + { + label: t('signingKey'), + value: , + button: , + }, + { + label: t('created'), + value: , + }, + { + label: batch.confirmed ? t('confirmed') : '', + value: batch.confirmed ? ( + + ) : ( + <> + ), + }, + ]); + }, [batch]); + + return ( + <> + {dataList.map( + (d, idx) => d.label !== '' && + )} + + ); +}; diff --git a/src/components/Lists/PoolList.tsx b/src/components/Lists/PoolList.tsx index a531c841..9a2cd440 100644 --- a/src/components/Lists/PoolList.tsx +++ b/src/components/Lists/PoolList.tsx @@ -32,7 +32,7 @@ export const PoolList: React.FC = ({ pool }) => { }, { label: t('transactionID'), - value: pool.tx.id ? ( + value: pool.tx?.id ? ( ) : ( = ({ pool }) => { text={t('transactionIDUnavailable')} /> ), - button: pool.tx.id ? ( + button: pool.tx?.id ? ( <> diff --git a/src/components/Lists/TransferList.tsx b/src/components/Lists/TransferList.tsx index e9d04fd7..e2a44934 100644 --- a/src/components/Lists/TransferList.tsx +++ b/src/components/Lists/TransferList.tsx @@ -41,7 +41,7 @@ export const TransferList: React.FC = ({ }, { label: t('transactionID'), - value: transfer.tx.id ? ( + value: transfer.tx?.id ? ( ) : ( = ({ text={t('transactionIDUnavailable')} /> ), - button: transfer.tx.id ? ( + button: transfer.tx?.id ? ( <> {showTxLink && ( diff --git a/src/components/Lists/TxList.tsx b/src/components/Lists/TxList.tsx index b14ac1ff..5e0ba002 100644 --- a/src/components/Lists/TxList.tsx +++ b/src/components/Lists/TxList.tsx @@ -10,7 +10,6 @@ import { IDataListItem } from '../../interfaces/lists'; import { FFCopyButton } from '../Buttons/CopyButton'; import { TxButton } from '../Buttons/TxButton'; import { TxStatusChip } from '../Chips/TxStatusChip'; -import { FFCircleLoader } from '../Loaders/FFCircleLoader'; import { HashPopover } from '../Popovers/HashPopover'; import { FFListItem } from './FFListItem'; import { FFListText } from './FFListText'; @@ -85,6 +84,10 @@ export const TxList: React.FC = ({ value: , button: ( <> + {FF_TX_STATUS_CATEGORY_MAP[type.type]?.referenceIDButton( + selectedNamespace, + type.id + )} ), @@ -95,17 +98,11 @@ export const TxList: React.FC = ({ return ( <> - {!tx || !txStatus ? ( - - ) : ( - <> - {dataList.map((d, idx) => ( - - ))} - {txDetails?.map( - (d) => d !== undefined && - )} - + {dataList.map((d, idx) => ( + + ))} + {txDetails?.map( + (d) => d !== undefined && )} ); diff --git a/src/components/Navigation/OffChainNav.tsx b/src/components/Navigation/OffChainNav.tsx index c327a648..9ca5925a 100644 --- a/src/components/Navigation/OffChainNav.tsx +++ b/src/components/Navigation/OffChainNav.tsx @@ -32,6 +32,7 @@ export const OffChainNav = () => { const messagesPath = FF_NAV_PATHS.offchainMessagesPath(selectedNamespace); const dataPath = FF_NAV_PATHS.offchainDataPath(selectedNamespace); const groupsPath = FF_NAV_PATHS.offchainGroupsPath(selectedNamespace); + const batchesPath = FF_NAV_PATHS.offchainBatchesPath(selectedNamespace); const datatypesPath = FF_NAV_PATHS.offchainDatatypesPath(selectedNamespace); const navItems: INavItem[] = [ @@ -51,15 +52,20 @@ export const OffChainNav = () => { itemIsActive: pathname === dataPath, }, { - name: t('groups'), - action: () => navigate(groupsPath), - itemIsActive: pathname === groupsPath, + name: t('batches'), + action: () => navigate(batchesPath), + itemIsActive: pathname === batchesPath, }, { name: t('datatypes'), action: () => navigate(datatypesPath), itemIsActive: pathname === datatypesPath, }, + { + name: t('groups'), + action: () => navigate(groupsPath), + itemIsActive: pathname === groupsPath, + }, ]; return ( diff --git a/src/components/Slides/BatchSlide.tsx b/src/components/Slides/BatchSlide.tsx new file mode 100644 index 00000000..79414f01 --- /dev/null +++ b/src/components/Slides/BatchSlide.tsx @@ -0,0 +1,61 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { IBatch } from '../../interfaces'; +import { DEFAULT_PADDING } from '../../theme'; +import { JsonViewAccordion } from '../Accordions/JsonViewerAccordion'; +import { BatchList } from '../Lists/BatchList'; +import { DisplaySlide } from './DisplaySlide'; +import { SlideHeader } from './SlideHeader'; + +interface Props { + batch: IBatch; + open: boolean; + onClose: () => void; +} + +export const BatchSlide: React.FC = ({ batch, open, onClose }) => { + const { t } = useTranslation(); + + return ( + <> + + + {/* Header */} + + {/* Data list */} + + + + {/* Manifest */} + {batch.manifest && ( + + + + )} + + + + ); +}; diff --git a/src/components/Slides/TransactionSlide.tsx b/src/components/Slides/TransactionSlide.tsx index 02f0393b..74556efa 100644 --- a/src/components/Slides/TransactionSlide.tsx +++ b/src/components/Slides/TransactionSlide.tsx @@ -20,7 +20,6 @@ import { useTranslation } from 'react-i18next'; import { ApplicationContext } from '../../contexts/ApplicationContext'; import { SnackbarContext } from '../../contexts/SnackbarContext'; import { - FF_NAV_PATHS, IBlockchainEvent, IOperation, ITransaction, @@ -138,13 +137,7 @@ export const TransactionSlide: React.FC = ({ {/* Operations */} {txOperations?.length > 0 && ( <> - + {txOperations?.map((op, idx) => ( diff --git a/src/components/Slides/TransferSlide.tsx b/src/components/Slides/TransferSlide.tsx index a8082748..5a6a3a2f 100644 --- a/src/components/Slides/TransferSlide.tsx +++ b/src/components/Slides/TransferSlide.tsx @@ -61,7 +61,7 @@ export const TransferSlide: React.FC = ({ transfer, open, onClose }) => { useEffect(() => { // Transaction Status isMounted && - transfer.tx.id && + transfer.tx?.id && fetchCatcher( `${ FF_Paths.nsPrefix @@ -75,7 +75,7 @@ export const TransferSlide: React.FC = ({ transfer, open, onClose }) => { }); // Transfer Blockchain Event isMounted && - transfer.tx.id && + transfer.tx?.id && fetchCatcher( `${ FF_Paths.nsPrefix diff --git a/src/components/Viewers/FFJsonViewer.tsx b/src/components/Viewers/FFJsonViewer.tsx index f56843c4..60667eee 100644 --- a/src/components/Viewers/FFJsonViewer.tsx +++ b/src/components/Viewers/FFJsonViewer.tsx @@ -1,4 +1,3 @@ -import { useTheme } from '@mui/material'; import ReactJson from 'react-json-view'; import { FFTextField } from '../Inputs/FFTextField'; @@ -17,7 +16,6 @@ const isValidJson = (obj: object) => { }; export const FFJsonViewer: React.FC = ({ json }) => { - const theme = useTheme(); const handleCopy = (copy: any) => { navigator.clipboard.writeText(JSON.stringify(copy.src, null, '\t')); }; @@ -26,10 +24,10 @@ export const FFJsonViewer: React.FC = ({ json }) => { string; + nicename: string; + referenceIDButton: (ns: string, refID: string) => JSX.Element; +} export enum FF_TX_STATUS { OPERATION = 'Operation', @@ -27,36 +37,64 @@ export enum FF_TX_STATUS { } export const FF_TX_STATUS_CATEGORY_MAP: { - [key in FF_TX_STATUS]: IBlockchainCategory; + [key in FF_TX_STATUS]: ITxStatusCategory; } = { [FF_TX_STATUS.OPERATION]: { category: '', color: FFColors.Yellow, nicename: 'operation', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, [FF_TX_STATUS.BLOCKCHAIN_EVENT]: { category: '', color: FFColors.Yellow, nicename: 'blockchainEvent', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, [FF_TX_STATUS.BATCH]: { category: '', color: FFColors.Yellow, nicename: 'batch', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, [FF_TX_STATUS.TOKEN_POOL]: { category: '', color: FFColors.Yellow, nicename: 'tokenPool', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, [FF_TX_STATUS.TOKEN_TRANSFER]: { category: '', color: FFColors.Yellow, nicename: 'tokenTransfer', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, [FF_TX_STATUS.TOKEN_APPROVAL]: { category: '', color: FFColors.Yellow, nicename: 'tokenApproval', + referenceIDButton: (ns: string, refID: string): JSX.Element => ( + + ), }, }; diff --git a/src/interfaces/filters.ts b/src/interfaces/filters.ts index 76432db2..316f95b3 100644 --- a/src/interfaces/filters.ts +++ b/src/interfaces/filters.ts @@ -57,6 +57,22 @@ export const BalanceFilters = [ 'updated', ]; +export const BatchFilters = [ + 'id', + 'namespace', + 'type', + 'author', + 'key', + 'group', + 'hash', + 'payloadref', + 'created', + 'confirmed', + 'tx.type', + 'tx.id', + 'node', +]; + export const BlockchainEventFilters = [ 'id', 'source', diff --git a/src/interfaces/navigation.ts b/src/interfaces/navigation.ts index f5aa9ad6..c1f7d976 100644 --- a/src/interfaces/navigation.ts +++ b/src/interfaces/navigation.ts @@ -27,6 +27,7 @@ export const ACTIVITY_PATH = 'activity'; export const APIS_PATH = 'apis'; export const APPROVALS_PATH = 'approvals'; export const BALANCES_PATH = 'balances'; +export const BATCHES_PATH = 'batches'; export const BLOCKCHAIN_PATH = 'blockchain'; export const DATA_PATH = 'data'; export const DATATYPES_PATH = 'datatypes'; @@ -68,9 +69,9 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${TRANSACTIONS_PATH}/${txID}`, activityTxDetailPathWithSlide: (ns: string, txID: string, slideID: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${TRANSACTIONS_PATH}/${txID}?slide=${slideID}`, - activityOpPath: (ns: string, txID?: string) => + activityOpPath: (ns: string, opID?: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${OPERATIONS_PATH}${ - txID ? `?filters=tx==${txID}` : '' + opID ? `?slide=${opID}` : '' }`, activityOpErrorPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${OPERATIONS_PATH}?filters=error=!=`, @@ -109,6 +110,10 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${DATATYPES_PATH}${ datatypeID ? `?filters=id==${datatypeID}&slide=${datatypeID}` : '' }`, + offchainBatchesPath: (ns: string, batchID?: string) => + `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${BATCHES_PATH}${ + batchID ? `?slide=${batchID}` : '' + }`, offchainGroupsPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${GROUPS_PATH}`, // Tokens diff --git a/src/pages/Activity/views/TransactionDetails.tsx b/src/pages/Activity/views/TransactionDetails.tsx index e506eee6..3034a3bd 100644 --- a/src/pages/Activity/views/TransactionDetails.tsx +++ b/src/pages/Activity/views/TransactionDetails.tsx @@ -159,9 +159,7 @@ export const TransactionDetails: () => JSX.Element = () => { headerText: t('blockchainOperations'), headerComponent: ( - navigate(FF_NAV_PATHS.activityOpPath(selectedNamespace, tx?.id)) - } + onClick={() => navigate(FF_NAV_PATHS.activityOpPath(selectedNamespace))} > diff --git a/src/pages/Off-Chain/Routes.tsx b/src/pages/Off-Chain/Routes.tsx index f1a3037d..8fb34142 100644 --- a/src/pages/Off-Chain/Routes.tsx +++ b/src/pages/Off-Chain/Routes.tsx @@ -1,5 +1,6 @@ import { RouteObject } from 'react-router-dom'; import { NAMESPACES_PATH } from '../../interfaces'; +import { OffChainBatches } from './views/Batches'; import { OffChainDashboard } from './views/Dashboard'; import { OffChainData } from './views/Data'; import { OffChainDataTypes } from './views/DataTypes'; @@ -23,12 +24,16 @@ export const OffChainRoutes: RouteObject = { element: , }, { - path: 'groups', - element: , + path: 'batches', + element: , }, { path: 'datatypes', element: , }, + { + path: 'groups', + element: , + }, ], }; diff --git a/src/pages/Off-Chain/views/Batches.tsx b/src/pages/Off-Chain/views/Batches.tsx new file mode 100644 index 00000000..b2f26e5a --- /dev/null +++ b/src/pages/Off-Chain/views/Batches.tsx @@ -0,0 +1,239 @@ +// Copyright © 2022 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Grid } from '@mui/material'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DownloadJsonButton } from '../../../components/Buttons/DownloadJsonButton'; +import { FilterButton } from '../../../components/Filters/FilterButton'; +import { FilterModal } from '../../../components/Filters/FilterModal'; +import { Header } from '../../../components/Header'; +import { ChartTableHeader } from '../../../components/Headers/ChartTableHeader'; +import { HashPopover } from '../../../components/Popovers/HashPopover'; +import { BatchSlide } from '../../../components/Slides/BatchSlide'; +import { FFTableText } from '../../../components/Tables/FFTableText'; +import { DataTable } from '../../../components/Tables/Table'; +import { ApplicationContext } from '../../../contexts/ApplicationContext'; +import { DateFilterContext } from '../../../contexts/DateFilterContext'; +import { FilterContext } from '../../../contexts/FilterContext'; +import { SlideContext } from '../../../contexts/SlideContext'; +import { SnackbarContext } from '../../../contexts/SnackbarContext'; +import { + BatchFilters, + FF_Paths, + IBatch, + IDataTableRecord, + IPagedBatchResponse, +} from '../../../interfaces'; +import { DEFAULT_PADDING, DEFAULT_PAGE_LIMITS } from '../../../theme'; +import { fetchCatcher, getFFTime } from '../../../utils'; +import { hasDataEvent } from '../../../utils/wsEvents'; + +export const OffChainBatches: () => JSX.Element = () => { + const { newEvents, lastRefreshTime, clearNewEvents, selectedNamespace } = + useContext(ApplicationContext); + const { dateFilter } = useContext(DateFilterContext); + const { filterAnchor, setFilterAnchor, filterString } = + useContext(FilterContext); + const { slideID, setSlideSearchParam } = useContext(SlideContext); + const { reportFetchError } = useContext(SnackbarContext); + const { t } = useTranslation(); + const [isMounted, setIsMounted] = useState(false); + + // Batches + const [batches, setBatches] = useState(); + // Batch total + const [batchTotal, setBatchTotal] = useState(0); + const [viewBatch, setViewBatch] = useState(); + const [currentPage, setCurrentPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_PAGE_LIMITS[1]); + + useEffect(() => { + setIsMounted(true); + return () => { + setIsMounted(false); + }; + }, []); + + useEffect(() => { + isMounted && + slideID && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.batchesByBatchId( + slideID + )}` + ) + .then((batchRes: IBatch) => { + setViewBatch(batchRes); + }) + .catch((err) => { + reportFetchError(err); + }); + }, [slideID, isMounted]); + + // Batch + useEffect(() => { + isMounted && + dateFilter && + fetchCatcher( + `${FF_Paths.nsPrefix}/${selectedNamespace}${ + FF_Paths.batches + }?limit=${rowsPerPage}&count&skip=${rowsPerPage * currentPage}${ + dateFilter.filterString + }${filterString ?? ''}` + ) + .then((batchRes: IPagedBatchResponse) => { + if (isMounted) { + setBatches(batchRes.items); + setBatchTotal(batchRes.total); + } + }) + .catch((err) => { + reportFetchError(err); + }); + }, [ + rowsPerPage, + currentPage, + selectedNamespace, + dateFilter, + filterString, + lastRefreshTime, + isMounted, + ]); + + const batchColHeaders = [ + t('id'), + t('type'), + t('hash'), + t('node'), + t('group'), + t('author'), + t('signingKey'), + t('created'), + t('manifest'), + ]; + + const batchRecords: IDataTableRecord[] | undefined = batches?.map( + (batch) => ({ + key: batch.id, + columns: [ + { + value: , + }, + { + value: , + }, + { + value: , + }, + { + value: , + }, + { + value: batch.group ? ( + + ) : ( + + ), + }, + { + value: , + }, + { + value: , + }, + { + value: ( + + ), + }, + { + value: ( + + ), + }, + ], + onClick: () => { + setViewBatch(batch); + setSlideSearchParam(batch.id); + }, + }) + ); + + return ( + <> +
+ + + ) => + setFilterAnchor(e.currentTarget) + } + /> + } + /> + + setCurrentPage(currentPage) + } + onHandleRowsPerPage={(rowsPerPage: number) => + setRowsPerPage(rowsPerPage) + } + stickyHeader={true} + minHeight="300px" + maxHeight="calc(100vh - 340px)" + records={batchRecords} + columnHeaders={batchColHeaders} + paginate={true} + emptyStateText={t('noBatchesToDisplay')} + dataTotal={batchTotal} + currentPage={currentPage} + rowsPerPage={rowsPerPage} + /> + + + {filterAnchor && ( + { + setFilterAnchor(null); + }} + fields={BatchFilters} + /> + )} + {viewBatch && ( + { + setViewBatch(undefined); + setSlideSearchParam(null); + }} + /> + )} + + ); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 55501b68..40c83811 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -19,6 +19,7 @@ "balance": "Balance", "balances": "Balances", "batch": "Batch", + "batches": "Batches", "batchPin": "Batch Pin", "blobName": "Blob Name", "blobs": "Blobs", @@ -131,6 +132,7 @@ "listeners": "Listeners", "localID": "Local ID", "location": "Location", + "manifest": "Manifest", "matches": "Matches", "message": "Message", "messageClaim": "Message Claim", @@ -157,6 +159,7 @@ "noActivity": "No Activity", "noAddressForListener": "No Address for Listener", "noApisToDisplay": "No APIs to Display", + "noBatchesToDisplay": "No Batches To Display", "noBlobInData": "No Blob in Data", "noBlobName": "No Blob Name", "noBlockchainEvents": "No Blockchain Events to Display", diff --git a/src/utils/files.ts b/src/utils/files.ts index d7582ef2..f350ef5b 100644 --- a/src/utils/files.ts +++ b/src/utils/files.ts @@ -17,6 +17,25 @@ export const downloadBlobFile = async (id: string, filename?: string) => { document.body.removeChild(link); }; +export const downloadJsonString = async ( + jsonString: string, + filename: string +) => { + const blob = new Blob([jsonString], { + type: 'application/json', + }); + const href = await URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = href; + if (filename) { + link.download = filename; + } + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + export const downloadExternalFile = async (url: string, filename?: string) => { const file = await fetch(url); const blob = await file.blob(); From 5446b18b0c47c3fb7fc396f2ac66054c62bcd15e Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Fri, 8 Apr 2022 10:45:31 -0400 Subject: [PATCH 6/7] [misc-tweaks] launch buttons, nav, event enriching Signed-off-by: David Echelberger --- src/components/Accordions/ApiAccordion.tsx | 1 + .../Accordions/BlockchainEventAccordion.tsx | 52 ++++++------ .../Accordions/ListenerAccordion.tsx | 1 + .../Accordions/MessageAccordion.tsx | 3 +- .../Accordions/MessageDataAccordion.tsx | 8 +- .../Accordions/TransactionAccordion.tsx | 1 + .../Buttons/EventReferenceButton.tsx | 25 ------ src/components/Buttons/FFArrowButton.tsx | 17 ++++ src/components/Buttons/LaunchButton.tsx | 20 +++-- .../Cards/EventCards/EventCardWrapper.tsx | 4 +- src/components/Lists/BatchList.tsx | 8 +- src/components/Lists/EventList.tsx | 15 ++-- src/components/Slides/EventSlide.tsx | 76 +++++++++++++---- src/components/Slides/SlideSectionHeader.tsx | 11 +-- src/interfaces/api.ts | 8 +- src/interfaces/enums/eventTypes.tsx | 66 +++++---------- src/interfaces/enums/txStatusTypes.tsx | 24 ++---- src/interfaces/navigation.ts | 14 ++-- src/pages/Activity/views/Events.tsx | 4 +- src/pages/Activity/views/Timeline.tsx | 9 +- .../Activity/views/TransactionDetails.tsx | 27 ++---- src/pages/Blockchain/views/Dashboard.tsx | 28 ++----- src/pages/Home/views/Dashboard.tsx | 83 +++++++------------ src/pages/Off-Chain/views/Batches.tsx | 8 +- src/pages/Off-Chain/views/Dashboard.tsx | 30 ++----- src/pages/Tokens/views/Dashboard.tsx | 28 ++----- src/pages/Tokens/views/PoolDetails.tsx | 37 +++------ src/translations/en.json | 3 + 28 files changed, 266 insertions(+), 345 deletions(-) delete mode 100644 src/components/Buttons/EventReferenceButton.tsx create mode 100644 src/components/Buttons/FFArrowButton.tsx diff --git a/src/components/Accordions/ApiAccordion.tsx b/src/components/Accordions/ApiAccordion.tsx index 883e4f1f..8f301a46 100644 --- a/src/components/Accordions/ApiAccordion.tsx +++ b/src/components/Accordions/ApiAccordion.tsx @@ -62,6 +62,7 @@ export const ApiAccordion: React.FC = ({ api, isOpen = false }) => { selectedNamespace, api.name )} + noColor /> } /> diff --git a/src/components/Accordions/BlockchainEventAccordion.tsx b/src/components/Accordions/BlockchainEventAccordion.tsx index dfae010d..c5e511b0 100644 --- a/src/components/Accordions/BlockchainEventAccordion.tsx +++ b/src/components/Accordions/BlockchainEventAccordion.tsx @@ -15,6 +15,7 @@ import { } from '../../interfaces'; import { DEFAULT_PADDING } from '../../theme'; import { getFFTime } from '../../utils'; +import { DownloadJsonButton } from '../Buttons/DownloadJsonButton'; import { LaunchButton } from '../Buttons/LaunchButton'; import { HashPopover } from '../Popovers/HashPopover'; import { FFJsonViewer } from '../Viewers/FFJsonViewer'; @@ -67,37 +68,34 @@ export const BlockchainEventAccordion: React.FC = ({ /> } rightContent={ - + <> + + + } /> - {be.info && ( - - - - - )} - {be.output && ( - - - - - - - )} + + + {accInfo.map((info, idx) => ( diff --git a/src/components/Accordions/ListenerAccordion.tsx b/src/components/Accordions/ListenerAccordion.tsx index ae78b3b4..8bfc1e88 100644 --- a/src/components/Accordions/ListenerAccordion.tsx +++ b/src/components/Accordions/ListenerAccordion.tsx @@ -77,6 +77,7 @@ export const ListenerAccordion: React.FC = ({ selectedNamespace, listener.id )} + noColor /> } /> diff --git a/src/components/Accordions/MessageAccordion.tsx b/src/components/Accordions/MessageAccordion.tsx index 62e1a3db..7c397334 100644 --- a/src/components/Accordions/MessageAccordion.tsx +++ b/src/components/Accordions/MessageAccordion.tsx @@ -80,7 +80,8 @@ export const MessageAccordion: React.FC = ({ selectedNamespace, message.header.id )} - > + noColor + /> } /> diff --git a/src/components/Accordions/MessageDataAccordion.tsx b/src/components/Accordions/MessageDataAccordion.tsx index 5c68adf2..613d96bd 100644 --- a/src/components/Accordions/MessageDataAccordion.tsx +++ b/src/components/Accordions/MessageDataAccordion.tsx @@ -15,6 +15,7 @@ import { FF_NAV_PATHS, IData, IDataWithHeader } from '../../interfaces'; import { DEFAULT_PADDING } from '../../theme'; import { getFFTime } from '../../utils'; import { DownloadButton } from '../Buttons/DownloadButton'; +import { DownloadJsonButton } from '../Buttons/DownloadJsonButton'; import { HashPopover } from '../Popovers/HashPopover'; import { FFJsonViewer } from '../Viewers/FFJsonViewer'; import { FFAccordionHeader } from './FFAccordionHeader'; @@ -65,12 +66,17 @@ export const MessageDataAccordion: React.FC = ({ leftContent={} rightContent={ <> - {data.blob && ( + {data.blob ? ( + ) : ( + )} {showLink && ( = ({ selectedNamespace, tx.id )} + noColor /> } diff --git a/src/components/Buttons/EventReferenceButton.tsx b/src/components/Buttons/EventReferenceButton.tsx deleted file mode 100644 index 3d8ee87a..00000000 --- a/src/components/Buttons/EventReferenceButton.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import LaunchIcon from '@mui/icons-material/Launch'; -import { IconButton } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import { FFColors } from '../../theme'; - -interface Props { - link: string; - small?: boolean; -} - -export const EventReferenceButton: React.FC = ({ - link, - small = false, -}) => { - const navigate = useNavigate(); - return ( - navigate(link)} - > - - - ); -}; diff --git a/src/components/Buttons/FFArrowButton.tsx b/src/components/Buttons/FFArrowButton.tsx new file mode 100644 index 00000000..54deaf83 --- /dev/null +++ b/src/components/Buttons/FFArrowButton.tsx @@ -0,0 +1,17 @@ +import { ArrowForward } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +interface Props { + link: string; +} + +export const FFArrowButton: React.FC = ({ link }) => { + const navigate = useNavigate(); + + return ( + navigate(link)}> + + + ); +}; diff --git a/src/components/Buttons/LaunchButton.tsx b/src/components/Buttons/LaunchButton.tsx index 95522077..81b51017 100644 --- a/src/components/Buttons/LaunchButton.tsx +++ b/src/components/Buttons/LaunchButton.tsx @@ -1,16 +1,22 @@ import { Launch } from '@mui/icons-material'; -import { IconButton, Link } from '@mui/material'; +import { IconButton } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { FFColors } from '../../theme'; interface Props { link: string; + noColor?: boolean; } -export const LaunchButton: React.FC = ({ link }) => { +export const LaunchButton: React.FC = ({ link, noColor = false }) => { + const navigate = useNavigate(); + return ( - - - - - + navigate(link)} + > + + ); }; diff --git a/src/components/Cards/EventCards/EventCardWrapper.tsx b/src/components/Cards/EventCards/EventCardWrapper.tsx index 2e472e4a..d33f76e7 100644 --- a/src/components/Cards/EventCards/EventCardWrapper.tsx +++ b/src/components/Cards/EventCards/EventCardWrapper.tsx @@ -14,7 +14,7 @@ interface Props { event: IEvent; link?: string; linkState?: any; - onHandleViewEvent: any; + onHandleViewEvent?: any; onHandleViewTx?: any; } @@ -30,7 +30,7 @@ export const EventCardWrapper = ({ <> onHandleViewTx(event.transaction) : () => onHandleViewEvent(event) } diff --git a/src/components/Lists/BatchList.tsx b/src/components/Lists/BatchList.tsx index 6249ac13..daf2f102 100644 --- a/src/components/Lists/BatchList.tsx +++ b/src/components/Lists/BatchList.tsx @@ -35,13 +35,15 @@ export const BatchList: React.FC = ({ batch }) => { button: , }, { - label: t('node'), - value: , - button: ( + label: batch.node ? t('node') : '', + value: batch.node && , + button: batch.node ? ( <> + ) : ( + <> ), }, { diff --git a/src/components/Lists/EventList.tsx b/src/components/Lists/EventList.tsx index 798b521b..f5242d84 100644 --- a/src/components/Lists/EventList.tsx +++ b/src/components/Lists/EventList.tsx @@ -5,7 +5,6 @@ import { FF_EVENTS_CATEGORY_MAP, IEvent } from '../../interfaces'; import { IDataListItem } from '../../interfaces/lists'; import { FFCopyButton } from '../Buttons/CopyButton'; import { TxButton } from '../Buttons/TxButton'; -import { FFCircleLoader } from '../Loaders/FFCircleLoader'; import { FFListItem } from './FFListItem'; import { FFListText } from './FFListText'; import { FFListTimestamp } from './FFListTimestamp'; @@ -43,15 +42,17 @@ export const EventList: React.FC = ({ event, showTxLink = true }) => { ), }, { - label: t('transactionID'), - value: , - button: ( + label: event.tx ? t('transactionID') : '', + value: event.tx && , + button: event.tx ? ( <> {showTxLink && ( )} + ) : ( + <> ), }, { @@ -64,10 +65,8 @@ export const EventList: React.FC = ({ event, showTxLink = true }) => { return ( <> - {!event ? ( - - ) : ( - dataList.map((d, idx) => ) + {dataList.map( + (d, idx) => d.label !== '' && )} ); diff --git a/src/components/Slides/EventSlide.tsx b/src/components/Slides/EventSlide.tsx index 27e4ca31..cb37193d 100644 --- a/src/components/Slides/EventSlide.tsx +++ b/src/components/Slides/EventSlide.tsx @@ -22,21 +22,26 @@ import { ApplicationContext } from '../../contexts/ApplicationContext'; import { SnackbarContext } from '../../contexts/SnackbarContext'; import { FF_EVENTS_CATEGORY_MAP, - FF_NAV_PATHS, FF_Paths, IData, IEvent, } from '../../interfaces'; import { DEFAULT_PADDING } from '../../theme'; import { fetchCatcher } from '../../utils'; -import { ApiAccordion } from '../Accordions/ApiAccordion'; import { BlockchainEventAccordion } from '../Accordions/BlockchainEventAccordion'; import { JsonViewAccordion } from '../Accordions/JsonViewerAccordion'; import { MessageAccordion } from '../Accordions/MessageAccordion'; import { MessageDataAccordion } from '../Accordions/MessageDataAccordion'; import { TransactionAccordion } from '../Accordions/TransactionAccordion'; +import { ApiList } from '../Lists/ApiList'; +import { ApprovalList } from '../Lists/ApprovalList'; import { EventList } from '../Lists/EventList'; +import { IdentityList } from '../Lists/IdentityList'; +import { InterfaceList } from '../Lists/InterfaceList'; +import { NamespaceList } from '../Lists/NamespaceList'; +import { PoolList } from '../Lists/PoolList'; import { TransferList } from '../Lists/TransferList'; +import { TxList } from '../Lists/TxList'; import { DisplaySlide } from './DisplaySlide'; import { SlideHeader } from './SlideHeader'; import { SlideSectionHeader } from './SlideSectionHeader'; @@ -109,13 +114,13 @@ export const EventSlide: React.FC = ({ event, open, onClose }) => {
{/* Blockchain event */} - {enrichedEvent && enrichedEvent['blockchainevent'] && ( + {enrichedEvent && enrichedEvent['blockchainEvent'] && ( <> @@ -125,10 +130,20 @@ export const EventSlide: React.FC = ({ event, open, onClose }) => { <> - + )} + {/* Contract Interface */} + {enrichedEvent && enrichedEvent['contractInterface'] && ( + <> + + + + + + )} + {/* Datatype */} {enrichedEvent && enrichedEvent['datatype'] && ( <> @@ -141,12 +156,21 @@ export const EventSlide: React.FC = ({ event, open, onClose }) => {
)} + {/* Identity */} + {enrichedEvent && enrichedEvent['identity'] && ( + <> + + + + + + )} {/* Message */} {enrichedEvent && enrichedEvent['message'] && ( <> - + )} @@ -161,17 +185,39 @@ export const EventSlide: React.FC = ({ event, open, onClose }) => {
)} + {/* Namespace */} + {enrichedEvent && enrichedEvent['namespaceDetails'] && ( + <> + + + + + + )} + {/* Token Approval */} + {enrichedEvent && enrichedEvent['tokenApproval'] && ( + <> + + + + + + )} + {/* Token Pool */} + {enrichedEvent && enrichedEvent['tokenPool'] && ( + <> + + + + + + )} {/* Transaction */} {enrichedEvent && enrichedEvent['transaction'] && ( <> - + + @@ -183,10 +229,6 @@ export const EventSlide: React.FC = ({ event, open, onClose }) => { title={`${t( 'tokenTransfer' )} - ${enrichedEvent.tokenTransfer.type.toUpperCase()}`} - clickPath={FF_NAV_PATHS.tokensTransfersPathLocalID( - selectedNamespace, - enrichedEvent.tokenTransfer.localId - )} /> diff --git a/src/components/Slides/SlideSectionHeader.tsx b/src/components/Slides/SlideSectionHeader.tsx index 8e1ff2c6..f1ae9dc6 100644 --- a/src/components/Slides/SlideSectionHeader.tsx +++ b/src/components/Slides/SlideSectionHeader.tsx @@ -14,11 +14,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton, Typography } from '@mui/material'; +import { Grid, Typography } from '@mui/material'; import React from 'react'; -import { useNavigate } from 'react-router-dom'; import { DEFAULT_PADDING } from '../../theme'; +import { FFArrowButton } from '../Buttons/FFArrowButton'; interface Props { clickPath?: string; @@ -26,8 +25,6 @@ interface Props { } export const SlideSectionHeader: React.FC = ({ clickPath, title }) => { - const navigate = useNavigate(); - return ( = ({ clickPath, title }) => { {clickPath && ( - navigate(clickPath)}> - - + )} diff --git a/src/interfaces/api.ts b/src/interfaces/api.ts index f96761fb..3dab0ed2 100644 --- a/src/interfaces/api.ts +++ b/src/interfaces/api.ts @@ -12,7 +12,7 @@ export interface IBatch { id: string; type: string; namespace: string; - node: string; + node?: string; author: string; key: string; group: null | string; @@ -106,12 +106,12 @@ export interface IEvent { namespace: string; reference: string; created: string; - tx: string; - blockchainevent?: IBlockchainEvent; + tx?: string; + blockchainEvent?: IBlockchainEvent; contractAPI?: IFireflyApi; contractInterface?: IContractInterface; datatype?: IDatatype; - identity?: IOrganization; + identity?: IIdentity; message?: IMessage; namespaceDetails?: INamespace; tokenApproval?: ITokenApproval; diff --git a/src/interfaces/enums/eventTypes.tsx b/src/interfaces/enums/eventTypes.tsx index dd45e6ed..b6afc0e8 100644 --- a/src/interfaces/enums/eventTypes.tsx +++ b/src/interfaces/enums/eventTypes.tsx @@ -1,5 +1,5 @@ import { t } from 'i18next'; -import { EventReferenceButton } from '../../components/Buttons/EventReferenceButton'; +import { LaunchButton } from '../../components/Buttons/LaunchButton'; import { FFColors } from '../../theme'; import { getShortHash } from '../../utils'; import { IEvent } from '../api'; @@ -75,16 +75,14 @@ export const FF_EVENTS_CATEGORY_MAP: { nicename: 'blockchainEventReceived', enrichedEventKey: 'blockchainevent', enrichedEventString: (event: IEvent): string => - `${event.blockchainevent?.name}${ - event.blockchainevent?.protocolId - ? ', Protocol ID=' + getShortHash(event.blockchainevent?.protocolId) + `${event.blockchainEvent?.name}${ + event.blockchainEvent?.protocolId + ? ', Protocol ID=' + getShortHash(event.blockchainEvent?.protocolId) : '' }`, referenceIDName: 'blockchainEventID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.CONTRACT_API_CONFIRMED]: { @@ -100,7 +98,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'apiID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.CONTRACT_INTERFACE_CONFIRMED]: { @@ -116,9 +114,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'interfaceID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.DATATYPE_CONFIRMED]: { @@ -136,9 +132,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'datatypeID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.IDENTITY_CONFIRMED]: { @@ -152,9 +146,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, referenceIDName: 'identityID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.IDENTITY_UPDATED]: { @@ -168,9 +160,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }${event.identity?.type ? ', Type=' + event.identity?.type : ''}`, referenceIDName: 'identityID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.NS_CONFIRMED]: { @@ -186,9 +176,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'nsID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, // Message Events @@ -206,9 +194,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'messageID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.MSG_REJECTED]: { @@ -225,9 +211,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }`, referenceIDName: 'messageID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.TX_SUBMITTED]: { @@ -245,9 +229,7 @@ export const FF_EVENTS_CATEGORY_MAP: { : t('transactionSubmitted'), referenceIDName: 'transactionID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, // Token Events @@ -264,9 +246,7 @@ export const FF_EVENTS_CATEGORY_MAP: { }${event.tokenPool?.type ? ', Type=' + event.tokenPool?.type : ''}`, referenceIDName: 'poolID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.TOKEN_APPROVAL_CONFIRMED]: { @@ -280,9 +260,7 @@ export const FF_EVENTS_CATEGORY_MAP: { )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, referenceIDName: 'approvalID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.TOKEN_APPROVAL_OP_FAILED]: { @@ -296,9 +274,7 @@ export const FF_EVENTS_CATEGORY_MAP: { )}, Operator=${getShortHash(event.tokenApproval?.operator ?? '')}`, referenceIDName: 'approvalID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.TOKEN_TRANSFER_CONFIRMED]: { @@ -325,9 +301,7 @@ export const FF_EVENTS_CATEGORY_MAP: { )}`, referenceIDName: 'transferID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_EVENTS.TOKEN_TRANSFER_FAILED]: { @@ -339,9 +313,7 @@ export const FF_EVENTS_CATEGORY_MAP: { `${t('event')} ID=${event.id}`, referenceIDName: 'transferID', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, }; diff --git a/src/interfaces/enums/txStatusTypes.tsx b/src/interfaces/enums/txStatusTypes.tsx index 6d42cfa0..3306b132 100644 --- a/src/interfaces/enums/txStatusTypes.tsx +++ b/src/interfaces/enums/txStatusTypes.tsx @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { EventReferenceButton } from '../../components/Buttons/EventReferenceButton'; +import { LaunchButton } from '../../components/Buttons/LaunchButton'; import { FFColors } from '../../theme'; import { FF_NAV_PATHS } from '../navigation'; @@ -44,7 +44,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'operation', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_TX_STATUS.BLOCKCHAIN_EVENT]: { @@ -52,9 +52,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'blockchainEvent', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_TX_STATUS.BATCH]: { @@ -62,9 +60,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'batch', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_TX_STATUS.TOKEN_POOL]: { @@ -72,9 +68,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'tokenPool', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_TX_STATUS.TOKEN_TRANSFER]: { @@ -82,9 +76,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'tokenTransfer', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, [FF_TX_STATUS.TOKEN_APPROVAL]: { @@ -92,9 +84,7 @@ export const FF_TX_STATUS_CATEGORY_MAP: { color: FFColors.Yellow, nicename: 'tokenApproval', referenceIDButton: (ns: string, refID: string): JSX.Element => ( - + ), }, }; diff --git a/src/interfaces/navigation.ts b/src/interfaces/navigation.ts index c1f7d976..38601101 100644 --- a/src/interfaces/navigation.ts +++ b/src/interfaces/navigation.ts @@ -59,9 +59,9 @@ export const FF_NAV_PATHS = { // Activity activityTimelinePath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}`, - activityEventsPath: (ns: string, txID?: string) => + activityEventsPath: (ns: string, eventID?: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${EVENTS_PATH}${ - txID ? `?filters=tx==${txID}` : '' + eventID ? `?slide=${eventID}` : '' }`, activityTxPath: (ns: string) => `/${NAMESPACES_PATH}/${ns}/${ACTIVITY_PATH}/${TRANSACTIONS_PATH}`, @@ -90,10 +90,8 @@ export const FF_NAV_PATHS = { `/${NAMESPACES_PATH}/${ns}/${BLOCKCHAIN_PATH}/${INTERFACES_PATH}${ interfaceID ? `?slide=${interfaceID}` : '' }`, - blockchainListenersPath: (ns: string, interfaceID?: string) => - `/${NAMESPACES_PATH}/${ns}/${BLOCKCHAIN_PATH}/${LISTENERS_PATH}${ - interfaceID ? `?filters=interface==${interfaceID}` : '' - }`, + blockchainListenersPath: (ns: string) => + `/${NAMESPACES_PATH}/${ns}/${BLOCKCHAIN_PATH}/${LISTENERS_PATH}`, blockchainListenersSinglePath: (ns: string, listenerID: string) => `/${NAMESPACES_PATH}/${ns}/${BLOCKCHAIN_PATH}/${LISTENERS_PATH}${`?slide=${listenerID}`}`, // Off-Chain @@ -104,11 +102,11 @@ export const FF_NAV_PATHS = { }`, offchainDataPath: (ns: string, dataID?: string) => `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${DATA_PATH}${ - dataID ? `?slide=${dataID}&filters=id=${dataID}` : '' + dataID ? `?slide=${dataID}` : '' }`, offchainDatatypesPath: (ns: string, datatypeID?: string) => `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${DATATYPES_PATH}${ - datatypeID ? `?filters=id==${datatypeID}&slide=${datatypeID}` : '' + datatypeID ? `?slide=${datatypeID}` : '' }`, offchainBatchesPath: (ns: string, batchID?: string) => `/${NAMESPACES_PATH}/${ns}/${OFFCHAIN_PATH}/${BATCHES_PATH}${ diff --git a/src/pages/Activity/views/Events.tsx b/src/pages/Activity/views/Events.tsx index 9dcb3cdf..cd00d0cc 100644 --- a/src/pages/Activity/views/Events.tsx +++ b/src/pages/Activity/views/Events.tsx @@ -197,8 +197,10 @@ export const ActivityEvents: () => JSX.Element = () => { ), }, { - value: ( + value: event.tx ? ( + ) : ( + ), }, { diff --git a/src/pages/Activity/views/Timeline.tsx b/src/pages/Activity/views/Timeline.tsx index 5a3b5dd4..e7211b3c 100644 --- a/src/pages/Activity/views/Timeline.tsx +++ b/src/pages/Activity/views/Timeline.tsx @@ -185,10 +185,11 @@ export const ActivityTimeline: () => JSX.Element = () => { setViewTx(tx); setSlideSearchParam(tx?.id); }} - link={FF_NAV_PATHS.activityTxDetailPath( - selectedNamespace, - event?.tx - )} + link={ + event.tx + ? FF_NAV_PATHS.activityTxDetailPath(selectedNamespace, event.tx) + : FF_NAV_PATHS.activityEventsPath(selectedNamespace, event.id) + } {...{ event }} /> ), diff --git a/src/pages/Activity/views/TransactionDetails.tsx b/src/pages/Activity/views/TransactionDetails.tsx index 3034a3bd..c3302066 100644 --- a/src/pages/Activity/views/TransactionDetails.tsx +++ b/src/pages/Activity/views/TransactionDetails.tsx @@ -14,13 +14,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton, Paper, Typography } from '@mui/material'; +import { Grid, Paper, Typography } from '@mui/material'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import { FFBreadcrumb } from '../../../components/Breadcrumbs/FFBreadcrumb'; import { FFCopyButton } from '../../../components/Buttons/CopyButton'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { EventCardWrapper } from '../../../components/Cards/EventCards/EventCardWrapper'; import { OpCardWrapper } from '../../../components/Cards/EventCards/OpCardWrapper'; import { FireFlyCard } from '../../../components/Cards/FireFlyCard'; @@ -51,7 +51,6 @@ export const TransactionDetails: () => JSX.Element = () => { const { slideID, setSlideSearchParam } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); const { t } = useTranslation(); - const navigate = useNavigate(); const location = useLocation(); const [isMounted, setIsMounted] = useState(false); @@ -79,12 +78,12 @@ export const TransactionDetails: () => JSX.Element = () => { fetchCatcher( `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.events}?id=${slideID}` ).then((eventRes: IEvent[]) => { - isMounted && eventRes.length > 0 && setViewEvent(eventRes[0]); + isMounted && eventRes.length === 1 && setViewEvent(eventRes[0]); }); fetchCatcher( `${FF_Paths.nsPrefix}/${selectedNamespace}${FF_Paths.operations}?id=${slideID}` ).then((opRes: IOperation[]) => { - isMounted && opRes.length > 0 && setViewOp(opRes[0]); + isMounted && opRes.length === 1 && setViewOp(opRes[0]); }); } }, [slideID, isMounted]); @@ -158,11 +157,7 @@ export const TransactionDetails: () => JSX.Element = () => { const operationsCard: IFireFlyCard = { headerText: t('blockchainOperations'), headerComponent: ( - navigate(FF_NAV_PATHS.activityOpPath(selectedNamespace))} - > - - + ), component: ( <> @@ -189,13 +184,9 @@ export const TransactionDetails: () => JSX.Element = () => { const networkEventsCard: IFireFlyCard = { headerText: t('events'), headerComponent: ( - - navigate(FF_NAV_PATHS.activityEventsPath(selectedNamespace, tx?.id)) - } - > - - + ), component: ( <> diff --git a/src/pages/Blockchain/views/Dashboard.tsx b/src/pages/Blockchain/views/Dashboard.tsx index d3d6f276..268d5ced 100644 --- a/src/pages/Blockchain/views/Dashboard.tsx +++ b/src/pages/Blockchain/views/Dashboard.tsx @@ -15,14 +15,13 @@ // limitations under the License. import { Launch } from '@mui/icons-material'; -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { Grid, IconButton, Link } from '@mui/material'; import { BarDatum } from '@nivo/bar'; import dayjs from 'dayjs'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; import { DownloadButton } from '../../../components/Buttons/DownloadButton'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { FireFlyCard } from '../../../components/Cards/FireFlyCard'; import { SmallCard } from '../../../components/Cards/SmallCard'; import { Histogram } from '../../../components/Charts/Histogram'; @@ -79,7 +78,6 @@ export const BlockchainDashboard: () => JSX.Element = () => { const { dateFilter } = useContext(DateFilterContext); const { setSlideSearchParam, slideID } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); - const navigate = useNavigate(); const [isMounted, setIsMounted] = useState(false); // Small cards // Blockchain Operations @@ -294,11 +292,7 @@ export const BlockchainDashboard: () => JSX.Element = () => { const mediumCards: IFireFlyCard[] = [ { headerText: t('recentBlockchainEvents'), - headerComponent: ( - navigate(EVENTS_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('apis'), - headerComponent: ( - navigate(APIS_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('contractListeners'), - headerComponent: ( - navigate(LISTENERS_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { currentPage={currentPage} rowsPerPage={rowsPerPage} dashboardSize - headerBtn={ - navigate(EVENTS_PATH)}> - - - } + headerBtn={} />
diff --git a/src/pages/Home/views/Dashboard.tsx b/src/pages/Home/views/Dashboard.tsx index 86ffe18c..853349d2 100644 --- a/src/pages/Home/views/Dashboard.tsx +++ b/src/pages/Home/views/Dashboard.tsx @@ -1,10 +1,9 @@ -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton, Typography } from '@mui/material'; +import { Grid, Typography } from '@mui/material'; import { BarDatum } from '@nivo/bar'; import dayjs from 'dayjs'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { EmptyStateCard } from '../../../components/Cards/EmptyStateCard'; import { EventCardWrapper } from '../../../components/Cards/EventCards/EventCardWrapper'; import { SkeletonCard } from '../../../components/Cards/EventCards/SkeletonCard'; @@ -65,7 +64,6 @@ export const HomeDashboard: () => JSX.Element = () => { const { dateFilter } = useContext(DateFilterContext); const { slideID, setSlideSearchParam } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); - const navigate = useNavigate(); const [isMounted, setIsMounted] = useState(false); const [viewTx, setViewTx] = useState(); const [viewEvent, setViewEvent] = useState(); @@ -291,13 +289,9 @@ export const HomeDashboard: () => JSX.Element = () => { const mediumCards: IFireFlyCard[] = [ { headerComponent: ( - - navigate(FF_NAV_PATHS.activityTimelinePath(selectedNamespace)) - } - > - - + ), headerText: t('activity'), component: ( @@ -316,22 +310,14 @@ export const HomeDashboard: () => JSX.Element = () => { }, { headerComponent: ( - navigate(FF_NAV_PATHS.networkPath(selectedNamespace))} - > - - + ), headerText: t('networkMap'), component: , }, { headerComponent: ( - navigate(FF_NAV_PATHS.myNodePath(selectedNamespace))} - > - - + ), headerText: t('myNode'), component: ( @@ -411,13 +397,9 @@ export const HomeDashboard: () => JSX.Element = () => { { headerText: t('myRecentTransactions'), headerComponent: ( - - navigate(FF_NAV_PATHS.activityTimelinePath(selectedNamespace)) - } - > - - + ), component: ( <> @@ -442,18 +424,18 @@ export const HomeDashboard: () => JSX.Element = () => { key={idx} > { - setViewEvent(event); - setSlideSearchParam(event.id); - }} onHandleViewTx={(tx: ITransaction) => { setViewTx(tx); setSlideSearchParam(tx.id); }} - link={FF_NAV_PATHS.activityTxDetailPath( - selectedNamespace, + link={ event.tx - )} + ? FF_NAV_PATHS.activityTxDetailPath( + selectedNamespace, + event.tx + ) + : FF_NAV_PATHS.activityTxPath(selectedNamespace) + } {...{ event }} /> @@ -468,13 +450,9 @@ export const HomeDashboard: () => JSX.Element = () => { { headerText: t('recentNetworkEvents'), headerComponent: ( - - navigate(FF_NAV_PATHS.activityTimelinePath(selectedNamespace)) - } - > - - + ), component: ( <> @@ -503,15 +481,18 @@ export const HomeDashboard: () => JSX.Element = () => { setViewEvent(event); setSlideSearchParam(event.id); }} - onHandleViewTx={(tx: ITransaction) => { - setViewTx(tx); - setSlideSearchParam(tx.id); - }} - link={FF_NAV_PATHS.activityTxDetailPathWithSlide( - selectedNamespace, - event.tx, - event.id - )} + link={ + event.tx + ? FF_NAV_PATHS.activityTxDetailPathWithSlide( + selectedNamespace, + event.tx, + event.id + ) + : FF_NAV_PATHS.activityEventsPath( + selectedNamespace, + event.id + ) + } {...{ event }} /> diff --git a/src/pages/Off-Chain/views/Batches.tsx b/src/pages/Off-Chain/views/Batches.tsx index b2f26e5a..6a5e90d4 100644 --- a/src/pages/Off-Chain/views/Batches.tsx +++ b/src/pages/Off-Chain/views/Batches.tsx @@ -140,13 +140,17 @@ export const OffChainBatches: () => JSX.Element = () => { value: , }, { - value: , + value: batch.node ? ( + + ) : ( + + ), }, { value: batch.group ? ( ) : ( - + ), }, { diff --git a/src/pages/Off-Chain/views/Dashboard.tsx b/src/pages/Off-Chain/views/Dashboard.tsx index 7f6a078f..798f7171 100644 --- a/src/pages/Off-Chain/views/Dashboard.tsx +++ b/src/pages/Off-Chain/views/Dashboard.tsx @@ -14,14 +14,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton } from '@mui/material'; +import { Grid } from '@mui/material'; import { BarDatum } from '@nivo/bar'; import dayjs from 'dayjs'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; import { DownloadButton } from '../../../components/Buttons/DownloadButton'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { FireFlyCard } from '../../../components/Cards/FireFlyCard'; import { SmallCard } from '../../../components/Cards/SmallCard'; import { Histogram } from '../../../components/Charts/Histogram'; @@ -77,7 +76,6 @@ export const OffChainDashboard: () => JSX.Element = () => { const { dateFilter } = useContext(DateFilterContext); const { slideID, setSlideSearchParam } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); - const navigate = useNavigate(); const [isMounted, setIsMounted] = useState(false); // Small cards // Message count @@ -264,11 +262,7 @@ export const OffChainDashboard: () => JSX.Element = () => { const mediumCards: IFireFlyCard[] = [ { headerText: t('recentMessages'), - headerComponent: ( - navigate(MESSAGES_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('recentData'), - headerComponent: ( - navigate(DATA_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('datatypes'), - headerComponent: ( - navigate(DATATYPES_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { currentPage={currentPage} rowsPerPage={rowsPerPage} dashboardSize - headerBtn={ - navigate(MESSAGES_PATH)}> - - - } + headerBtn={} /> diff --git a/src/pages/Tokens/views/Dashboard.tsx b/src/pages/Tokens/views/Dashboard.tsx index a8a2ec6d..de7c712c 100644 --- a/src/pages/Tokens/views/Dashboard.tsx +++ b/src/pages/Tokens/views/Dashboard.tsx @@ -14,14 +14,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton } from '@mui/material'; +import { Grid } from '@mui/material'; import { BarDatum } from '@nivo/bar'; import dayjs from 'dayjs'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Jazzicon from 'react-jazzicon'; import { useNavigate } from 'react-router-dom'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { FireFlyCard } from '../../../components/Cards/FireFlyCard'; import { SmallCard } from '../../../components/Cards/SmallCard'; import { Histogram } from '../../../components/Charts/Histogram'; @@ -323,11 +323,7 @@ export const TokensDashboard: () => JSX.Element = () => { const mediumCards: IFireFlyCard[] = [ { headerText: t('tokenTransferTypes'), - headerComponent: ( - navigate(TRANSFERS_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('accountBalances'), - headerComponent: ( - navigate(BALANCES_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { }, { headerText: t('tokenPools'), - headerComponent: ( - navigate(POOLS_PATH)}> - - - ), + headerComponent: , component: ( JSX.Element = () => { navigate(TRANSFERS_PATH)}> - - - } + headerBtn={} onHandleCurrPageChange={(currentPage: number) => setCurrentPage(currentPage) } diff --git a/src/pages/Tokens/views/PoolDetails.tsx b/src/pages/Tokens/views/PoolDetails.tsx index 989fdda3..73d13514 100644 --- a/src/pages/Tokens/views/PoolDetails.tsx +++ b/src/pages/Tokens/views/PoolDetails.tsx @@ -14,14 +14,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { Grid, IconButton, Paper, Typography } from '@mui/material'; +import { Grid, Paper, Typography } from '@mui/material'; import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Jazzicon from 'react-jazzicon'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { FFBreadcrumb } from '../../../components/Breadcrumbs/FFBreadcrumb'; import { FFCopyButton } from '../../../components/Buttons/CopyButton'; +import { FFArrowButton } from '../../../components/Buttons/FFArrowButton'; import { FireFlyCard } from '../../../components/Cards/FireFlyCard'; import { Header } from '../../../components/Header'; import { PoolList } from '../../../components/Lists/PoolList'; @@ -64,7 +64,6 @@ export const PoolDetails: () => JSX.Element = () => { const { slideID, setSlideSearchParam } = useContext(SlideContext); const { reportFetchError } = useContext(SnackbarContext); const { t } = useTranslation(); - const navigate = useNavigate(); const { poolID } = useParams<{ poolID: string }>(); // Pools const [pool, setPool] = useState(); @@ -201,15 +200,9 @@ export const PoolDetails: () => JSX.Element = () => { const accountsCard = { headerText: t('accountsInPool'), headerComponent: pool && ( - - navigate( - FF_NAV_PATHS.tokensBalancesPathByPool(selectedNamespace, pool.id) - ) - } - > - - + ), component: ( JSX.Element = () => { rowsPerPage={rowsPerPage} dashboardSize headerBtn={ - - navigate( - FF_NAV_PATHS.tokensTransfersPath( - selectedNamespace, - pool?.id - ) - ) - } - > - - + } /> diff --git a/src/translations/en.json b/src/translations/en.json index 40c83811..32e2bce4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -179,6 +179,7 @@ "noEvents": "No Events", "noEventsToDisplay": "No Events to Display", "noFromAccount": "No From Account", + "noGroup": "No Group", "noGroupsToDisplay": "No Groups to Display", "noIdentitiesInNs": "No Identities in Namespace", "noInterfacesToDisplay": "No Interfaces to Display", @@ -192,6 +193,7 @@ "noNamespaces": "No Namespaces", "noNameSpecified": "No Name Specified", "none": "None", + "noNode": "No Node", "noNodesToDisplay": "No Nodes to Display", "noOperations": "No Operations", "noOperationsToDisplay": "No Operations to Display", @@ -215,6 +217,7 @@ "noTokenTransfersToDisplay": "No Token Transfers to Display", "noTopicInListener": "No Topic in Listener", "noTopicInMessage": "No Topic in Message", + "noTransaction": "No Transaction", "noTransactions": "No Transactions", "noTransactionsToDisplay": "No Transactions to Display", "noTransfers": "No Transfers", From 8425bfa7e1500e711065c8ca15695734a5fc8a0f Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Fri, 8 Apr 2022 12:28:49 -0400 Subject: [PATCH 7/7] [misc-tweaks] removing ns picker in namespace page Signed-off-by: David Echelberger --- src/pages/Network/views/Namespaces.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Network/views/Namespaces.tsx b/src/pages/Network/views/Namespaces.tsx index e909fe00..a44a5a18 100644 --- a/src/pages/Network/views/Namespaces.tsx +++ b/src/pages/Network/views/Namespaces.tsx @@ -155,6 +155,7 @@ export const NetworkNamespaces: () => JSX.Element = () => { title={t('namespaces')} subtitle={t('network')} noDateFilter + noNsFilter showRefreshBtn={hasIdentityEvent(newEvents)} onRefresh={clearNewEvents} >