{data.isBlocked ? (
diff --git a/apps/renterd/contexts/hosts/dataset.ts b/apps/renterd/contexts/hosts/dataset.ts
index 9803604ae..2132efa96 100644
--- a/apps/renterd/contexts/hosts/dataset.ts
+++ b/apps/renterd/contexts/hosts/dataset.ts
@@ -14,23 +14,19 @@ import { Maybe, objectEntries } from '@siafoundation/design-system'
export function useDataset({
response,
allContracts,
- autopilotID,
allowlist,
blocklist,
isAllowlistActive,
geoHosts,
- onHostSelect,
}: {
response: ReturnType
- autopilotID?: string
allContracts: Maybe
allowlist: ReturnType
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) {
@@ -39,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,
@@ -47,16 +42,15 @@ export function useDataset({
blocklist: block,
isAllowlistActive,
}),
- ...getAutopilotFields(
- autopilotID ? host.checks?.[autopilotID] : undefined
- ),
+ ...getAutopilotFields(host.checks?.autopilot),
location: sch?.location,
countryCode: sch?.country_code,
+ // selectable
+ onClick: () => null,
+ isSelected: false,
}
})
}, [
- onHostSelect,
- autopilotID,
response.data,
allContracts,
allowlist.data,
diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx
index 841152e50..902eb3c76 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'
@@ -53,7 +54,6 @@ function useHostsMain() {
useServerFilters()
const { dataset: allContracts } = useContracts()
- const autopilotState = useAutopilotState()
const keyIn = useMemo(() => {
let keyIn: string[] = []
@@ -121,8 +121,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)
@@ -136,55 +134,13 @@ 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,
- autopilotID: autopilotState.data?.id,
allowlist,
blocklist,
isAllowlistActive,
geoHosts,
- onHostSelect: onHostListClick,
})
const {
@@ -213,31 +169,84 @@ 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 autopilotState = useAutopilotState()
const isAutopilotConfigured = !!autopilotState.data?.configured
const tableContext: HostContext = useMemo(
() => ({
isAutopilotConfigured,
siascanUrl,
+ multiSelect,
}),
- [isAutopilotConfigured, siascanUrl]
+ [isAutopilotConfigured, 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,
@@ -245,9 +254,9 @@ function useHostsMain() {
dataState,
offset,
limit,
- pageCount: dataset?.length || 0,
+ pageCount: datasetPage?.length || 0,
columns: filteredTableColumns,
- dataset,
+ datasetPage,
tableContext,
configurableColumns,
enabledColumns,
@@ -265,6 +274,7 @@ function useHostsMain() {
removeFilter,
removeLastFilter,
resetFilters,
+ multiSelect,
}
}
diff --git a/apps/renterd/contexts/hosts/types.tsx b/apps/renterd/contexts/hosts/types.tsx
index 4606035e7..b525f24b8 100644
--- a/apps/renterd/contexts/hosts/types.tsx
+++ b/apps/renterd/contexts/hosts/types.tsx
@@ -1,8 +1,13 @@
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 = { isAutopilotConfigured: boolean; siascanUrl: string }
+export type HostContext = {
+ isAutopilotConfigured: boolean
+ siascanUrl: string
+ multiSelect: MultiSelect
+}
export type HostData = {
id: string
@@ -60,8 +65,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/autopilot.ts b/libs/renterd-types/src/autopilot.ts
index 1c2672be2..de0da424e 100644
--- a/libs/renterd-types/src/autopilot.ts
+++ b/libs/renterd-types/src/autopilot.ts
@@ -6,7 +6,6 @@ export const autopilotConfigRoute = '/autopilot/config'
export const autopilotTriggerRoute = '/autopilot/trigger'
type AutopilotStatus = {
- id: string
configured: boolean
migrating: boolean
migratingLastStart: string
diff --git a/libs/renterd-types/src/bus.ts b/libs/renterd-types/src/bus.ts
index 3bf8fb9e1..653da3a11 100644
--- a/libs/renterd-types/src/bus.ts
+++ b/libs/renterd-types/src/bus.ts
@@ -269,7 +269,7 @@ export type HostsBlocklistUpdatePayload = {
export type HostsBlocklistUpdateResponse = void
export type HostResetLostSectorCountParams = {
- publicKey: string
+ publickey: string
}
export type HostResetLostSectorCountPayload = void
export type HostResetLostSectorCountResponse = void
diff --git a/libs/renterd-types/src/types.ts b/libs/renterd-types/src/types.ts
index 1b6c32ee7..7c0f75532 100644
--- a/libs/renterd-types/src/types.ts
+++ b/libs/renterd-types/src/types.ts
@@ -186,7 +186,9 @@ export type Host = {
storedData: number
resolvedAddresses?: string[]
subnets?: string[]
- checks?: Record
+ checks?: {
+ autopilot: HostAutopilotChecks
+ }
}
export type HostAutopilotChecks = {