diff --git a/package-lock.json b/package-lock.json index 1e0e5ad92..8e716d71f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "react-json-view": "^1.21.3", "react-redux": "^7.2.8", "react-router-dom": "^6.2.2", + "react-tooltip": "^4.2.21", "styled-components": "^5.3.5", "winston": "^3.7.2", "winston-daily-rotate-file": "^4.6.1" @@ -14090,6 +14091,30 @@ "react": "^16.8.0 || ^17.0.0" } }, + "node_modules/react-tooltip": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.21.tgz", + "integrity": "sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==", + "dependencies": { + "prop-types": "^15.7.2", + "uuid": "^7.0.3" + }, + "engines": { + "npm": ">=6.13" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/react-tooltip/node_modules/uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/read-config-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", @@ -28011,6 +28036,22 @@ "use-latest": "^1.0.0" } }, + "react-tooltip": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.21.tgz", + "integrity": "sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==", + "requires": { + "prop-types": "^15.7.2", + "uuid": "^7.0.3" + }, + "dependencies": { + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } + } + }, "read-config-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", diff --git a/package.json b/package.json index 830fca97d..d99b8b5ab 100644 --- a/package.json +++ b/package.json @@ -263,6 +263,7 @@ "react-json-view": "^1.21.3", "react-redux": "^7.2.8", "react-router-dom": "^6.2.2", + "react-tooltip": "^4.2.21", "styled-components": "^5.3.5", "winston": "^3.7.2", "winston-daily-rotate-file": "^4.6.1" diff --git a/src/main/geth.ts b/src/main/geth.ts index 3a1e121d6..e0447c5c8 100644 --- a/src/main/geth.ts +++ b/src/main/geth.ts @@ -130,16 +130,14 @@ export const startGeth = async () => { const gethDataPath = gethDataDir(); const gethInput = [ - '--ws', - '--ws.origins', - 'https://ethvis.xyz,nice-node://', - '--ws.api', - 'admin,engine,net,eth,web3', + // '--ws', + // '--ws.origins', + // 'nice-node://', + // '--ws.api', + // 'admin,engine,net,eth,web3', '--http', '--http.corsdomain', 'nice-node://', - '--identity', - 'NiceNode-0.2.0-1', // '--syncmode', // 'light', '--datadir', diff --git a/src/main/monitor.ts b/src/main/monitor.ts index e16f430e1..42814c640 100644 --- a/src/main/monitor.ts +++ b/src/main/monitor.ts @@ -16,8 +16,13 @@ export const getNodeUsage = async () => { if (typeof nodePid !== 'number') { return undefined; } - const gethUsage = await getProcessUsageByPid(nodePid); - return gethUsage; + try { + const gethUsage = await getProcessUsageByPid(nodePid); + return gethUsage; + } catch (err) { + console.error(err); + return undefined; + } }; export const getMainProcessUsage = async () => { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 8a67610fe..fae7bb7d0 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; import * as Sentry from '@sentry/electron/renderer'; +import ReactTooltip from 'react-tooltip'; import { CHANNELS } from './messages'; import electron from './electronGlobal'; @@ -9,6 +10,8 @@ import './App.css'; import Header from './Header'; import Footer from './Footer/Footer'; import Warnings from './Warnings'; +import { useGetExecutionNodeInfoQuery } from './state/services'; +import { detectExecutionClient } from './utils'; Sentry.init({ dsn: electron.SENTRY_DSN, @@ -19,8 +22,13 @@ Sentry.init({ const MainScreen = () => { const [sStatus, setStatus] = useState('loading...'); + const [sNodeInfo, setNodeInfo] = useState(undefined); const [sIsOpenOnLogin, setIsOpenOnLogin] = useState(false); + const qNodeInfo = useGetExecutionNodeInfoQuery(null, { + pollingInterval: 15000, + }); + const refreshGethStatus = async () => { const status = await electron.getGethStatus(); setStatus(status); @@ -39,6 +47,14 @@ const MainScreen = () => { refreshGethStatus(); }, []); + useEffect(() => { + if (typeof qNodeInfo?.data === 'string') { + setNodeInfo(qNodeInfo.data); + } else { + setNodeInfo(undefined); + } + }, [qNodeInfo]); + // Wait for message that says Geth is ready to start const onClickStartGeth = async () => { @@ -81,6 +97,21 @@ const MainScreen = () => {

An Ethereum Node

Status: {sStatus}

+ {sStatus === 'running' && ( + <> +

+ {detectExecutionClient(sNodeInfo, true)} +

+ + {sNodeInfo} + + + )}
  -
diff --git a/src/renderer/Footer/Footer.tsx b/src/renderer/Footer/Footer.tsx index 24e076b95..a14f9b4ac 100644 --- a/src/renderer/Footer/Footer.tsx +++ b/src/renderer/Footer/Footer.tsx @@ -9,11 +9,14 @@ import electron from '../electronGlobal'; import MenuDrawer from './MenuDrawer'; import { useAppSelector } from '../state/hooks'; import { selectNumFreeDiskGB, selectNumGethDiskUsedGB } from '../state/node'; +import { useGetExecutionNodeInfoQuery } from '../state/services'; const Footer = () => { const sGethDiskUsed = useAppSelector(selectNumGethDiskUsedGB); const sFreeDisk = useAppSelector(selectNumFreeDiskGB); - + const qNodeInfo = useGetExecutionNodeInfoQuery(null, { + pollingInterval: 60000, + }); const [sSelectedMenuDrawer, setSelectedMenuDrawer] = useState(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [sLogs, setLogs] = useState(); @@ -196,6 +199,10 @@ const Footer = () => { isSelected={sSelectedMenuDrawer === 'settings'} onClickCloseButton={() => setSelectedMenuDrawer(undefined)} > +

Node

+ {qNodeInfo?.currentData && !qNodeInfo?.isError && ( +

Running: {qNodeInfo.currentData}

+ )}

Storage

Delete node data

diff --git a/src/renderer/Header.tsx b/src/renderer/Header.tsx index 69ed8730d..9248aa64e 100644 --- a/src/renderer/Header.tsx +++ b/src/renderer/Header.tsx @@ -4,8 +4,10 @@ import { HiUserGroup } from 'react-icons/hi'; import { FaSync } from 'react-icons/fa'; import { MdSignalWifiStatusbarConnectedNoInternet } from 'react-icons/md'; import { FiHardDrive } from 'react-icons/fi'; +import { SiHiveBlockchain } from 'react-icons/si'; import { useGetExecutionIsSyncingQuery, + useGetExecutionLatestBlockQuery, useGetExecutionPeersQuery, } from './state/services'; import { useGetNetworkConnectedQuery } from './state/network'; @@ -18,6 +20,7 @@ const Header = () => { const [sIsSyncing, setIsSyncing] = useState(); const [sSyncPercent, setSyncPercent] = useState(''); const [sPeers, setPeers] = useState(); + const [sLatestBlockNumber, setLatestBlockNumber] = useState(); const qExeuctionIsSyncing = useGetExecutionIsSyncingQuery(null, { pollingInterval: 15000, }); @@ -28,12 +31,16 @@ const Header = () => { // Only polls network connection if there are exactly 0 peers pollingInterval: typeof sPeers === 'number' && sPeers === 0 ? 30000 : 0, }); + const qLatestBlock = useGetExecutionLatestBlockQuery(null, { + // Only polls network connection if there are exactly 0 peers + pollingInterval: 15000, + }); useEffect(() => { // console.log('qExeuctionIsSyncing: ', qExeuctionIsSyncing); if (qExeuctionIsSyncing.isError) { setSyncPercent(''); - setIsSyncing(false); + setIsSyncing(undefined); return; } const syncingData = qExeuctionIsSyncing.data; @@ -41,14 +48,17 @@ const Header = () => { const syncRatio = syncingData.currentBlock / syncingData.highestBlock; setSyncPercent((syncRatio * 100).toFixed(1)); setIsSyncing(true); - } else { + } else if (syncingData === false) { + // light client geth, it is done syncing if data is false setSyncPercent(''); setIsSyncing(false); + } else { + setSyncPercent(''); + setIsSyncing(undefined); } }, [qExeuctionIsSyncing]); useEffect(() => { - // console.log('qExecutionPeers: ', qExecutionPeers); if (qExecutionPeers.isError) { setPeers(undefined); return; @@ -60,6 +70,22 @@ const Header = () => { } }, [qExecutionPeers]); + useEffect(() => { + if (qLatestBlock.isError) { + setLatestBlockNumber(undefined); + return; + } + if ( + qLatestBlock?.data?.number && + typeof qLatestBlock.data.number === 'string' + ) { + const latestBlockNum = hexToDecimal(qLatestBlock.data.number); + setLatestBlockNumber(latestBlockNum); + } else { + setLatestBlockNumber(undefined); + } + }, [qLatestBlock]); + return (
{ fontSize: '1.1rem', }} > + {typeof sLatestBlockNumber === 'number' && sLatestBlockNumber > 0 && ( +
+ + + {sLatestBlockNumber} + +
+ )} {sGethDiskUsed && (
@@ -102,7 +142,7 @@ const Header = () => { style={{ display: 'flex', flexDirection: 'row', - alignContent: 'center', + alignItems: 'center', }} > @@ -111,11 +151,12 @@ const Header = () => {
)} + {/* {sIsSyncing !== false && ( */}
@@ -123,12 +164,12 @@ const Header = () => { {sSyncPercent}% synced
- + {/* )} */}
diff --git a/src/renderer/Tooltip.tsx b/src/renderer/Tooltip.tsx deleted file mode 100644 index 99f90e985..000000000 --- a/src/renderer/Tooltip.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import styled from 'styled-components'; - -export const Tooltip = styled.div` - position: relative; - display: inline-block; - - .tooltiptext { - visibility: hidden; - width: 120px; - background-color: #555; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - position: absolute; - z-index: 1; - bottom: 125%; - left: 50%; - margin-left: -60px; - opacity: 0; - transition: opacity 0.3s; - } - - .tooltiptext::after { - content: ''; - position: absolute; - top: 100%; - left: 50%; - margin-left: -5px; - border-width: 5px; - border-style: solid; - border-color: #555 transparent transparent transparent; - } - - &:hover .tooltiptext { - visibility: visible; - opacity: 1; - } -`; diff --git a/src/renderer/Warnings.tsx b/src/renderer/Warnings.tsx index 1763f698b..cfdf60579 100644 --- a/src/renderer/Warnings.tsx +++ b/src/renderer/Warnings.tsx @@ -31,7 +31,6 @@ const Warnings = () => { const updateGethDiskUsed = async () => { const gethDiskUsed = await electron.getGethDiskUsed(); if (gethDiskUsed) { - console.log('dispatch gethDiskUsed: ', gethDiskUsed); dispatch(updateNodeNumGethDiskUsedGB(gethDiskUsed)); } }; diff --git a/src/renderer/state/network.ts b/src/renderer/state/network.ts index 58a4c8caa..277f35c3c 100644 --- a/src/renderer/state/network.ts +++ b/src/renderer/state/network.ts @@ -22,7 +22,6 @@ export const RtkqNetwork: any = createApi({ try { await fetch('https://google.com', { mode: 'no-cors' }); data.isConnected = true; - console.log('fetched google'); } catch (e) { const error = { message: 'Unable to confirm internet connection.' }; console.log(error.message); diff --git a/src/renderer/state/services.ts b/src/renderer/state/services.ts index aa69a026f..05b596817 100644 --- a/src/renderer/state/services.ts +++ b/src/renderer/state/services.ts @@ -17,23 +17,19 @@ export const RtkqExecutionWs: any = createApi({ reducerPath: 'RtkqExecutionWs', baseQuery: fakeBaseQuery(), endpoints: (builder) => ({ - getExecutionLatestBlock: builder.query({ + getExecutionLatestBlock: builder.query({ queryFn: async () => { let data; try { - data = await provider.getBlockNumber(); + data = await provider.send('eth_getBlockByNumber', ['latest', false]); } catch (e) { + const error = { message: 'Unable to get syncing value' }; console.log(e); + return { error }; } return { data }; }, }), - getExecutionBlock: builder.query({ - queryFn: async (blockId) => { - const block = await provider.getBlock(blockId); - return { data: block }; - }, - }), getExecutionIsSyncing: builder.query({ queryFn: async () => { let data; @@ -53,6 +49,20 @@ export const RtkqExecutionWs: any = createApi({ return { data: network }; }, }), + getExecutionNodeInfo: builder.query({ + queryFn: async () => { + let data; + // let error; + try { + data = await provider.send('web3_clientVersion'); + } catch (e) { + const error = { message: 'Unable to get client version.' }; + console.log(e); + return { error }; + } + return { data }; + }, + }), getExecutionPeers: builder.query({ queryFn: async () => { let data; @@ -71,9 +81,10 @@ export const RtkqExecutionWs: any = createApi({ }); export const { - useGetExecutionBlockQuery, + useGetExecutionLatestBlockQuery, useGetExecutionIsSyncingQuery, useGetExecutionNetworkInfoQuery, + useGetExecutionNodeInfoQuery, useGetExecutionChainIdQuery, useGetExecutionPeersQuery, } = RtkqExecutionWs; diff --git a/src/renderer/utils.ts b/src/renderer/utils.ts index ff9bd99b7..f6ef4a3a9 100644 --- a/src/renderer/utils.ts +++ b/src/renderer/utils.ts @@ -1 +1,29 @@ export const hexToDecimal = (hex: string) => parseInt(hex, 16); + +const EXECUTION_CLIENTS = ['Geth', 'Nethermind', 'Besu']; + +export const detectExecutionClient = ( + clientName: string | undefined, + version: boolean | undefined +) => { + if (clientName === undefined) { + return undefined; + } + let formattedClientName = EXECUTION_CLIENTS.find((currEc) => { + if (clientName.toLowerCase().includes(currEc.toLowerCase())) { + return currEc; + } + return false; + }); + if (formattedClientName) { + if (version) { + // parse version + const matchedVersion = clientName.match(/v\d+.\d+.\d+/i); + if (matchedVersion) { + formattedClientName = `${formattedClientName} ${matchedVersion}`; + } + } + return formattedClientName; + } + return clientName; +};