Skip to content

Commit

Permalink
feat(renterd): hosts multi-select and batch manage blocklist and allo…
Browse files Browse the repository at this point in the history
…wlist
  • Loading branch information
alexfreska committed Nov 27, 2024
1 parent 3801cd1 commit c9858f5
Show file tree
Hide file tree
Showing 34 changed files with 645 additions and 273 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-numbers-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

The hosts multi-select menu now supports bulk adding and removing to both the allowlist and blocklists.
5 changes: 5 additions & 0 deletions .changeset/pink-buses-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

- The hosts table now supports multi-select.
5 changes: 5 additions & 0 deletions .changeset/sour-eagles-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

The host map must now be explicitly toggled open with the action button in the navbar.
17 changes: 10 additions & 7 deletions apps/renterd-e2e/src/fixtures/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ export const getContractRowByIndex = step(
}
)

export const getContractRows = step('get contract rows', async (page: Page) => {
return page
.getByTestId('contractsTable')
.locator('tbody')
.getByRole('row')
.all()
})
export function getContractRows(page: Page) {
return page.getByTestId('contractsTable').locator('tbody').getByRole('row')
}

export const getContractRowsAll = step(
'get contract rows',
async (page: Page) => {
return getContractRows(page).all()
}
)
8 changes: 8 additions & 0 deletions apps/renterd-e2e/src/fixtures/hosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export const openRowHostContextMenu = step(
}
)

export function getHostRows(page: Page) {
return page.getByTestId('hostsTable').locator('tbody').getByRole('row')
}

export const getHostRowsAll = step('get host rows', async (page: Page) => {
return getHostRows(page).all()
})

export const openManageListsDialog = step(
'open manage lists dialog',
async (page: Page) => {
Expand Down
10 changes: 5 additions & 5 deletions apps/renterd-e2e/src/specs/contracts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { navigateToContracts } from '../fixtures/navigate'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import {
getContractRowByIndex,
getContractRows,
getContractRowsAll,
getContractsSummaryRow,
} from '../fixtures/contracts'
import { openManageListsDialog } from '../fixtures/hosts'
Expand Down Expand Up @@ -56,7 +56,7 @@ test('contracts prunable size', async ({ page }) => {
await expect(summarySize).toBeVisible()

// Check that the prunable size is visible for all contracts.
const rows = await getContractRows(page)
const rows = await getContractRowsAll(page)
for (const row of rows) {
const prunableSize = row.getByLabel('prunable size')
await expect(prunableSize).toBeVisible()
Expand All @@ -65,7 +65,7 @@ test('contracts prunable size', async ({ page }) => {

test('contracts bulk delete', async ({ page }) => {
await navigateToContracts({ page })
const rows = await getContractRows(page)
const rows = await getContractRowsAll(page)
for (const row of rows) {
await row.click()
}
Expand All @@ -83,7 +83,7 @@ test('contracts bulk delete', async ({ page }) => {

test('contracts bulk allowlist', async ({ page }) => {
await navigateToContracts({ page })
const rows = await getContractRows(page)
const rows = await getContractRowsAll(page)
for (const row of rows) {
await row.click()
}
Expand Down Expand Up @@ -119,7 +119,7 @@ test('contracts bulk allowlist', async ({ page }) => {

test('contracts bulk blocklist', async ({ page }) => {
await navigateToContracts({ page })
const rows = await getContractRows(page)
const rows = await getContractRowsAll(page)
for (const row of rows) {
await row.click()
}
Expand Down
105 changes: 104 additions & 1 deletion apps/renterd-e2e/src/specs/hosts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { test, expect } from '@playwright/test'
import { navigateToHosts } from '../fixtures/navigate'
import { afterTest, beforeTest } from '../fixtures/beforeTest'
import { getHostRowByIndex } from '../fixtures/hosts'
import {
getHostRowByIndex,
getHostRows,
getHostRowsAll,
openManageListsDialog,
} from '../fixtures/hosts'

test.beforeEach(async ({ page }) => {
await beforeTest(page, {
Expand All @@ -23,3 +28,101 @@ test('hosts explorer shows all hosts', async ({ page }) => {
await expect(row2).toBeVisible()
await expect(row3).toBeVisible()
})

test('hosts bulk allowlist', async ({ page }) => {
await navigateToHosts({ page })
const rows = await getHostRowsAll(page)
for (const row of rows) {
await row.click()
}

const menu = page.getByLabel('host multi-select menu')
const dialog = page.getByRole('dialog')

// Add selected hosts to the allowlist.
await menu.getByLabel('add host public keys to allowlist').click()
await dialog.getByRole('button', { name: 'Add to allowlist' }).click()

await openManageListsDialog(page)
await expect(dialog.getByText('The blocklist is empty')).toBeVisible()
await dialog.getByLabel('view allowlist').click()
await expect(
dialog.getByTestId('allowlistPublicKeys').getByTestId('item')
).toHaveCount(3)
await dialog.getByLabel('close').click()
await expect(
getHostRows(page).getByTestId('allow').getByTestId('blocked')
).toHaveCount(0)
await expect(
getHostRows(page).getByTestId('allow').getByTestId('allowed')
).toHaveCount(3)

for (const row of rows) {
await row.click()
}

// Remove selected hosts from the allowlist.
await menu.getByLabel('remove host public keys from allowlist').click()
await dialog.getByRole('button', { name: 'Remove from allowlist' }).click()

await openManageListsDialog(page)
await expect(dialog.getByText('The blocklist is empty')).toBeVisible()
await dialog.getByLabel('view allowlist').click()
await expect(dialog.getByText('The allowlist is empty')).toBeVisible()
await dialog.getByLabel('close').click()
await expect(
getHostRows(page).getByTestId('allow').getByTestId('blocked')
).toHaveCount(0)
await expect(
getHostRows(page).getByTestId('allow').getByTestId('allowed')
).toHaveCount(3)
})

test('hosts bulk blocklist', async ({ page }) => {
await navigateToHosts({ page })
const rows = await getHostRowsAll(page)
for (const row of rows) {
await row.click()
}

const menu = page.getByLabel('host multi-select menu')
const dialog = page.getByRole('dialog')

// Add selected hosts to the allowlist.
await menu.getByLabel('add host addresses to blocklist').click()
await dialog.getByRole('button', { name: 'Add to blocklist' }).click()

await openManageListsDialog(page)
await expect(
dialog.getByTestId('blocklistAddresses').getByTestId('item')
).toHaveCount(3)
await dialog.getByLabel('view allowlist').click()
await expect(dialog.getByText('The allowlist is empty')).toBeVisible()
await dialog.getByLabel('close').click()
await expect(
getHostRows(page).getByTestId('allow').getByTestId('blocked')
).toHaveCount(3)
await expect(
getHostRows(page).getByTestId('allow').getByTestId('allowed')
).toHaveCount(0)

for (const row of rows) {
await row.click()
}

// Remove selected hosts from the blocklist.
await menu.getByLabel('remove host addresses from blocklist').click()
await dialog.getByRole('button', { name: 'Remove from blocklist' }).click()

await openManageListsDialog(page)
await expect(dialog.getByText('The blocklist is empty')).toBeVisible()
await dialog.getByLabel('view allowlist').click()
await expect(dialog.getByText('The allowlist is empty')).toBeVisible()
await dialog.getByLabel('close').click()
await expect(
getHostRows(page).getByTestId('allow').getByTestId('blocked')
).toHaveCount(0)
await expect(
getHostRows(page).getByTestId('allow').getByTestId('allowed')
).toHaveCount(3)
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { Button, Paragraph } from '@siafoundation/design-system'
import { ListChecked16 } from '@siafoundation/react-icons'
import { useCallback, useMemo } from 'react'
import { useDialog } from '../../../contexts/dialog'
import { useMemo } from 'react'
import { useContracts } from '../../../contexts/contracts'
import { pluralize } from '@siafoundation/units'
import { useAllowlistUpdate } from '../../../hooks/useAllowlistUpdate'
import { BulkAddAllowlist } from '../../bulkActions/BulkAddAllowlist'

export function ContractsAddAllowlist() {
const { multiSelect } = useContracts()
Expand All @@ -14,41 +10,6 @@ export function ContractsAddAllowlist() {
Object.entries(multiSelect.selectionMap).map(([_, item]) => item.hostKey),
[multiSelect.selectionMap]
)
const { openConfirmDialog } = useDialog()
const allowlistUpdate = useAllowlistUpdate()

const add = useCallback(async () => {
allowlistUpdate(publicKeys, [])
multiSelect.deselectAll()
}, [allowlistUpdate, multiSelect, publicKeys])

return (
<Button
aria-label="add host public keys to allowlist"
tip="Add host public keys to allowlist"
onClick={() => {
openConfirmDialog({
title: `Add ${pluralize(
multiSelect.selectionCount,
'host'
)} to allowlist`,
action: 'Add to allowlist',
variant: 'accent',
body: (
<div className="flex flex-col gap-1">
<Paragraph size="14">
Are you sure you would like to add{' '}
{pluralize(multiSelect.selectionCount, 'host public key')} to
the allowlist?
</Paragraph>
</div>
),
onConfirm: add,
})
}}
>
<ListChecked16 />
Add to allowlist
</Button>
)
return <BulkAddAllowlist multiSelect={multiSelect} publicKeys={publicKeys} />
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { Button, Paragraph } from '@siafoundation/design-system'
import { ListChecked16 } from '@siafoundation/react-icons'
import { useCallback, useMemo } from 'react'
import { useDialog } from '../../../contexts/dialog'
import { useMemo } from 'react'
import { useContracts } from '../../../contexts/contracts'
import { pluralize } from '@siafoundation/units'
import { useBlocklistUpdate } from '../../../hooks/useBlocklistUpdate'
import { BulkAddBlocklist } from '../../bulkActions/BulkAddBlocklist'

export function ContractsAddBlocklist() {
const { multiSelect } = useContracts()
Expand All @@ -14,45 +10,7 @@ export function ContractsAddBlocklist() {
Object.entries(multiSelect.selectionMap).map(([_, item]) => item.hostIp),
[multiSelect.selectionMap]
)
const { openConfirmDialog } = useDialog()
const blocklistUpdate = useBlocklistUpdate()

const add = useCallback(async () => {
blocklistUpdate(hostAddresses, [])
multiSelect.deselectAll()
}, [blocklistUpdate, multiSelect, hostAddresses])

return (
<Button
aria-label="add host addresses to blocklist"
tip="Add host addresses to blocklist"
onClick={() => {
openConfirmDialog({
title: `Add ${pluralize(
multiSelect.selectionCount,
'host'
)} to blocklist`,
action: 'Add to blocklist',
variant: 'red',
body: (
<div className="flex flex-col gap-1">
<Paragraph size="14">
Are you sure you would like to add{' '}
{pluralize(
multiSelect.selectionCount,
'host address',
'host addresses'
)}{' '}
to the blocklist?
</Paragraph>
</div>
),
onConfirm: add,
})
}}
>
<ListChecked16 />
Add to blocklist
</Button>
<BulkAddBlocklist multiSelect={multiSelect} hostAddresses={hostAddresses} />
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { Button, Paragraph } from '@siafoundation/design-system'
import { ListChecked16 } from '@siafoundation/react-icons'
import { useCallback, useMemo } from 'react'
import { useDialog } from '../../../contexts/dialog'
import { useMemo } from 'react'
import { useContracts } from '../../../contexts/contracts'
import { pluralize } from '@siafoundation/units'
import { useAllowlistUpdate } from '../../../hooks/useAllowlistUpdate'
import { BulkRemoveAllowlist } from '../../bulkActions/BulkRemoveAllowlist'

export function ContractsRemoveAllowlist() {
const { multiSelect } = useContracts()
Expand All @@ -14,41 +10,8 @@ export function ContractsRemoveAllowlist() {
Object.entries(multiSelect.selectionMap).map(([_, item]) => item.hostKey),
[multiSelect.selectionMap]
)
const { openConfirmDialog } = useDialog()
const allowlistUpdate = useAllowlistUpdate()

const remove = useCallback(async () => {
await allowlistUpdate([], publicKeys)
multiSelect.deselectAll()
}, [allowlistUpdate, multiSelect, publicKeys])

return (
<Button
aria-label="remove host public keys from allowlist"
tip="Remove host public keys from allowlist"
onClick={() => {
openConfirmDialog({
title: `Remove ${pluralize(
multiSelect.selectionCount,
'host'
)} from allowlist`,
action: 'Remove from allowlist',
variant: 'accent',
body: (
<div className="flex flex-col gap-1">
<Paragraph size="14">
Are you sure you would like to remove{' '}
{pluralize(multiSelect.selectionCount, 'host public key')} from
the allowlist?
</Paragraph>
</div>
),
onConfirm: remove,
})
}}
>
<ListChecked16 />
Remove from allowlist
</Button>
<BulkRemoveAllowlist multiSelect={multiSelect} publicKeys={publicKeys} />
)
}
Loading

0 comments on commit c9858f5

Please sign in to comment.