Skip to content

Commit 8d16124

Browse files
committed
feat(indexd): contract explorer
1 parent 2f33630 commit 8d16124

10 files changed

Lines changed: 900 additions & 1 deletion

File tree

.changeset/dark-women-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'indexd': minor
3+
---
4+
5+
The app now has a contract explorer with contract and related host metadata.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
DataTable,
3+
useDataTable,
4+
useDataTableParams,
5+
} from '@siafoundation/design-system'
6+
import { useCallback } from 'react'
7+
import { contractsColumns } from './contractsColumns'
8+
import { DataViewSelect, type DataView } from '../Views'
9+
import { SidePanel } from '../SidePanel'
10+
import { SidePanelContract } from './SidePanelContract'
11+
import { SidePanelContractList } from './SidePanelContractList'
12+
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
13+
import { ContractData } from './types'
14+
import { useContracts } from './useContracts'
15+
16+
export function ContractsTab() {
17+
const params = useSearchParams()
18+
const router = useRouter()
19+
const pathname = usePathname()
20+
const view = params.get('view') as DataView
21+
const setView = (view: DataView) => {
22+
const paramsObj = new URLSearchParams(Array.from(params.entries()))
23+
paramsObj.set('view', view)
24+
router.push(`${pathname}?${paramsObj.toString()}`)
25+
}
26+
const {
27+
offset,
28+
limit,
29+
setOffset,
30+
setLimit,
31+
selectedId,
32+
setSelectedId,
33+
columnFilters,
34+
setColumnFilters,
35+
columnSorts,
36+
setColumnSorts,
37+
} = useDataTableParams('contractList')
38+
const onRowClick = useCallback(
39+
(id: string) => {
40+
setSelectedId(id)
41+
},
42+
[setSelectedId],
43+
)
44+
45+
const contracts = useContracts()
46+
const table = useDataTable<ContractData>({
47+
data: contracts,
48+
columns: contractsColumns,
49+
columnFilters,
50+
setColumnFilters,
51+
columnSorts,
52+
setColumnSorts,
53+
offset,
54+
limit,
55+
onRowClick,
56+
setOffset,
57+
setLimit,
58+
})
59+
60+
return (
61+
<div className="w-full h-full overflow-hidden flex flex-col">
62+
<div className="flex flex-row flex-1 overflow-hidden w-full">
63+
<div className="flex-1 h-full overflow-hidden p-4">
64+
<DataTable
65+
{...table}
66+
heading={<DataViewSelect value={view} onValueChange={setView} />}
67+
/>
68+
</div>
69+
<div className="py-4 pr-4">
70+
{selectedId ? (
71+
<SidePanel showCloseButton onClose={() => setSelectedId(undefined)}>
72+
<SidePanelContract />
73+
</SidePanel>
74+
) : (
75+
<SidePanel
76+
showCloseButton={false}
77+
onClose={() => setSelectedId(undefined)}
78+
>
79+
<SidePanelContractList contracts={table.rows} />
80+
</SidePanel>
81+
)}
82+
</div>
83+
</div>
84+
</div>
85+
)
86+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
Heading,
3+
HostMap,
4+
Separator,
5+
Text,
6+
ValueCopyable,
7+
} from '@siafoundation/design-system'
8+
import { useDataTableParams } from '@siafoundation/design-system'
9+
import { useMemo } from 'react'
10+
import { useContracts } from './useContracts'
11+
import { UsabilityBadges } from '../UsabilityBadges'
12+
import { StateBadge, StatusBadge } from './contractsColumns'
13+
import { InfoRow } from '../InfoRow'
14+
15+
export function SidePanelContract() {
16+
const { selectedId } = useDataTableParams('contractList')
17+
const contracts = useContracts()
18+
const contract = useMemo(
19+
() => contracts.find((c) => c.id === selectedId),
20+
[selectedId, contracts],
21+
)
22+
const mapContract = useMemo(() => {
23+
if (!contract) return null
24+
return {
25+
id: contract.hostKey,
26+
publicKey: contract.hostKey,
27+
location: contract.host?.location,
28+
v2Settings: contract.host?.settings,
29+
usable: contract.host?.usable,
30+
}
31+
}, [contract])
32+
if (!contract) {
33+
return (
34+
<div className="flex flex-col items-center justify-center overflow-hidden">
35+
<Text>Contract not found</Text>
36+
</div>
37+
)
38+
}
39+
return (
40+
<div className="flex flex-col overflow-hidden">
41+
<Heading size="24" className="mr-[50px] truncate">
42+
Contract: {contract?.id}
43+
</Heading>
44+
<HostMap
45+
hosts={mapContract ? [mapContract] : []}
46+
activeHost={null}
47+
onHostMapClick={() => null}
48+
showLegend={false}
49+
scale={180}
50+
/>
51+
<Separator className="mb-2" />
52+
<Text size="16" weight="medium" className="mb-2">
53+
Info
54+
</Text>
55+
<div className="flex flex-col gap-2">
56+
<InfoRow label="State" value={<StateBadge value={contract.state} />} />
57+
<InfoRow
58+
label="Status"
59+
value={<StatusBadge value={contract.good ? 'Good' : 'Bad'} />}
60+
/>
61+
<InfoRow
62+
label="Host public key"
63+
value={
64+
<ValueCopyable value={contract.hostKey} type="hostPublicKey" />
65+
}
66+
/>
67+
{contract.host?.addresses.map((address) => (
68+
<InfoRow
69+
key={address.address}
70+
label={address.protocol}
71+
value={<ValueCopyable value={address.address} maxLength={24} />}
72+
/>
73+
))}
74+
</div>
75+
<Separator className="mt-4 mb-2" />
76+
<Text size="16" weight="medium" className="mb-2">
77+
Usability
78+
</Text>
79+
<UsabilityBadges
80+
usable={contract.host?.usable || false}
81+
usability={contract.host?.usability}
82+
className="w-full overflow-hidden flex-wrap"
83+
/>
84+
</div>
85+
)
86+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {
2+
Heading,
3+
HostMap,
4+
useDataTableParams,
5+
} from '@siafoundation/design-system'
6+
import { ContractData } from './types'
7+
import { Row } from '@tanstack/react-table'
8+
import { useMapHosts } from '../useMapHosts'
9+
10+
export function SidePanelContractList({
11+
contracts,
12+
}: {
13+
contracts: Row<ContractData>[]
14+
}) {
15+
const { setSelectedId } = useDataTableParams('contractList')
16+
const mapHosts = useMapHosts({
17+
hosts: contracts.map((contract) => ({
18+
...contract.original,
19+
settings: contract.original.host?.settings,
20+
usable: contract.original.host?.usable,
21+
location: contract.original.host?.location,
22+
})),
23+
})
24+
return (
25+
<div className="flex flex-col overflow-hidden">
26+
<Heading size="24" className="mb-2">
27+
Contracts ({contracts.length})
28+
</Heading>
29+
<HostMap
30+
hosts={mapHosts}
31+
activeHost={null}
32+
onHostMapClick={(id) => setSelectedId(id)}
33+
scale={180}
34+
showLegend={false}
35+
/>
36+
</div>
37+
)
38+
}

0 commit comments

Comments
 (0)