{data.isBlocked ? (
diff --git a/apps/renterd/contexts/hosts/dataset.ts b/apps/renterd/contexts/hosts/dataset.ts
index 7b32d9fc2..26ca7c430 100644
--- a/apps/renterd/contexts/hosts/dataset.ts
+++ b/apps/renterd/contexts/hosts/dataset.ts
@@ -18,7 +18,6 @@ export function useDataset({
blocklist,
isAllowlistActive,
geoHosts,
- onHostSelect,
}: {
response: ReturnType
allContracts: Maybe
@@ -26,9 +25,8 @@ export function useDataset({
blocklist: ReturnType
isAllowlistActive: boolean
geoHosts: SiaCentralHost[]
- onHostSelect: (publicKey: string, location?: [number, number]) => void
}) {
- return useMemo(() => {
+ return useMemo>(() => {
const allow = allowlist.data
const block = blocklist.data
if (!response.data || !allow || !block) {
@@ -37,7 +35,6 @@ export function useDataset({
return response.data.map((host) => {
const sch = geoHosts.find((gh) => gh.public_key === host.publicKey)
return {
- onClick: () => onHostSelect(host.publicKey, sch?.location),
...getHostFields(host, allContracts),
...getAllowedFields({
host,
@@ -48,10 +45,12 @@ export function useDataset({
...getAutopilotFields(host.checks),
location: sch?.location,
countryCode: sch?.country_code,
+ // selectable
+ onClick: () => null,
+ isSelected: false,
}
})
}, [
- onHostSelect,
response.data,
allContracts,
allowlist.data,
diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx
index caf63aba5..dca55b327 100644
--- a/apps/renterd/contexts/hosts/index.tsx
+++ b/apps/renterd/contexts/hosts/index.tsx
@@ -1,7 +1,8 @@
import {
- triggerErrorToast,
+ triggerToast,
truncate,
useDatasetEmptyState,
+ useMultiSelect,
useServerFilters,
useTableState,
} from '@siafoundation/design-system'
@@ -119,8 +120,6 @@ function useHostsMain() {
[cmdRef]
)
- const [activeHostPublicKey, setActiveHostPublicKey] = useState()
-
const scrollToHost = useCallback((publicKey: string) => {
// move table to host, select via data id data-table
const rowEl = document.getElementById(publicKey)
@@ -134,46 +133,6 @@ function useHostsMain() {
})
}, [])
- const onHostMapClick = useCallback(
- (publicKey: string, location?: [number, number]) => {
- if (activeHostPublicKey === publicKey) {
- setActiveHostPublicKey(undefined)
- return
- }
- setActiveHostPublicKey(publicKey)
- if (location) {
- cmdRef.current.moveToLocation(location)
- }
- scrollToHost(publicKey)
- },
- [setActiveHostPublicKey, cmdRef, activeHostPublicKey, scrollToHost]
- )
-
- const onHostListClick = useCallback(
- (publicKey: string, location?: [number, number]) => {
- if (activeHostPublicKey === publicKey) {
- setActiveHostPublicKey(undefined)
- return
- }
- setActiveHostPublicKey(publicKey)
- if (location) {
- cmdRef.current.moveToLocation(location)
- } else {
- triggerErrorToast({
- title: 'Geographic location is unknown for host',
- body: truncate(publicKey, 20),
- })
- }
- scrollToHost(publicKey)
- },
- [setActiveHostPublicKey, cmdRef, activeHostPublicKey, scrollToHost]
- )
-
- const onHostMapHover = useCallback(
- (publicKey: string, location?: [number, number]) => null,
- []
- )
-
const dataset = useDataset({
response,
allContracts,
@@ -181,7 +140,6 @@ function useHostsMain() {
blocklist,
isAllowlistActive,
geoHosts,
- onHostSelect: onHostListClick,
})
const {
@@ -210,29 +168,81 @@ function useHostsMain() {
const error = response.error
const dataState = useDatasetEmptyState(dataset, isValidating, error, filters)
+ const hostsWithLocation = useMemo(
+ () => dataset?.filter((h) => h.location) as HostDataWithLocation[],
+ [dataset]
+ )
+
+ const multiSelect = useMultiSelect(dataset)
+
+ const activeHost = useMemo(() => {
+ if (multiSelect.selectedIds.length === 1) {
+ return dataset?.find((d) => d.publicKey === multiSelect.selectedIds[0])
+ }
+ }, [dataset, multiSelect.selectedIds])
+
+ const datasetPage = useMemo(() => {
+ if (!dataset) {
+ return undefined
+ }
+ return dataset.map((datum) => {
+ return {
+ ...datum,
+ onClick: (e: React.MouseEvent) =>
+ multiSelect.onSelect(datum.id, e),
+ isSelected: !!multiSelect.selectionMap[datum.id],
+ }
+ })
+ }, [dataset, multiSelect])
+
const siascanUrl = useSiascanUrl()
const tableContext: HostContext = useMemo(
() => ({
siascanUrl,
+ multiSelect,
}),
- [siascanUrl]
+ [siascanUrl, multiSelect]
)
- const hostsWithLocation = useMemo(
- () => dataset?.filter((h) => h.location) as HostDataWithLocation[],
- [dataset]
+ const onHostMapClick = useCallback(
+ (publicKey: string, location?: [number, number]) => {
+ if (activeHost?.publicKey !== publicKey) {
+ multiSelect.deselectAll()
+ multiSelect.onSelect(publicKey)
+ if (location) {
+ cmdRef.current.moveToLocation(location)
+ }
+ scrollToHost(publicKey)
+ }
+ },
+ [activeHost, multiSelect, cmdRef, scrollToHost]
)
- const activeHost = useMemo(
- () => dataset?.find((d) => d.publicKey === activeHostPublicKey),
- [dataset, activeHostPublicKey]
- )
+ useEffect(() => {
+ if (!activeHost) {
+ return
+ }
+ if (viewMode !== 'map') {
+ return
+ }
+ const { location } = activeHost || {}
+ if (location) {
+ cmdRef.current.moveToLocation(location)
+ } else {
+ triggerToast({
+ title: `Location not available for host ${truncate(
+ activeHost.publicKey,
+ 20
+ )}`,
+ })
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [activeHost])
return {
setCmd,
viewMode,
activeHost,
- onHostMapHover,
onHostMapClick,
setViewMode,
hostsWithLocation,
@@ -240,9 +250,9 @@ function useHostsMain() {
dataState,
offset,
limit,
- pageCount: dataset?.length || 0,
+ pageCount: datasetPage?.length || 0,
columns: filteredTableColumns,
- dataset,
+ datasetPage,
tableContext,
configurableColumns,
enabledColumns,
@@ -260,6 +270,7 @@ function useHostsMain() {
removeFilter,
removeLastFilter,
resetFilters,
+ multiSelect,
}
}
diff --git a/apps/renterd/contexts/hosts/types.tsx b/apps/renterd/contexts/hosts/types.tsx
index bf05cc423..454b1853e 100644
--- a/apps/renterd/contexts/hosts/types.tsx
+++ b/apps/renterd/contexts/hosts/types.tsx
@@ -1,8 +1,12 @@
import { HostPriceTable, HostSettings } from '@siafoundation/types'
import BigNumber from 'bignumber.js'
import { ContractData } from '../contracts/types'
+import { MultiSelect } from '@siafoundation/design-system'
-export type HostContext = { siascanUrl: string }
+export type HostContext = {
+ siascanUrl: string
+ multiSelect: MultiSelect
+}
export type HostData = {
id: string
@@ -60,8 +64,13 @@ export type HostData = {
location?: [number, number]
countryCode?: string
+
+ onClick: (e: React.MouseEvent) => void
+ isSelected: boolean
}
+export type HostDataWithoutSelectable = Omit
+
const generalColumns = [
'actions',
'allow',
diff --git a/libs/design-system/src/multi/useMultiSelect.tsx b/libs/design-system/src/multi/useMultiSelect.tsx
index e2f361ab3..0738a5be2 100644
--- a/libs/design-system/src/multi/useMultiSelect.tsx
+++ b/libs/design-system/src/multi/useMultiSelect.tsx
@@ -16,7 +16,7 @@ export function useMultiSelect- (dataset?: Item[]) {
}>()
const onSelect = useCallback(
- (id: string, e: MouseEvent) => {
+ (id: string, e?: MouseEvent) => {
if (!dataset) {
return
}
@@ -34,7 +34,7 @@ export function useMultiSelect
- (dataset?: Item[]) {
const newSelection = { ...prevSelectionMap }
setLastSelectedItem((prevSelection) => {
// If shift click, select all items between current and last selection indices.
- if (e.shiftKey && prevSelection) {
+ if (e?.shiftKey && prevSelection) {
if (prevSelection.index < selected.index) {
for (let i = prevSelection.index; i <= selected.index; i++) {
const item = dataset[i]
diff --git a/libs/renterd-types/src/bus.ts b/libs/renterd-types/src/bus.ts
index 2d23450bc..169c7c4c3 100644
--- a/libs/renterd-types/src/bus.ts
+++ b/libs/renterd-types/src/bus.ts
@@ -48,7 +48,7 @@ export const busHostsHostKeyRoute = '/bus/hosts/:hostkey'
export const busHostsBlocklistRoute = '/bus/hosts/blocklist'
export const busHostsAllowlistRoute = '/bus/hosts/allowlist'
export const busHostPublicKeyResetlostsectorsRoute =
- '/bus/host/:publickey/resetlostsectors'
+ '/bus/host/:hostkey/resetlostsectors'
export const busContractsRoute = '/bus/contracts'
export const busContractIdAcquireRoute = '/bus/contract/:id/acquire'
export const busContractIdReleaseRoute = '/bus/contract/:id/release'
@@ -268,7 +268,7 @@ export type HostsBlocklistUpdatePayload = {
export type HostsBlocklistUpdateResponse = void
export type HostResetLostSectorCountParams = {
- publicKey: string
+ hostkey: string
}
export type HostResetLostSectorCountPayload = void
export type HostResetLostSectorCountResponse = void