From e838bcd86c83528d57d7913b781c04f6d9056d0d Mon Sep 17 00:00:00 2001 From: devinxl Date: Tue, 19 Mar 2024 14:30:46 +0800 Subject: [PATCH 1/4] fix(dcellar-web-ui): disable selection of SPs with invalid status in SPSelector --- apps/dcellar-web-ui/package.json | 3 +- .../components/SPSelector/ErrorBadge.tsx | 69 +++++++ .../components/SPSelector/OptionItem.tsx | 82 ++++++++ .../{SPSelector.tsx => SPSelector/index.tsx} | 177 ++---------------- .../bucket/components/SPSelector/style.ts | 57 ++++++ common/config/rush/pnpm-lock.yaml | 12 ++ 6 files changed, 234 insertions(+), 166 deletions(-) create mode 100644 apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/ErrorBadge.tsx create mode 100644 apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/OptionItem.tsx rename apps/dcellar-web-ui/src/modules/bucket/components/{SPSelector.tsx => SPSelector/index.tsx} (51%) create mode 100644 apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/style.ts diff --git a/apps/dcellar-web-ui/package.json b/apps/dcellar-web-ui/package.json index 92cf7a20..7c84177c 100644 --- a/apps/dcellar-web-ui/package.json +++ b/apps/dcellar-web-ui/package.json @@ -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", diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/ErrorBadge.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/ErrorBadge.tsx new file mode 100644 index 00000000..6857d11e --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/ErrorBadge.tsx @@ -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 = () => ( + + + SP Error + + + ); + + const renderStatusBadge = () => { + const statusText = (StorageProviderStatus[status] || 'Unknown') + .replace('STATUS_', '') + .split('_') + .map((s) => capitalize(s)) + .join(' '); + + return ( + + {statusText} + + ); + }; + + const renderAccessIcon = () => ( + e.stopPropagation()} + > + + + ); + + if (!access) { + if (status === 0) { + return renderUnavailableBadge(); + } else { + return renderStatusBadge(); + } + } else { + return renderAccessIcon(); + } +}); diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/OptionItem.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/OptionItem.tsx new file mode 100644 index 00000000..8e57740e --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/OptionItem.tsx @@ -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 ( + + + + + {name} + + + + + + + {endpoint} + + + + + {meta ? formatBytes(meta.FreeReadQuota) : '--'} + + + {access && spLatency ? `${spLatency}ms` : '--'} + + + ); +}); diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx similarity index 51% rename from apps/dcellar-web-ui/src/modules/bucket/components/SPSelector.tsx rename to apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx index 7a0a046c..f6316fb2 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx @@ -1,28 +1,22 @@ -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(function SPSelector(props) { +export const SPSelector = memo(function SPSelector({ onChange }) { const dispatch = useAppDispatch(); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const unAvailableSps = useAppSelector((root) => root.persist.unAvailableSps); @@ -30,12 +24,9 @@ export const SPSelector = memo(function SPSelector(props) { 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; @@ -46,6 +37,7 @@ export const SPSelector = memo(function SPSelector(props) { address={sp.operatorAddress} name={sp.moniker} endpoint={sp.endpoint} + status={sp.status} access={!disabled} /> ); @@ -66,17 +58,22 @@ export const SPSelector = memo(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, @@ -161,153 +158,3 @@ export const SPSelector = memo(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 ? ( - - - - SP Error - - - - ) : ( - e.stopPropagation()} - > - - - ); - - const spLatency = spLatencyRecords[endpoint.toLowerCase()] || 0; - - return ( - - - - - {name} - - {link} - - - - - {endpoint} - - - - - {meta ? formatBytes(meta.FreeReadQuota) : '--'} - - - {spLatency && access ? spLatency + 'ms' : '--'} - - - ); -} - -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'}; - } - `} -`; diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/style.ts b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/style.ts new file mode 100644 index 00000000..313ad87e --- /dev/null +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/style.ts @@ -0,0 +1,57 @@ +import { transientOptions } from '@/utils/css'; +import styled from '@emotion/styled'; +import { Box } from '@node-real/uikit'; +import { css } from '@emotion/react'; + +export const A = styled.a` + :hover { + color: #00ba34; + } + + margin-left: 4px; +`; + +export const TH = styled(Box)` + padding: 8px; + + &:first-of-type { + padding-left: 12px; + padding-right: 12px; + } + + svg { + color: #aeb4bc; + + :hover { + color: #76808f; + } + } +`; + +export 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'}; + } + `} +`; diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 53581af4..b7d1bcab 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -55,6 +55,7 @@ importers: next-transpile-modules: ~10.0.1 prettier: ^2.8.4 query-string: ^8.1.0 + radash: ~12.1.0 react: ~18.2.0 react-dnd: 16.0.1 react-dnd-html5-backend: 16.0.1 @@ -101,6 +102,7 @@ importers: next-redux-wrapper: 8.1.0_sqwlmqhdtfirfp7b3jp6tjdu3a next-transpile-modules: 10.0.1 query-string: 8.2.0 + radash: 12.1.0 react: 18.2.0 react-dnd: 16.0.1_7acmz257hs6yohiyquldbuobdm react-dnd-html5-backend: 16.0.1 @@ -2822,6 +2824,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.5 + napi-wasm: 1.1.0 dev: false bundledDependencies: - napi-wasm @@ -7918,6 +7921,10 @@ packages: hasBin: true dev: false + /napi-wasm/1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + dev: false + /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -8469,6 +8476,11 @@ packages: engines: {node: '>=8'} dev: true + /radash/12.1.0: + resolution: {integrity: sha512-b0Zcf09AhqKS83btmUeYBS8tFK7XL2e3RvLmZcm0sTdF1/UUlHSsjXdCcWNxe7yfmAlPve5ym0DmKGtTzP6kVQ==} + engines: {node: '>=14.18.0'} + dev: false + /radix3/1.1.0: resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==} dev: false From ee76ef63c564912549e0d99905db5df4489b6056 Mon Sep 17 00:00:00 2001 From: devinxl Date: Tue, 19 Mar 2024 20:34:50 +0800 Subject: [PATCH 2/4] fix(dcellar-web-ui): the sort rule of sps --- .../bucket/components/SPSelector/index.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx index f6316fb2..316b48ad 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx @@ -5,8 +5,8 @@ import { SpEntity, setSpLatency, setupSpLatency } from '@/store/slices/sp'; import { trimLongStr } from '@/utils/string'; import { useMount } from 'ahooks'; -import { find, sortBy } from 'lodash-es'; import { useEffect, useMemo, useRef, useState } from 'react'; +import { sort } from 'radash'; import { memo } from 'react'; import { TH } from './style'; @@ -31,7 +31,7 @@ export const SPSelector = memo(function SPSelector({ onChange } saveOnChangeRef.current = onChange; const renderOption = ({ value, disabled }: MenuOption) => { - const sp = find(allSpList, (sp) => sp.operatorAddress === value)!; + const sp = allSpList.find((sp) => sp.operatorAddress === value)!; return ( (function SPSelector({ onChange } // 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) => { + sort(allSpList, (sp) => { + const meta = spMetaRecords[sp.endpoint]; + if (!meta) { + return Number.MAX_SAFE_INTEGER; + } + if (unAvailableSps.includes(sp.operatorAddress) || sp.status !== 0) { + return Infinity; + } + + return meta.Latency; + }).map((item) => { const { operatorAddress, moniker, status } = item; const spServiceAvailable = !unAvailableSps.includes(operatorAddress); const spStatusAvailable = status === 0; From c40bece80f4681fefae47fde452516769af50d8d Mon Sep 17 00:00:00 2001 From: devinxl Date: Wed, 20 Mar 2024 15:11:49 +0800 Subject: [PATCH 3/4] fix(dcellar-web-ui): unavailable sp not placed last, dev startup port auto-increment issue --- apps/dcellar-web-ui/.eslintrc.js | 2 +- apps/dcellar-web-ui/package.json | 2 +- apps/dcellar-web-ui/scripts/dev.js | 40 +++++++++++++++++++ .../bucket/components/SPSelector/index.tsx | 8 ++-- 4 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 apps/dcellar-web-ui/scripts/dev.js diff --git a/apps/dcellar-web-ui/.eslintrc.js b/apps/dcellar-web-ui/.eslintrc.js index a800745f..72b3cf05 100644 --- a/apps/dcellar-web-ui/.eslintrc.js +++ b/apps/dcellar-web-ui/.eslintrc.js @@ -20,7 +20,7 @@ module.exports = { sourceType: 'module', }, plugins: ['react', '@typescript-eslint'], - ignorePatterns: ['next.config.js', 'public/wasm/**.js'], + ignorePatterns: ['next.config.js', 'public/wasm/**.js', 'scripts/**.js'], rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', diff --git a/apps/dcellar-web-ui/package.json b/apps/dcellar-web-ui/package.json index 7c84177c..d255e98a 100644 --- a/apps/dcellar-web-ui/package.json +++ b/apps/dcellar-web-ui/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "private": false, "scripts": { - "dev": "next dev -p 3101", + "dev": "node ./scripts/dev.js -p 3200", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/apps/dcellar-web-ui/scripts/dev.js b/apps/dcellar-web-ui/scripts/dev.js new file mode 100644 index 00000000..4517b626 --- /dev/null +++ b/apps/dcellar-web-ui/scripts/dev.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); + +const args = process.argv.slice(2); + +let port = 3200; // Default port is 3200 + +// Parsing command line arguments +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === '-p' || arg === '--port') { + const nextArg = args[i + 1]; + if (nextArg && !isNaN(nextArg)) { + port = parseInt(nextArg, 10); + } else { + console.error('Invalid port number. Please provide a valid port number.'); + process.exit(1); + } + } +} + +function getNextAvailablePort(port) { + const command = `lsof -ti:${port}`; + try { + execSync(command); + return getNextAvailablePort(port + 1); + } catch (error) { + return port; + } +} + +function startNextServer(port) { + const command = `next dev -p ${port}`; + console.log(`Starting Next.js server on port ${port}`); + execSync(command, { stdio: 'inherit' }); +} + +const availablePort = getNextAvailablePort(port); +startNextServer(availablePort); diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx index 316b48ad..993a59bc 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/SPSelector/index.tsx @@ -58,17 +58,17 @@ export const SPSelector = memo(function SPSelector({ onChange } return tmpValue.includes(tmpKeyword) || tmpName.includes(tmpKeyword); }; - // Sort SPs with unavailable HTTP services or unavailable statuses last, and the rest by ascending latency. + // Sort: Based on the recommended system's sp data, ascending order of latency values for sps -> No sp data from the recommended system -> Unavailable sps const options: MenuOption[] = useMemo( () => sort(allSpList, (sp) => { const meta = spMetaRecords[sp.endpoint]; - if (!meta) { - return Number.MAX_SAFE_INTEGER; - } if (unAvailableSps.includes(sp.operatorAddress) || sp.status !== 0) { return Infinity; } + if (!meta) { + return Number.MAX_SAFE_INTEGER; + } return meta.Latency; }).map((item) => { From 17542360ee4129cfe2fb08a8e7de573045e625d7 Mon Sep 17 00:00:00 2001 From: devinxl Date: Wed, 20 Mar 2024 15:14:52 +0800 Subject: [PATCH 4/4] docs(dcellar-web-ui): update changelog.md --- apps/dcellar-web-ui/CHANGELOG.json | 12 ++++++++++++ apps/dcellar-web-ui/CHANGELOG.md | 9 ++++++++- apps/dcellar-web-ui/package.json | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/dcellar-web-ui/CHANGELOG.json b/apps/dcellar-web-ui/CHANGELOG.json index 071305e0..d27e4eb3 100644 --- a/apps/dcellar-web-ui/CHANGELOG.json +++ b/apps/dcellar-web-ui/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "dcellar-web-ui", "entries": [ + { + "version": "0.3.1", + "tag": "dcellar-web-ui_v0.3.1", + "date": "Wed, 20 Mar 2024 07:14:31 GMT", + "comments": { + "patch": [ + { + "comment": "disable selection of SPs with invalid status in SPSelector" + } + ] + } + }, { "version": "0.3.0", "tag": "dcellar-web-ui_v0.3.0", diff --git a/apps/dcellar-web-ui/CHANGELOG.md b/apps/dcellar-web-ui/CHANGELOG.md index 44d8a855..2ef316e6 100644 --- a/apps/dcellar-web-ui/CHANGELOG.md +++ b/apps/dcellar-web-ui/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - dcellar-web-ui -This log was last generated on Fri, 15 Mar 2024 09:48:45 GMT and should not be manually modified. +This log was last generated on Wed, 20 Mar 2024 07:14:31 GMT and should not be manually modified. + +## 0.3.1 +Wed, 20 Mar 2024 07:14:31 GMT + +### Patches + +- disable selection of SPs with invalid status in SPSelector ## 0.3.0 Fri, 15 Mar 2024 09:48:45 GMT diff --git a/apps/dcellar-web-ui/package.json b/apps/dcellar-web-ui/package.json index d255e98a..d3fa57d1 100644 --- a/apps/dcellar-web-ui/package.json +++ b/apps/dcellar-web-ui/package.json @@ -1,6 +1,6 @@ { "name": "dcellar-web-ui", - "version": "0.3.0", + "version": "0.3.1", "private": false, "scripts": { "dev": "node ./scripts/dev.js -p 3200",