diff --git a/.changeset/cyan-dots-cover.md b/.changeset/cyan-dots-cover.md
new file mode 100644
index 000000000..84fe4a0b4
--- /dev/null
+++ b/.changeset/cyan-dots-cover.md
@@ -0,0 +1,5 @@
+---
+'@siafoundation/react-renterd': minor
+---
+
+useObject now includes partialSlab.
diff --git a/.changeset/dull-readers-sit.md b/.changeset/dull-readers-sit.md
new file mode 100644
index 000000000..bc4f2da89
--- /dev/null
+++ b/.changeset/dull-readers-sit.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+File upload and directory creation are now disabled until enough contracts are formed.
diff --git a/.changeset/eight-wombats-sneeze.md b/.changeset/eight-wombats-sneeze.md
new file mode 100644
index 000000000..330279843
--- /dev/null
+++ b/.changeset/eight-wombats-sneeze.md
@@ -0,0 +1,5 @@
+---
+'hostd': minor
+---
+
+Fixed an issue where alert error messages were being cut off. Full error messages are now displayed above all other fields.
diff --git a/.changeset/happy-hornets-tickle.md b/.changeset/happy-hornets-tickle.md
new file mode 100644
index 000000000..687fe7109
--- /dev/null
+++ b/.changeset/happy-hornets-tickle.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+The failed to migrate slab alert now lists the associated objects/files.
diff --git a/.changeset/odd-pillows-try.md b/.changeset/odd-pillows-try.md
new file mode 100644
index 000000000..dc077238c
--- /dev/null
+++ b/.changeset/odd-pillows-try.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+New users are now more clearly instructed to configure autopilot and to wait for enough contracts before files can be uploaded.
diff --git a/.changeset/plenty-baboons-leave.md b/.changeset/plenty-baboons-leave.md
new file mode 100644
index 000000000..99bcb07f0
--- /dev/null
+++ b/.changeset/plenty-baboons-leave.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+Node profile information now includes the build version.
diff --git a/.changeset/pretty-rules-bake.md b/.changeset/pretty-rules-bake.md
new file mode 100644
index 000000000..039f245dd
--- /dev/null
+++ b/.changeset/pretty-rules-bake.md
@@ -0,0 +1,5 @@
+---
+'@siafoundation/react-renterd': minor
+---
+
+Add useSlabObjects.
diff --git a/.changeset/six-mice-work.md b/.changeset/six-mice-work.md
new file mode 100644
index 000000000..ef2f6e53a
--- /dev/null
+++ b/.changeset/six-mice-work.md
@@ -0,0 +1,5 @@
+---
+'@siafoundation/react-renterd': minor
+---
+
+Add useAutopilotState, useBusState, useWorkerState, remove useAutopilotStatus.
diff --git a/.changeset/slimy-stingrays-ring.md b/.changeset/slimy-stingrays-ring.md
new file mode 100644
index 000000000..c08373205
--- /dev/null
+++ b/.changeset/slimy-stingrays-ring.md
@@ -0,0 +1,7 @@
+---
+'hostd': minor
+'renterd': minor
+'walletd': minor
+---
+
+The connectivity and login check no longer depends on consensus APIs which in some rare cases can be unresponsive.
diff --git a/.changeset/smart-scissors-help.md b/.changeset/smart-scissors-help.md
new file mode 100644
index 000000000..d217a3c2f
--- /dev/null
+++ b/.changeset/smart-scissors-help.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+Alerts now show contract additions and removals, formatted address, balance, and more.
diff --git a/.changeset/wise-phones-bathe.md b/.changeset/wise-phones-bathe.md
new file mode 100644
index 000000000..d396231bc
--- /dev/null
+++ b/.changeset/wise-phones-bathe.md
@@ -0,0 +1,5 @@
+---
+'renterd': minor
+---
+
+File health tooltip now includes redundancy info and supports partial slabs.
diff --git a/apps/hostd/config/routes.ts b/apps/hostd/config/routes.ts
index 95b8cd9bc..a9e36f402 100644
--- a/apps/hostd/config/routes.ts
+++ b/apps/hostd/config/routes.ts
@@ -1,3 +1,5 @@
+import { stateHostKey } from '@siafoundation/react-hostd'
+
export const routes = {
home: '/',
volumes: {
@@ -20,4 +22,4 @@ export const routes = {
login: '/login',
}
-export const connectivityRoute = '/state/consensus'
+export const connectivityRoute = stateHostKey
diff --git a/apps/hostd/dialogs/AlertsDialog.tsx b/apps/hostd/dialogs/AlertsDialog.tsx
index 14a275250..73c3f5efb 100644
--- a/apps/hostd/dialogs/AlertsDialog.tsx
+++ b/apps/hostd/dialogs/AlertsDialog.tsx
@@ -13,7 +13,7 @@ import {
import { useAlerts, useAlertsDismiss } from '@siafoundation/react-hostd'
import { humanDate, humanTime } from '@siafoundation/sia-js'
import { cx } from 'class-variance-authority'
-import { times } from 'lodash'
+import { difference, times } from 'lodash'
import { useCallback } from 'react'
type Props = {
@@ -142,6 +142,11 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
+ {!!a.data.error && (
+
+ {a.data.error}
+
+ )}
timestamp
@@ -150,7 +155,7 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
{humanDate(a.timestamp, { timeStyle: 'medium' })}
- {getOrderedKeys(a.data).map((key) => {
+ {getOrderedKeys(a.data, skipFields).map((key) => {
const value = a.data[key]
if (value === undefined) {
return null
@@ -177,6 +182,8 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
)
}
+const skipFields = ['error']
+
const dataFieldOrder = [
'contractID',
'blockHeight',
@@ -199,8 +206,8 @@ const dataFieldOrder = [
]
// Sort keys by dataFieldOrder, then alphabetically
-function getOrderedKeys(obj) {
- return Object.keys(obj).sort((a, b) => {
+function getOrderedKeys(obj, skip: string[]) {
+ const keys = Object.keys(obj).sort((a, b) => {
const aIndex = dataFieldOrder.indexOf(a)
const bIndex = dataFieldOrder.indexOf(b)
if (aIndex === -1 && bIndex === -1) {
@@ -214,6 +221,7 @@ function getOrderedKeys(obj) {
}
return aIndex - bIndex
})
+ return difference(keys, skip)
}
const dataFields = {
diff --git a/apps/renterd/components/CmdRoot/index.tsx b/apps/renterd/components/CmdRoot/index.tsx
index 841a077ad..182a10054 100644
--- a/apps/renterd/components/CmdRoot/index.tsx
+++ b/apps/renterd/components/CmdRoot/index.tsx
@@ -105,7 +105,7 @@ export function CmdRoot({ panel }: Props) {
afterSelect()
}}
/>
- {autopilot.state === 'on' && (
+ {autopilot.status === 'on' && (
)}
diff --git a/apps/renterd/components/Files/Columns/FilesHealthColumn/FilesHealthColumnContents.tsx b/apps/renterd/components/Files/Columns/FilesHealthColumn/FilesHealthColumnContents.tsx
index d9610ef6e..48bc84a46 100644
--- a/apps/renterd/components/Files/Columns/FilesHealthColumn/FilesHealthColumnContents.tsx
+++ b/apps/renterd/components/Files/Columns/FilesHealthColumn/FilesHealthColumnContents.tsx
@@ -66,27 +66,44 @@ export function FilesHealthColumnContents({
'contractSetShards'
)
+ const { partialSlab } = obj.data.object
+
return (
15 ? 'h-[300px]' : ''}
displayHealth={displayHealth}
label={label}
+ minShards={partialSlab ? partialSlab.minShards : slabs[0]?.minShards}
+ totalShards={
+ partialSlab ? partialSlab.totalShards : slabs[0]?.shards.length
+ }
>
- {slabs.map((slab) => (
-
-
- Slab {slab.key.replace('key:', '').slice(0, 4)}:
-
-
- {slab.contractSetShards}/{slab.shards.length}
-
-
- ))}
+ {partialSlab ? (
+
+ partial slab
+
+ ) : (
+ slabs.map((slab) => (
+
+
+ Slab {slab.key.replace('key:', '').slice(0, 4)}:
+
+
+ {slab.contractSetShards}/{slab.shards.length}
+
+
+ ))
+ )}
)
}
@@ -96,11 +113,15 @@ function Layout({
displayHealth,
label,
children,
+ minShards,
+ totalShards,
}: {
className?: string
children: React.ReactNode
displayHealth: number
label: string
+ minShards?: number
+ totalShards?: number
}) {
return (
{label}
{(displayHealth * 100).toFixed(0)}%
+ {minShards && totalShards ? (
+
+
+ redundancy
+
+
+ {minShards} of {totalShards}
+
+
+ ) : null}
diff --git a/apps/renterd/components/Files/EmptyState.tsx b/apps/renterd/components/Files/EmptyState.tsx
new file mode 100644
index 000000000..18175ca67
--- /dev/null
+++ b/apps/renterd/components/Files/EmptyState.tsx
@@ -0,0 +1,79 @@
+import { CloudUpload32, LinkButton, Text } from '@siafoundation/design-system'
+import { routes } from '../../config/routes'
+import { useFiles } from '../../contexts/files'
+import { useAutopilotNotConfigured } from './checks/useAutopilotNotConfigured'
+import { useNotEnoughContracts } from './checks/useNotEnoughContracts'
+import { StateError } from './StateError'
+import { StateNoneMatching } from './StateNoneMatching'
+import { StateNoneYet } from './StateNoneYet'
+
+export function EmptyState() {
+ const { dataState, activeDirectoryPath } = useFiles()
+
+ const autopilotNotConfigured = useAutopilotNotConfigured()
+ const notEnoughContracts = useNotEnoughContracts()
+
+ if (dataState === 'noneMatchingFilters') {
+ return
+ }
+
+ if (dataState === 'error') {
+ return
+ }
+
+ // only show on root directory and when there are no files
+ if (
+ activeDirectoryPath === '/' &&
+ dataState === 'noneYet' &&
+ autopilotNotConfigured.active
+ ) {
+ return (
+
+
+
+
+
+
+ Before you can upload files you must configure autopilot. Autopilot
+ finds contracts with hosts based on the settings you choose.
+ Autopilot also repairs your data as hosts come and go.
+
+
+ Configure autopilot →
+
+
+
+ )
+ }
+
+ // only show on root directory and when there are no files
+ if (
+ activeDirectoryPath === '/' &&
+ dataState === 'noneYet' &&
+ notEnoughContracts.active
+ ) {
+ return (
+
+
+
+
+
+
+ There are not enough contracts to upload data yet. Redundancy is
+ configured to use {notEnoughContracts.required} shards which means
+ at least that many contracts are required.
+
+
+ {notEnoughContracts.count}/{notEnoughContracts.required}
+
+
+
+ )
+ }
+
+ if (dataState === 'noneYet') {
+ return
+ }
+
+ return null
+}
diff --git a/apps/renterd/components/Files/FilesActionsMenu.tsx b/apps/renterd/components/Files/FilesActionsMenu.tsx
index aeab2e015..8694f8482 100644
--- a/apps/renterd/components/Files/FilesActionsMenu.tsx
+++ b/apps/renterd/components/Files/FilesActionsMenu.tsx
@@ -8,13 +8,17 @@ import { useFiles } from '../../contexts/files'
import { useDropzone } from 'react-dropzone'
import { FilesViewDropdownMenu } from './FilesViewDropdownMenu'
import { useDialog } from '../../contexts/dialog'
+import { useCanUpload } from './useCanUpload'
export function FilesActionsMenu() {
const { openDialog } = useDialog()
const { uploadFiles } = useFiles()
+ const canUpload = useCanUpload()
+
const { getRootProps, getInputProps } = useDropzone({
noDrag: true,
+ noClick: !canUpload,
onDrop: uploadFiles,
})
@@ -23,11 +27,12 @@ export function FilesActionsMenu() {
openDialog('filesSearch')} tip="Search files">
-
+
openDialog('filesCreateDirectory')}
tip="Create directory"
>
diff --git a/apps/renterd/components/Files/FilesExplorer.tsx b/apps/renterd/components/Files/FilesExplorer.tsx
index 1cff5a056..6dcf59fd1 100644
--- a/apps/renterd/components/Files/FilesExplorer.tsx
+++ b/apps/renterd/components/Files/FilesExplorer.tsx
@@ -1,8 +1,7 @@
import { Table, Dropzone } from '@siafoundation/design-system'
import { useFiles } from '../../contexts/files'
-import { StateError } from './StateError'
-import { StateNoneMatching } from './StateNoneMatching'
-import { StateNoneYet } from './StateNoneYet'
+import { EmptyState } from './EmptyState'
+import { useCanUpload } from './useCanUpload'
export function FilesExplorer() {
const {
@@ -16,20 +15,17 @@ export function FilesExplorer() {
sortableColumns,
toggleSort,
} = useFiles()
+ const canUpload = useCanUpload()
return (
-
0}>
+ 0}
+ noDrag={!canUpload}
+ >
- ) : dataState === 'noneYet' ? (
-
- ) : dataState === 'error' ? (
-
- ) : null
- }
+ emptyState={ }
pageSize={10}
data={datasetPage}
columns={columns}
diff --git a/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx b/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx
index 239e8f6a5..5621eeed3 100644
--- a/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx
+++ b/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx
@@ -1,106 +1,122 @@
import { Link, Text, Tooltip, Warning16 } from '@siafoundation/design-system'
-import { useAutopilotConfig } from '@siafoundation/react-renterd'
-import { useIsApcsEqDcs } from '../../../hooks/useIsApcsEqDcs'
+import { useFiles } from '../../../contexts/files'
import { routes } from '../../../config/routes'
-import { useContractSetSettings } from '../../../hooks/useContractSetSettings'
-import { useApp } from '../../../contexts/app'
+import { useContractSetMismatch } from '../checks/useContractSetMismatch'
+import { useDefaultContractSetNotSet } from '../checks/useDefaultContractSetNotSet'
+import { useAutopilotNotConfigured } from '../checks/useAutopilotNotConfigured'
+import { useNotEnoughContracts } from '../checks/useNotEnoughContracts'
export function FilesStatsMenuWarnings() {
- const { autopilot } = useApp()
- const apc = useAutopilotConfig({
- config: {
- swr: {
- errorRetryCount: 0,
- },
- },
- })
- const css = useContractSetSettings()
- const isApcsEqDcs = useIsApcsEqDcs()
+ const { dataState, activeDirectoryPath } = useFiles()
+ const contractSetMismatch = useContractSetMismatch()
+ const defaultContractSetNotSet = useDefaultContractSetNotSet()
+ const autopilotNotConfigured = useAutopilotNotConfigured()
+ const notEnoughContracts = useNotEnoughContracts()
- let warning = 'none'
-
- if (
- autopilot.state === 'on' &&
- !isApcsEqDcs.isValidating &&
- !isApcsEqDcs.data
- ) {
- warning = 'contractSetMismatch'
+ // onboard/warn about default contract set
+ if (defaultContractSetNotSet.active) {
+ return (
+
+
+
+
+
+ Configure a default contract set to get started.{' '}
+
+ Configuration →
+
+
+
+ )
}
- if (autopilot.state === 'on' && apc.error) {
- warning = 'setupAutopilot'
- }
-
- if (css.data && !css.data?.default) {
- warning = 'setupDefaultContractSet'
- }
-
- return (
- <>
- {warning === 'setupDefaultContractSet' && (
-
-
-
-
-
- Configure a default contract set to get started.{' '}
-
- Configuration →
-
-
-
- )}
- {warning === 'setupAutopilot' && (
+ // warn about contract set mismatch
+ if (contractSetMismatch.active) {
+ return (
+
+ The autopilot contract set does not match the default contract set.
+ This means that by default workers will not upload data to contracts
+ that autopilot manages. Unless these contract are being manually
+ maintained, this will result in data loss. Continue with caution or
+ update the autopilot contract set to match the default contract set.
+ >
+ }
+ >
- Configure autopilot to get started.{' '}
-
- Autopilot →
-
+ Uploaded data will not be managed by autopilot.
- )}
- {warning === 'contractSetMismatch' && (
-
- The autopilot contract set does not match the default contract
- set. This means that by default workers will not upload data to
- contracts that autopilot manages. Unless these contract are being
- manually maintained, this will result in data loss. Continue with
- caution or update the autopilot contract set to match the default
- contract set.
- >
- }
- >
-
-
-
-
-
- Uploaded data will not be managed by autopilot.
-
-
-
- )}
- >
- )
+
+ )
+ }
+
+ // only show if not on the root directory because the explorer empty state shows the same info
+ const autopilotNotConfiguredRootDirectory =
+ autopilotNotConfigured.active &&
+ activeDirectoryPath === '/' &&
+ dataState !== 'noneYet'
+ const autopilotNotConfiguredNotRootDirectory =
+ autopilotNotConfigured.active && activeDirectoryPath !== '/'
+ if (
+ autopilotNotConfiguredRootDirectory ||
+ autopilotNotConfiguredNotRootDirectory
+ ) {
+ return (
+
+
+
+
+
+ Configure autopilot to get started.{' '}
+
+ Autopilot →
+
+
+
+ )
+ }
+
+ // only show if not on the root directory because the explorer empty state shows the same info
+ const notEnoughContractsRootDirectory =
+ notEnoughContracts.active &&
+ activeDirectoryPath === '/' &&
+ dataState !== 'noneYet'
+ const notEnoughContractsNotRootDirectory =
+ notEnoughContracts.active && activeDirectoryPath !== '/'
+ if (notEnoughContractsRootDirectory || notEnoughContractsNotRootDirectory) {
+ return (
+
+
+
+
+
+ Not enought contracts to upload files. {notEnoughContracts.count}/
+ {notEnoughContracts.required}
+
+
+ )
+ }
+
+ return null
}
diff --git a/apps/renterd/components/Files/checks/useAutopilotNotConfigured.tsx b/apps/renterd/components/Files/checks/useAutopilotNotConfigured.tsx
new file mode 100644
index 000000000..316c60ed6
--- /dev/null
+++ b/apps/renterd/components/Files/checks/useAutopilotNotConfigured.tsx
@@ -0,0 +1,17 @@
+import { useAutopilotConfig } from '@siafoundation/react-renterd'
+import { useApp } from '../../../contexts/app'
+
+export function useAutopilotNotConfigured() {
+ const { autopilot } = useApp()
+ const apc = useAutopilotConfig({
+ config: {
+ swr: {
+ errorRetryCount: 0,
+ },
+ },
+ })
+
+ return {
+ active: autopilot.status === 'on' && !!apc.error,
+ }
+}
diff --git a/apps/renterd/components/Files/checks/useContractSetMismatch.tsx b/apps/renterd/components/Files/checks/useContractSetMismatch.tsx
new file mode 100644
index 000000000..3fa0fe3a7
--- /dev/null
+++ b/apps/renterd/components/Files/checks/useContractSetMismatch.tsx
@@ -0,0 +1,15 @@
+import { useIsApcsEqDcs } from '../../../hooks/useIsApcsEqDcs'
+import { useApp } from '../../../contexts/app'
+
+export function useContractSetMismatch() {
+ const { autopilot } = useApp()
+ const isApcsEqDcs = useIsApcsEqDcs()
+
+ // warn about contract set mismatch
+ const active =
+ autopilot.status === 'on' && !isApcsEqDcs.isValidating && !isApcsEqDcs.data
+
+ return {
+ active,
+ }
+}
diff --git a/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx b/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx
new file mode 100644
index 000000000..628e3a860
--- /dev/null
+++ b/apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx
@@ -0,0 +1,9 @@
+import { useContractSetSettings } from '../../../hooks/useContractSetSettings'
+
+export function useDefaultContractSetNotSet() {
+ const css = useContractSetSettings()
+
+ return {
+ active: css.data && !css.data?.default,
+ }
+}
diff --git a/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx b/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx
new file mode 100644
index 000000000..5e1dd44bc
--- /dev/null
+++ b/apps/renterd/components/Files/checks/useNotEnoughContracts.tsx
@@ -0,0 +1,22 @@
+import { useContracts } from '../../../contexts/contracts'
+import { useRedundancySettings } from '../../../hooks/useRedundancySettings'
+
+export function useNotEnoughContracts() {
+ const redundancy = useRedundancySettings({
+ config: {
+ swr: {
+ // Do not automatically refetch
+ revalidateOnFocus: false,
+ },
+ },
+ })
+ const { datasetCount } = useContracts()
+
+ const active = redundancy.data && datasetCount < redundancy.data.totalShards
+
+ return {
+ active,
+ count: datasetCount,
+ required: redundancy.data?.totalShards || 0,
+ }
+}
diff --git a/apps/renterd/components/Files/useCanUpload.tsx b/apps/renterd/components/Files/useCanUpload.tsx
new file mode 100644
index 000000000..ed876d692
--- /dev/null
+++ b/apps/renterd/components/Files/useCanUpload.tsx
@@ -0,0 +1,8 @@
+import { useAutopilotNotConfigured } from './checks/useAutopilotNotConfigured'
+import { useNotEnoughContracts } from './checks/useNotEnoughContracts'
+
+export function useCanUpload() {
+ const autopilotNotConfigured = useAutopilotNotConfigured()
+ const notEnoughContracts = useNotEnoughContracts()
+ return !autopilotNotConfigured.active && !notEnoughContracts.active
+}
diff --git a/apps/renterd/components/Hosts/HostMap/Globe.tsx b/apps/renterd/components/Hosts/HostMap/Globe.tsx
index 9371fb4eb..b4bfda2f2 100644
--- a/apps/renterd/components/Hosts/HostMap/Globe.tsx
+++ b/apps/renterd/components/Hosts/HostMap/Globe.tsx
@@ -166,11 +166,11 @@ export function Globe({
}}
pointsTransitionDuration={0}
pointColor={(h: HostDataWithLocation) => {
- const { color } = getHostStatus(h)
+ const { colorHex } = getHostStatus(h)
if (!activeHost || h.publicKey === activeHost?.publicKey) {
- return color
+ return colorHex
}
- return colorWithOpacity(color, 0.2)
+ return colorWithOpacity(colorHex, 0.2)
}}
pointRadius={(h: HostDataWithLocation) => {
let radius = 0
diff --git a/apps/renterd/components/Hosts/HostMap/index.tsx b/apps/renterd/components/Hosts/HostMap/index.tsx
index 4974172e1..b82845367 100644
--- a/apps/renterd/components/Hosts/HostMap/index.tsx
+++ b/apps/renterd/components/Hosts/HostMap/index.tsx
@@ -3,6 +3,7 @@ import { Globe } from './Globe'
import { useHosts } from '../../../contexts/hosts'
import { HostDataWithLocation } from '../../../contexts/hosts/types'
import { DataLabel } from '@siafoundation/design-system'
+import { hostColors } from '../../../contexts/hosts/status'
export function HostMap() {
const { gpu } = useAppSettings()
@@ -13,17 +14,13 @@ export function HostMap() {
onHostMapHover: onHostHover,
hostsWithLocation,
} = useHosts()
- if (!(gpu.hasCheckedGpu && gpu.shouldRender)) {
+
+ if (!gpu.shouldRender) {
return null
}
return (
)
}
diff --git a/apps/renterd/components/Hosts/HostsActionsMenu.tsx b/apps/renterd/components/Hosts/HostsActionsMenu.tsx
index 103b85a82..76240cbd2 100644
--- a/apps/renterd/components/Hosts/HostsActionsMenu.tsx
+++ b/apps/renterd/components/Hosts/HostsActionsMenu.tsx
@@ -1,4 +1,9 @@
-import { Button, Earth16, ListChecked16 } from '@siafoundation/design-system'
+import {
+ Button,
+ Earth16,
+ ListChecked16,
+ Tooltip,
+} from '@siafoundation/design-system'
import { HostsViewDropdownMenu } from './HostsViewDropdownMenu'
import { useDialog } from '../../contexts/dialog'
import { useHosts } from '../../contexts/hosts'
@@ -17,14 +22,26 @@ export function HostsActionsMenu() {
Manage lists
- {gpu.canGpuRender && (
+
setViewMode(viewMode === 'map' ? 'list' : 'map')}
- tip="Toggle interactive map"
+ disabled={!gpu.canGpuRender}
+ onClick={() => {
+ if (gpu.isGpuEnabled) {
+ setViewMode(viewMode === 'map' ? 'list' : 'map')
+ } else {
+ openDialog('settings')
+ }
+ }}
>
- )}
+
)
diff --git a/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterCmdGroups/index.tsx b/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterCmdGroups/index.tsx
index dddcdb919..6a6b9ae08 100644
--- a/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterCmdGroups/index.tsx
+++ b/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterCmdGroups/index.tsx
@@ -17,7 +17,7 @@ export function ContractFilterCmdGroups({ currentPage, select }: Props) {
const { autopilot } = useApp()
return (
<>
- {autopilot.state === 'on' && (
+ {autopilot.status === 'on' && (
)}
diff --git a/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterNav/index.tsx b/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterNav/index.tsx
index c8fb262fd..a7a5958d5 100644
--- a/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterNav/index.tsx
+++ b/apps/renterd/components/Hosts/HostsCmd/HostsFilterCmd/HostsFilterNav/index.tsx
@@ -29,7 +29,7 @@ export function HostsFilterNav({
const { autopilot } = useApp()
return (
<>
- {autopilot.state === 'on' && (
+ {autopilot.status === 'on' && (
}
diff --git a/apps/renterd/components/Profile/index.tsx b/apps/renterd/components/Profile/index.tsx
index ab2e26acb..479058f79 100644
--- a/apps/renterd/components/Profile/index.tsx
+++ b/apps/renterd/components/Profile/index.tsx
@@ -1,12 +1,13 @@
import {
DaemonProfile,
Label,
+ Link,
Text,
ValueCopyable,
} from '@siafoundation/design-system'
import { useSyncStatus } from '../../hooks/useSyncStatus'
import {
- useConsensusNetwork,
+ useBusState,
useSyncerPeers,
useWallet,
} from '@siafoundation/react-renterd'
@@ -14,7 +15,7 @@ import { useDialog } from '../../contexts/dialog'
export function Profile() {
const { openDialog } = useDialog()
- const network = useConsensusNetwork({
+ const state = useBusState({
config: {
swr: {
revalidateOnFocus: false,
@@ -30,6 +31,15 @@ export function Profile() {
})
const peers = useSyncerPeers()
const syncStatus = useSyncStatus()
+
+ const version = state.data?.version
+ const versionUrl =
+ version === '?'
+ ? `https://github.com/SiaFoundation/renterd/commits/`
+ : version?.match(/^v\d+\.\d+\.\d+/)
+ ? `https://github.com/SiaFoundation/renterd/releases/${version}`
+ : `https://github.com/SiaFoundation/renterd/tree/${version}`
+
return (
Network
- {network.data?.Name}
+
+ {state.data?.network}
+
- {/*
-
- Version
-
-
+
+
+ Version
+
+
+
{state.data?.version}
-
*/}
+
+
)
}
diff --git a/apps/renterd/components/RenterdSidenav.tsx b/apps/renterd/components/RenterdSidenav.tsx
index a9384b073..64575460a 100644
--- a/apps/renterd/components/RenterdSidenav.tsx
+++ b/apps/renterd/components/RenterdSidenav.tsx
@@ -29,7 +29,7 @@ export function RenterdSidenav() {
- {autopilot.state === 'on' && (
+ {autopilot.status === 'on' && (
diff --git a/apps/renterd/components/RenterdTestnetWarningBanner.tsx b/apps/renterd/components/RenterdTestnetWarningBanner.tsx
index 686b3d87a..c02b7db08 100644
--- a/apps/renterd/components/RenterdTestnetWarningBanner.tsx
+++ b/apps/renterd/components/RenterdTestnetWarningBanner.tsx
@@ -1,8 +1,8 @@
import { TestnetWarningBanner } from '@siafoundation/design-system'
-import { useConsensusNetwork } from '@siafoundation/react-renterd'
+import { useBusState } from '@siafoundation/react-renterd'
export function RenterdTestnetWarningBanner() {
- const network = useConsensusNetwork({
+ const state = useBusState({
config: {
swr: {
revalidateOnFocus: false,
@@ -10,11 +10,9 @@ export function RenterdTestnetWarningBanner() {
},
})
- if (!network.data || network.data.Name === 'mainnet') {
+ if (!state.data || state.data.network === 'Mainnet') {
return null
}
- const testnetName =
- network.data.Name === 'zen' ? 'Zen Testnet' : network.data.Name
- return
+ return
}
diff --git a/apps/renterd/config/routes.ts b/apps/renterd/config/routes.ts
index ed6208a46..258dd52aa 100644
--- a/apps/renterd/config/routes.ts
+++ b/apps/renterd/config/routes.ts
@@ -1,3 +1,5 @@
+import { busStateKey } from '@siafoundation/react-renterd'
+
export const routes = {
home: '/',
files: {
@@ -29,4 +31,4 @@ export const routes = {
login: '/login',
}
-export const connectivityRoute = '/bus/consensus/state'
+export const connectivityRoute = busStateKey
diff --git a/apps/renterd/contexts/app/useAutopilot.tsx b/apps/renterd/contexts/app/useAutopilot.tsx
index ac55b0820..b2cbc97b2 100644
--- a/apps/renterd/contexts/app/useAutopilot.tsx
+++ b/apps/renterd/contexts/app/useAutopilot.tsx
@@ -1,8 +1,8 @@
-import { useAutopilotStatus } from '@siafoundation/react-renterd'
+import { useAutopilotState } from '@siafoundation/react-renterd'
import { useEffect, useState } from 'react'
export function useAutopilot() {
- const status = useAutopilotStatus({
+ const state = useAutopilotState({
config: {
swr: {
dedupingInterval: 5_000,
@@ -12,24 +12,24 @@ export function useAutopilot() {
},
})
- const [state, setState] = useState<'on' | 'off' | 'init'>('init')
+ const [status, setStatus] = useState<'on' | 'off' | 'init'>('init')
useEffect(() => {
- if (status.isLoading) {
- setState('init')
- } else if (status.isValidating) {
+ if (state.isLoading) {
+ setStatus('init')
+ } else if (state.isValidating) {
return
- } else if (status.error) {
- setState('off')
- } else if (status.data) {
+ } else if (state.error) {
+ setStatus('off')
+ } else if (state.data) {
// This check is required because the API currently returns html when the endpoint does not exist
- const validResponse = typeof status.data === 'object'
- setState(validResponse ? 'on' : 'off')
+ const validResponse = typeof state.data === 'object'
+ setStatus(validResponse ? 'on' : 'off')
}
- }, [status])
+ }, [state])
return {
- state,
status,
+ state,
}
}
diff --git a/apps/renterd/contexts/hosts/dataset.ts b/apps/renterd/contexts/hosts/dataset.ts
index ad08adc55..1fc466f0e 100644
--- a/apps/renterd/contexts/hosts/dataset.ts
+++ b/apps/renterd/contexts/hosts/dataset.ts
@@ -13,7 +13,7 @@ import { useApp } from '../app'
import { SiaCentralHost } from '@siafoundation/react-core'
export function useDataset({
- autopilotState,
+ autopilotStatus,
regularResponse,
autopilotResponse,
allContracts,
@@ -23,7 +23,7 @@ export function useDataset({
geoHosts,
onHostSelect,
}: {
- autopilotState: ReturnType['autopilot']['state']
+ autopilotStatus: ReturnType['autopilot']['status']
regularResponse: ReturnType
autopilotResponse: ReturnType
allContracts: ContractData[]
@@ -34,7 +34,7 @@ export function useDataset({
onHostSelect: (publicKey: string, location?: [number, number]) => void
}) {
return useMemo(() => {
- if (autopilotState === 'off') {
+ if (autopilotStatus === 'off') {
return (
regularResponse.data?.map((host) => {
const sch = geoHosts.find((gh) => gh.public_key === host.publicKey)
@@ -53,7 +53,7 @@ export function useDataset({
}
}) || null
)
- } else if (autopilotState === 'on') {
+ } else if (autopilotStatus === 'on') {
return (
autopilotResponse.data?.map((ah) => {
const sch = geoHosts.find((gh) => gh.public_key === ah.host.publicKey)
@@ -76,7 +76,7 @@ export function useDataset({
return null
}, [
onHostSelect,
- autopilotState,
+ autopilotStatus,
regularResponse.data,
autopilotResponse.data,
allContracts,
diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx
index 8f9088edf..cd372805e 100644
--- a/apps/renterd/contexts/hosts/index.tsx
+++ b/apps/renterd/contexts/hosts/index.tsx
@@ -62,7 +62,7 @@ function useHostsMain() {
disabled:
// prevents an extra fetch when allContracts is null
(filters.find((f) => f.id === 'hasActiveContracts') && !allContracts) ||
- autopilot.state !== 'on',
+ autopilot.status !== 'on',
payload: {
limit,
offset,
@@ -82,7 +82,7 @@ function useHostsMain() {
})
const regularResponse = useHostsSearch({
- disabled: autopilot.state !== 'off',
+ disabled: autopilot.status !== 'off',
payload: {
limit,
offset,
@@ -175,7 +175,7 @@ function useHostsMain() {
)
const dataset = useDataset({
- autopilotState: autopilot.state,
+ autopilotStatus: autopilot.status,
autopilotResponse,
regularResponse,
allContracts,
@@ -187,8 +187,8 @@ function useHostsMain() {
})
const disabledCategories = useMemo(
- () => (autopilot.state === 'off' ? ['autopilot'] : []),
- [autopilot.state]
+ () => (autopilot.status === 'off' ? ['autopilot'] : []),
+ [autopilot.status]
)
const {
@@ -215,14 +215,14 @@ function useHostsMain() {
)
const isValidating =
- autopilot.state === 'on'
+ autopilot.status === 'on'
? autopilotResponse.isValidating
: regularResponse.isValidating
const error =
- autopilot.state === 'on' ? autopilotResponse.error : regularResponse.error
+ autopilot.status === 'on' ? autopilotResponse.error : regularResponse.error
const dataState = useDatasetEmptyState(dataset, isValidating, error, filters)
- const isAutopilotConfigured = autopilot.status.data?.configured
+ const isAutopilotConfigured = autopilot.state.data?.configured
const tableContext = useMemo(
() => ({
isAutopilotConfigured,
diff --git a/apps/renterd/contexts/hosts/status.ts b/apps/renterd/contexts/hosts/status.ts
index 1e43e49c9..c8c4bbc02 100644
--- a/apps/renterd/contexts/hosts/status.ts
+++ b/apps/renterd/contexts/hosts/status.ts
@@ -1,25 +1,40 @@
import { colors } from '@siafoundation/design-system'
import { HostData } from './types'
+export const hostColors = {
+ activeAndUsable: {
+ colorHex: colors.green[600],
+ colorName: 'green',
+ },
+ activeAndUnusable: {
+ colorHex: colors.amber[600],
+ colorName: 'amber',
+ },
+ potentialHost: {
+ colorHex: colors.blue[600],
+ colorName: 'blue',
+ },
+} as const
+
export function getHostStatus(h: HostData) {
// red
if (h.activeContractsCount.gt(0) && !h.usable) {
return {
status: 'activeAndUnusable',
- color: colors.red[600],
+ ...hostColors.activeAndUnusable,
}
}
// blue
if (h.activeContractsCount.gt(0)) {
return {
status: 'activeAndUsable',
- color: colors.blue[600],
+ ...hostColors.activeAndUsable,
}
}
// green
return {
status: 'potentialHost',
- color: colors.green[600],
+ ...hostColors.potentialHost,
}
}
diff --git a/apps/renterd/dialogs/AlertsDialog.tsx b/apps/renterd/dialogs/AlertsDialog.tsx
index b4fca58cb..515a3209a 100644
--- a/apps/renterd/dialogs/AlertsDialog.tsx
+++ b/apps/renterd/dialogs/AlertsDialog.tsx
@@ -1,19 +1,29 @@
import {
Dialog,
Heading,
+ Link,
+ ScrollArea,
Skeleton,
Text,
useDatasetEmptyState,
ValueCopyable,
ValueMenu,
+ ValueSc,
} from '@siafoundation/design-system'
-import { useAlerts, useHost } from '@siafoundation/react-renterd'
+import {
+ useAlerts,
+ useHost,
+ useSlabObjects,
+} from '@siafoundation/react-renterd'
import { humanDate } from '@siafoundation/sia-js'
+import BigNumber from 'bignumber.js'
import { cx } from 'class-variance-authority'
import { times } from 'lodash'
import { useMemo } from 'react'
-import { ContractContextMenuFromId } from '../components/Contracts/ContractContextMenuFromId'
import { HostContextMenu } from '../components/Hosts/HostContextMenu'
+import { useDialog } from '../contexts/dialog'
+import { useFiles } from '../contexts/files'
+import { getDirectoryFromPath } from '../contexts/files/utils'
type Props = {
open: boolean
@@ -35,60 +45,165 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
const dataFields = useMemo(
() => ({
hostKey: {
- label: 'host key',
render: function HostField({ value }: { value: string }) {
const host = useHost({ params: { hostKey: value } })
if (!host.data) {
return null
}
return (
-
-
- }
- />
+
+
+ host key
+
+
+
+ }
+ />
+
)
},
},
- contractID: {
- label: 'contract ID',
- render: function ContractField({ value }: { value: string }) {
+ slabKey: {
+ render: function SlabField({ value }: { value: string }) {
+ const { setActiveDirectory } = useFiles()
+ const { closeDialog } = useDialog()
+ const objects = useSlabObjects({
+ params: {
+ key: value,
+ },
+ })
return (
-
-
- }
- />
-
+ <>
+
+
+ key
+
+
+
+ {objects.data && (
+
+
+ {objects.data.map((o) => (
+ {
+ setActiveDirectory(() => getDirectoryFromPath(o.name))
+ closeDialog()
+ }}
+ >
+ {o.name}
+
+ ))}
+
+
+ )}
+ >
+ )
+ },
+ },
+ additions: {
+ render: function AdditionsField({ value }: { value: string[] }) {
+ return (
+ <>
+
+
+ additions
+
+
+ {value && (
+
+
+ {value.map((contractId) => (
+
+
+
+ ))}
+
+
+ )}
+ >
+ )
+ },
+ },
+ removals: {
+ render: function RemovalsField({
+ value,
+ }: {
+ value: Record
+ }) {
+ return (
+ <>
+
+
+ removals
+
+
+ {value && (
+
+
+ {Object.entries(value).map(([contractId, reason]) => (
+
+
+
+ {reason}
+
+
+ ))}
+
+
+ )}
+ >
)
},
},
+ balance: {
+ render: ({ value }: { value: string }) => (
+
+
+ address
+
+
+
+ ),
+ },
+ address: {
+ render: ({ value }: { value: string }) => (
+
+
+ address
+
+
+
+ ),
+ },
account: {
- label: 'account',
render: ({ value }: { value: string }) => (
-
+
+
+ account
+
+
+
),
},
}),
@@ -169,19 +284,16 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
{getOrderedKeys(a.data).map((key) => {
const value = a.data[key]
- if (value === undefined) {
+ if (
+ value === undefined ||
+ value === null ||
+ (typeof value === 'object' && !Object.keys(value).length)
+ ) {
return null
}
const label = dataFields[key]?.label || key
const Component = dataFields[key]?.render || DefaultDisplay
- return (
-
-
- {label}
-
-
-
- )
+ return
})}
))}
@@ -192,11 +304,26 @@ export function AlertsDialog({ open, onOpenChange }: Props) {
)
}
-function DefaultDisplay({ value }) {
- return {value}
+function DefaultDisplay({ label, value }) {
+ return (
+
+ {label}
+
+ {String(value)}
+
+
+ )
}
-const dataFieldOrder = ['hostKey', 'contractID', 'account']
+const dataFieldOrder = [
+ 'origin',
+ 'hostKey',
+ 'contractID',
+ 'account',
+ 'slabKey',
+ 'additions',
+ 'removals',
+]
// Sort keys by dataFieldOrder, then alphabetically
function getOrderedKeys(obj) {
diff --git a/apps/renterd/hooks/useIsApcsEqDcs.tsx b/apps/renterd/hooks/useIsApcsEqDcs.tsx
index fa987a31c..558667df7 100644
--- a/apps/renterd/hooks/useIsApcsEqDcs.tsx
+++ b/apps/renterd/hooks/useIsApcsEqDcs.tsx
@@ -6,7 +6,7 @@ import { useContractSetSettings } from './useContractSetSettings'
export function useIsApcsEqDcs() {
const { autopilot } = useApp()
const apc = useAutopilotConfig({
- disabled: autopilot.state !== 'on',
+ disabled: autopilot.status !== 'on',
})
const css = useContractSetSettings()
diff --git a/apps/walletd/config/routes.ts b/apps/walletd/config/routes.ts
index de501391e..9cbbbbd06 100644
--- a/apps/walletd/config/routes.ts
+++ b/apps/walletd/config/routes.ts
@@ -1,3 +1,5 @@
+import { syncerPeersKey } from '@siafoundation/react-walletd'
+
export const routes = {
home: '/',
wallet: {
@@ -13,4 +15,4 @@ export const routes = {
login: '/login',
}
-export const connectivityRoute = '/consensus/tip'
+export const connectivityRoute = syncerPeersKey
diff --git a/libs/design-system/src/app/DataLabel.tsx b/libs/design-system/src/app/DataLabel.tsx
index 90fb62ba9..90893e2b9 100644
--- a/libs/design-system/src/app/DataLabel.tsx
+++ b/libs/design-system/src/app/DataLabel.tsx
@@ -7,9 +7,16 @@ type Props = {
enabled?: boolean
onChange?: (val: boolean) => void
color?: string
+ size?: React.ComponentProps['size']
}
-export function DataLabel({ label, enabled = true, onChange, color }: Props) {
+export function DataLabel({
+ size,
+ label,
+ enabled = true,
+ onChange,
+ color,
+}: Props) {
return (
)}
- {label}
+ {label}
)
}
diff --git a/libs/design-system/src/app/SettingsDialog.tsx b/libs/design-system/src/app/SettingsDialog.tsx
index e9f97efcc..e3a2334e0 100644
--- a/libs/design-system/src/app/SettingsDialog.tsx
+++ b/libs/design-system/src/app/SettingsDialog.tsx
@@ -31,7 +31,7 @@ export function SettingsDialog({
showSiaStats = true,
securityEl,
}: Props) {
- const { settings, setSettings, setCurrency, currencyOptions } =
+ const { settings, setSettings, setCurrency, currencyOptions, gpu } =
useAppSettings()
return (
@@ -95,6 +95,30 @@ export function SettingsDialog({
+
+
+
+
+
+
+
+ GPU
+
+
+
+
+ Enable features that require a GPU.{' '}
+ {!gpu.canGpuRender
+ ? ''
+ : 'This device does not support GPU rendering.'}
+
+
+
diff --git a/libs/design-system/src/components/Table.tsx b/libs/design-system/src/components/Table.tsx
index 02a590840..7ad2f1751 100644
--- a/libs/design-system/src/components/Table.tsx
+++ b/libs/design-system/src/components/Table.tsx
@@ -47,7 +47,7 @@ type Props<
isLoading: boolean
emptyState?: React.ReactNode
focusId?: string
- focusColor?: 'green' | 'red' | 'blue' | 'default'
+ focusColor?: 'green' | 'red' | 'amber' | 'blue' | 'default'
}
export function Table<
@@ -236,6 +236,9 @@ export function Table<
focusColor === 'red'
? '!shadow-red-500 dark:!shadow-red-400'
: '',
+ focusColor === 'amber'
+ ? '!shadow-amber-500 dark:!shadow-amber-500'
+ : '',
focusColor === 'green'
? '!shadow-green-500 dark:!shadow-green-400'
: ''
@@ -258,7 +261,7 @@ export function Table<
times(pageSize).map((i) => (
{columns.map(({ id, contentClassName, cellClassName }, i) => (
) {
return useGetSwr({
...args,
- route: '/state/host',
+ route: stateHostKey,
})
}
diff --git a/libs/react-renterd/src/autopilot.ts b/libs/react-renterd/src/autopilot.ts
index 1838abf76..40e7ceeb9 100644
--- a/libs/react-renterd/src/autopilot.ts
+++ b/libs/react-renterd/src/autopilot.ts
@@ -1,5 +1,5 @@
import { Action, AutopilotConfig, Host } from './siaTypes'
-import { HostsSearchPayload } from './bus'
+import { HostsSearchPayload, StateResponse } from './bus'
import {
useGetSwr,
usePostSwr,
@@ -20,12 +20,14 @@ type AutopilotStatus = {
uptimeMS: string
}
-const autopilotStatusKey = '/autopilot/status'
+type AutopilotState = AutopilotStatus & StateResponse
-export function useAutopilotStatus(args?: HookArgsSwr) {
+const autopilotStateKey = '/autopilot/state'
+
+export function useAutopilotState(args?: HookArgsSwr) {
return useGetSwr({
...args,
- route: autopilotStatusKey,
+ route: autopilotStateKey,
})
}
@@ -46,7 +48,7 @@ export function useAutopilotConfigUpdate(
// or not autopilot is configured
const func = async () => {
await delay(1000)
- mutate((key) => key === autopilotStatusKey)
+ mutate((key) => key === autopilotStateKey)
}
func()
})
diff --git a/libs/react-renterd/src/bus.ts b/libs/react-renterd/src/bus.ts
index f3f18175e..50246d4e7 100644
--- a/libs/react-renterd/src/bus.ts
+++ b/libs/react-renterd/src/bus.ts
@@ -38,30 +38,40 @@ import {
WalletTransaction,
} from './siaTypes'
-// consensus
+// state
-export function useConsensusState(args?: HookArgsSwr) {
+type BuildState = {
+ network: 'Mainnet' | 'Zen Testnet'
+ version: string
+ commit: string
+ OS: string
+ buildTime: number
+}
+
+export type StateResponse = BuildState & {
+ startTime: number
+}
+
+export const busStateKey = '/bus/state'
+
+export function useBusState(args?: HookArgsSwr) {
return useGetSwr({
...args,
- route: '/bus/consensus/state',
+ route: busStateKey,
})
}
-export type ConsensusNetwork = {
- Name: 'mainnet' | 'zen'
-}
+// consensus
-export function useConsensusNetwork(
- args?: HookArgsSwr
-) {
+export function useConsensusState(args?: HookArgsSwr) {
return useGetSwr({
...args,
- route: '/bus/consensus/network',
+ route: '/bus/consensus/state',
})
}
export function useEstimatedNetworkBlockHeight(): number {
- const network = useConsensusNetwork({
+ const state = useBusState({
config: {
swr: {
revalidateOnFocus: false,
@@ -69,9 +79,9 @@ export function useEstimatedNetworkBlockHeight(): number {
},
})
const res = useSWR(
- network,
+ state,
() => {
- if (network.data?.Name === 'zen') {
+ if (state.data?.network === 'Zen Testnet') {
return getTestnetZenBlockHeight()
}
return getMainnetBlockHeight()
@@ -507,9 +517,16 @@ type Alert = {
data: {
account?: string
host?: string
+ key?: string
}
}
export function useAlerts(args?: HookArgsSwr) {
return useGetSwr({ ...args, route: '/bus/alerts' })
}
+
+// slabs
+
+export function useSlabObjects(args: HookArgsSwr<{ key: string }, ObjEntry[]>) {
+ return useGetSwr({ ...args, route: '/bus/slab/:key/objects' })
+}
diff --git a/libs/react-renterd/src/siaTypes.ts b/libs/react-renterd/src/siaTypes.ts
index 12118b972..bf6aa69cc 100644
--- a/libs/react-renterd/src/siaTypes.ts
+++ b/libs/react-renterd/src/siaTypes.ts
@@ -147,6 +147,11 @@ export interface Obj {
health: number
key: EncryptionKey
slabs?: SlabSlice[]
+ partialSlab?: {
+ minShards: number
+ totalShards: number
+ data?: string
+ }
}
export interface AddObjectRequest {
diff --git a/libs/react-renterd/src/worker.ts b/libs/react-renterd/src/worker.ts
index 843ea71ae..497e00732 100644
--- a/libs/react-renterd/src/worker.ts
+++ b/libs/react-renterd/src/worker.ts
@@ -6,7 +6,25 @@ import {
usePutFunc,
usePostFunc,
HookArgsCallback,
+ HookArgsSwr,
+ useGetSwr,
} from '@siafoundation/react-core'
+import { StateResponse } from './bus'
+
+// state
+
+type WorkerState = StateResponse & {
+ id: string
+}
+
+const workerStateKey = '/worker/state'
+
+export function useWorkerState(args?: HookArgsSwr) {
+ return useGetSwr({
+ ...args,
+ route: workerStateKey,
+ })
+}
export function useObjectDownloadFunc(
args?: HookArgsCallback<{ key: string }, void, Blob>
diff --git a/libs/react-walletd/src/api.ts b/libs/react-walletd/src/api.ts
index bf65b8da2..ff80237ce 100644
--- a/libs/react-walletd/src/api.ts
+++ b/libs/react-walletd/src/api.ts
@@ -52,7 +52,6 @@ export function useConsensusNetwork(
})
}
-// TODO
export function useEstimatedNetworkBlockHeight(): number {
const network = useConsensusNetwork({
config: {
@@ -90,12 +89,12 @@ type GatewayPeer = {
syncDuration: number
}
-const syncerPeers = '/syncer/peers'
+export const syncerPeersKey = '/syncer/peers'
export function useSyncerPeers(args?: HookArgsSwr) {
return useGetSwr({
...args,
- route: syncerPeers,
+ route: syncerPeersKey,
})
}
@@ -106,7 +105,7 @@ export function useSyncerConnect(args?: HookArgsCallback) {
route: '/syncer/connect',
},
async (mutate) => {
- mutate((key) => key === syncerPeers)
+ mutate((key) => key === syncerPeersKey)
}
)
}