diff --git a/.changeset/purple-impalas-beam.md b/.changeset/purple-impalas-beam.md
new file mode 100644
index 000000000..62d0438e8
--- /dev/null
+++ b/.changeset/purple-impalas-beam.md
@@ -0,0 +1,5 @@
+---
+'indexd': minor
+---
+
+Added public key filtering for hosts and contracts. Closes https://github.com/SiaFoundation/indexd/issues/752
diff --git a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/PublicKey.tsx b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/PublicKey.tsx
new file mode 100644
index 000000000..482ef5197
--- /dev/null
+++ b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/PublicKey.tsx
@@ -0,0 +1,66 @@
+import {
+ CommandGroup,
+ CommandItemNav,
+ CommandItemSearch,
+} from '../../../../../CmdRoot/Item'
+import { Page } from '../../../../../CmdRoot/types'
+import { useDialog } from '../../../../../../contexts/dialog'
+
+export const contractsFilterPublicKeyPage = {
+ namespace: 'contracts/filterPublicKey',
+ label: 'Contracts filter by public key',
+}
+
+export function PublicKeyCmdGroup({
+ select,
+ currentPage,
+}: {
+ currentPage: Page
+ select: () => void
+}) {
+ const { openDialog } = useDialog()
+ return (
+
+ {
+ select()
+ openDialog('contractsFilterPublicKey')
+ }}
+ >
+ Filter by host public key
+
+
+ )
+}
+
+export function PublicKeyCmdNav({
+ select,
+ currentPage,
+ parentPage,
+ commandPage,
+}: {
+ currentPage: Page
+ parentPage?: Page
+ commandPage: Page
+ select: () => void
+}) {
+ const { openDialog } = useDialog()
+ return (
+ {
+ select()
+ openDialog('contractsFilterPublicKey')
+ }}
+ >
+ {contractsFilterPublicKeyPage.label}
+
+ )
+}
diff --git a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/index.tsx b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/index.tsx
index 46998bce8..f2107a6e2 100644
--- a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/index.tsx
+++ b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterCmdGroups/index.tsx
@@ -1,14 +1,18 @@
import { Page } from '../../../../../CmdRoot/types'
import { StatusCmdGroup } from './Status'
+import { PublicKeyCmdGroup } from './PublicKey'
import { ContractFilter } from '../../../types'
type Props = {
currentPage: Page
- select: (filter: ContractFilter) => void
+ select: (filter?: ContractFilter) => void
}
export function ContractFilterCmdGroups({ currentPage, select }: Props) {
return (
-
+ <>
+
+
+ >
)
}
diff --git a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterNav/index.tsx b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterNav/index.tsx
index 9262c5b24..b17d5f1dd 100644
--- a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterNav/index.tsx
+++ b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/ContractFilterNav/index.tsx
@@ -1,6 +1,7 @@
import { CommandItemNav } from '../../../../../CmdRoot/Item'
import { Page } from '../../../../../CmdRoot/types'
import { contractsFilterStatusPage } from '../ContractFilterCmdGroups/Status'
+import { PublicKeyCmdNav } from '../ContractFilterCmdGroups/PublicKey'
import { ContractFilter } from '../../../types'
export const commandPage = {
@@ -12,16 +13,18 @@ type Props = {
currentPage: Page
parentPage?: Page
pushPage: (page: Page) => void
- select: (filter: ContractFilter) => void
+ select: (filter?: ContractFilter) => void
}
export function ContractFilterNav({
currentPage,
parentPage,
pushPage,
+ select,
}: Props) {
return (
-
+
{contractsFilterStatusPage.label}
+
+ >
)
}
diff --git a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/index.tsx b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/index.tsx
index a63d53155..193887d15 100644
--- a/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/index.tsx
+++ b/apps/indexd/components/Data/Contracts/ContractsCmd/ContractsFilterCmd/index.tsx
@@ -21,11 +21,13 @@ export function ContractsFilterCmd({
const { addColumnFilter } = useContractsParams()
const select = useCallback(
- (filter: ContractFilter) => {
+ (filter?: ContractFilter) => {
if (beforeSelect) {
beforeSelect()
}
- addColumnFilter(filter)
+ if (filter) {
+ addColumnFilter(filter)
+ }
if (afterSelect) {
afterSelect()
}
diff --git a/apps/indexd/components/Data/Contracts/types.ts b/apps/indexd/components/Data/Contracts/types.ts
index 5ec0010dd..279ee1571 100644
--- a/apps/indexd/components/Data/Contracts/types.ts
+++ b/apps/indexd/components/Data/Contracts/types.ts
@@ -1,4 +1,5 @@
import { AdminContractsSortBy, Contract } from '@siafoundation/indexd-types'
+import { truncate } from '@siafoundation/design-system'
import { CurrencyOption } from '@siafoundation/react-core'
import BigNumber from 'bignumber.js'
import {
@@ -17,7 +18,15 @@ export type ContractFilterRevisable = {
value: boolean
}
-export type ContractFilter = ContractFilterStatus | ContractFilterRevisable
+export type ContractFilterPublicKey = {
+ id: 'hostkey'
+ value: string
+}
+
+export type ContractFilter =
+ | ContractFilterStatus
+ | ContractFilterRevisable
+ | ContractFilterPublicKey
export type ContractFilters = ContractFilter[]
export type ContractSorts = DataTableSortColumn[]
@@ -29,6 +38,9 @@ export function getFilterLabel(filter: ContractFilter): string {
if (filter.id === 'revisable') {
return filter.value ? 'Revisable' : 'Not revisable'
}
+ if (filter.id === 'hostkey') {
+ return `Public key is ${truncate(filter.value, 20)}`
+ }
return ''
}
diff --git a/apps/indexd/components/Data/Contracts/useContracts.tsx b/apps/indexd/components/Data/Contracts/useContracts.tsx
index fc6ef4c46..a42877665 100644
--- a/apps/indexd/components/Data/Contracts/useContracts.tsx
+++ b/apps/indexd/components/Data/Contracts/useContracts.tsx
@@ -26,6 +26,10 @@ export function useContracts() {
if (revisable !== undefined) {
filters.revisable = revisable
}
+ const hostkey = columnFilters.find((f) => f.id === 'hostkey')?.value
+ if (hostkey !== undefined) {
+ filters.hostkey = [hostkey]
+ }
// Map all active sorts to API sortby and desc arrays.
if (columnSorts.length > 0) {
const sortby: AdminContractsSortBy[] = []
diff --git a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/PublicKey.tsx b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/PublicKey.tsx
new file mode 100644
index 000000000..4f555a3a6
--- /dev/null
+++ b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/PublicKey.tsx
@@ -0,0 +1,66 @@
+import {
+ CommandGroup,
+ CommandItemNav,
+ CommandItemSearch,
+} from '../../../../../CmdRoot/Item'
+import { Page } from '../../../../../CmdRoot/types'
+import { useDialog } from '../../../../../../contexts/dialog'
+
+export const hostsFilterPublicKeyPage = {
+ namespace: 'hosts/filterPublicKey',
+ label: 'Hosts filter by public key',
+}
+
+export function PublicKeyCmdGroup({
+ select,
+ currentPage,
+}: {
+ currentPage: Page
+ select: () => void
+}) {
+ const { openDialog } = useDialog()
+ return (
+
+ {
+ select()
+ openDialog('hostsFilterPublicKey')
+ }}
+ >
+ Filter by public key
+
+
+ )
+}
+
+export function PublicKeyCmdNav({
+ select,
+ currentPage,
+ parentPage,
+ commandPage,
+}: {
+ currentPage: Page
+ parentPage?: Page
+ commandPage: Page
+ select: () => void
+}) {
+ const { openDialog } = useDialog()
+ return (
+ {
+ select()
+ openDialog('hostsFilterPublicKey')
+ }}
+ >
+ {hostsFilterPublicKeyPage.label}
+
+ )
+}
diff --git a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/index.tsx b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/index.tsx
index d202b2661..472844f60 100644
--- a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/index.tsx
+++ b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterCmdGroups/index.tsx
@@ -2,11 +2,12 @@ import { Page } from '../../../../../CmdRoot/types'
import { UsableCmdGroup } from './Usable'
import { BlockedCmdGroup } from './Blocked'
import { ActiveContractsCmdGroup } from './ActiveContracts'
+import { PublicKeyCmdGroup } from './PublicKey'
import { HostFilter } from '../../../types'
type Props = {
currentPage: Page
- select: (filter: HostFilter) => void
+ select: (filter?: HostFilter) => void
}
export function HostFilterCmdGroups({ currentPage, select }: Props) {
@@ -15,6 +16,7 @@ export function HostFilterCmdGroups({ currentPage, select }: Props) {
+
>
)
}
diff --git a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterNav/index.tsx b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterNav/index.tsx
index d8d45b1dc..461d81d8e 100644
--- a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterNav/index.tsx
+++ b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/HostFilterNav/index.tsx
@@ -3,6 +3,7 @@ import { Page } from '../../../../../CmdRoot/types'
import { hostsFilterUsablePage } from '../HostFilterCmdGroups/Usable'
import { hostsFilterBlockedPage } from '../HostFilterCmdGroups/Blocked'
import { hostsFilterActiveContractsPage } from '../HostFilterCmdGroups/ActiveContracts'
+import { PublicKeyCmdNav } from '../HostFilterCmdGroups/PublicKey'
import { HostFilter } from '../../../types'
export const commandPage = {
@@ -14,10 +15,15 @@ type Props = {
currentPage: Page
parentPage?: Page
pushPage: (page: Page) => void
- select: (filter: HostFilter) => void
+ select: (filter?: HostFilter) => void
}
-export function HostFilterNav({ currentPage, parentPage, pushPage }: Props) {
+export function HostFilterNav({
+ currentPage,
+ parentPage,
+ pushPage,
+ select,
+}: Props) {
return (
<>
{hostsFilterActiveContractsPage.label}
+
>
)
}
diff --git a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/index.tsx b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/index.tsx
index fb90eaa52..b79d1f7cd 100644
--- a/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/index.tsx
+++ b/apps/indexd/components/Data/Hosts/HostsCmd/HostsFilterCmd/index.tsx
@@ -21,11 +21,13 @@ export function HostsFilterCmd({
const { addColumnFilter } = useHostsParams()
const select = useCallback(
- (filter: HostFilter) => {
+ (filter?: HostFilter) => {
if (beforeSelect) {
beforeSelect()
}
- addColumnFilter(filter)
+ if (filter) {
+ addColumnFilter(filter)
+ }
if (afterSelect) {
afterSelect()
}
diff --git a/apps/indexd/components/Data/Hosts/types.ts b/apps/indexd/components/Data/Hosts/types.ts
index ff955622e..0d9e94399 100644
--- a/apps/indexd/components/Data/Hosts/types.ts
+++ b/apps/indexd/components/Data/Hosts/types.ts
@@ -1,4 +1,5 @@
import { AdminHostsSortBy, Host } from '@siafoundation/indexd-types'
+import { truncate } from '@siafoundation/design-system'
import { CurrencyOption } from '@siafoundation/react-core'
import BigNumber from 'bignumber.js'
import {
@@ -21,10 +22,16 @@ export type HostFilterActiveContracts = {
value: boolean
}
+export type HostFilterPublicKey = {
+ id: 'hostkey'
+ value: string
+}
+
export type HostFilter =
| HostFilterUsable
| HostFilterBlocked
| HostFilterActiveContracts
+ | HostFilterPublicKey
export type HostFilters = HostFilter[]
export type HostSorts = DataTableSortColumn[]
@@ -37,6 +44,8 @@ export function getFilterLabel(filter: HostFilter): string {
return filter.value ? 'Blocked' : 'Not Blocked'
case 'activecontracts':
return filter.value ? 'Active Contracts' : 'No Active Contracts'
+ case 'hostkey':
+ return `Public key is ${truncate(filter.value, 20)}`
default:
return ''
}
diff --git a/apps/indexd/components/Data/Hosts/useHosts.tsx b/apps/indexd/components/Data/Hosts/useHosts.tsx
index 8efe3521c..50131e644 100644
--- a/apps/indexd/components/Data/Hosts/useHosts.tsx
+++ b/apps/indexd/components/Data/Hosts/useHosts.tsx
@@ -27,6 +27,10 @@ export function useHosts() {
if (activecontracts !== undefined) {
filters.activecontracts = activecontracts
}
+ const hostkey = columnFilters.find((f) => f.id === 'hostkey')?.value
+ if (hostkey !== undefined) {
+ filters.hostkey = [hostkey]
+ }
// Map all active sorts to API sortby and desc arrays.
if (columnSorts.length > 0) {
const sortby: AdminHostsSortBy[] = []
diff --git a/apps/indexd/contexts/dialog.tsx b/apps/indexd/contexts/dialog.tsx
index 83540bb77..d953a3e06 100644
--- a/apps/indexd/contexts/dialog.tsx
+++ b/apps/indexd/contexts/dialog.tsx
@@ -25,6 +25,8 @@ import { AccountFilterConnectKeyDialog } from '../dialogs/AccountFilterConnectKe
import { QuotaCreateDialog } from '../dialogs/QuotaCreateDialog'
import { QuotaDeleteDialog } from '../dialogs/QuotaDeleteDialog'
import { KeyQuotaReassignDialog } from '../dialogs/KeyQuotaReassignDialog'
+import { HostsFilterPublicKeyDialog } from '../dialogs/HostsFilterPublicKeyDialog'
+import { ContractsFilterPublicKeyDialog } from '../dialogs/ContractsFilterPublicKeyDialog'
export type DialogType =
| 'cmdk'
@@ -43,6 +45,8 @@ export type DialogType =
| 'quotaDelete'
| 'keyQuotaReassign'
| 'hostBlocklistAdd'
+ | 'hostsFilterPublicKey'
+ | 'contractsFilterPublicKey'
type DialogData = {
hostBlocklistAdd?: {
@@ -231,6 +235,14 @@ export function Dialogs() {
open={dialog === 'accountFilterConnectKey'}
onOpenChange={onOpenChange}
/>
+
+
>
)
}
diff --git a/apps/indexd/dialogs/ContractsFilterPublicKeyDialog.tsx b/apps/indexd/dialogs/ContractsFilterPublicKeyDialog.tsx
new file mode 100644
index 000000000..dd3d4873f
--- /dev/null
+++ b/apps/indexd/dialogs/ContractsFilterPublicKeyDialog.tsx
@@ -0,0 +1,84 @@
+import {
+ Dialog,
+ useOnInvalid,
+ FormSubmitButton,
+ FieldText,
+ ConfigFields,
+ useDialogFormHelpers,
+} from '@siafoundation/design-system'
+import { useCallback } from 'react'
+import { useForm } from 'react-hook-form'
+import { useContractsParams } from '../components/Data/Contracts/useContractsParams'
+
+type Props = {
+ trigger?: React.ReactNode
+ open: boolean
+ onOpenChange: (val: boolean) => void
+}
+
+const defaultValues = {
+ publicKey: '',
+}
+
+type Values = typeof defaultValues
+
+const fields: ConfigFields = {
+ publicKey: {
+ type: 'text',
+ title: 'Public key',
+ placeholder: 'ed25519:b050c0c6...',
+ validation: {
+ required: 'required',
+ },
+ },
+}
+
+export function ContractsFilterPublicKeyDialog({
+ trigger,
+ open,
+ onOpenChange,
+}: Props) {
+ const { addColumnFilter } = useContractsParams()
+
+ const form = useForm({
+ mode: 'all',
+ defaultValues,
+ })
+
+ const { closeAndReset, handleOpenChange } = useDialogFormHelpers({
+ form,
+ onOpenChange,
+ defaultValues,
+ })
+
+ const onSubmit = useCallback(
+ async (values: Values) => {
+ addColumnFilter({
+ id: 'hostkey',
+ value: values.publicKey,
+ })
+ closeAndReset()
+ },
+ [addColumnFilter, closeAndReset],
+ )
+
+ const onInvalid = useOnInvalid(fields)
+
+ return (
+
+ )
+}
diff --git a/apps/indexd/dialogs/HostsFilterPublicKeyDialog.tsx b/apps/indexd/dialogs/HostsFilterPublicKeyDialog.tsx
new file mode 100644
index 000000000..b5db585a5
--- /dev/null
+++ b/apps/indexd/dialogs/HostsFilterPublicKeyDialog.tsx
@@ -0,0 +1,84 @@
+import {
+ Dialog,
+ useOnInvalid,
+ FormSubmitButton,
+ FieldText,
+ ConfigFields,
+ useDialogFormHelpers,
+} from '@siafoundation/design-system'
+import { useCallback } from 'react'
+import { useForm } from 'react-hook-form'
+import { useHostsParams } from '../components/Data/Hosts/useHostsParams'
+
+type Props = {
+ trigger?: React.ReactNode
+ open: boolean
+ onOpenChange: (val: boolean) => void
+}
+
+const defaultValues = {
+ publicKey: '',
+}
+
+type Values = typeof defaultValues
+
+const fields: ConfigFields = {
+ publicKey: {
+ type: 'text',
+ title: 'Public key',
+ placeholder: 'ed25519:b050c0c6...',
+ validation: {
+ required: 'required',
+ },
+ },
+}
+
+export function HostsFilterPublicKeyDialog({
+ trigger,
+ open,
+ onOpenChange,
+}: Props) {
+ const { addColumnFilter } = useHostsParams()
+
+ const form = useForm({
+ mode: 'all',
+ defaultValues,
+ })
+
+ const { closeAndReset, handleOpenChange } = useDialogFormHelpers({
+ form,
+ onOpenChange,
+ defaultValues,
+ })
+
+ const onSubmit = useCallback(
+ async (values: Values) => {
+ addColumnFilter({
+ id: 'hostkey',
+ value: values.publicKey,
+ })
+ closeAndReset()
+ },
+ [addColumnFilter, closeAndReset],
+ )
+
+ const onInvalid = useOnInvalid(fields)
+
+ return (
+
+ )
+}