From 4bda88023ca694ce57bf47b51b1c05baaa1d9c5a Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Aug 2023 12:13:33 -0400 Subject: [PATCH 1/8] fix: hostd error alert --- .changeset/eight-wombats-sneeze.md | 5 +++++ apps/hostd/dialogs/AlertsDialog.tsx | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .changeset/eight-wombats-sneeze.md 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/apps/hostd/dialogs/AlertsDialog.tsx b/apps/hostd/dialogs/AlertsDialog.tsx index 14a275250..1a258a912 100644 --- a/apps/hostd/dialogs/AlertsDialog.tsx +++ b/apps/hostd/dialogs/AlertsDialog.tsx @@ -3,6 +3,7 @@ import { Checkmark16, Dialog, Heading, + Separator, Skeleton, Text, triggerErrorToast, @@ -13,7 +14,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, omit, times } from 'lodash' import { useCallback } from 'react' type Props = { @@ -142,6 +143,11 @@ export function AlertsDialog({ open, onOpenChange }: Props) { + {!!a.data.error && ( + + {a.data.error} + + )}
timestamp @@ -150,7 +156,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 +183,8 @@ export function AlertsDialog({ open, onOpenChange }: Props) { ) } +const skipFields = ['error'] + const dataFieldOrder = [ 'contractID', 'blockHeight', @@ -199,8 +207,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 +222,7 @@ function getOrderedKeys(obj) { } return aIndex - bIndex }) + return difference(keys, skip) } const dataFields = { From 59fdaec810799e87bbc6ceba7d1a266bde7ecd65 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Aug 2023 12:38:47 -0400 Subject: [PATCH 2/8] fix: renterd state, show build version --- .changeset/plenty-baboons-leave.md | 5 +++ .changeset/six-mice-work.md | 5 +++ apps/hostd/dialogs/AlertsDialog.tsx | 3 +- apps/renterd/components/CmdRoot/index.tsx | 2 +- .../FilesStatsMenu/FilesStatsMenuWarnings.tsx | 4 +-- .../HostsFilterCmdGroups/index.tsx | 2 +- .../HostsFilterCmd/HostsFilterNav/index.tsx | 2 +- apps/renterd/components/Profile/index.tsx | 36 ++++++++++++------- apps/renterd/components/RenterdSidenav.tsx | 2 +- .../RenterdTestnetWarningBanner.tsx | 10 +++--- apps/renterd/contexts/app/useAutopilot.tsx | 26 +++++++------- apps/renterd/contexts/hosts/dataset.ts | 10 +++--- apps/renterd/contexts/hosts/index.tsx | 14 ++++---- apps/renterd/hooks/useIsApcsEqDcs.tsx | 2 +- libs/react-renterd/src/autopilot.ts | 12 ++++--- libs/react-renterd/src/bus.ts | 36 ++++++++++++------- libs/react-renterd/src/worker.ts | 18 ++++++++++ 17 files changed, 118 insertions(+), 71 deletions(-) create mode 100644 .changeset/plenty-baboons-leave.md create mode 100644 .changeset/six-mice-work.md 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/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/apps/hostd/dialogs/AlertsDialog.tsx b/apps/hostd/dialogs/AlertsDialog.tsx index 1a258a912..73c3f5efb 100644 --- a/apps/hostd/dialogs/AlertsDialog.tsx +++ b/apps/hostd/dialogs/AlertsDialog.tsx @@ -3,7 +3,6 @@ import { Checkmark16, Dialog, Heading, - Separator, Skeleton, Text, triggerErrorToast, @@ -14,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 { difference, omit, times } from 'lodash' +import { difference, times } from 'lodash' import { useCallback } from 'react' type Props = { 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/FilesStatsMenu/FilesStatsMenuWarnings.tsx b/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx index 239e8f6a5..7a7dc7f72 100644 --- a/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx +++ b/apps/renterd/components/Files/FilesStatsMenu/FilesStatsMenuWarnings.tsx @@ -20,14 +20,14 @@ export function FilesStatsMenuWarnings() { let warning = 'none' if ( - autopilot.state === 'on' && + autopilot.status === 'on' && !isApcsEqDcs.isValidating && !isApcsEqDcs.data ) { warning = 'contractSetMismatch' } - if (autopilot.state === 'on' && apc.error) { + if (autopilot.status === 'on' && apc.error) { warning = 'setupAutopilot' } 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' && ( Network - {network.data?.Name} +
+ {state.data?.network} +
- {/*
- - +
+ +
+ {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/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..82d8f1d19 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,11 +215,11 @@ 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 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/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..e39b43ef5 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 +} + +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() diff --git a/libs/react-renterd/src/worker.ts b/libs/react-renterd/src/worker.ts index 843ea71ae..291ab5aeb 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 useBusState(args?: HookArgsSwr) { + return useGetSwr({ + ...args, + route: workerStateKey, + }) +} export function useObjectDownloadFunc( args?: HookArgsCallback<{ key: string }, void, Blob> From c120d7c124c5199c606ce0056e61c9efa2f7171b Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Aug 2023 14:29:51 -0400 Subject: [PATCH 3/8] feat: failed to migrate slab alert objects --- .changeset/happy-hornets-tickle.md | 5 + .changeset/pretty-rules-bake.md | 5 + apps/renterd/dialogs/AlertsDialog.tsx | 137 ++++++++++++-------- libs/design-system/src/components/Table.tsx | 2 +- libs/react-renterd/src/bus.ts | 7 + 5 files changed, 104 insertions(+), 52 deletions(-) create mode 100644 .changeset/happy-hornets-tickle.md create mode 100644 .changeset/pretty-rules-bake.md 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/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/apps/renterd/dialogs/AlertsDialog.tsx b/apps/renterd/dialogs/AlertsDialog.tsx index b4fca58cb..f5d887414 100644 --- a/apps/renterd/dialogs/AlertsDialog.tsx +++ b/apps/renterd/dialogs/AlertsDialog.tsx @@ -1,19 +1,27 @@ import { Dialog, Heading, + Link, + ScrollArea, Skeleton, Text, useDatasetEmptyState, ValueCopyable, ValueMenu, } 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 { 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 +43,87 @@ 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 }) { + key: { + 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} + + ))} +
+
+ )} + ) }, }, account: { - label: 'account', render: ({ value }: { value: string }) => ( - +
+ + account + + +
), }, }), @@ -174,14 +209,7 @@ export function AlertsDialog({ open, onOpenChange }: Props) { } const label = dataFields[key]?.label || key const Component = dataFields[key]?.render || DefaultDisplay - return ( -
- - {label} - - -
- ) + return })}
))} @@ -192,11 +220,18 @@ export function AlertsDialog({ open, onOpenChange }: Props) { ) } -function DefaultDisplay({ value }) { - return {value} +function DefaultDisplay({ label, value }) { + return ( +
+ + {label} + + {value} +
+ ) } -const dataFieldOrder = ['hostKey', 'contractID', 'account'] +const dataFieldOrder = ['hostKey', 'contractID', 'account', 'key'] // Sort keys by dataFieldOrder, then alphabetically function getOrderedKeys(obj) { diff --git a/libs/design-system/src/components/Table.tsx b/libs/design-system/src/components/Table.tsx index 02a590840..8a64c9199 100644 --- a/libs/design-system/src/components/Table.tsx +++ b/libs/design-system/src/components/Table.tsx @@ -258,7 +258,7 @@ export function Table< times(pageSize).map((i) => ( {columns.map(({ id, contentClassName, cellClassName }, i) => ( ) { return useGetSwr({ ...args, route: '/bus/alerts' }) } + +// slabs + +export function useSlabObjects(args: HookArgsSwr<{ key: string }, ObjEntry[]>) { + return useGetSwr({ ...args, route: '/bus/slab/:key/objects' }) +} From 2659481e8abd8f8d03fb58b22ebb7ee7f80fd031 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Aug 2023 14:39:58 -0400 Subject: [PATCH 4/8] fix: move connectivity/login off consensus --- .changeset/slimy-stingrays-ring.md | 7 +++++++ apps/hostd/config/routes.ts | 4 +++- apps/renterd/config/routes.ts | 4 +++- apps/walletd/config/routes.ts | 4 +++- libs/react-hostd/src/api.ts | 4 +++- libs/react-renterd/src/bus.ts | 2 +- libs/react-walletd/src/api.ts | 7 +++---- 7 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 .changeset/slimy-stingrays-ring.md 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/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/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/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/react-hostd/src/api.ts b/libs/react-hostd/src/api.ts index b4dc5aa01..e78610edf 100644 --- a/libs/react-hostd/src/api.ts +++ b/libs/react-hostd/src/api.ts @@ -32,10 +32,12 @@ export type StateHost = { buildTime: string } +export const stateHostKey = '/state/host' + export function useStateHost(args?: HookArgsSwr) { return useGetSwr({ ...args, - route: '/state/host', + route: stateHostKey, }) } diff --git a/libs/react-renterd/src/bus.ts b/libs/react-renterd/src/bus.ts index fdaad3799..50246d4e7 100644 --- a/libs/react-renterd/src/bus.ts +++ b/libs/react-renterd/src/bus.ts @@ -52,7 +52,7 @@ export type StateResponse = BuildState & { startTime: number } -const busStateKey = '/bus/state' +export const busStateKey = '/bus/state' export function useBusState(args?: HookArgsSwr) { return useGetSwr({ 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) } ) } From f669717bdcc8b9808a0a90f5ce4d8d72f4483495 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 18 Aug 2023 15:28:56 -0400 Subject: [PATCH 5/8] feat: renterd autopilot and not enough contract onboarding --- .changeset/dull-readers-sit.md | 5 + .changeset/odd-pillows-try.md | 5 + apps/renterd/components/Files/EmptyState.tsx | 79 +++++++ .../components/Files/FilesActionsMenu.tsx | 7 +- .../components/Files/FilesExplorer.tsx | 22 +- .../FilesStatsMenu/FilesStatsMenuWarnings.tsx | 198 ++++++++++-------- .../checks/useAutopilotNotConfigured.tsx | 17 ++ .../Files/checks/useContractSetMismatch.tsx | 15 ++ .../checks/useDefaultContractSetNotSet.tsx | 9 + .../Files/checks/useNotEnoughContracts.tsx | 22 ++ .../renterd/components/Files/useCanUpload.tsx | 8 + apps/renterd/contexts/hosts/index.tsx | 2 +- libs/react-renterd/src/worker.ts | 2 +- 13 files changed, 284 insertions(+), 107 deletions(-) create mode 100644 .changeset/dull-readers-sit.md create mode 100644 .changeset/odd-pillows-try.md create mode 100644 apps/renterd/components/Files/EmptyState.tsx create mode 100644 apps/renterd/components/Files/checks/useAutopilotNotConfigured.tsx create mode 100644 apps/renterd/components/Files/checks/useContractSetMismatch.tsx create mode 100644 apps/renterd/components/Files/checks/useDefaultContractSetNotSet.tsx create mode 100644 apps/renterd/components/Files/checks/useNotEnoughContracts.tsx create mode 100644 apps/renterd/components/Files/useCanUpload.tsx 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/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/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() { -