diff --git a/models/multi_bucket_replication.go b/models/multi_bucket_replication.go index db5b90d776..36d3972624 100644 --- a/models/multi_bucket_replication.go +++ b/models/multi_bucket_replication.go @@ -56,6 +56,9 @@ type MultiBucketReplication struct { // prefix Prefix string `json:"prefix,omitempty"` + // priority + Priority int32 `json:"priority,omitempty"` + // region Region string `json:"region,omitempty"` diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddReplicationModal.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddReplicationModal.tsx index 257bdec1d0..83391b8981 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddReplicationModal.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddReplicationModal.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { connect } from "react-redux"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; @@ -28,7 +28,7 @@ import { modalStyleUtils, spacingUtils, } from "../../Common/FormComponents/common/styleLibrary"; -import { BulkReplicationResponse } from "../types"; +import { BucketReplicationRule, BulkReplicationResponse } from "../types"; import { setModalErrorSnackMessage } from "../../../../actions"; import { ErrorResponseHandler } from "../../../../common/types"; import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; @@ -46,6 +46,7 @@ interface IReplicationModal { classes: any; bucketName: string; setModalErrorSnackMessage: typeof setModalErrorSnackMessage; + setReplicationRules: BucketReplicationRule[]; } const styles = (theme: Theme) => @@ -81,8 +82,10 @@ const AddReplicationModal = ({ classes, bucketName, setModalErrorSnackMessage, + setReplicationRules, }: IReplicationModal) => { const [addLoading, setAddLoading] = useState(false); + const [priority, setPriority] = useState("1"); const [accessKey, setAccessKey] = useState(""); const [secretKey, setSecretKey] = useState(""); const [targetURL, setTargetURL] = useState(""); @@ -99,6 +102,23 @@ const AddReplicationModal = ({ const [bandwidthUnit, setBandwidthUnit] = useState("Gi"); const [healthCheck, setHealthCheck] = useState("60"); + useEffect(() => { + if (setReplicationRules.length === 0) { + setPriority("1"); + return; + } + + const greatestValue = setReplicationRules.reduce((prevAcc, currValue) => { + if (currValue.priority > prevAcc) { + return currValue.priority; + } + return prevAcc; + }, 0); + + const nextPriority = greatestValue + 1; + setPriority(nextPriority.toString()); + }, [setReplicationRules]); + const addRecord = () => { const replicate = [ { @@ -127,6 +147,7 @@ const AddReplicationModal = ({ tags: tags, replicateDeleteMarkers: repDeleteMarker, replicateDeletes: repDelete, + priority: parseInt(priority), }; api @@ -184,6 +205,20 @@ const AddReplicationModal = ({ > + + ) => { + if (e.target.validity.valid) { + setPriority(e.target.value); + } + }} + label="Priority" + value={priority} + pattern={"[0-9]*"} + /> + replicationRules.length > 1, + disableButtonFunction: () => replicationRules.length === 1, }, ]; @@ -170,6 +170,7 @@ const BucketReplicationPanel = ({ closeModalAndRefresh={closeAddReplication} open={openSetReplication} bucketName={bucketName} + setReplicationRules={replicationRules} /> )} diff --git a/restapi/admin_remote_buckets.go b/restapi/admin_remote_buckets.go index 300cde76f7..0d57fef960 100644 --- a/restapi/admin_remote_buckets.go +++ b/restapi/admin_remote_buckets.go @@ -269,7 +269,7 @@ func addRemoteBucket(ctx context.Context, client MinioAdmin, params models.Creat return bucketARN, err } -func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, prefix, destinationARN string, repDelMark, repDels, repMeta bool, tags string) error { +func addBucketReplicationItem(ctx context.Context, session *models.Principal, minClient minioClient, bucketName, prefix, destinationARN string, repDelMark, repDels, repMeta bool, tags string, priority int32) error { // we will tolerate this call failing cfg, err := minClient.getBucketReplication(ctx, bucketName) if err != nil { @@ -278,12 +278,17 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi // add rule maxPrio := 0 - for _, r := range cfg.Rules { - if r.Priority > maxPrio { - maxPrio = r.Priority + + if priority <= 0 { // We pick next priority by default + for _, r := range cfg.Rules { + if r.Priority > maxPrio { + maxPrio = r.Priority + } } + maxPrio++ + } else { // User picked priority, we try to set this manually + maxPrio = int(priority) } - maxPrio++ s3Client, err := newS3BucketClient(session, bucketName, prefix) if err != nil { @@ -366,7 +371,8 @@ func setMultiBucketReplication(ctx context.Context, session *models.Principal, c params.Body.ReplicateDeleteMarkers, params.Body.ReplicateDeletes, params.Body.ReplicateMetadata, - params.Body.Tags) + params.Body.Tags, + params.Body.Priority) } var errorReturn = "" diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 9875e5a158..7cb4fe770a 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -4578,6 +4578,11 @@ func init() { "prefix": { "type": "string" }, + "priority": { + "type": "integer", + "format": "int32", + "default": 0 + }, "region": { "type": "string" }, @@ -10409,6 +10414,11 @@ func init() { "prefix": { "type": "string" }, + "priority": { + "type": "integer", + "format": "int32", + "default": 0 + }, "region": { "type": "string" }, diff --git a/swagger-console.yml b/swagger-console.yml index 9c19ab97a3..72c1c92087 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -2967,6 +2967,10 @@ definitions: type: boolean replicateMetadata: type: boolean + priority: + type: integer + format: int32 + default: 0 bucketsRelation: type: array minLength: 1