Compare commits

..

No commits in common. "5a90dd8377ab99624f99fa263fc4bc811c30b787" and "4861e1bbb1c5879629c2af09298c4002d4007417" have entirely different histories.

9 changed files with 17 additions and 86 deletions

View File

@ -55,7 +55,7 @@ services:
Get the latest binary from the [release page](https://github.com/khairul169/garage-webui/releases/latest) according to your OS architecture. For example: Get the latest binary from the [release page](https://github.com/khairul169/garage-webui/releases/latest) according to your OS architecture. For example:
```sh ```sh
$ wget -O garage-webui https://github.com/khairul169/garage-webui/releases/download/1.0.3/garage-webui-v1.0.3-linux-amd64 $ wget -O garage-webui https://github.com/khairul169/garage-webui/releases/download/1.0.2/garage-webui-v1.0.2-linux-amd64
$ chmod +x garage-webui $ chmod +x garage-webui
$ sudo cp garage-webui /usr/local/bin $ sudo cp garage-webui /usr/local/bin
``` ```

View File

@ -16,7 +16,6 @@ import (
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go" "github.com/aws/smithy-go"
) )
@ -184,8 +183,6 @@ func (b *Browse) PutObject(w http.ResponseWriter, r *http.Request) {
func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) { func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) {
bucket := r.PathValue("bucket") bucket := r.PathValue("bucket")
key := r.PathValue("key") key := r.PathValue("key")
recursive := r.URL.Query().Get("recursive") == "true"
isDirectory := strings.HasSuffix(key, "/")
client, err := getS3Client(bucket) client, err := getS3Client(bucket)
if err != nil { if err != nil {
@ -193,52 +190,7 @@ func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) {
return return
} }
// Delete directory and its content _, err = client.DeleteObject(context.Background(), &s3.DeleteObjectInput{
if isDirectory && recursive {
objects, err := client.ListObjectsV2(context.Background(), &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(key),
})
if err != nil {
utils.ResponseError(w, err)
return
}
if len(objects.Contents) == 0 {
utils.ResponseSuccess(w, true)
return
}
keys := make([]types.ObjectIdentifier, 0, len(objects.Contents))
for _, object := range objects.Contents {
keys = append(keys, types.ObjectIdentifier{
Key: object.Key,
})
}
res, err := client.DeleteObjects(context.Background(), &s3.DeleteObjectsInput{
Bucket: aws.String(bucket),
Delete: &types.Delete{Objects: keys},
})
if err != nil {
utils.ResponseError(w, fmt.Errorf("cannot delete object: %w", err))
return
}
if len(res.Errors) > 0 {
utils.ResponseError(w, fmt.Errorf("cannot delete object: %v", res.Errors[0]))
return
}
utils.ResponseSuccess(w, res)
return
}
// Delete single object
res, err := client.DeleteObject(context.Background(), &s3.DeleteObjectInput{
Bucket: aws.String(bucket), Bucket: aws.String(bucket),
Key: aws.String(key), Key: aws.String(key),
}) })
@ -248,7 +200,7 @@ func (b *Browse) DeleteObject(w http.ResponseWriter, r *http.Request) {
return return
} }
utils.ResponseSuccess(w, res) utils.ResponseSuccess(w, nil)
} }
func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) { func getBucketCredentials(bucket string) (aws.CredentialsProvider, error) {

View File

@ -1,7 +1,7 @@
{ {
"name": "garage-webui", "name": "garage-webui",
"private": true, "private": true,
"version": "1.0.3", "version": "1.0.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:client": "vite", "dev:client": "vite",

View File

@ -21,7 +21,7 @@ const MainLayout = () => {
<Drawer <Drawer
open={sidebar.isOpen} open={sidebar.isOpen}
onClickOverlay={sidebar.onClose} onClickOverlay={sidebar.onClose}
className="md:drawer-open h-screen max-h-dvh" className="md:drawer-open h-screen"
side={<Sidebar />} side={<Sidebar />}
contentClassName="flex flex-col overflow-hidden" contentClassName="flex flex-col overflow-hidden"
> >

View File

@ -40,13 +40,10 @@ export const usePutObject = (
export const useDeleteObject = ( export const useDeleteObject = (
bucket: string, bucket: string,
options?: UseMutationOptions<any, Error, { key: string; recursive?: boolean }> options?: UseMutationOptions<any, Error, string>
) => { ) => {
return useMutation({ return useMutation({
mutationFn: (data) => mutationFn: (key) => api.delete(`/browse/${bucket}/${key}`),
api.delete(`/browse/${bucket}/${data.key}`, {
params: { recursive: data.recursive },
}),
...options, ...options,
}); });
}; };

View File

@ -13,10 +13,9 @@ import { shareDialog } from "./share-dialog";
type Props = { type Props = {
prefix?: string; prefix?: string;
object: Pick<Object, "objectKey" | "downloadUrl">; object: Pick<Object, "objectKey" | "downloadUrl">;
end?: boolean;
}; };
const ObjectActions = ({ prefix = "", object, end }: Props) => { const ObjectActions = ({ prefix = "", object }: Props) => {
const { bucketName } = useBucketContext(); const { bucketName } = useBucketContext();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isDirectory = object.objectKey.endsWith("/"); const isDirectory = object.objectKey.endsWith("/");
@ -34,17 +33,8 @@ const ObjectActions = ({ prefix = "", object, end }: Props) => {
}; };
const onDelete = () => { const onDelete = () => {
if ( if (window.confirm("Are you sure you want to delete this object?")) {
window.confirm( deleteObject.mutate(prefix + object.objectKey);
`Are you sure you want to delete this ${
isDirectory ? "directory and its content" : "object"
}?`
)
) {
deleteObject.mutate({
key: prefix + object.objectKey,
recursive: isDirectory,
});
} }
}; };
@ -55,12 +45,12 @@ const ObjectActions = ({ prefix = "", object, end }: Props) => {
<Button icon={DownloadIcon} color="ghost" onClick={onDownload} /> <Button icon={DownloadIcon} color="ghost" onClick={onDownload} />
)} )}
<Dropdown end vertical={end ? "top" : "bottom"}> <Dropdown end>
<Dropdown.Toggle button={false}> <Dropdown.Toggle button={false}>
<Button icon={EllipsisVertical} color="ghost" /> <Button icon={EllipsisVertical} color="ghost" />
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="gap-y-1"> <Dropdown.Menu className="bg-base-300 gap-y-1">
<Dropdown.Item <Dropdown.Item
onClick={() => onClick={() =>
shareDialog.open({ key: object.objectKey, prefix }) shareDialog.open({ key: object.objectKey, prefix })

View File

@ -23,7 +23,7 @@ const ObjectList = ({ prefix, onPrefixChange }: Props) => {
}; };
return ( return (
<div className="overflow-x-auto overflow-y-hidden"> <div className="overflow-x-auto pb-32">
<Table> <Table>
<Table.Head> <Table.Head>
<span>Name</span> <span>Name</span>
@ -63,7 +63,7 @@ const ObjectList = ({ prefix, onPrefixChange }: Props) => {
</tr> </tr>
))} ))}
{data?.objects.map((object, idx) => { {data?.objects.map((object) => {
const extIdx = object.objectKey.lastIndexOf("."); const extIdx = object.objectKey.lastIndexOf(".");
const filename = const filename =
extIdx >= 0 extIdx >= 0
@ -93,11 +93,7 @@ const ObjectList = ({ prefix, onPrefixChange }: Props) => {
<td className="whitespace-nowrap"> <td className="whitespace-nowrap">
{dayjs(object.lastModified).fromNow()} {dayjs(object.lastModified).fromNow()}
</td> </td>
<ObjectActions <ObjectActions prefix={data.prefix} object={object} />
prefix={data.prefix}
object={object}
end={idx >= data.objects.length - 2}
/>
</tr> </tr>
); );
})} })}

View File

@ -184,7 +184,7 @@ const NodesList = ({ nodes }: NodeListProps) => {
</Alert> </Alert>
) : null} ) : null}
<div className="w-full overflow-x-auto overflow-y-hidden min-h-[400px]"> <div className="w-full overflow-x-auto min-h-[400px] pb-16">
<Table size="sm" className="min-w-[800px]"> <Table size="sm" className="min-w-[800px]">
<Table.Head> <Table.Head>
<span>#</span> <span>#</span>
@ -266,10 +266,7 @@ const NodesList = ({ nodes }: NodeListProps) => {
: "Inactive"} : "Inactive"}
</Badge> </Badge>
<Dropdown <Dropdown end>
end
vertical={idx >= items.length - 2 ? "top" : "bottom"}
>
<Dropdown.Toggle button={false}> <Dropdown.Toggle button={false}>
<Button shape="circle" color="ghost"> <Button shape="circle" color="ghost">
<EllipsisVertical /> <EllipsisVertical />

View File

@ -96,7 +96,6 @@ const KeysPage = () => {
icon={Eye} icon={Eye}
size="sm" size="sm"
onClick={() => fetchSecretKey(key.id)} onClick={() => fetchSecretKey(key.id)}
className="shrink-0 min-w-[80px]"
> >
View View
</Button> </Button>