Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/SetupRepository.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SetupRepositoryFilesystem } from "./SetupRepositoryFilesystem";
import { SetupRepositoryGCS } from "./SetupRepositoryGCS";
import { SetupRepositoryServer } from "./SetupRepositoryServer";
import { SetupRepositoryRclone } from "./SetupRepositoryRclone";
import { SetupRepositoryR2 } from "./SetupRepositoryR2";
import { SetupRepositoryS3 } from "./SetupRepositoryS3";
import { SetupRepositorySFTP } from "./SetupRepositorySFTP";
import { SetupRepositoryToken } from "./SetupRepositoryToken";
Expand All @@ -40,6 +41,11 @@ const supportedProviders = [
description: "Amazon S3 or Compatible Storage",
component: SetupRepositoryS3,
},
{
provider: "r2",
description: "Cloudflare R2",
component: SetupRepositoryR2,
},
{ provider: "b2", description: "Backblaze B2", component: SetupRepositoryB2 },
{
provider: "azureBlob",
Expand Down
87 changes: 87 additions & 0 deletions src/components/SetupRepositoryR2.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { Component } from "react";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import Row from "react-bootstrap/Row";
import { handleChange, validateRequiredFields } from "../forms";
import { OptionalField } from "../forms/OptionalField";
import { RequiredBoolean } from "../forms/RequiredBoolean";
import { RequiredField } from "../forms/RequiredField";
import PropTypes from "prop-types";

export class SetupRepositoryR2 extends Component {
constructor(props) {
super();

this.state = {
jurisdiction: "default",
doNotUseTLS: false,
doNotVerifyTLS: false,
...props.initial,
};
this.handleChange = handleChange.bind(this);
}

validate() {
return validateRequiredFields(this, ["accountID", "bucket", "accessKeyID", "secretAccessKey"]);
}

render() {
return (
<>
<Row>
{RequiredField(this, "Account ID", "accountID", {
autoFocus: true,
placeholder: "enter Cloudflare account ID",
})}
{RequiredField(this, "Bucket", "bucket", {
placeholder: "enter bucket name",
})}
<Form.Group as={Col}>
<Form.Label>Jurisdiction</Form.Label>
<Form.Control
as="select"
size="sm"
name="jurisdiction"
value={this.state.jurisdiction}
data-testid="control-jurisdiction"
onChange={this.handleChange}
>
<option value="default">Default</option>
<option value="eu">EU</option>
<option value="fedramp">FedRAMP</option>
</Form.Control>
</Form.Group>
</Row>
<Row>
{OptionalField(this, "Endpoint Override", "endpoint", {
placeholder: "leave empty to derive from account ID",
})}
{OptionalField(this, "Object Name Prefix", "prefix", {
placeholder: "enter object name prefix or leave empty",
})}
</Row>
<Row>
{RequiredBoolean(this, "Use HTTP connection (insecure)", "doNotUseTLS")}
{RequiredBoolean(this, "Do not verify TLS certificate", "doNotVerifyTLS")}
</Row>
<Row>
{RequiredField(this, "Access Key ID", "accessKeyID", {
placeholder: "enter access key ID",
})}
{RequiredField(this, "Secret Access Key", "secretAccessKey", {
placeholder: "enter secret access key",
type: "password",
})}
{OptionalField(this, "Session Token", "sessionToken", {
placeholder: "enter session token or leave empty",
type: "password",
})}
</Row>
</>
);
}
}

SetupRepositoryR2.propTypes = {
initial: PropTypes.object,
};
29 changes: 29 additions & 0 deletions tests/components/SetupRepository.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,35 @@ it("can connect to existing repository when already initialized", async () => {
await waitFor(() => serverMock.history.post.length == 1);
});

it("can verify Cloudflare R2 storage", async () => {
serverMock
.onPost("/api/v1/repo/exists", {
storage: {
type: "r2",
config: {
accountID: "some-account-id",
bucket: "some-bucket",
jurisdiction: "default",
doNotUseTLS: false,
doNotVerifyTLS: false,
accessKeyID: "some-access-key-id",
secretAccessKey: "some-secret-access-key",
},
},
})
.reply(200, {});

const { getByTestId, container } = await act(() => render(<SetupRepository />));
fireEvent.click(getByTestId("provider-r2"));
fireEvent.change(await findByTestId(container, "control-accountID"), { target: { value: "some-account-id" } });
fireEvent.change(getByTestId("control-bucket"), { target: { value: "some-bucket" } });
fireEvent.change(getByTestId("control-accessKeyID"), { target: { value: "some-access-key-id" } });
fireEvent.change(getByTestId("control-secretAccessKey"), { target: { value: "some-secret-access-key" } });

await act(() => fireEvent.click(getByTestId("submit-button")));
await waitFor(() => serverMock.history.post.length == 1);
});

it("can connect to existing repository using token", async () => {
serverMock
.onPost("/api/v1/repo/connect", {
Expand Down
42 changes: 42 additions & 0 deletions tests/components/SetupRepositoryR2.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { fireEvent, render, act } from "@testing-library/react";
import React from "react";
import { SetupRepositoryR2 } from "../../src/components/SetupRepositoryR2";

it("can set fields", async () => {
let ref = React.createRef();
const { getByTestId } = render(<SetupRepositoryR2 ref={ref} />);

act(() => expect(ref.current.validate()).toBe(false));
// required
fireEvent.change(getByTestId("control-accountID"), { target: { value: "some-accountID" } });
fireEvent.change(getByTestId("control-bucket"), { target: { value: "some-bucket" } });
fireEvent.change(getByTestId("control-accessKeyID"), { target: { value: "some-accessKeyID" } });
fireEvent.change(getByTestId("control-secretAccessKey"), { target: { value: "some-secretAccessKey" } });
act(() => expect(ref.current.validate()).toBe(true));
// optional
fireEvent.change(getByTestId("control-jurisdiction"), { target: { value: "eu" } });
fireEvent.change(getByTestId("control-endpoint"), { target: { value: "some-endpoint" } });
fireEvent.click(getByTestId("control-doNotUseTLS"));
fireEvent.click(getByTestId("control-doNotVerifyTLS"));
fireEvent.change(getByTestId("control-prefix"), { target: { value: "some-prefix" } });
fireEvent.change(getByTestId("control-sessionToken"), { target: { value: "some-sessionToken" } });
act(() => expect(ref.current.validate()).toBe(true));

expect(ref.current.state).toStrictEqual({
accountID: "some-accountID",
accessKeyID: "some-accessKeyID",
bucket: "some-bucket",
endpoint: "some-endpoint",
prefix: "some-prefix",
jurisdiction: "eu",
doNotUseTLS: true,
doNotVerifyTLS: true,
secretAccessKey: "some-secretAccessKey",
sessionToken: "some-sessionToken",
});

fireEvent.click(getByTestId("control-doNotUseTLS"));
fireEvent.click(getByTestId("control-doNotVerifyTLS"));
expect(ref.current.state.doNotUseTLS).toBe(false);
expect(ref.current.state.doNotVerifyTLS).toBe(false);
});