Skip to content

Commit

Permalink
fix(dcellar-web-ui): disable selection of SPs with invalid status in …
Browse files Browse the repository at this point in the history
…SPSelector
  • Loading branch information
devinxl committed Mar 19, 2024
1 parent 1b305b6 commit e838bcd
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 166 deletions.
3 changes: 2 additions & 1 deletion apps/dcellar-web-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"react-dnd-html5-backend": "16.0.1",
"viem": "~1.19.11",
"@wagmi/core": "~1.4.10",
"set-interval-async": "~3.0.3"
"set-interval-async": "~3.0.3",
"radash": "~12.1.0"
},
"devDependencies": {
"@commitlint/cli": "^17.4.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { GREENFIELD_CHAIN_EXPLORER_URL } from '@/base/env';
import { IconFont } from '@/components/IconFont';
import { DCTooltip } from '@/components/common/DCTooltip';
import { Status as StorageProviderStatus } from '@bnb-chain/greenfield-cosmos-types/greenfield/sp/types';
import { ExternalLinkIcon } from '@node-real/icons';
import { Badge } from '@node-real/uikit';
import { A } from './style';
import { capitalize } from 'radash';
import { memo } from 'react';

type ErrorBadgeProps = {
access: boolean;
address: string;
status: number;
};

export const ErrorBadge = memo(function ErrorBadge({ access, address, status }: ErrorBadgeProps) {
const renderUnavailableBadge = () => (
<DCTooltip title="Check reasons in documentations" placement="bottomLeft">
<Badge
as="a"
target="_blank"
href="https://docs.nodereal.io/docs/dcellar-faq#storage-provider-related"
ml={4}
colorScheme="danger"
cursor="pointer"
_hover={{
color: 'scene.danger.normal',
}}
>
SP Error <ExternalLinkIcon boxSize={12} ml={2} />
</Badge>
</DCTooltip>
);

const renderStatusBadge = () => {
const statusText = (StorageProviderStatus[status] || 'Unknown')
.replace('STATUS_', '')
.split('_')
.map((s) => capitalize(s))
.join(' ');

return (
<Badge ml={4} colorScheme="danger">
{statusText}
</Badge>
);
};

const renderAccessIcon = () => (
<A
href={`${GREENFIELD_CHAIN_EXPLORER_URL}/account/${address}`}
target="_blank"
onClick={(e) => e.stopPropagation()}
>
<IconFont type="external" w={12} />
</A>
);

if (!access) {
if (status === 0) {
return renderUnavailableBadge();
} else {
return renderStatusBadge();
}
} else {
return renderAccessIcon();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useAppSelector } from '@/store';
import { Flex, Text } from '@node-real/uikit';
import { TD } from './style';
import { DCTooltip } from '@/components/common/DCTooltip';
import { formatBytes } from '@/utils/formatter';
import { ErrorBadge } from './ErrorBadge';
import { memo } from 'react';

interface OptionItemProps {
address: string;
name: string;
endpoint: string;
access: boolean;
status: number;
}

export const OptionItem = memo(function OptionItem({
address,
name,
endpoint,
access,
status,
}: OptionItemProps) {
const spMetaRecords = useAppSelector((root) => root.sp.spMetaRecords);
const spLatencyRecords = useAppSelector((root) => root.sp.spLatencyRecords);
const meta = spMetaRecords[endpoint];
const spLatency = spLatencyRecords[endpoint.toLowerCase()] || 0;
const textColor = access ? 'readable.secondary' : 'readable.disable';
const tooltipColor = access ? 'tertiary' : 'disable';

return (
<Flex key={address} alignItems="center" cursor={access ? 'pointer' : 'not-allowed'}>
<TD
w={251}
key={address}
display="flex"
flexDir="column"
alignItems="flex-start"
whiteSpace="normal"
color={textColor}
>
<Flex alignItems="center" w="100%">
<Text
maxW="max-content"
minW={0}
flex={1}
lineHeight="17px"
fontSize={14}
fontWeight={400}
w="100%"
color={textColor}
noOfLines={1}
>
{name}
</Text>
<ErrorBadge access={access} address={address} status={status} />
</Flex>

<DCTooltip title={endpoint} placement="bottomLeft">
<Text
lineHeight="14px"
wordBreak="break-all"
fontSize={12}
transformOrigin="0 50%"
transform={'scale(0.85)'}
fontWeight={400}
color={tooltipColor}
noOfLines={1}
>
{endpoint}
</Text>
</DCTooltip>
</TD>
<TD w={120} color={textColor}>
{meta ? formatBytes(meta.FreeReadQuota) : '--'}
</TD>
<TD $dot={access ? spLatency : 0} color={textColor}>
{access && spLatency ? `${spLatency}ms` : '--'}
</TD>
</Flex>
);
});
Original file line number Diff line number Diff line change
@@ -1,41 +1,32 @@
import { GREENFIELD_CHAIN_EXPLORER_URL } from '@/base/env';
import { IconFont } from '@/components/IconFont';
import { MenuOption } from '@/components/common/DCMenuList';
import { DCSelect } from '@/components/common/DCSelect';
import { DCTooltip } from '@/components/common/DCTooltip';
import { useAppDispatch, useAppSelector } from '@/store';
import { SpEntity, setSpLatency, setupSpLatency } from '@/store/slices/sp';
import { transientOptions } from '@/utils/css';
import { formatBytes } from '@/utils/formatter';
import { trimLongStr } from '@/utils/string';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { ExternalLinkIcon } from '@node-real/icons';
import { Box, Flex, Text } from '@node-real/uikit';

import { useMount } from 'ahooks';
import { find, sortBy } from 'lodash-es';
import { useEffect, useMemo, useRef, useState } from 'react';

import { memo } from 'react';
import { TH } from './style';
import { OptionItem } from './OptionItem';

interface SPSelectorProps {
onChange: (value: SpEntity) => void;
}

export const SPSelector = memo<SPSelectorProps>(function SPSelector(props) {
export const SPSelector = memo<SPSelectorProps>(function SPSelector({ onChange }) {
const dispatch = useAppDispatch();
const loginAccount = useAppSelector((root) => root.persist.loginAccount);
const unAvailableSps = useAppSelector((root) => root.persist.unAvailableSps);
const spRecords = useAppSelector((root) => root.sp.spRecords);
const specifiedSp = useAppSelector((root) => root.sp.specifiedSp);
const allSpList = useAppSelector((root) => root.sp.allSpList);
const spMetaRecords = useAppSelector((root) => root.sp.spMetaRecords);

const [sp, setSP] = useState({} as SpEntity);
const [total, setTotal] = useState(0);

const len = allSpList.length;
const { onChange } = props;
const saveOnChangeRef = useRef(onChange);
saveOnChangeRef.current = onChange;

Expand All @@ -46,6 +37,7 @@ export const SPSelector = memo<SPSelectorProps>(function SPSelector(props) {
address={sp.operatorAddress}
name={sp.moniker}
endpoint={sp.endpoint}
status={sp.status}
access={!disabled}
/>
);
Expand All @@ -66,17 +58,22 @@ export const SPSelector = memo<SPSelectorProps>(function SPSelector(props) {
return tmpValue.includes(tmpKeyword) || tmpName.includes(tmpKeyword);
};

// Sort SPs with unavailable HTTP services or unavailable statuses last, and the rest by ascending latency.
const options: MenuOption[] = useMemo(
() =>
sortBy(allSpList, [
(i) => (unAvailableSps.includes(i.operatorAddress) ? 1 : 0),
(i) => (i.status !== 0 ? 1 : 0),
(sp) => {
const meta = spMetaRecords[sp.endpoint];
return meta ? meta.Latency : Infinity;
},
]).map((item) => {
const { operatorAddress, moniker } = item;
const access = !unAvailableSps.includes(operatorAddress);
const { operatorAddress, moniker, status } = item;
const spServiceAvailable = !unAvailableSps.includes(operatorAddress);
const spStatusAvailable = status === 0;
const access = spServiceAvailable && spStatusAvailable;

return {
label: moniker,
value: operatorAddress,
Expand Down Expand Up @@ -161,153 +158,3 @@ export const SPSelector = memo<SPSelectorProps>(function SPSelector(props) {
const renderItem = (moniker: string, address: string) => {
return [moniker, trimLongStr(address, 10, 6, 4)].filter(Boolean).join(' | ');
};

function OptionItem(props: any) {
const spMetaRecords = useAppSelector((root) => root.sp.spMetaRecords);
const spLatencyRecords = useAppSelector((root) => root.sp.spLatencyRecords);

const { address, name, endpoint, access } = props;
const meta = spMetaRecords[endpoint];

const link = !access ? (
<DCTooltip title="Check reasons in documentations" placement="bottomLeft">
<Text
as="a"
target="_blank"
href="https://docs.nodereal.io/docs/dcellar-faq#storage-provider-related"
w={64}
h={18}
whiteSpace="nowrap"
ml={4}
bgColor="#FDEBE7"
borderRadius={'360px'}
color="#F15D3C"
fontWeight={400}
lineHeight="18px"
cursor="pointer"
_hover={{
color: '#EE3911',
}}
>
<Box as="span" transform="scale(.8)" display="inline-flex" alignItems="center">
SP Error <ExternalLinkIcon boxSize={12} ml={2} />
</Box>
</Text>
</DCTooltip>
) : (
<A
href={`${GREENFIELD_CHAIN_EXPLORER_URL}/account/${address}`}
target="_blank"
onClick={(e) => e.stopPropagation()}
>
<IconFont type="external" w={12} />
</A>
);

const spLatency = spLatencyRecords[endpoint.toLowerCase()] || 0;

return (
<Flex key={address} alignItems="center" cursor={access ? 'pointer' : 'not-allowed'}>
<TD
w={251}
key={address}
display="flex"
flexDir="column"
alignItems="flex-start"
whiteSpace="normal"
color={access ? '#474D57' : '#AEB4BC'}
>
<Flex alignItems="center" w="100%">
<Text
maxW="max-content"
minW={0}
flex={1}
lineHeight="17px"
fontSize={14}
fontWeight={400}
w="100%"
color={access ? '#474D57' : '#AEB4BC'}
noOfLines={1}
>
{name}
</Text>
{link}
</Flex>

<DCTooltip title={endpoint} placement="bottomLeft">
<Text
lineHeight="14px"
wordBreak="break-all"
fontSize={12}
transformOrigin="0 50%"
transform={'scale(0.85)'}
fontWeight={400}
color={access ? '#76808F' : '#AEB4BC'}
noOfLines={1}
>
{endpoint}
</Text>
</DCTooltip>
</TD>
<TD w={120} color={access ? '#474D57' : '#AEB4BC'}>
{meta ? formatBytes(meta.FreeReadQuota) : '--'}
</TD>
<TD $dot={access ? spLatency : 0} color={access ? '#474D57' : '#AEB4BC'}>
{spLatency && access ? spLatency + 'ms' : '--'}
</TD>
</Flex>
);
}

const A = styled.a`
:hover {
color: #00ba34;
}
margin-left: 4px;
`;

const TH = styled(Box)`
padding: 8px;
&:first-of-type {
padding-left: 12px;
padding-right: 12px;
}
svg {
color: #aeb4bc;
:hover {
color: #76808f;
}
}
`;

const TD = styled(Box, transientOptions)<{ $dot?: number }>`
height: 31px;
position: relative;
font-size: 14px;
font-weight: 400;
${(props) =>
props.$dot &&
css`
:before {
position: relative;
top: -1px;
margin-right: 4px;
display: inline-flex;
content: '';
width: 8px;
height: 8px;
border-radius: 100%;
background-color: ${props.$dot < 100
? '#00BA34'
: props.$dot < 200
? '#EEBE11'
: '#EE3911'};
}
`}
`;
Loading

0 comments on commit e838bcd

Please sign in to comment.