Skip to content

Commit

Permalink
feat: renterd bucket policy and read access
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Nov 13, 2023
1 parent 0b3226a commit 69a696c
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-cats-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'renterd': minor
---

The bucket context menu now allows you to edit the bucket policy and toggle the read access between public and private.
12 changes: 11 additions & 1 deletion apps/renterd/components/Files/BucketContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
DropdownMenuLeftSlot,
DropdownMenuLabel,
} from '@siafoundation/design-system'
import { Delete16, BucketIcon } from '@siafoundation/react-icons'
import { Delete16, BucketIcon, Rule16 } from '@siafoundation/react-icons'
import { useDialog } from '../../contexts/dialog'

type Props = {
Expand All @@ -24,6 +24,16 @@ export function BucketContextMenu({ name }: Props) {
contentProps={{ align: 'start' }}
>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onSelect={() => {
openDialog('filesBucketPolicy', name)
}}
>
<DropdownMenuLeftSlot>
<Rule16 />
</DropdownMenuLeftSlot>
Change policy
</DropdownMenuItem>
<DropdownMenuItem
disabled={name === 'default'}
onSelect={() => {
Expand Down
133 changes: 133 additions & 0 deletions apps/renterd/components/Files/FilesBucketPolicyDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
Paragraph,
Dialog,
triggerErrorToast,
triggerSuccessToast,
ConfigFields,
useOnInvalid,
FormSubmitButton,
FieldSelect,
} from '@siafoundation/design-system'
import { useCallback, useEffect, useMemo } from 'react'
import { useForm } from 'react-hook-form'
import { useDialog } from '../../contexts/dialog'
import { useBucket, useBucketPolicyUpdate } from '@siafoundation/react-renterd'

const defaultValues = {
visibility: 'public',
}

function getFields(name: string): ConfigFields<typeof defaultValues, never> {
return {
visibility: {
type: 'text',
title: 'Read Access',
placeholder: name,
validation: {
required: 'required',
},
options: [
{
label: 'Public',
value: 'public',
},
{
label: 'Private',
value: 'private',
},
],
},
}
}

type Props = {
trigger?: React.ReactNode
open: boolean
onOpenChange: (val: boolean) => void
}

export function FilesBucketPolicyDialog({
trigger,
open,
onOpenChange,
}: Props) {
const { id: name, closeDialog } = useDialog()
const bucket = useBucket({
disabled: !open,
params: {
name: name,
},
config: {
swr: {
revalidateOnFocus: false,
},
},
})

const policyUpdate = useBucketPolicyUpdate()
const form = useForm({
mode: 'all',
defaultValues,
})

useEffect(() => {
form.reset({
visibility: bucket.data?.policy?.publicReadAccess ? 'public' : 'private',
})
}, [form, bucket.data])

const onSubmit = useCallback(
async (values: typeof defaultValues) => {
const response = await policyUpdate.put({
params: {
name,
},
payload: {
policy: {
publicReadAccess: values.visibility === 'public',
},
},
})
if (response.error) {
triggerErrorToast(response.error)
} else {
triggerSuccessToast('Bucket policy has been updated.')
form.reset()
closeDialog()
}
},
[form, name, policyUpdate, closeDialog]
)

const fields = useMemo(() => getFields(name), [name])

const onInvalid = useOnInvalid(fields)

return (
<Dialog
title={`Change Policy: ${name}`}
trigger={trigger}
open={open}
onOpenChange={(val) => {
if (!val) {
form.reset(defaultValues)
}
onOpenChange(val)
}}
contentVariants={{
className: 'w-[400px]',
}}
onSubmit={form.handleSubmit(onSubmit, onInvalid)}
>
<div className="flex flex-col gap-4">
<Paragraph size="14">
{`Update the bucket's policy to set read access to either private or public. Files in public read access buckets can be accessed without authentication via the S3 API.`}
</Paragraph>
<FieldSelect name="visibility" form={form} fields={fields} />
<FormSubmitButton variant="accent" form={form}>
Update policy
</FormSubmitButton>
</div>
</Dialog>
)
}
6 changes: 6 additions & 0 deletions apps/renterd/contexts/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RenterdTransactionDetailsDialog } from '../dialogs/RenterdTransactionDe
import { AlertsDialog } from '../dialogs/AlertsDialog'
import { HostsFilterPublicKeyDialog } from '../components/Hosts/HostsFilterPublicKeyDialog'
import { FilesBucketDeleteDialog } from '../components/Files/FilesBucketDeleteDialog'
import { FilesBucketPolicyDialog } from '../components/Files/FilesBucketPolicyDialog'
import { FilesBucketCreateDialog } from '../components/Files/FilesBucketCreateDialog'

export type DialogType =
Expand All @@ -38,6 +39,7 @@ export type DialogType =
| 'filesCreateBucket'
| 'filesDeleteBucket'
| 'filesCreateDirectory'
| 'filesBucketPolicy'
| 'filesSearch'
| 'alerts'
| 'confirm'
Expand Down Expand Up @@ -161,6 +163,10 @@ export function Dialogs() {
open={dialog === 'filesDeleteBucket'}
onOpenChange={(val) => (val ? openDialog(dialog) : closeDialog())}
/>
<FilesBucketPolicyDialog
open={dialog === 'filesBucketPolicy'}
onOpenChange={(val) => (val ? openDialog(dialog) : closeDialog())}
/>
<FilesCreateDirectoryDialog
open={dialog === 'filesCreateDirectory'}
onOpenChange={(val) => (val ? openDialog(dialog) : closeDialog())}
Expand Down
41 changes: 40 additions & 1 deletion apps/renterd/contexts/files/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import {
Badge,
Button,
LoadingDots,
TableColumn,
Text,
Tooltip,
ValueNum,
} from '@siafoundation/design-system'
import { Document16, FolderIcon } from '@siafoundation/react-icons'
import {
Document16,
Earth16,
FolderIcon,
Globe16,
Locked16,
} from '@siafoundation/react-icons'
import { humanBytes } from '@siafoundation/sia-js'
import { FileContextMenu } from '../../components/Files/FileContextMenu'
import { DirectoryContextMenu } from '../../components/Files/DirectoryContextMenu'
Expand Down Expand Up @@ -130,6 +138,37 @@ export const columns: FilesTableColumn[] = [
)
},
},
{
id: 'readAccess',
label: 'read access',
contentClassName: 'justify-center',
render: function ReadAccessColumn({ data }) {
if (data.name === '..') {
return null
}
const isPublic = data.bucket?.policy?.publicReadAccess
return (
<Tooltip
content={
isPublic
? 'The bucket policy allows public read access.'
: 'The bucket policy only allows private read access.'
}
>
<div>
<Button variant="ghost" state="waiting">
<Text
color={isPublic ? 'contrast' : 'verySubtle'}
className="flex gap-0.5 items-center"
>
{isPublic ? <Earth16 /> : <Locked16 />}
</Text>
</Button>
</div>
</Tooltip>
)
},
},
{
id: 'size',
label: 'size',
Expand Down
21 changes: 11 additions & 10 deletions apps/renterd/contexts/files/dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ export function useDataset({ activeDirectoryPath, uploadsList }: Props) {
const router = useRouter()
const limit = Number(router.query.limit || defaultLimit)
const offset = Number(router.query.offset || 0)
const bucket = getBucketFromPath(activeDirectoryPath)
const activeBucketName = getBucketFromPath(activeDirectoryPath)
const activeBucket = buckets.data?.find((b) => b.name === activeBucketName)
const response = useObjectDirectory({
disabled: !bucket,
disabled: !activeBucketName,
params: {
...bucketAndKeyParamsFromPath(activeDirectoryPath),
offset,
Expand All @@ -53,32 +54,32 @@ export function useDataset({ activeDirectoryPath, uploadsList }: Props) {
uploadsList,
allContracts,
buckets.data,
bucket,
activeBucketName,
activeDirectoryPath,
],
() => {
const dataMap: Record<string, ObjectData> = {}
if (!bucket) {
buckets.data?.forEach(({ name }) => {
const bucket = name
const path = getDirPath(bucket, '')
if (!activeBucket) {
buckets.data?.forEach((bucket) => {
const name = bucket.name
const path = getDirPath(name, '')
dataMap[name] = {
id: path,
path,
bucket,
size: 0,
health: 0,
name: name,
name,
type: 'bucket',
}
})
} else if (response.data) {
response.data.entries?.forEach(({ name: key, size, health }) => {
const path = bucketAndResponseKeyToFilePath(bucket, key)
const path = bucketAndResponseKeyToFilePath(activeBucketName, key)
dataMap[path] = {
id: path,
path,
bucket,
bucket: activeBucket,
size,
health,
name: getFilename(key),
Expand Down
12 changes: 10 additions & 2 deletions apps/renterd/contexts/files/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Bucket } from '@siafoundation/react-renterd'
import { FullPath } from './paths'

export type ObjectType = 'bucket' | 'directory' | 'file'
Expand All @@ -7,7 +8,7 @@ export type ObjectData = {
// path is exacty bucket + returned key
// eg: default + /path/to/file.txt = default/path/to/file.txt
path: FullPath
bucket: string
bucket: Bucket
name: string
health?: number
size: number
Expand All @@ -16,11 +17,18 @@ export type ObjectData = {
loaded?: number
}

export type TableColumnId = 'actions' | 'type' | 'name' | 'size' | 'health'
export type TableColumnId =
| 'actions'
| 'type'
| 'name'
| 'readAccess'
| 'size'
| 'health'

export const columnsDefaultVisible: TableColumnId[] = [
'type',
'name',
'readAccess',
'size',
'health',
]
Expand Down

0 comments on commit 69a696c

Please sign in to comment.