Compare commits

..

13 Commits

Author SHA1 Message Date
bee-worker 9b5b2973cb chore: release 0.14.0 (#319) 2022-04-14 16:03:11 +05:00
bee-worker 36da804ca4 docs: update supported bee (#337) 2022-04-14 15:42:02 +05:00
Vojtech Simetka 8f51aa9e89 ci: migrate to swarm-actions for PR previews (#310) 2022-04-14 15:31:09 +05:00
Vojtech Simetka 0a31a04148 chore(deps): update bee-js to 3.3.4 (#336) 2022-04-14 15:30:46 +05:00
Vojtech Simetka eb9e309c8b feat: add hook that detects if the bee-dashboard is run within bee-desktop (#334)
* feat: add hook that detects if the bee-dashboard is run withing bee-desktop

* chore: make the URL configurable

* feat: remove error and instead return false

* test: add testing with mockserver
2022-04-13 19:00:37 +05:00
Vojtech Simetka 5d0fbf705d feat: optional status checks (e.g. connected peers > 0 or funded chequebook) (#331)
* feat: make some check optional (e.g. connected peers > 0 or funded chequebook)

* fix: alter setup step text to better describe what needs to be done

* refactor: rename isOk from boolean value to checkState enum

* fix: add checking for any error
2022-04-13 18:09:30 +05:00
Ivan Vandot cd332c4dfd chore: replace REPO_GHA_PAT with GHA_PAT_BASIC (#330) 2022-04-08 22:23:50 +02:00
Cafe137 224fe4ce25 refactor: add missing props to generic components (#325)
* refactor: add missing props to generic components

* fix: remove undefined from variant

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
2022-04-05 22:54:16 +02:00
Vojtech Simetka 4736e82da5 ci: enable depcheck (#320) 2022-04-01 11:22:27 +02:00
Vojtech Simetka 8baecb783f feat: detect bee mode and enable/disable status checks accordingly (#318) 2022-03-29 15:37:40 +02:00
bee-worker bf24d61584 docs: update supported bee (#316) 2022-03-27 23:11:31 +02:00
Vojtech Simetka 01351a0380 chore(deps): update to bee-js 3.3.3 (#315)
* chore(deps): update to bee-js 3.3.3-pre.0

* chore: update to bee-js 3.3.3
2022-03-27 22:40:49 +02:00
Vojtech Simetka d0b3f1abee fix: postage stamp price and TTL calculation (#305)
* fix: postage stamp price and TTL calculation

* chore: removed logs and fixed linter issues
2022-03-10 17:49:09 +01:00
30 changed files with 982 additions and 461 deletions
+12 -6
View File
@@ -52,6 +52,9 @@ jobs:
env:
CI: true
- name: Dependency check
run: npm run depcheck
- name: Types check
run: npm run check:types
@@ -62,7 +65,7 @@ jobs:
uses: ethersphere/update-supported-bee-action@v1
if: github.ref == 'refs/heads/master'
with:
token: ${{ secrets.REPO_GHA_PAT }}
token: ${{ secrets.GHA_PAT_BASIC }}
- name: Build
run: npm run build
@@ -71,15 +74,18 @@ jobs:
run: npm run build:component
- name: Create preview
uses: ethersphere/beeload-action@v1
uses: ethersphere/swarm-actions/pr-preview@v0
with:
bee-url: https://unlimited.gateway.ethswarm.org
preview: 'true'
token: ${{ secrets.REPO_GHA_PAT }}
extra-params: '-H "${{ secrets.GATEWAY_AUTHORIZATION_HEADER }}"'
token: ${{ secrets.GHA_PAT_BASIC }}
error-document: index.html
headers: "${{ secrets.GATEWAY_AUTHORIZATION_HEADER }}"
- name: Upload to testnet
uses: ethersphere/swarm-actions/upload-dir@v0
continue-on-error: true
uses: ethersphere/beeload-action@v1
with:
index-document: index.html
error-document: index.html
dir: ./build
bee-url: https://api.gateway.testnet.ethswarm.org
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- uses: GoogleCloudPlatform/release-please-action@v2
id: release
with:
token: ${{ secrets.REPO_GHA_PAT }}
token: ${{ secrets.GHA_PAT_BASIC }}
release-type: node
package-name: bee-dashboard
bump-minor-pre-major: true
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## [0.14.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.13.0...v0.14.0) (2022-04-14)
### Features
* add hook that detects if the bee-dashboard is run within bee-desktop ([#334](https://www.github.com/ethersphere/bee-dashboard/issues/334)) ([eb9e309](https://www.github.com/ethersphere/bee-dashboard/commit/eb9e309c8bc0327d137f190d6873618cb215fece))
* detect bee mode and enable/disable status checks accordingly ([#318](https://www.github.com/ethersphere/bee-dashboard/issues/318)) ([8baecb7](https://www.github.com/ethersphere/bee-dashboard/commit/8baecb783f1574af1cd1f17738efae4b0ac9f0c8))
* optional status checks (e.g. connected peers > 0 or funded chequebook) ([#331](https://www.github.com/ethersphere/bee-dashboard/issues/331)) ([5d0fbf7](https://www.github.com/ethersphere/bee-dashboard/commit/5d0fbf705dfed6738980c751a9654199d60a3787))
### Bug Fixes
* postage stamp price and TTL calculation ([#305](https://www.github.com/ethersphere/bee-dashboard/issues/305)) ([d0b3f1a](https://www.github.com/ethersphere/bee-dashboard/commit/d0b3f1abee7ea017bdd05954d5fadafb67365efd))
## [0.13.0](https://www.github.com/ethersphere/bee-dashboard/compare/v0.12.0...v0.13.0) (2022-01-28)
+1 -1
View File
@@ -13,7 +13,7 @@
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and
working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.4.1-238867f1<!-- SUPPORTED_BEE_END -->**.
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.5.1-d0a77598<!-- SUPPORTED_BEE_END -->**.
Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
[releases tab](https://github.com/ethersphere/bee-dashboard/releases).
+549 -278
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ethersphere/bee-dashboard",
"version": "0.13.0",
"version": "0.14.0",
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
"keywords": [
"bee",
@@ -26,7 +26,7 @@
"url": "https://github.com/ethersphere/bee-dashboard.git"
},
"dependencies": {
"@ethersphere/bee-js": "3.1.0",
"@ethersphere/bee-js": "^3.3.4",
"@ethersphere/manifest-js": "1.1.0",
"@ethersphere/swarm-cid": "^0.1.0",
"@material-ui/core": "4.12.3",
@@ -64,6 +64,9 @@
"@commitlint/config-conventional": "14.1.0",
"@testing-library/jest-dom": "5.15.0",
"@testing-library/react": "12.1.2",
"@testing-library/react-hooks": "^8.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/file-saver": "2.0.4",
"@types/jest": "27.0.2",
"@types/qrcode.react": "1.0.2",
@@ -80,7 +83,8 @@
"babel-loader": "8.1.0",
"babel-plugin-syntax-dynamic-import": "6.18.0",
"babel-plugin-tsconfig-paths": "1.0.2",
"depcheck": "1.4.2",
"cors": "^2.8.5",
"depcheck": "^1.4.3",
"eslint": "7.24.0",
"eslint-config-prettier": "8.2.0",
"eslint-config-react-app": "6.0.0",
@@ -92,6 +96,7 @@
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-testing-library": "3.10.2",
"express": "^4.17.3",
"file-loader": "6.2.0",
"prettier": "2.4.1",
"react-scripts": "4.0.3",
+2 -4
View File
@@ -66,11 +66,9 @@ export default function SideBarItem({ path }: Props): ReactElement {
disableRipple
>
<ListItemIcon style={{ marginLeft: '30px' }}>
<StatusIcon isOk={status.all} isLoading={isLoading} />
<StatusIcon checkState={status.all} isLoading={isLoading} />
</ListItemIcon>
<ListItemText
primary={<Typography className={classes.smallerText}>{`Node ${status.all ? 'OK' : 'Error'}`}</Typography>}
/>
<ListItemText primary={<Typography className={classes.smallerText}>{`Node ${status.all}`}</Typography>} />
<ListItemIcon className={classes.icon}>
{status.all ? null : <ArrowRight className={classes.iconSmall} />}
</ListItemIcon>
+20 -3
View File
@@ -1,23 +1,40 @@
import type { ReactElement } from 'react'
import { CircularProgress } from '@material-ui/core'
import { CheckState } from '../providers/Bee'
interface Props {
isOk: boolean
checkState: CheckState
isLoading?: boolean
size?: number | string
className?: string
}
export default function StatusIcon({ isOk, size, className, isLoading }: Props): ReactElement {
export default function StatusIcon({ checkState, size, className, isLoading }: Props): ReactElement {
const s = size || '1rem'
if (isLoading) return <CircularProgress size={s} className={className} />
let backgroundColor: string
switch (checkState) {
case CheckState.OK:
backgroundColor = '#1de600'
break
case CheckState.WARNING:
backgroundColor = 'orange'
break
case CheckState.ERROR:
backgroundColor = '#ff3a52'
break
default:
// Default is error
backgroundColor = '#ff3a52'
}
return (
<span
className={className}
style={{
backgroundColor: isOk ? '#1de600' : '#ff3a52',
backgroundColor,
height: s,
width: s,
borderRadius: '50%',
+3 -1
View File
@@ -10,6 +10,7 @@ interface Props {
disabled?: boolean
loading?: boolean
cancel?: boolean
variant?: 'text' | 'contained' | 'outlined'
}
const useStyles = makeStyles(() =>
@@ -49,6 +50,7 @@ export function SwarmButton({
disabled,
loading,
cancel,
variant = 'contained',
}: Props): ReactElement {
const classes = useStyles()
@@ -76,7 +78,7 @@ export function SwarmButton({
onClick()
event.currentTarget.blur()
}}
variant="contained"
variant={variant}
startIcon={icon}
disabled={disabled}
>
+12 -3
View File
@@ -9,6 +9,7 @@ interface Props {
password?: boolean
formik?: boolean
optional?: boolean
defaultValue?: string
onChange?: (event: ChangeEvent<HTMLTextAreaElement>) => void
}
@@ -32,7 +33,15 @@ const useStyles = makeStyles((theme: Theme) =>
}),
)
export function SwarmTextInput({ name, label, password, optional, formik, onChange }: Props): ReactElement {
export function SwarmTextInput({
name,
label,
password,
optional,
formik,
onChange,
defaultValue,
}: Props): ReactElement {
const classes = useStyles()
if (formik) {
@@ -46,7 +55,7 @@ export function SwarmTextInput({ name, label, password, optional, formik, onChan
fullWidth
variant="filled"
className={classes.field}
defaultValue=""
defaultValue={defaultValue || ''}
InputProps={{ disableUnderline: true }}
/>
)
@@ -60,7 +69,7 @@ export function SwarmTextInput({ name, label, password, optional, formik, onChan
fullWidth
variant="filled"
className={classes.field}
defaultValue=""
defaultValue={defaultValue || ''}
onChange={onChange}
InputProps={{ disableUnderline: true }}
/>
+2
View File
@@ -9,6 +9,7 @@ class Config {
public readonly BEE_DOCS_HOST: string
public readonly BEE_DISCORD_HOST: string
public readonly GITHUB_REPO_URL: string
public readonly BEE_DESKTOP_URL: string
constructor() {
this.BEE_API_HOST =
@@ -21,6 +22,7 @@ class Config {
this.BEE_DISCORD_HOST = getProcessEnv('REACT_APP_BEE_DISCORD_HOST') || 'https://discord.gg/eKr9XPv7'
this.GITHUB_REPO_URL =
getProcessEnv('REACT_APP_BEE_GITHUB_REPO_URL') || 'https://api.github.com/repos/ethersphere/bee'
this.BEE_DESKTOP_URL = getProcessEnv('REACT_APP_BEE_DESKTOP_URL') || window.location.origin
}
}
+73
View File
@@ -0,0 +1,73 @@
import { renderHook } from '@testing-library/react-hooks'
import express from 'express'
import cors from 'cors'
import type { Server } from 'http'
import { useIsBeeDesktop } from './apiHooks'
interface AddressInfo {
address: string
family: string
port: number
}
export function mockServer(data: Record<string | number | symbol, string>): Promise<Server> {
const app = express()
app.use(cors())
app.get('/info', (req, res) => {
res.send(data)
})
return new Promise(resolve => {
const server = app.listen(() => {
resolve(server)
})
})
}
let serverCorrect: Server
let serverWrong: Server
let serverCorrectURL: string
let serverWrongURL: string
beforeAll(async () => {
serverCorrect = await mockServer({ name: 'bee-desktop' })
const portServerCorrect = (serverCorrect.address() as AddressInfo).port
serverCorrectURL = `http://localhost:${portServerCorrect}`
serverWrong = await mockServer({ foo: 'bar' })
const portServerWrong = (serverWrong.address() as AddressInfo).port
serverWrongURL = `http://localhost:${portServerWrong}`
})
afterAll(async () => {
await new Promise(resolve => serverCorrect.close(resolve))
await new Promise(resolve => serverWrong.close(resolve))
})
describe('useIsBeeDesktop', () => {
it('should fail when connected to wrong server', async () => {
const { result, waitFor } = renderHook(() => useIsBeeDesktop({ BEE_DESKTOP_URL: serverWrongURL }))
expect(result.current.isLoading).toBe(true)
expect(result.current.isBeeDesktop).toBe(false)
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
})
expect(result.current.isBeeDesktop).toBe(false)
})
it('should return isBeeDesktop true when connected to bee-desktop', async () => {
const { result, waitFor } = renderHook(() => useIsBeeDesktop({ BEE_DESKTOP_URL: serverCorrectURL }))
expect(result.current.isLoading).toBe(true)
expect(result.current.isBeeDesktop).toBe(false)
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
})
expect(result.current.isBeeDesktop).toBe(true)
})
})
+36
View File
@@ -8,6 +8,42 @@ export interface LatestBeeReleaseHook {
error: Error | null
}
export interface IsBeeDesktopHook {
isBeeDesktop: boolean
isLoading: boolean
}
interface Config {
BEE_DESKTOP_URL: string
}
/**
* Detect if the dashboard is run within bee-desktop
*
* @returns isBeeDesktop true if this is run within bee-desktop
*/
export const useIsBeeDesktop = (conf: Config = config): IsBeeDesktopHook => {
const [isBeeDesktop, setIsBeeDesktop] = useState<boolean>(false)
const [isLoading, setLoading] = useState<boolean>(true)
useEffect(() => {
axios
.get(`${conf.BEE_DESKTOP_URL}/info`)
.then(res => {
if (res.data?.name === 'bee-desktop') setIsBeeDesktop(true)
else setIsBeeDesktop(false)
})
.catch(() => {
setIsBeeDesktop(false)
})
.finally(() => {
setLoading(false)
})
}, [conf])
return { isBeeDesktop, isLoading }
}
export const useLatestBeeRelease = (): LatestBeeReleaseHook => {
const [latestBeeRelease, setLatestBeeRelease] = useState<LatestBeeRelease | null>(null)
const [isLoadingLatestBeeRelease, setLoading] = useState<boolean>(false)
+21
View File
@@ -57,4 +57,25 @@ export class Token {
toFixedDecimal(digits = 7): string {
return this.toDecimal.toFixed(digits)
}
toSignificantDigits(digits = 4): string {
const asString = this.toDecimal.toFixed(16)
let indexOfSignificantDigit = -1
let reachedDecimalPoint = false
for (let i = 0; i < asString.length; i++) {
const char = asString[i]
if (char === '.') {
reachedDecimalPoint = true
indexOfSignificantDigit = i + 1
} else if (reachedDecimalPoint && char !== '0') {
indexOfSignificantDigit = i
break
}
}
return asString.slice(0, indexOfSignificantDigit + digits)
}
}
+2 -2
View File
@@ -2,7 +2,7 @@ import { ReactElement, useContext } from 'react'
import PeerBalances from './PeerBalances'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { useAccounting } from '../../hooks/accounting'
import ExpandableList from '../../components/ExpandableList'
@@ -19,7 +19,7 @@ export default function Accounting(): ReactElement {
const { accounting, totalUncashed, isLoadingUncashed } = useAccounting(beeDebugApi, settlements, peerBalances)
if (!status.all) return <TroubleshootConnectionCard />
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
return (
<div>
+2 -2
View File
@@ -8,7 +8,7 @@ import ExpandableListItemActions from '../../components/ExpandableListItemAction
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../providers/Feeds'
import { ROUTES } from '../../routes'
import { formatEnum } from '../../utils'
@@ -60,7 +60,7 @@ export default function Feeds(): ReactElement {
setShowDelete(true)
}
if (!status.all) return <TroubleshootConnectionCard />
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
return (
<div>
+2 -2
View File
@@ -6,7 +6,7 @@ import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../providers/Feeds'
import { Context as FileContext } from '../../providers/File'
import { Context as SettingsContext } from '../../providers/Settings'
@@ -43,7 +43,7 @@ export function Upload(): ReactElement {
refresh()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
if (!status.all) return <TroubleshootConnectionCard />
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
if (!files.length) {
setFiles([])
+4 -2
View File
@@ -2,7 +2,7 @@ import { ReactElement, useContext } from 'react'
import { Button } from '@material-ui/core'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -17,13 +17,15 @@ export default function Status(): ReactElement {
topology,
nodeAddresses,
chequebookAddress,
nodeInfo,
} = useContext(BeeContext)
if (!status.all) return <TroubleshootConnectionCard />
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
return (
<div>
<ExpandableList label="Bee Node" defaultOpen>
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
<ExpandableListItem
label="Agent"
value={
+19 -22
View File
@@ -2,20 +2,14 @@ import { Box, Grid, Typography } from '@material-ui/core'
import BigNumber from 'bignumber.js'
import { Form, Formik, FormikHelpers } from 'formik'
import { useSnackbar } from 'notistack'
import React, { ReactElement, useContext } from 'react'
import { ReactElement, useContext } from 'react'
import { Check } from 'react-feather'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmTextInput } from '../../components/SwarmTextInput'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as StampsContext } from '../../providers/Stamps'
import {
calculateStampPrice,
convertAmountToSeconds,
convertDepthToBytes,
formatBzz,
secondsToTimeString,
} from '../../utils'
import { calculateStampPrice, convertAmountToSeconds, convertDepthToBytes, secondsToTimeString } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
interface FormValues {
@@ -50,24 +44,27 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
}
function getTtl(amount: number): string {
if (isNaN(amount) || amount <= 0) {
return '-'
}
return secondsToTimeString(convertAmountToSeconds(amount))
}
function getPrice(depth: number, amount: number): string {
const hasInvalidInput = isNaN(amount) || amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
const isCurrentPriceAvailable = chainState && chainState.currentPrice
if (hasInvalidInput || !isCurrentPriceAvailable) {
if (amount <= 0 || !isCurrentPriceAvailable) {
return '-'
}
const price = calculateStampPrice(depth, amount, chainState.currentPrice)
const pricePerBlock = Number.parseInt(chainState.currentPrice, 10)
return `${formatBzz(price)} BZZ`
return `${secondsToTimeString(convertAmountToSeconds(amount, pricePerBlock))} (with price of 0 per block)`
}
function getPrice(depth: number, amount: bigint): string {
const hasInvalidInput = amount <= 0 || isNaN(depth) || depth < 17 || depth > 255
if (hasInvalidInput) {
return '-'
}
const price = calculateStampPrice(depth, amount)
return `${price.toSignificantDigits()} BZZ`
}
return (
@@ -136,7 +133,7 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
<Box mt={0.25} sx={{ bgcolor: '#f6f6f6' }} p={2}>
<Grid container justifyContent="space-between">
<Typography>Corresponding TTL (Time to live)</Typography>
<Typography>{getTtl(parseInt(values.amount || '0', 10))}</Typography>
<Typography>{getTtl(Number.parseInt(values.amount || '0', 10))}</Typography>
</Grid>
</Box>
</Box>
@@ -146,7 +143,7 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
<Box mb={4} sx={{ bgcolor: '#fcf2e8' }} p={2}>
<Grid container justifyContent="space-between">
<Typography>Indicative Price</Typography>
<Typography>{getPrice(parseInt(values.depth || '0', 10), parseInt(values.amount || '0', 10))}</Typography>
<Typography>{getPrice(parseInt(values.depth || '0', 10), BigInt(values.amount || '0'))}</Typography>
</Grid>
</Box>
<SwarmButton
+2 -2
View File
@@ -5,7 +5,7 @@ import { PlusSquare } from 'react-feather'
import { useNavigate } from 'react-router'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as StampsContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import StampsTable from './StampsTable'
@@ -41,7 +41,7 @@ export default function Stamp(): ReactElement {
return () => stop()
}, [status]) // eslint-disable-line react-hooks/exhaustive-deps
if (!status.all) return <TroubleshootConnectionCard />
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
function navigateToNewStamp() {
navigate(ROUTES.STAMPS_NEW)
@@ -1,39 +1,59 @@
import { useContext } from 'react'
import DepositModal from '../../../containers/DepositModal'
import type { ReactElement } from 'react'
import type { ReactElement, ReactNode } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
const ChequebookDeployFund = (): ReactElement | null => {
const { status, isLoading, chequebookAddress } = useContext(Context)
const isOk = status.chequebook
const { checkState, isEnabled } = status.chequebook
if (!isEnabled) return null
let text: ReactNode
switch (checkState) {
case CheckState.OK:
text = 'Your chequebook is deployed and funded'
break
case CheckState.WARNING:
text = (
<>
Your chequebook is not funded. Please deposit some xBZZ to your chequebook address. You may need to aquire BZZ
(e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and bridge it to the xDai network through the{' '}
<a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To pay the transaction fees, you will also need
xDAI token. You can purchase DAI on the network and bridge it to xDai network through the{' '}
<a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
</>
)
break
default:
text = (
<>
Your chequebook is either not deployed nor funded. To run the node you will need xDAI and xBZZ on the xDai
network. You may need to aquire BZZ (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and bridge it to
the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To pay the
transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai
network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
</>
)
}
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Chequebook Deployment & Funding
<StatusIcon checkState={checkState} isLoading={isLoading} /> Chequebook Deployment & Funding
</>
}
>
<ExpandableListItemNote>
{isOk ? (
'Your chequebook is deployed and funded'
) : (
<>
Your chequebook is either not deployed or funded. To run the node you will need xDAI and xBZZ on the xDai
network. You may need to aquire BZZ (e.g. <a href="https://bzz.exchange/">bzz.exchange</a>) and bridge it to
the xDai network through the <a href="https://omni.xdaichain.com/bridge">omni bridge</a>. To pay the
transaction fees, you will also need xDAI token. You can purchase DAI on the network and bridge it to xDai
network through the <a href="https://bridge.xdaichain.com/">xDai Bridge</a>. See the{' '}
<a href="https://www.xdaichain.com/#xdai-stable-chain">official xDai website</a> for more information.
</>
)}
</ExpandableListItemNote>
<ExpandableListItemNote>{text}</ExpandableListItemNote>
{chequebookAddress && (
<>
<ExpandableListItemKey label="Chequebook Address" value={chequebookAddress.chequebookAddress} />
@@ -6,30 +6,32 @@ import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemInput from '../../../components/ExpandableListItemInput'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
export default function NodeConnectionCheck(): ReactElement | null {
const { status, isLoading } = useContext(Context)
const { setDebugApiUrl, apiDebugUrl } = useContext(SettingsContext)
const isOk = status.debugApiConnection
const { checkState, isEnabled } = status.debugApiConnection
if (!isEnabled) return null
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Bee Debug API
<StatusIcon checkState={checkState} isLoading={isLoading} /> Connection to Bee Debug API
</>
}
>
<ExpandableListItemNote>
{isOk
{checkState === CheckState.OK
? 'The connection to the Bee nodes debug API has been successful'
: 'We cannot connect to your nodes debug API. Please check the following to troubleshoot your issue.'}
</ExpandableListItemNote>
<ExpandableListItemInput label="Bee Debug API" value={apiDebugUrl} onConfirm={setDebugApiUrl} />
{!isOk && (
{checkState === CheckState.ERROR && (
<ExpandableList level={1} label="Troubleshoot">
<ExpandableListItem
label={
@@ -3,22 +3,24 @@ import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
export default function EthereumConnectionCheck(): ReactElement | null {
const { status, isLoading, nodeAddresses } = useContext(Context)
const isOk = status.blockchainConnection
const { checkState, isEnabled } = status.blockchainConnection
if (!isEnabled) return null
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Blockchain
<StatusIcon checkState={checkState} isLoading={isLoading} /> Connection to Blockchain
</>
}
>
<ExpandableListItemNote>
{isOk ? (
{checkState === CheckState.OK ? (
'Your node is connected to the xDai blockchain'
) : (
<>
@@ -7,28 +7,30 @@ import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import ExpandableListItemInput from '../../../components/ExpandableListItemInput'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
export default function NodeConnectionCheck(): ReactElement | null {
const { setApiUrl, apiUrl } = useContext(SettingsContext)
const { status, isLoading } = useContext(Context)
const isOk = status.apiConnection
const { isEnabled, checkState } = status.apiConnection
if (!isEnabled) return null
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Bee API
<StatusIcon checkState={checkState} isLoading={isLoading} /> Connection to Bee API
</>
}
>
<ExpandableListItemNote>
{isOk
{checkState === CheckState.OK
? 'The connection to the Bee nodes API has been successful'
: 'Could not connect to your Bee nodes API. Please check the troubleshoot below on how you may resolve it.'}
</ExpandableListItemNote>
<ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} />
{!isOk && (
{checkState === CheckState.ERROR && (
<ExpandableList level={1} label="Troubleshoot">
<ExpandableListItem
label={
+19 -9
View File
@@ -1,27 +1,37 @@
import { ReactElement, useContext } from 'react'
import { ReactElement, ReactNode, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import TopologyStats from '../../../components/TopologyStats'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
export default function PeerConnection(): ReactElement | null {
const { status, isLoading, topology } = useContext(Context)
const isOk = status.topology
const { isEnabled, checkState } = status.topology
if (!isEnabled) return null
let text: ReactNode
switch (checkState) {
case CheckState.OK:
text = 'You are connected to other Bee nodes'
break
// Both error state and warning state
default:
text =
'Your node is not connected to any peers. Please wait a bit if you just started the node, otherwise review your configuration file.'
}
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Connection to Peers
<StatusIcon checkState={checkState} isLoading={isLoading} /> Connection to Peers
</>
}
>
<ExpandableListItemNote>
{isOk
? 'You are connected to other Bee nodes'
: 'Your node is not connected to any peers. Please wait a bit if you just started the node, otherwise review your configuration file.'}
</ExpandableListItemNote>
<ExpandableListItemNote>{text}</ExpandableListItemNote>
<TopologyStats topology={topology} />
</ExpandableList>
+6 -4
View File
@@ -4,22 +4,24 @@ import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { Context } from '../../../providers/Bee'
import { CheckState, Context } from '../../../providers/Bee'
export default function VersionCheck(): ReactElement | null {
const { status, isLoading, latestUserVersion, latestPublishedVersion, latestBeeVersionUrl } = useContext(Context)
const isOk = status.version
const { isEnabled, checkState } = status.version
if (!isEnabled) return null
return (
<ExpandableList
label={
<>
<StatusIcon isOk={isOk} isLoading={isLoading} /> Bee Version
<StatusIcon checkState={checkState} isLoading={isLoading} /> Bee Version
</>
}
>
<ExpandableListItemNote>
{isOk ? (
{checkState === CheckState.OK ? (
'You are running the latest version of Bee.'
) : (
<>
+85 -40
View File
@@ -1,12 +1,13 @@
import type {
import {
ChainState,
ChequebookAddressResponse,
Health,
LastChequesResponse,
NodeAddresses,
NodesInfo,
NodeInfo,
Peer,
Topology,
BeeModes,
} from '@ethersphere/bee-js'
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
import semver from 'semver'
@@ -16,14 +17,25 @@ import { Token } from '../models/Token'
import type { Balance, ChequebookBalance, Settlements } from '../types'
import { Context as SettingsContext } from './Settings'
export enum CheckState {
OK = 'OK',
WARNING = 'Warning',
ERROR = 'Error',
}
interface StatusItem {
isEnabled: boolean
checkState: CheckState
}
interface Status {
all: boolean
version: boolean
blockchainConnection: boolean
debugApiConnection: boolean
apiConnection: boolean
topology: boolean
chequebook: boolean
all: CheckState
version: StatusItem
blockchainConnection: StatusItem
debugApiConnection: StatusItem
apiConnection: StatusItem
topology: StatusItem
chequebook: StatusItem
}
interface ContextInterface {
@@ -37,7 +49,7 @@ interface ContextInterface {
apiHealth: boolean
debugApiHealth: Health | null
nodeAddresses: NodeAddresses | null
nodeInfo: NodesInfo | null
nodeInfo: NodeInfo | null
topology: Topology | null
chequebookAddress: ChequebookAddressResponse | null
peers: Peer[] | null
@@ -55,17 +67,15 @@ interface ContextInterface {
refresh: () => Promise<void>
}
const startedInDevMode = window.location.search.includes('devMode=1')
const initialValues: ContextInterface = {
status: {
all: false,
version: false,
blockchainConnection: false,
debugApiConnection: false,
apiConnection: false,
topology: false,
chequebook: false,
all: CheckState.ERROR,
version: { isEnabled: false, checkState: CheckState.ERROR },
blockchainConnection: { isEnabled: false, checkState: CheckState.ERROR },
debugApiConnection: { isEnabled: false, checkState: CheckState.ERROR },
apiConnection: { isEnabled: false, checkState: CheckState.ERROR },
topology: { isEnabled: false, checkState: CheckState.ERROR },
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
},
latestPublishedVersion: undefined,
latestUserVersion: undefined,
@@ -104,34 +114,69 @@ interface Props {
function getStatus(
debugApiHealth: Health | null,
nodeAddresses: NodeAddresses | null,
nodeInfo: NodesInfo | null,
nodeInfo: NodeInfo | null,
apiHealth: boolean,
topology: Topology | null,
chequebookAddress: ChequebookAddressResponse | null,
chequebookBalance: ChequebookBalance | null,
error: Error | null,
): Status {
// FIXME: `devMode` is a temporary workaround to be able to develop with only one node
const devMode = startedInDevMode || Boolean(process.env.REACT_APP_DEV_MODE) || nodeInfo?.beeMode === 'dev'
const status = {
version: Boolean(
debugApiHealth &&
semver.satisfies(debugApiHealth.version, engines.bee, {
includePrerelease: true,
}),
),
blockchainConnection: Boolean(nodeAddresses?.ethereum),
debugApiConnection: Boolean(debugApiHealth?.status === 'ok'),
apiConnection: apiHealth,
topology: Boolean(topology?.connected && topology?.connected > 0) || devMode,
chequebook:
(Boolean(chequebookAddress?.chequebookAddress) &&
chequebookBalance !== null &&
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)) ||
devMode,
const status: Status = { ...initialValues.status }
// Version check
status.version.isEnabled = true
status.version.checkState =
debugApiHealth &&
semver.satisfies(debugApiHealth.version, engines.bee, {
includePrerelease: true,
})
? CheckState.OK
: CheckState.ERROR
// Blockchain connection check
status.blockchainConnection.isEnabled = true
status.blockchainConnection.checkState = Boolean(debugApiHealth?.status === 'ok') ? CheckState.OK : CheckState.ERROR
// Debug API connection check
status.debugApiConnection.isEnabled = true
status.debugApiConnection.checkState = Boolean(debugApiHealth?.status === 'ok') ? CheckState.OK : CheckState.ERROR
// API connection check
status.apiConnection.isEnabled = true
status.apiConnection.checkState = apiHealth ? CheckState.OK : CheckState.ERROR
// Topology check
if (nodeInfo && [BeeModes.FULL, BeeModes.LIGHT, BeeModes.ULTRA_LIGHT].includes(nodeInfo.beeMode)) {
status.topology.isEnabled = true
status.topology.checkState = topology?.connected && topology?.connected > 0 ? CheckState.OK : CheckState.WARNING
}
return { ...status, all: !error && Object.values(status).every(v => v) }
// Chequebook check
if (error || (nodeInfo && [BeeModes.FULL, BeeModes.LIGHT].includes(nodeInfo.beeMode))) {
status.chequebook.isEnabled = true
if (
chequebookAddress?.chequebookAddress &&
chequebookBalance !== null &&
chequebookBalance?.totalBalance.toBigNumber.isGreaterThan(0)
) {
status.chequebook.checkState = CheckState.OK
} else if (chequebookAddress?.chequebookAddress) status.chequebook.checkState = CheckState.WARNING
else status.chequebook.checkState = CheckState.OK
}
// Determine overall status
if (Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.ERROR)) {
status.all = CheckState.ERROR
} else if (
Object.values(status).some(({ isEnabled, checkState }) => isEnabled && checkState === CheckState.WARNING)
) {
status.all = CheckState.WARNING
} else {
status.all = CheckState.OK
}
return status
}
export function Provider({ children }: Props): ReactElement {
@@ -139,7 +184,7 @@ export function Provider({ children }: Props): ReactElement {
const [apiHealth, setApiHealth] = useState<boolean>(false)
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
const [nodeInfo, setNodeInfo] = useState<NodesInfo | null>(null)
const [nodeInfo, setNodeInfo] = useState<NodeInfo | null>(null)
const [topology, setNodeTopology] = useState<Topology | null>(null)
const [chequebookAddress, setChequebookAddress] = useState<ChequebookAddressResponse | null>(null)
const [peers, setPeers] = useState<Peer[] | null>(null)
-17
View File
@@ -5,23 +5,6 @@ interface LatestBeeRelease {
html_url: string
}
interface StatusHookCommon {
isOk: boolean
}
interface StatusNodeVersionHook extends StatusHookCommon {
userVersion?: string
latestVersion?: string
latestUrl: string
isLatestBeeVersion: boolean
}
interface StatusEthereumConnectionHook extends StatusHookCommon {
nodeAddresses: NodeAddresses | null
}
interface StatusTopologyHook extends StatusHookCommon {
topology: Topology | null
}
interface SwarmMetadata {
size: number
name: string
+19
View File
@@ -1,4 +1,23 @@
import type { NodeAddresses, Topology } from '@ethersphere/bee-js'
import type { Token } from './models/Token'
import { CheckState } from './providers/Bee'
export interface StatusHookCommon {
checkState: CheckState
}
export interface StatusNodeVersionHook extends StatusHookCommon {
userVersion?: string
latestVersion?: string
latestUrl: string
isLatestBeeVersion: boolean
}
export interface StatusEthereumConnectionHook extends StatusHookCommon {
nodeAddresses: NodeAddresses | null
}
export interface StatusTopologyHook extends StatusHookCommon {
topology: Topology | null
}
export interface ChequebookBalance {
totalBalance: Token
+10 -27
View File
@@ -1,5 +1,5 @@
import { NumberString } from '@ethersphere/bee-js'
import { BigNumber } from 'bignumber.js'
import { Token } from '../models/Token'
/**
* Test if value is an integer
@@ -159,38 +159,21 @@ export function secondsToTimeString(seconds: number): string {
return `${unit.toFixed(1)} years`
}
export function formatBzz(amount: number): string {
const asString = amount.toFixed(16)
let indexOfSignificantDigit = -1
let reachedDecimalPoint = false
for (let i = 0; i < asString.length; i++) {
const char = asString[i]
if (char === '.') {
reachedDecimalPoint = true
} else if (reachedDecimalPoint && char !== '0') {
indexOfSignificantDigit = i
break
}
}
return asString.slice(0, indexOfSignificantDigit + 4)
}
export function convertDepthToBytes(depth: number): number {
return 2 ** depth * 4096
}
export function convertAmountToSeconds(amount: number): number {
return amount / 10 / 1
export function convertAmountToSeconds(amount: number, pricePerBlock: number): number {
// TODO: blocktime should come directly from the blockchain as it may differ between different networks
const blockTime = 5 // On mainnet there is 5 seconds between blocks
// See https://github.com/ethersphere/bee/blob/66f079930d739182c4c79eb6008784afeeba1096/pkg/debugapi/postage.go#L410-L413
return (amount * blockTime) / pricePerBlock
}
export function calculateStampPrice(depth: number, amount: number, currentPrice: NumberString): number {
const price = parseInt(currentPrice, 10)
return (amount * 2 ** (depth - 16) * price) / 1e16
export function calculateStampPrice(depth: number, amount: bigint): Token {
// See https://github.com/ethersphere/bee/blob/66f079930d739182c4c79eb6008784afeeba1096/pkg/debugapi/postage.go#L410-L413
return new Token(amount * BigInt(2 ** depth)) // FIXME: the 2 ** depth should be performed on bigint already
}
export function shortenText(text: string, length = 20, separator = '[…]'): string {