diff --git a/apps/dcellar-web-ui/CHANGELOG.json b/apps/dcellar-web-ui/CHANGELOG.json index a758872c..6600f228 100644 --- a/apps/dcellar-web-ui/CHANGELOG.json +++ b/apps/dcellar-web-ui/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "dcellar-web-ui", "entries": [ + { + "version": "1.4.0", + "tag": "dcellar-web-ui_v1.4.0", + "date": "Thu, 16 May 2024 09:14:19 GMT", + "comments": { + "minor": [ + { + "comment": "Introduce the activities feature for bucket, object and group" + }, + { + "comment": "Introduce the stop upload feature" + } + ] + } + }, { "version": "1.3.2", "tag": "dcellar-web-ui_v1.3.2", diff --git a/apps/dcellar-web-ui/CHANGELOG.md b/apps/dcellar-web-ui/CHANGELOG.md index 41ff6340..1c0b8d7a 100644 --- a/apps/dcellar-web-ui/CHANGELOG.md +++ b/apps/dcellar-web-ui/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log - dcellar-web-ui -This log was last generated on Sat, 11 May 2024 05:44:13 GMT and should not be manually modified. +This log was last generated on Thu, 16 May 2024 09:14:19 GMT and should not be manually modified. + +## 1.4.0 +Thu, 16 May 2024 09:14:19 GMT + +### Minor changes + +- Introduce the activities feature for bucket, object and group +- Introduce the stop upload feature ## 1.3.2 Sat, 11 May 2024 05:44:13 GMT diff --git a/apps/dcellar-web-ui/package.json b/apps/dcellar-web-ui/package.json index 15050e08..94210fef 100644 --- a/apps/dcellar-web-ui/package.json +++ b/apps/dcellar-web-ui/package.json @@ -1,6 +1,6 @@ { "name": "dcellar-web-ui", - "version": "1.3.2", + "version": "1.4.0", "private": false, "scripts": { "dev": "node ./scripts/dev.js -p 3200", diff --git a/apps/dcellar-web-ui/public/js/iconfont_v0.1.1.min.js b/apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js similarity index 98% rename from apps/dcellar-web-ui/public/js/iconfont_v0.1.1.min.js rename to apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js index 5057417d..1e4a3576 100644 --- a/apps/dcellar-web-ui/public/js/iconfont_v0.1.1.min.js +++ b/apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js @@ -1 +1 @@ -!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window); \ No newline at end of file +!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window); \ No newline at end of file diff --git a/apps/dcellar-web-ui/src/components/Activities/index.tsx b/apps/dcellar-web-ui/src/components/Activities/index.tsx new file mode 100644 index 00000000..3529f86d --- /dev/null +++ b/apps/dcellar-web-ui/src/components/Activities/index.tsx @@ -0,0 +1,80 @@ +import { GREENFIELD_CHAIN_EXPLORER_URL } from '@/base/env'; +import { IconFont } from '@/components/IconFont'; +import { CopyText } from '@/components/common/CopyText'; +import { ListEmpty } from '@/components/common/DCTable/ListEmpty'; +import { Activity } from '@/store/slices/object'; +import { formatMsgType } from '@/utils/object'; +import { trimAddress } from '@/utils/string'; +import { formatFullTime } from '@/utils/time'; +import { Box, Center, Flex, Link, Loading, Text } from '@node-real/uikit'; +import { memo } from 'react'; + +interface ActivitiesProps { + loading: boolean; + activities: Activity[]; +} + +export const Activities = memo(function Activities({ loading, activities }) { + if (loading) return ; + if (!activities.length) + return ( + + ); + + return ( + <> + {activities.map((item, index) => ( + + +
+ +
+ {index < activities.length - 1 && ( + + )} +
+ + + + {formatMsgType(item.tx_result.type)}  + + + Transaction Hash + +   ( + + 0x{trimAddress(item.hash, 28, 6, 5)} + + ) + + + + + + {formatFullTime(item.time)} + + + +
+ ))} + {activities.length >= 100 && ( + + Only showing the latest 100 activities ~ + + )} + + ); +}); diff --git a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx index d776f4fb..a94a817a 100644 --- a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx +++ b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx @@ -222,6 +222,7 @@ export const GlobalObjectUploadManager = memo( } else { axios .put(url, task.waitObject.file, { + signal: task.abortController?.signal, async onUploadProgress(progressEvent) { const progress = progressEvent.total ? Math.floor((progressEvent.loaded / progressEvent.total) * 100) @@ -265,6 +266,7 @@ export const GlobalObjectUploadManager = memo( setupUploadTaskErrorMsg({ account: loginAccount, task, + status: e?.code === 'ERR_CANCELED' ? 'CANCEL' : 'ERROR', errorMsg: authExpired ? 'Authentication expired.' : message || e?.message || 'upload error', @@ -461,8 +463,28 @@ export const GlobalObjectUploadManager = memo( // 3. upload useAsyncEffect(async () => { if (!uploadTasks.length) return; - dispatch(updateUploadStatus({ ids: uploadTasks, status: 'UPLOAD', account: loginAccount })); - const tasks = queue.filter((t) => uploadTasks.includes(t.id)); + // Add abortController to each task + const extraFields: Record> = uploadTasks.reduce( + (acc, id) => { + acc[id] = { + abortController: new AbortController(), + }; + return acc; + }, + {} as Record>, + ); + dispatch( + updateUploadStatus({ + ids: uploadTasks, + status: 'UPLOAD', + account: loginAccount, + extraFields, + }), + ); + + const tasks = queue + .filter((t) => uploadTasks.includes(t.id)) + .map((t) => ({ ...t, ...extraFields[t.id] })); tasks.forEach(runUploadTask); }, [uploadTasks.join('')]); diff --git a/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx b/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx index 1a262b66..37e96621 100644 --- a/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx +++ b/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx @@ -1,7 +1,7 @@ import { IconFont } from '@/components/IconFont'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; -import { Box, Text } from '@node-real/uikit'; +import { Box, Flex, Text, Tooltip } from '@node-real/uikit'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { memo } from 'react'; @@ -43,9 +43,21 @@ const ASIDE = [ { link: 'https://docs.bnbchain.org/greenfield-docs/', trackId: 'dc.main.nav.doc.click', - icon: 'doc', + icon: 'book', text: 'BNB Greenfield Docs', }, + { + link: 'https://docs.bnbchain.org/greenfield-docs/docs/release-notes/releaseNotes/', + trackId: 'dc.main.nav.release_note.click', + icon: 'doc', + text: 'Release Notes', + }, + { + link: 'https://discord.com/invite/bnbchain', + trackId: 'dc.main.nav.discord.click', + icon: 'discord', + text: 'Discord', + }, { link: 'https://docs.nodereal.io/docs/dcellar-get-started', trackId: 'dc.main.nav.faq.click', @@ -78,18 +90,38 @@ export const Nav = memo(function Nav() { ); })} - + {ASIDE.map((menu) => ( - - - - - - {menu.text} - - + + + + + + + + + ))} - + ); }); @@ -114,6 +146,7 @@ const MenuItem = styled.li<{ $active?: boolean }>` position: relative; font-weight: 500; transition: all 0.15s; + list-style-type: none; a { display: grid; gap: 12px; diff --git a/apps/dcellar-web-ui/src/facade/bucket.ts b/apps/dcellar-web-ui/src/facade/bucket.ts index c842552b..1c52fb7d 100644 --- a/apps/dcellar-web-ui/src/facade/bucket.ts +++ b/apps/dcellar-web-ui/src/facade/bucket.ts @@ -10,7 +10,7 @@ import { import { getClient } from '@/facade/index'; import { BroadcastResponse } from '@/facade/object'; import { signTypedDataCallback } from '@/facade/wallet'; -import { ObjectResource } from '@/store/slices/object'; +import { Activity, ObjectResource } from '@/store/slices/object'; import { parseError } from '@/utils/string'; import { getTimestampInSeconds } from '@/utils/time'; import { @@ -447,3 +447,12 @@ export const updateBucketTags = async (params: UpdateBucketTagsParams, connector return tx.broadcast(payload).then(resolve, broadcastFault); }; + +export const getBucketActivities = async (id: string): Promise => { + const url = `/api/tx/list/by_bucket/${id}`; + + const [result] = await axios.get<{ result: Activity[] }>(url).then(resolve, commonFault); + if (!result) return []; + + return result.data.result || []; +}; diff --git a/apps/dcellar-web-ui/src/facade/group.ts b/apps/dcellar-web-ui/src/facade/group.ts index 7beb6c8b..ed45b681 100644 --- a/apps/dcellar-web-ui/src/facade/group.ts +++ b/apps/dcellar-web-ui/src/facade/group.ts @@ -10,6 +10,7 @@ import { getClient } from '@/facade/index'; import { BroadcastResponse, DeliverResponse, xmlParser } from '@/facade/object'; import { signTypedDataCallback } from '@/facade/wallet'; import { GroupMember } from '@/store/slices/group'; +import { Activity } from '@/store/slices/object'; import { MsgCreateGroup, MsgDeleteGroup, @@ -252,3 +253,12 @@ export const updateGroupTags = async ( return tx.broadcast(payload).then(resolve, broadcastFault); }; + +export const getGroupActivities = async (id: string): Promise => { + const url = `/api/tx/list/by_group/${id}`; + + const [result] = await axios.get<{ result: Activity[] }>(url).then(resolve, commonFault); + if (!result) return []; + + return result.data.result || []; +}; diff --git a/apps/dcellar-web-ui/src/facade/object.ts b/apps/dcellar-web-ui/src/facade/object.ts index d47760b0..cfcd6fcc 100644 --- a/apps/dcellar-web-ui/src/facade/object.ts +++ b/apps/dcellar-web-ui/src/facade/object.ts @@ -58,7 +58,7 @@ import { ObjectMeta } from '@bnb-chain/greenfield-js-sdk/dist/esm/types/sp/Commo import axios from 'axios'; import { XMLParser } from 'fast-xml-parser'; import { Connector } from 'wagmi'; -import { ObjectVersion } from '@/store/slices/object'; +import { Activity, ObjectVersion } from '@/store/slices/object'; export type DeliverResponse = Awaited>; @@ -653,6 +653,15 @@ export const getObjectVersions = async (id: string): Promise => return result.data.result || []; }; +export const getObjectActivities = async (id: string): Promise => { + const url = `/api/tx/list/by_object/${id}`; + + const [result] = await axios.get<{ result: Activity[] }>(url).then(resolve, commonFault); + if (!result) return []; + + return result.data.result || []; +}; + export type UpdateObjectTagsParams = { address: string; bucketName: string; diff --git a/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx b/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx index 7c47b9ec..b19ec3ba 100644 --- a/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/bucket/components/DetailBucketOperation.tsx @@ -11,6 +11,7 @@ import { setBucketEditQuota, setupBucketQuota, TBucket, + setupBucketActivity, } from '@/store/slices/bucket'; import { selectBucketSp } from '@/store/slices/sp'; import { convertObjectKey } from '@/utils/common'; @@ -27,13 +28,19 @@ import { QDrawerBody, QDrawerFooter, QDrawerHeader, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, Text, Tooltip, } from '@node-real/uikit'; import dayjs from 'dayjs'; import { memo, PropsWithChildren, useEffect, useState } from 'react'; -import { useUnmount } from 'ahooks'; +import { useMount, useUnmount } from 'ahooks'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; +import { Activities } from '@/components/Activities'; export const Label = ({ children }: PropsWithChildren) => ( @@ -41,6 +48,8 @@ export const Label = ({ children }: PropsWithChildren) => ( ); +const VERSION_TABS = ['General Info', 'Activities']; + interface DetailBucketOperationProps { selectedBucketInfo: TBucket; } @@ -68,6 +77,7 @@ export const DetailBucketOperation = memo(function D }) { const dispatch = useAppDispatch(); const bucketQuotaRecords = useAppSelector((root) => root.bucket.bucketQuotaRecords); + const bucketActivityRecords = useAppSelector((root) => root.bucket.bucketActivityRecords); const accountInfos = useAppSelector((root) => root.accounts.accountInfos); const primarySp = useAppSelector(selectBucketSp(selectedBucketInfo))!; @@ -76,6 +86,10 @@ export const DetailBucketOperation = memo(function D const endDate = dayjs().utc?.().endOf('month').format('D MMM, YYYY'); const formattedQuota = formatQuota(bucketQuota); + const activityKey = selectedBucketInfo.BucketName; + const loading = !(activityKey in bucketActivityRecords); + const bucketActivities = bucketActivityRecords[activityKey]; + const nullObjectMeta: ObjectMeta = { ...defaultNullObject, ObjectInfo: { @@ -367,6 +381,10 @@ export const DetailBucketOperation = memo(function D dispatch(setupBucketQuota(selectedBucketInfo.BucketName)); }, [selectedBucketInfo.BucketName, dispatch]); + useMount(() => { + dispatch(setupBucketActivity(selectedBucketInfo.BucketName, selectedBucketInfo.Id)); + }); + useUnmount(() => dispatch(setBucketTagsEditData([DEFAULT_TAG]))); return ( @@ -432,10 +450,25 @@ export const DetailBucketOperation = memo(function D - - {getContent()} - - + + + {VERSION_TABS.map((tab) => ( + + {tab} + + ))} + + + + {getContent()} + + + + + + + + diff --git a/apps/dcellar-web-ui/src/modules/group/components/DetailGroupOperation.tsx b/apps/dcellar-web-ui/src/modules/group/components/DetailGroupOperation.tsx index 03a5b2f0..469a34a1 100644 --- a/apps/dcellar-web-ui/src/modules/group/components/DetailGroupOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/group/components/DetailGroupOperation.tsx @@ -1,4 +1,5 @@ import { GREENFIELD_CHAIN_EXPLORER_URL } from '@/base/env'; +import { Activities } from '@/components/Activities'; import { Avatar } from '@/components/Avatar'; import { IconFont } from '@/components/IconFont'; import { CopyText } from '@/components/common/CopyText'; @@ -6,14 +7,33 @@ import { DCButton } from '@/components/common/DCButton'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; import { LoadingAdaptor } from '@/modules/accounts/components/LoadingAdaptor'; import { useAppDispatch, useAppSelector } from '@/store'; -import { setGroupTagsEditData, setGroupOperation, setupGroupMembers } from '@/store/slices/group'; +import { + setGroupTagsEditData, + setGroupOperation, + setupGroupMembers, + setupGroupActivity, +} from '@/store/slices/group'; import { GroupInfo } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types'; import styled from '@emotion/styled'; -import { Box, Divider, Flex, QDrawerBody, QDrawerHeader, Text } from '@node-real/uikit'; +import { + Box, + Divider, + Flex, + QDrawerBody, + QDrawerHeader, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, + Text, +} from '@node-real/uikit'; import { useAsyncEffect, useUnmount } from 'ahooks'; import { ethers } from 'ethers'; import { memo } from 'react'; +import { useMount } from 'react-use'; +const VERSION_TABS = ['General Info', 'Activities']; interface DetailGroupOperationProps { selectGroup: GroupInfo; } @@ -23,6 +43,7 @@ export const DetailGroupOperation = memo(function Gro }) { const dispatch = useAppDispatch(); const groupMemberListRecords = useAppSelector((root) => root.group.groupMemberListRecords); + const groupActivityRecords = useAppSelector((root) => root.group.groupActivityRecords); const spRecords = useAppSelector((root) => root.sp.spRecords); const specifiedSp = useAppSelector((root) => root.sp.specifiedSp); @@ -36,6 +57,10 @@ export const DetailGroupOperation = memo(function Gro 32, ); + const activityKey = selectGroup.groupName; + const loadingActivity = !(activityKey in groupActivityRecords); + const bucketActivities = groupActivityRecords[activityKey]; + const onEditTag = () => { dispatch(setGroupTagsEditData(selectGroup?.tags?.tags ?? [DEFAULT_TAG])); dispatch(setGroupOperation({ level: 1, operation: [selectGroup.id, 'update_tags'] })); @@ -45,6 +70,10 @@ export const DetailGroupOperation = memo(function Gro dispatch(setupGroupMembers(selectGroup.id, spRecords[specifiedSp].endpoint)); }, [dispatch, selectGroup]); + useMount(() => { + dispatch(setupGroupActivity(selectGroup.groupName, selectGroup.id)); + }); + useUnmount(() => dispatch(setGroupTagsEditData([DEFAULT_TAG]))); return ( @@ -80,99 +109,115 @@ export const DetailGroupOperation = memo(function Gro - - - - Group ID - - - {selectGroup.id} - - - - - Tags - - - {selectGroup?.tags?.tags?.length || 0} tags - - - - - - - Members - - - - - - {members.slice(0, 5).map((m) => { - return ( - - - - ); - })} - {moreText && ( - + + {VERSION_TABS.map((tab) => ( + + {tab} + + ))} + + + + + {/* */} + + Group ID + + - {moreText} - - )} + {selectGroup.id} + + - - - - dispatch(setGroupOperation({ level: 1, operation: [selectGroup.id, 'add'] })) - } - > - Manage Members - - - + + Tags + + + {selectGroup?.tags?.tags?.length || 0} tags + + + + + + + Members + + + + + + {members.slice(0, 5).map((m) => { + return ( + + + + ); + })} + {moreText && ( + + {moreText} + + )} + + + + + dispatch(setGroupOperation({ level: 1, operation: [selectGroup.id, 'add'] })) + } + > + Manage Members + + + + + + + + + ); diff --git a/apps/dcellar-web-ui/src/modules/object/components/DetailObjectOperation.tsx b/apps/dcellar-web-ui/src/modules/object/components/DetailObjectOperation.tsx index 9438a0cf..ee05d0ea 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/DetailObjectOperation.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/DetailObjectOperation.tsx @@ -19,6 +19,7 @@ import { ObjectActionType, setObjectEditTagsData, setObjectOperation, + setupObjectActivity, setupObjectVersion, } from '@/store/slices/object'; import { getSpOffChainData } from '@/store/slices/persist'; @@ -49,11 +50,10 @@ import { last } from 'lodash-es'; import { memo, useState } from 'react'; import { OBJECT_ERROR_TYPES, ObjectErrorType } from '../ObjectError'; import { setSignatureAction } from '@/store/slices/global'; -import { ListEmpty } from '@/components/common/DCTable/ListEmpty'; -import { TD, TH } from '@/modules/bucket/components/SPSelector/style'; import { VersionTable } from '@/modules/object/components/VersionTable'; +import { Activities } from '@/components/Activities'; -const VERSION_TABS = ['General Info', 'Versions']; +const VERSION_TABS = ['General Info', 'Activities', 'Versions']; interface DetailObjectOperationProps { selectObjectInfo: ObjectMeta; @@ -67,6 +67,7 @@ export const DetailObjectOperation = memo( const { selectObjectInfo, selectBucket, bucketAccountDetail, primarySp } = props; const dispatch = useAppDispatch(); const objectVersionRecords = useAppSelector((root) => root.object.objectVersionRecords); + const objectActivityRecords = useAppSelector((root) => root.object.objectActivityRecords); const accountRecords = useAppSelector((root) => root.persist.accountRecords); const loginAccount = useAppSelector((root) => root.persist.loginAccount); const currentBucketName = useAppSelector((root) => root.object.currentBucketName); @@ -82,6 +83,9 @@ export const DetailObjectOperation = memo( const loading = !(versionKey in objectVersionRecords); const objectVersions = objectVersionRecords[versionKey]; + const loadingActivity = !(versionKey in objectActivityRecords); + const objectActivities = objectActivityRecords[versionKey]; + const errorHandler = (type: string) => { setAction(''); if (type === E_OFF_CHAIN_AUTH) { @@ -150,6 +154,7 @@ export const DetailObjectOperation = memo( useMount(() => { dispatch(setupObjectVersion(objectInfo.ObjectName, objectInfo.Id)); + dispatch(setupObjectActivity(objectInfo.ObjectName, objectInfo.Id)); }); useUnmount(() => dispatch(setObjectEditTagsData([DEFAULT_TAG]))); @@ -252,6 +257,9 @@ export const DetailObjectOperation = memo( + + + diff --git a/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx b/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx index 38e17f07..99e96236 100644 --- a/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx +++ b/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx @@ -1,10 +1,21 @@ -import { Center, CircleProps, Flex, FlexProps, LinkProps, Tooltip } from '@node-real/uikit'; +import { + Center, + CircleProps, + Flex, + FlexProps, + LinkProps, + Tooltip, + useMediaQuery, +} from '@node-real/uikit'; export const Card = ({ children, ...props }: FlexProps) => { + const [isNormal] = useMediaQuery('(max-width: 1440px)'); return ( { return ( <> {data.map((item, index) => ( - + t.name === 'Link')?.url || ''} + target="_blank" + rel="noopener noreferrer" + > diff --git a/apps/dcellar-web-ui/src/modules/toolbox/config.ts b/apps/dcellar-web-ui/src/modules/toolbox/config.ts index 7d596ead..41bc75f2 100644 --- a/apps/dcellar-web-ui/src/modules/toolbox/config.ts +++ b/apps/dcellar-web-ui/src/modules/toolbox/config.ts @@ -72,7 +72,7 @@ export const toolList = [ url: 'https://github.com/bnb-chain/greenfield-go-sdk', }, ], - desc: 'Go sdk for Greenfield', + desc: 'Go SDK for Greenfield', }, { icon: 'cosmos', @@ -86,7 +86,7 @@ export const toolList = [ url: 'https://github.com/bnb-chain/greenfield-cosmos-sdk', }, ], - desc: 'A cosmos-sdk fork for greenfield', + desc: 'A cosmos-SDK fork for Greenfield', }, { icon: 'javascript', @@ -142,7 +142,7 @@ export const toolList = [ url: 'https://github.com/bnb-chain/greenfield-python-sdk', }, ], - desc: 'Python sdk for Greenfield', + desc: 'Python SDK for Greenfield', }, { icon: 'terminal', diff --git a/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx b/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx index 04d298e3..e0ab9a25 100644 --- a/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx @@ -1,10 +1,14 @@ import { UploadObject } from '@/store/slices/global'; -import { Text } from '@node-real/uikit'; import { Loading } from '@/components/common/Loading'; import { UploadProgress } from './UploadProgress'; import { IconFont } from '@/components/IconFont'; +import { memo } from 'react'; -export const ObjectUploadStatus = ({ task }: { task: UploadObject }) => { +export const ObjectUploadStatus = memo(function ObjectUploadStatus({ + task, +}: { + task: UploadObject; +}) { switch (task.status) { case 'RETRY_CHECK': case 'RETRY_CHECKING': @@ -61,11 +65,11 @@ export const ObjectUploadStatus = ({ task }: { task: UploadObject }) => { case 'CANCEL': return ( <> - - Cancelled + + Stopped ); default: return null; } -}; +}); diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx index 9721f0bc..aae1ce23 100644 --- a/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx @@ -1,36 +1,51 @@ import { IconFont } from '@/components/IconFont'; import { DCButton } from '@/components/common/DCButton'; import { useAppDispatch, useAppSelector } from '@/store'; -import { clearUploadRecords, retryUploadTasks } from '@/store/slices/global'; -import React from 'react'; +import { + cancelUploadingRequests, + clearUploadRecords, + retryUploadTasks, + updateUploadStatus, +} from '@/store/slices/global'; +import React, { useCallback } from 'react'; export type ActionButtonProps = { - type: 'clear' | 'retry' | 'clear-all' | 'retry-all'; + type: 'clear' | 'retry' | 'clear-all' | 'retry-all' | 'cancel' | 'cancel-all'; ids: number[]; text?: string; }; const actionItems = [ { - type: 'clear', - text: 'Clear', - icon: 'delete', + type: 'cancel', + text: 'Cancel', + icon: 'stop', + }, + { + type: 'cancel-all', + text: 'Stop Uploading', + icon: 'stop', }, { type: 'retry', text: 'Retry', icon: 'retry', }, - { - type: 'clear-all', - text: 'Clear All Records', - icon: 'delete', - }, { type: 'retry-all', text: 'Retry All', icon: 'retry', }, + { + type: 'clear', + text: 'Clear', + icon: 'delete', + }, + { + type: 'clear-all', + text: 'Clear All Records', + icon: 'delete', + }, ]; export const UploadActionButton = React.memo(function UploadActionButton({ @@ -42,6 +57,19 @@ export const UploadActionButton = React.memo(function UploadActionButton({ const dispatch = useAppDispatch(); const actionItem = actionItems.find((item) => item.type === type); + const onCancel = useCallback( + (ids: number[]) => { + dispatch( + updateUploadStatus({ + account: loginAccount, + ids, + status: 'CANCEL', + }), + ); + dispatch(cancelUploadingRequests({ ids })); + }, + [dispatch, loginAccount], + ); const onClear = (ids: number[]) => { dispatch(clearUploadRecords({ ids, loginAccount })); }; @@ -50,14 +78,18 @@ export const UploadActionButton = React.memo(function UploadActionButton({ }; const onClick = () => { switch (type) { - case 'clear': - case 'clear-all': - onClear(ids); + case 'cancel': + case 'cancel-all': + onCancel(ids); break; case 'retry': case 'retry-all': onRetry(ids); break; + case 'clear': + case 'clear-all': + onClear(ids); + break; default: break; } diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx index 4b9cd11d..d1c33a5e 100644 --- a/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx @@ -19,7 +19,7 @@ import { UploadingPanelKey, useTaskManagementTab } from './useTaskManagementTab' import { IconFont } from '@/components/IconFont'; import { UploadingObjectsList } from './UploadingObjectsList'; -import { UploadObject } from '@/store/slices/global'; +import { UPLOADING_STATUSES, UPLOAD_FAILED_STATUSES, UploadObject } from '@/store/slices/global'; import { UploadActionButton } from './UploadActionButton'; interface UploadingObjectsProps {} @@ -36,15 +36,20 @@ export const UploadingObjects = memo(function UploadingOb panelKey: UploadingPanelKey; data: UploadObject[]; }) => { - if ([UploadingPanelKey.ALL, UploadingPanelKey.UPLOADING].includes(panelKey)) { - return null; - } return ( {panelKey === UploadingPanelKey.COMPLETE && ( item.id)} /> )} - {panelKey === UploadingPanelKey.FAILED && ( + {(panelKey === UploadingPanelKey.UPLOADING || panelKey === UploadingPanelKey.ALL) && ( + UPLOADING_STATUSES.includes(item.status)) + .map((item) => item.id)} + /> + )} + {(panelKey === UploadingPanelKey.FAILED || panelKey === UploadingPanelKey.STOPPED) && ( item.id)} /> item.id)} /> diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx index 191cf48a..22060ca0 100644 --- a/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx @@ -1,10 +1,5 @@ import { DCTable } from '@/components/common/DCTable'; -import { - UPLOADING_STATUSES, - UPLOAD_FAILED_STATUSES, - UPLOAD_SUCCESS_STATUS, - UploadObject, -} from '@/store/slices/global'; +import { UploadObject } from '@/store/slices/global'; import { ColumnProps } from 'antd/es/table'; import React, { useState } from 'react'; import { NameItem } from './NameItem'; @@ -34,7 +29,6 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => { size={record.waitObject.size} msg={record.msg} status={record.status} - w={234} task={record} /> ); @@ -72,7 +66,8 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => { title: 'Action', width: 146, render: (record) => { - if (UPLOADING_STATUSES.includes(record.status)) { + const { status, id } = record; + if (['SEAL', 'SEALING'].includes(status)) { return ( -- @@ -80,15 +75,29 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => { ); } - if (UPLOAD_SUCCESS_STATUS === record.status) { - return ; - } else if (UPLOAD_FAILED_STATUSES.includes(record.status)) { - return ( - - - - - ); + switch (status) { + case 'FINISH': + return ; + + case 'CANCEL': + case 'ERROR': + return ( + + + + + ); + + case 'WAIT': + case 'HASH': + case 'HASHED': + case 'SIGN': + case 'SIGNED': + case 'UPLOAD': + return ; + + default: + return null; } }, }, diff --git a/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx b/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx index 19bcb443..1d63ff10 100644 --- a/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx +++ b/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx @@ -1,14 +1,15 @@ import { useAppSelector } from '@/store'; -import { UploadObject } from '@/store/slices/global'; +import { UPLOADING_STATUSES, UploadObject } from '@/store/slices/global'; import { sortBy } from 'lodash-es'; import { useMemo, useState } from 'react'; export enum UploadingPanelKey { ALL = 'ALL', - UPLOADING = 'HASH-UPLOAD-SEAL', + UPLOADING = 'RETRY-WAIT-HASH-UPLOAD-SIGN-SEAL', + STOPPED = 'CANCEL', COMPLETE = 'FINISH', - FAILED = 'ERROR-CANCEL', + FAILED = 'ERROR', } export const useTaskManagementTab = () => { @@ -17,14 +18,14 @@ export const useTaskManagementTab = () => { const queue = sortBy(objectUploadQueue[loginAccount] || [], (o) => o.waitObject.time); - const { uploadingQueue, completeQueue, errorQueue } = useMemo(() => { - const uploadingQueue = queue?.filter((i) => - ['HASH', 'UPLOAD', 'SEAL', 'SEALING'].includes(i.status), - ); + const { uploadingQueue, stoppedQueue, completeQueue, errorQueue } = useMemo(() => { + const uploadingQueue = queue?.filter((i) => UPLOADING_STATUSES.includes(i.status)); const completeQueue = queue?.filter((i) => i.status === 'FINISH'); - const errorQueue = queue?.filter((i) => ['ERROR', 'CANCEL'].includes(i.status)); + const stoppedQueue = queue?.filter((i) => i.status === 'CANCEL'); + const errorQueue = queue?.filter((i) => ['ERROR'].includes(i.status)); return { uploadingQueue, + stoppedQueue, completeQueue, errorQueue, }; @@ -46,6 +47,11 @@ export const useTaskManagementTab = () => { key: UploadingPanelKey.UPLOADING, data: uploadingQueue, }, + { + title: 'Stopped', + key: UploadingPanelKey.STOPPED, + data: stoppedQueue, + }, { title: 'Complete', key: UploadingPanelKey.COMPLETE, diff --git a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx index c1d1a1f3..30bd1de0 100644 --- a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx +++ b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx @@ -107,59 +107,57 @@ export const TransferIn = memo(function TransferIn() { }; return ( - <> + - - - - - - - - -
- - {isShowFee() ? ( - <> - - - - ) : ( - - )} - - - - + + + + + +
+
+ - + {isShowFee() ? ( + <> + + + + ) : ( + + )} + + + + - + ); }); diff --git a/apps/dcellar-web-ui/src/pages/_document.tsx b/apps/dcellar-web-ui/src/pages/_document.tsx index e45554c8..e5bed808 100644 --- a/apps/dcellar-web-ui/src/pages/_document.tsx +++ b/apps/dcellar-web-ui/src/pages/_document.tsx @@ -26,7 +26,7 @@ export default function Document() { __html: `window.__ASSET_PREFIX = ${flatted.stringify(assetPrefix)}`, }} > - + diff --git a/apps/dcellar-web-ui/src/pages/api/tx/[[...slug]].ts b/apps/dcellar-web-ui/src/pages/api/tx/[[...slug]].ts new file mode 100644 index 00000000..fc05aaae --- /dev/null +++ b/apps/dcellar-web-ui/src/pages/api/tx/[[...slug]].ts @@ -0,0 +1,18 @@ +import { BILLING_API_URL, EXPLORER_API_URL } from '@/base/env'; +import axios from 'axios'; +import { NextApiRequest, NextApiResponse } from 'next'; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + const { slug } = req.query; + const slugs = slug as string[]; + const url = `${EXPLORER_API_URL}/greenfield/tx/${slugs.join('/')}?page=1&per_page=100`; + try { + const { data } = await axios.get(url, { data: {} }); + res.json(data); + } catch (e) { + console.error('tx', e); + res.json({}); + } +}; + +export default handler; diff --git a/apps/dcellar-web-ui/src/store/slices/bucket.ts b/apps/dcellar-web-ui/src/store/slices/bucket.ts index 63005755..a565d4f5 100644 --- a/apps/dcellar-web-ui/src/store/slices/bucket.ts +++ b/apps/dcellar-web-ui/src/store/slices/bucket.ts @@ -6,11 +6,18 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { find, isEmpty, omit } from 'lodash-es'; import { SpEntity, setupPrimarySpInfo, setPrimarySpInfos } from './sp'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; -import { getBucketReadQuota, getUserBucketMeta, getUserBuckets } from '@/facade/bucket'; +import { + getBucketActivities, + getBucketReadQuota, + getUserBucketMeta, + getUserBuckets, +} from '@/facade/bucket'; import { AppDispatch, AppState, GetState } from '@/store'; import { setAuthModalOpen } from '@/store/slices/global'; import { getSpOffChainData } from '@/store/slices/persist'; import { convertObjectKey } from '@/utils/common'; +import { Activity } from './object'; +import { numberToHex } from 'viem'; export type BucketOperationsType = | 'detail' @@ -45,6 +52,7 @@ export interface BucketState { bucketEditQuota: string[]; bucketOperation: Record<0 | 1, [string, BucketOperationsType]>; bucketEditTagsData: ResourceTags_Tag[]; + bucketActivityRecords: Record; } const initialState: BucketState = { @@ -59,6 +67,7 @@ const initialState: BucketState = { bucketEditQuota: ['', ''], bucketOperation: { 0: ['', ''], 1: ['', ''] }, bucketEditTagsData: [DEFAULT_TAG], + bucketActivityRecords: {}, }; export const bucketSlice = createSlice({ @@ -139,6 +148,13 @@ export const bucketSlice = createSlice({ const { bucketName, paymentAddress } = payload; state.bucketRecords[bucketName]['PaymentAddress'] = paymentAddress; }, + setBucketActivity( + state, + { payload }: PayloadAction<{ activities: Activity[]; bucketName: string }>, + ) { + const { bucketName, activities } = payload; + state.bucketActivityRecords[bucketName] = activities; + }, }, }); @@ -155,6 +171,7 @@ export const { setBucketTags, setBucketTagsEditData, setBucketPaymentAccount, + setBucketActivity, } = bucketSlice.actions; const defaultBucketList = Array(); @@ -258,4 +275,10 @@ export const setupBucketQuota = dispatch(setBucketQuota({ bucketName, quota })); }; +export const setupBucketActivity = + (bucketName: string, id: string) => async (dispatch: AppDispatch) => { + const activities = await getBucketActivities(numberToHex(Number(id), { size: 32 })); + dispatch(setBucketActivity({ bucketName, activities })); + }; + export default bucketSlice.reducer; diff --git a/apps/dcellar-web-ui/src/store/slices/global.ts b/apps/dcellar-web-ui/src/store/slices/global.ts index 697906cc..2526221a 100644 --- a/apps/dcellar-web-ui/src/store/slices/global.ts +++ b/apps/dcellar-web-ui/src/store/slices/global.ts @@ -105,6 +105,7 @@ export type UploadObject = { msg: string; progress: number; delegateUpload?: boolean; + abortController?: AbortController; }; export interface GlobalState { @@ -175,14 +176,32 @@ export const globalSlice = createSlice({ state, { payload, - }: PayloadAction<{ account: string; ids: number[]; status: UploadObject['status'] }>, + }: PayloadAction<{ + account: string; + ids: number[]; + status: UploadObject['status']; + extraFields?: Record>; + }>, ) { - const { account, ids, status } = payload; + const { account, ids, status, extraFields } = payload; const isErrorStatus = UPLOAD_FAILED_STATUSES.includes(status); const queue = state.objectUploadQueue[account] || []; - state.objectUploadQueue[account] = queue.map((q) => - ids.includes(q.id) ? { ...q, status, msg: isErrorStatus ? q.msg : '' } : q, - ); + state.objectUploadQueue[account] = queue.map((item) => { + if (ids.includes(item.id)) { + const updatedItem = { ...item, ...extraFields?.[item.id], status }; + + if (status === 'RETRY_CHECK') { + return { ...updatedItem, msg: '', progress: 0 }; + } + if (isErrorStatus) { + return { ...updatedItem, msg: item.msg }; + } + + return updatedItem; + } + + return item; + }); if (status === 'SEAL') { ids.forEach((id) => { @@ -222,15 +241,17 @@ export const globalSlice = createSlice({ task.status = 'ERROR'; task.msg = msg; }, - updateUploadTaskMsg( + updateUploadTaskErrorMsg( state, - { payload }: PayloadAction<{ account: string; id: number; msg: string }>, + { + payload, + }: PayloadAction<{ account: string; id: number; msg: string; status?: UploadObjectStatus }>, ) { - const { id, msg } = payload; + const { id, msg, status } = payload; const task = find(state.objectUploadQueue[payload.account], (t) => t.id === id); if (!task) return; - task.status = 'ERROR'; - task.msg = msg; + task.status = task.status !== 'CANCEL' ? status ?? 'ERROR' : 'CANCEL'; + task.msg = task.status !== 'CANCEL' ? msg : ''; }, updateWaitObjectStatus( state, @@ -429,7 +450,7 @@ export const { setBnbUsdtExchangeRate, updateWaitObjectStatus, updateWaitTaskMsg, - updateUploadTaskMsg, + updateUploadTaskErrorMsg, updateUploadChecksum, addToUploadQueue, updateUploadStatus, @@ -697,14 +718,25 @@ export const addDelegatedTasksToUploadQueue = }; export const setupUploadTaskErrorMsg = - ({ account, task, errorMsg }: { account: string; task: UploadObject; errorMsg: string }) => + ({ + account, + task, + errorMsg, + status, + }: { + account: string; + task: UploadObject; + errorMsg: string; + status?: UploadObjectStatus; + }) => async (dispatch: AppDispatch) => { // const isFolder = task.waitObject.name.endsWith('/'); dispatch( - updateUploadTaskMsg({ + updateUploadTaskErrorMsg({ account, id: task.id, msg: errorMsg || 'The object failed to be created.', + status, }), ); // isFolder && dispatch(cancelUploadFolder({ account, folderName: task.waitObject.name })); @@ -738,4 +770,15 @@ export const retryUploadTasks = }); }; +export const cancelUploadingRequests = + ({ ids }: { ids: number[] }) => + async (dispatch: AppDispatch, getState: GetState) => { + const { loginAccount } = getState().persist; + const uploadQueue = getState().global.objectUploadQueue[loginAccount] || _emptyUploadQueue; + const tasks = uploadQueue.filter((task) => ids.includes(task.id)); + tasks.forEach(async (task) => { + task.abortController && task.abortController.abort(); + }); + }; + export default globalSlice.reducer; diff --git a/apps/dcellar-web-ui/src/store/slices/group.ts b/apps/dcellar-web-ui/src/store/slices/group.ts index de404803..9e38ff78 100644 --- a/apps/dcellar-web-ui/src/store/slices/group.ts +++ b/apps/dcellar-web-ui/src/store/slices/group.ts @@ -6,8 +6,10 @@ import { import { toast } from '@node-real/uikit'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { getGroupMembers, getGroups } from '@/facade/group'; +import { getGroupActivities, getGroupMembers, getGroups } from '@/facade/group'; import { AppDispatch, AppState, GetState } from '@/store'; +import { Activity } from './object'; +import { numberToHex } from 'viem'; export type GroupMember = { AccountId: string; @@ -41,6 +43,7 @@ interface GroupInitialState { groupSelectedMembers: string[]; editTags: [string, string]; groupEditTagsData: ResourceTags_Tag[]; + groupActivityRecords: Record; } const defaultGroupInfo: GroupInfo = { owner: '', @@ -61,6 +64,7 @@ const initialState: GroupInitialState = { groupSelectedMembers: [], editTags: ['', ''], groupEditTagsData: [DEFAULT_TAG], + groupActivityRecords: {}, }; export const groupSlice = createSlice({ @@ -116,6 +120,13 @@ export const groupSlice = createSlice({ setGroupTagsEditData(state, { payload }: PayloadAction) { state.groupEditTagsData = payload; }, + setGroupActivity( + state, + { payload }: PayloadAction<{ activities: Activity[]; groupName: string }>, + ) { + const { groupName, activities } = payload; + state.groupActivityRecords[groupName] = activities; + }, }, }); @@ -130,6 +141,7 @@ export const { setGroupSelectedMembers, setGroupTagsEditData, setGroupTags, + setGroupActivity, } = groupSlice.actions; const defaultGroupList = Array(); @@ -184,4 +196,11 @@ export const setupGroupMembers = return members; }; +export const setupGroupActivity = + (groupName: string, id: string) => async (dispatch: AppDispatch) => { + const activities = await getGroupActivities(numberToHex(Number(id), { size: 32 })); + + dispatch(setGroupActivity({ groupName, activities })); + }; + export default groupSlice.reducer; diff --git a/apps/dcellar-web-ui/src/store/slices/object.ts b/apps/dcellar-web-ui/src/store/slices/object.ts index e52e0c75..6f90df21 100644 --- a/apps/dcellar-web-ui/src/store/slices/object.ts +++ b/apps/dcellar-web-ui/src/store/slices/object.ts @@ -22,7 +22,12 @@ import { numberToHex } from 'viem'; import { DEFAULT_TAG } from '@/components/common/ManageTags'; import { getFolderPolicies, getObjectPolicies } from '@/facade/bucket'; import { ErrorResponse } from '@/facade/error'; -import { ListObjectsParams, getListObjects, getObjectVersions } from '@/facade/object'; +import { + ListObjectsParams, + getListObjects, + getObjectActivities, + getObjectVersions, +} from '@/facade/object'; import { AppDispatch, AppState, GetState } from '@/store'; import { convertObjectKey } from '@/utils/common'; import { getMillisecond } from '@/utils/time'; @@ -96,6 +101,31 @@ export type ObjectVersion = { Version: number; }; +export type Activity = { + hash: string; + height: number; + index: number; + code: number; + proof: { + data: any; + proof: any; + tx: any; + }; + tx_result: { + code: number; + gas_used: number; + gas_wanted: number; + fee: string; + log: string; + messages: string; + type: string; + module: string; + }; + time: string; + log: string; + messages: string; +}; + export interface ObjectState { currentBucketName: string; pathSegments: string[]; @@ -105,6 +135,7 @@ export interface ObjectState { objectListTruncated: Record; objectRecords: Record; objectVersionRecords: Record; + objectActivityRecords: Record; objectListPageRecords: Record; objectListPageRestored: boolean; objectSelectedKeys: Key[]; @@ -133,6 +164,7 @@ const initialState: ObjectState = { objectListRecords: {}, objectRecords: {}, objectVersionRecords: {}, + objectActivityRecords: {}, objectListPageRecords: {}, objectListPageRestored: true, objectSelectedKeys: [], @@ -397,6 +429,14 @@ export const objectSlice = createSlice({ const key = [state.currentBucketName, objectName].join('/'); state.objectVersionRecords[key] = versions; }, + setObjectActivity( + state, + { payload }: PayloadAction<{ activities: Activity[]; objectName: string }>, + ) { + const { objectName, activities } = payload; + const key = [state.currentBucketName, objectName].join('/'); + state.objectActivityRecords[key] = activities; + }, }, }); @@ -428,6 +468,7 @@ export const { setObjectTags, setObjectEditTagsData, setObjectVersion, + setObjectActivity, } = objectSlice.actions; export const selectPathLoading = (root: AppState) => { @@ -600,4 +641,10 @@ export const setupObjectVersion = dispatch(setObjectVersion({ objectName, versions })); }; +export const setupObjectActivity = + (objectName: string, id: number) => async (dispatch: AppDispatch) => { + const activities = await getObjectActivities(numberToHex(Number(id), { size: 32 })); + dispatch(setObjectActivity({ objectName, activities })); + }; + export default objectSlice.reducer; diff --git a/apps/dcellar-web-ui/src/utils/object/index.ts b/apps/dcellar-web-ui/src/utils/object/index.ts index 57117243..96ca4876 100644 --- a/apps/dcellar-web-ui/src/utils/object/index.ts +++ b/apps/dcellar-web-ui/src/utils/object/index.ts @@ -104,3 +104,14 @@ export const waitUploadFilterFn = (item: WaitObject) => { export const errorUploadFilterFn = (item: WaitObject) => { return item.status === 'ERROR' && !isUploadObjectUpdate(item); }; + +export const formatMsgType = (type: string) => { + const msgText = (type.split('.').pop() || '') + .replace(/^Msg/, '') + .match(/[A-Z0-9][a-z0-9]*/g) + ?.filter((item) => item !== 'V2') + .join(' '); + console.log(msgText); + + return msgText; +}; diff --git a/apps/dcellar-web-ui/src/utils/time.ts b/apps/dcellar-web-ui/src/utils/time.ts index fdde58b0..ab2af74e 100644 --- a/apps/dcellar-web-ui/src/utils/time.ts +++ b/apps/dcellar-web-ui/src/utils/time.ts @@ -58,7 +58,7 @@ export const formatTime = (utcZeroTimestamp = 0) => { }; export const formatFullTime = ( - utcZeroTimestamp = 0, + utcZeroTimestamp: number | string | Date | Dayjs | undefined, format?: 'MMM D, YYYY HH:mm A' | 'YYYY-MM-DD HH:mm:ss', ) => { if (!utcZeroTimestamp) {